@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.
- package/LICENSE +25 -0
- package/README.md +79 -0
- package/dist/agent-findings-ingestor.d.ts +40 -0
- package/dist/agent-findings-ingestor.d.ts.map +1 -0
- package/dist/agent-findings-ingestor.js +154 -0
- package/dist/agent-findings-ingestor.js.map +1 -0
- package/dist/classification-trust-store.d.ts +31 -0
- package/dist/classification-trust-store.d.ts.map +1 -0
- package/dist/classification-trust-store.js +154 -0
- package/dist/classification-trust-store.js.map +1 -0
- package/dist/coverage-gap-reporter.d.ts +35 -0
- package/dist/coverage-gap-reporter.d.ts.map +1 -0
- package/dist/coverage-gap-reporter.js +84 -0
- package/dist/coverage-gap-reporter.js.map +1 -0
- package/dist/fix-policy.d.ts +46 -0
- package/dist/fix-policy.d.ts.map +1 -0
- package/dist/fix-policy.js +162 -0
- package/dist/fix-policy.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/learning-health-reporter.d.ts +37 -0
- package/dist/learning-health-reporter.d.ts.map +1 -0
- package/dist/learning-health-reporter.js +141 -0
- package/dist/learning-health-reporter.js.map +1 -0
- package/dist/prisma.d.ts +31 -0
- package/dist/prisma.d.ts.map +1 -0
- package/dist/prisma.js +31 -0
- package/dist/prisma.js.map +1 -0
- package/dist/qa-fix-attempt-store.d.ts +28 -0
- package/dist/qa-fix-attempt-store.d.ts.map +1 -0
- package/dist/qa-fix-attempt-store.js +258 -0
- package/dist/qa-fix-attempt-store.js.map +1 -0
- package/dist/qa-pattern-store.d.ts +32 -0
- package/dist/qa-pattern-store.d.ts.map +1 -0
- package/dist/qa-pattern-store.js +123 -0
- package/dist/qa-pattern-store.js.map +1 -0
- package/dist/qa-revert-store.d.ts +24 -0
- package/dist/qa-revert-store.d.ts.map +1 -0
- package/dist/qa-revert-store.js +139 -0
- package/dist/qa-revert-store.js.map +1 -0
- package/dist/qa-run-store.d.ts +46 -0
- package/dist/qa-run-store.d.ts.map +1 -0
- package/dist/qa-run-store.js +201 -0
- package/dist/qa-run-store.js.map +1 -0
- package/dist/qa-ticket-store.d.ts +35 -0
- package/dist/qa-ticket-store.d.ts.map +1 -0
- package/dist/qa-ticket-store.js +293 -0
- package/dist/qa-ticket-store.js.map +1 -0
- package/dist/qa-uniformity-store.d.ts +41 -0
- package/dist/qa-uniformity-store.d.ts.map +1 -0
- package/dist/qa-uniformity-store.js +288 -0
- package/dist/qa-uniformity-store.js.map +1 -0
- package/dist/scripts/smoke-auto-fix.d.ts +37 -0
- package/dist/scripts/smoke-auto-fix.d.ts.map +1 -0
- package/dist/scripts/smoke-auto-fix.js +270 -0
- package/dist/scripts/smoke-auto-fix.js.map +1 -0
- package/dist/scripts/smoke-learning-loop.d.ts +21 -0
- package/dist/scripts/smoke-learning-loop.d.ts.map +1 -0
- package/dist/scripts/smoke-learning-loop.js +375 -0
- package/dist/scripts/smoke-learning-loop.js.map +1 -0
- package/dist/scripts/smoke-orchestration.d.ts +35 -0
- package/dist/scripts/smoke-orchestration.d.ts.map +1 -0
- package/dist/scripts/smoke-orchestration.js +215 -0
- package/dist/scripts/smoke-orchestration.js.map +1 -0
- package/dist/scripts/smoke-qa-ticket-store.d.ts +18 -0
- package/dist/scripts/smoke-qa-ticket-store.d.ts.map +1 -0
- package/dist/scripts/smoke-qa-ticket-store.js +233 -0
- package/dist/scripts/smoke-qa-ticket-store.js.map +1 -0
- package/package.json +69 -0
- package/prisma/migrations/20260501165631_init/migration.sql +407 -0
- package/prisma/migrations/20260503051425_0002_qap018b_qaticket_crosslink_fields/migration.sql +6 -0
- package/prisma/migrations/20260504231316_add_project_repofullname_and_webhooksecret/migration.sql +12 -0
- package/prisma/migrations/20260504232851_add_qaticket_resolvedby/migration.sql +2 -0
- package/prisma/migrations/20260505042646_add_qapattern_qarevert/migration.sql +77 -0
- package/prisma/migrations/20260505055826_add_qauniformity_and_agent_trigger/migration.sql +35 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +748 -0
- package/prisma/seed.ts +181 -0
- package/prisma-client/default.d.ts +1 -0
- package/prisma-client/default.js +1 -0
- package/prisma-client/edge.d.ts +1 -0
- package/prisma-client/edge.js +631 -0
- package/prisma-client/index-browser.js +615 -0
- package/prisma-client/index.d.ts +34509 -0
- package/prisma-client/index.js +660 -0
- package/prisma-client/libquery_engine-darwin-arm64.dylib.node +0 -0
- package/prisma-client/libquery_engine-linux-arm64-openssl-3.0.x.so.node +0 -0
- package/prisma-client/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
- package/prisma-client/package.json +97 -0
- package/prisma-client/runtime/edge-esm.js +31 -0
- package/prisma-client/runtime/edge.js +31 -0
- package/prisma-client/runtime/index-browser.d.ts +365 -0
- package/prisma-client/runtime/index-browser.js +13 -0
- package/prisma-client/runtime/library.d.ts +3403 -0
- package/prisma-client/runtime/library.js +143 -0
- package/prisma-client/runtime/react-native.js +80 -0
- package/prisma-client/runtime/wasm.js +32 -0
- package/prisma-client/schema.prisma +748 -0
- package/prisma-client/wasm.d.ts +1 -0
- package/prisma-client/wasm.js +615 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QARunStore — Prisma implementation against Derwin's QARun + RawSignal
|
|
3
|
+
* tables.
|
|
4
|
+
*
|
|
5
|
+
* QAP-019B (Sprint 2 Group D-1, Commit 4). Implements the QARunStore
|
|
6
|
+
* contract from @derwinjs/sdk under **Option β** (lossy forward-translation
|
|
7
|
+
* from Lifeline's test-centric runs model — see contract docstring at
|
|
8
|
+
* packages/sdk/src/types/qa-run-store.ts and the translation-rules table
|
|
9
|
+
* in tasks/group-d-1-plan.md).
|
|
10
|
+
*
|
|
11
|
+
* Two methods:
|
|
12
|
+
*
|
|
13
|
+
* - createRun(input): single-transaction insert of the QARun envelope
|
|
14
|
+
* plus its child RawSignal rows. Bumps `signalsRaised` to
|
|
15
|
+
* `signals.length` on the QARun row so the dashboard's "raised"
|
|
16
|
+
* counter is accurate without an extra COUNT(*) per render.
|
|
17
|
+
*
|
|
18
|
+
* - getRunDetail(ref): fetches the run, its signals, and computes
|
|
19
|
+
* test-centric counts at read time by scanning RawSignal.rawData.status.
|
|
20
|
+
* This is Option β's "derived counts" path. For runs with high signal
|
|
21
|
+
* count (>1k) the per-read cost is ~1ms scan; if Sprint 3 dashboard
|
|
22
|
+
* telemetry shows perf pressure we revisit with denormalized counters.
|
|
23
|
+
*
|
|
24
|
+
* Tenant isolation: every method scopes by projectId in the WHERE clause
|
|
25
|
+
* (defense-in-depth ahead of Sprint 3's RLS migration). Wrong-project
|
|
26
|
+
* reads return null; wrong-project writes throw `tenant_mismatch` because
|
|
27
|
+
* createRun's projectId is in the input rather than a ref filter — we
|
|
28
|
+
* surface the cross-tenant attempt distinctly from a missing FK.
|
|
29
|
+
*/
|
|
30
|
+
import { type PrismaClient } from './prisma.js';
|
|
31
|
+
import { type QARunStore, type RawSignalRecord, type TestResultCounts } from '@derwinjs/sdk';
|
|
32
|
+
export interface PrismaQARunStoreConfig {
|
|
33
|
+
/** Generated Prisma client. Pass an instance per process. */
|
|
34
|
+
prisma: PrismaClient;
|
|
35
|
+
}
|
|
36
|
+
export declare function createPrismaQARunStore(config: PrismaQARunStoreConfig): QARunStore;
|
|
37
|
+
/**
|
|
38
|
+
* Walks the signal list once, bucketing by `rawData.status`. Statuses outside
|
|
39
|
+
* the canonical TestResultStatus set (PASSED/FAILED/FLAKY/SKIPPED/TIMED_OUT)
|
|
40
|
+
* are ignored — the inbound payload validation at the route layer is the
|
|
41
|
+
* authoritative gate; we don't double-validate here. `total` counts ALL
|
|
42
|
+
* signals, regardless of status, so non-test signal types (telemetry
|
|
43
|
+
* anomalies etc.) are still represented in the aggregate.
|
|
44
|
+
*/
|
|
45
|
+
export declare function computeDerivedCounts(signals: readonly RawSignalRecord[]): TestResultCounts;
|
|
46
|
+
//# sourceMappingURL=qa-run-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qa-run-store.d.ts","sourceRoot":"","sources":["../src/qa-run-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,eAAe,EAIpB,KAAK,gBAAgB,EAEtB,MAAM,eAAe,CAAC;AAIvB,MAAM,WAAW,sBAAsB;IACrC,6DAA6D;IAC7D,MAAM,EAAE,YAAY,CAAC;CACtB;AAID,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,UAAU,CA2DjF;AA8HD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,gBAAgB,CA+B1F"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QARunStore — Prisma implementation against Derwin's QARun + RawSignal
|
|
3
|
+
* tables.
|
|
4
|
+
*
|
|
5
|
+
* QAP-019B (Sprint 2 Group D-1, Commit 4). Implements the QARunStore
|
|
6
|
+
* contract from @derwinjs/sdk under **Option β** (lossy forward-translation
|
|
7
|
+
* from Lifeline's test-centric runs model — see contract docstring at
|
|
8
|
+
* packages/sdk/src/types/qa-run-store.ts and the translation-rules table
|
|
9
|
+
* in tasks/group-d-1-plan.md).
|
|
10
|
+
*
|
|
11
|
+
* Two methods:
|
|
12
|
+
*
|
|
13
|
+
* - createRun(input): single-transaction insert of the QARun envelope
|
|
14
|
+
* plus its child RawSignal rows. Bumps `signalsRaised` to
|
|
15
|
+
* `signals.length` on the QARun row so the dashboard's "raised"
|
|
16
|
+
* counter is accurate without an extra COUNT(*) per render.
|
|
17
|
+
*
|
|
18
|
+
* - getRunDetail(ref): fetches the run, its signals, and computes
|
|
19
|
+
* test-centric counts at read time by scanning RawSignal.rawData.status.
|
|
20
|
+
* This is Option β's "derived counts" path. For runs with high signal
|
|
21
|
+
* count (>1k) the per-read cost is ~1ms scan; if Sprint 3 dashboard
|
|
22
|
+
* telemetry shows perf pressure we revisit with denormalized counters.
|
|
23
|
+
*
|
|
24
|
+
* Tenant isolation: every method scopes by projectId in the WHERE clause
|
|
25
|
+
* (defense-in-depth ahead of Sprint 3's RLS migration). Wrong-project
|
|
26
|
+
* reads return null; wrong-project writes throw `tenant_mismatch` because
|
|
27
|
+
* createRun's projectId is in the input rather than a ref filter — we
|
|
28
|
+
* surface the cross-tenant attempt distinctly from a missing FK.
|
|
29
|
+
*/
|
|
30
|
+
import { Prisma } from './prisma.js';
|
|
31
|
+
import { QARunStoreError, } from '@derwinjs/sdk';
|
|
32
|
+
// ─── Factory ─────────────────────────────────────────────────────────────
|
|
33
|
+
export function createPrismaQARunStore(config) {
|
|
34
|
+
const { prisma } = config;
|
|
35
|
+
return {
|
|
36
|
+
async createRun(input) {
|
|
37
|
+
validateCreateInput(input);
|
|
38
|
+
const signals = input.signals ?? [];
|
|
39
|
+
const signalsRaised = signals.length;
|
|
40
|
+
let created;
|
|
41
|
+
try {
|
|
42
|
+
created = await prisma.qARun.create({
|
|
43
|
+
data: {
|
|
44
|
+
projectId: input.projectId,
|
|
45
|
+
triggeredBy: input.triggeredBy,
|
|
46
|
+
triggerType: input.triggerType,
|
|
47
|
+
scope: input.scope,
|
|
48
|
+
completedAt: input.completedAt ?? null,
|
|
49
|
+
status: input.status ?? 'IN_PROGRESS',
|
|
50
|
+
signalsRaised,
|
|
51
|
+
// ticketsCreated + attemptsLaunched stay at default 0 — those
|
|
52
|
+
// are bumped by the orchestrator when Triage / Author run.
|
|
53
|
+
signals: {
|
|
54
|
+
create: signals.map((s) => ({
|
|
55
|
+
routeName: s.routeName,
|
|
56
|
+
signalType: s.signalType,
|
|
57
|
+
rawData: s.rawData,
|
|
58
|
+
rawArtifactRefs: (s.rawArtifactRefs ?? []),
|
|
59
|
+
})),
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
include: { signals: true },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
// Project FK violation surfaces as P2003 from Prisma. Distinguish
|
|
67
|
+
// from generic DB errors so the route handler can return 400 vs 500.
|
|
68
|
+
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2003') {
|
|
69
|
+
throw new QARunStoreError('fk_violation', `QARun.projectId references a non-existent Project: ${input.projectId}`, { projectId: input.projectId });
|
|
70
|
+
}
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
return mapRunDetail(created, mapRunRows(created.signals));
|
|
74
|
+
},
|
|
75
|
+
async getRunDetail(ref) {
|
|
76
|
+
const row = await prisma.qARun.findFirst({
|
|
77
|
+
where: { id: ref.id, projectId: ref.projectId },
|
|
78
|
+
include: { signals: true },
|
|
79
|
+
});
|
|
80
|
+
if (row === null)
|
|
81
|
+
return null;
|
|
82
|
+
return mapRunDetail(row, mapRunRows(row.signals));
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// ─── Validation ───────────────────────────────────────────────────────────
|
|
87
|
+
function validateCreateInput(input) {
|
|
88
|
+
if (!input.projectId || typeof input.projectId !== 'string') {
|
|
89
|
+
throw new QARunStoreError('invalid_input', 'QARunStore.createRun: projectId is required');
|
|
90
|
+
}
|
|
91
|
+
if (!input.triggeredBy || typeof input.triggeredBy !== 'string') {
|
|
92
|
+
throw new QARunStoreError('invalid_input', 'QARunStore.createRun: triggeredBy is required');
|
|
93
|
+
}
|
|
94
|
+
// triggerType is a required RunTrigger string-literal — TS forbids empty
|
|
95
|
+
// values at the type level, so no runtime check beyond presence.
|
|
96
|
+
if (input.signals !== undefined) {
|
|
97
|
+
// Defense-in-depth — the type system already enforces array-ness on the
|
|
98
|
+
// optional field, but the cast at the consumer boundary may be looser.
|
|
99
|
+
const signalsValue = input.signals;
|
|
100
|
+
if (!Array.isArray(signalsValue)) {
|
|
101
|
+
throw new QARunStoreError('invalid_input', 'QARunStore.createRun: signals must be an array');
|
|
102
|
+
}
|
|
103
|
+
input.signals.forEach((s, i) => {
|
|
104
|
+
validateSignal(s, i);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function validateSignal(s, idx) {
|
|
109
|
+
if (!s.routeName || typeof s.routeName !== 'string') {
|
|
110
|
+
throw new QARunStoreError('invalid_input', `QARunStore.createRun: signals[${String(idx)}].routeName is required`);
|
|
111
|
+
}
|
|
112
|
+
if (!s.signalType || typeof s.signalType !== 'string') {
|
|
113
|
+
throw new QARunStoreError('invalid_input', `QARunStore.createRun: signals[${String(idx)}].signalType is required`);
|
|
114
|
+
}
|
|
115
|
+
// rawData is typed Record<string, unknown> at the contract level; the
|
|
116
|
+
// runtime check defends against consumer-side casts that bypass TS.
|
|
117
|
+
const rawData = s.rawData;
|
|
118
|
+
if (typeof rawData !== 'object' || rawData === null) {
|
|
119
|
+
throw new QARunStoreError('invalid_input', `QARunStore.createRun: signals[${String(idx)}].rawData must be an object`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function mapRun(row) {
|
|
123
|
+
return {
|
|
124
|
+
id: row.id,
|
|
125
|
+
projectId: row.projectId,
|
|
126
|
+
triggeredBy: row.triggeredBy,
|
|
127
|
+
triggerType: row.triggerType,
|
|
128
|
+
scope: (row.scope ?? {}),
|
|
129
|
+
startedAt: row.startedAt,
|
|
130
|
+
completedAt: row.completedAt,
|
|
131
|
+
status: row.status,
|
|
132
|
+
signalsRaised: row.signalsRaised,
|
|
133
|
+
ticketsCreated: row.ticketsCreated,
|
|
134
|
+
attemptsLaunched: row.attemptsLaunched,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function mapSignal(row) {
|
|
138
|
+
return {
|
|
139
|
+
id: row.id,
|
|
140
|
+
qaRunId: row.qaRunId,
|
|
141
|
+
routeName: row.routeName,
|
|
142
|
+
signalType: row.signalType,
|
|
143
|
+
rawData: (row.rawData ?? {}),
|
|
144
|
+
rawArtifactRefs: (row.rawArtifactRefs ?? []),
|
|
145
|
+
qaTicketId: row.qaTicketId,
|
|
146
|
+
investigatedAt: row.investigatedAt,
|
|
147
|
+
capturedAt: row.capturedAt,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function mapRunRows(rows) {
|
|
151
|
+
return rows.map(mapSignal);
|
|
152
|
+
}
|
|
153
|
+
function mapRunDetail(row, signals) {
|
|
154
|
+
return {
|
|
155
|
+
run: mapRun(row),
|
|
156
|
+
signals: [...signals],
|
|
157
|
+
derivedCounts: computeDerivedCounts(signals),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// ─── Option β: derived test-result counts ────────────────────────────────
|
|
161
|
+
/**
|
|
162
|
+
* Walks the signal list once, bucketing by `rawData.status`. Statuses outside
|
|
163
|
+
* the canonical TestResultStatus set (PASSED/FAILED/FLAKY/SKIPPED/TIMED_OUT)
|
|
164
|
+
* are ignored — the inbound payload validation at the route layer is the
|
|
165
|
+
* authoritative gate; we don't double-validate here. `total` counts ALL
|
|
166
|
+
* signals, regardless of status, so non-test signal types (telemetry
|
|
167
|
+
* anomalies etc.) are still represented in the aggregate.
|
|
168
|
+
*/
|
|
169
|
+
export function computeDerivedCounts(signals) {
|
|
170
|
+
const counts = {
|
|
171
|
+
total: signals.length,
|
|
172
|
+
passed: 0,
|
|
173
|
+
failed: 0,
|
|
174
|
+
flaky: 0,
|
|
175
|
+
skipped: 0,
|
|
176
|
+
timedOut: 0,
|
|
177
|
+
};
|
|
178
|
+
for (const s of signals) {
|
|
179
|
+
const status = s.rawData.status;
|
|
180
|
+
switch (status) {
|
|
181
|
+
case 'PASSED':
|
|
182
|
+
counts.passed++;
|
|
183
|
+
break;
|
|
184
|
+
case 'FAILED':
|
|
185
|
+
counts.failed++;
|
|
186
|
+
break;
|
|
187
|
+
case 'FLAKY':
|
|
188
|
+
counts.flaky++;
|
|
189
|
+
break;
|
|
190
|
+
case 'SKIPPED':
|
|
191
|
+
counts.skipped++;
|
|
192
|
+
break;
|
|
193
|
+
case 'TIMED_OUT':
|
|
194
|
+
counts.timedOut++;
|
|
195
|
+
break;
|
|
196
|
+
// default: non-test signal type — not counted in any test bucket.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return counts;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=qa-run-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qa-run-store.js","sourceRoot":"","sources":["../src/qa-run-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,MAAM,EAAqB,MAAM,aAAa,CAAC;AACxD,OAAO,EACL,eAAe,GAWhB,MAAM,eAAe,CAAC;AASvB,4EAA4E;AAE5E,MAAM,UAAU,sBAAsB,CAAC,MAA8B;IACnE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAE1B,OAAO;QACL,KAAK,CAAC,SAAS,CAAC,KAAK;YACnB,mBAAmB,CAAC,KAAK,CAAC,CAAC;YAE3B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;YACpC,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;YAErC,IAAI,OAAO,CAAC;YACZ,IAAI,CAAC;gBACH,OAAO,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;oBAClC,IAAI,EAAE;wBACJ,SAAS,EAAE,KAAK,CAAC,SAAS;wBAC1B,WAAW,EAAE,KAAK,CAAC,WAAW;wBAC9B,WAAW,EAAE,KAAK,CAAC,WAAW;wBAC9B,KAAK,EAAE,KAAK,CAAC,KAA8B;wBAC3C,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,IAAI;wBACtC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,aAAa;wBACrC,aAAa;wBACb,8DAA8D;wBAC9D,2DAA2D;wBAC3D,OAAO,EAAE;4BACP,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gCAC1B,SAAS,EAAE,CAAC,CAAC,SAAS;gCACtB,UAAU,EAAE,CAAC,CAAC,UAAU;gCACxB,OAAO,EAAE,CAAC,CAAC,OAAgC;gCAC3C,eAAe,EAAE,CAAC,CAAC,CAAC,eAAe,IAAI,EAAE,CAAqC;6BAC/E,CAAC,CAAC;yBACJ;qBACF;oBACD,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;iBAC3B,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,kEAAkE;gBAClE,qEAAqE;gBACrE,IAAI,GAAG,YAAY,MAAM,CAAC,6BAA6B,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAChF,MAAM,IAAI,eAAe,CACvB,cAAc,EACd,sDAAsD,KAAK,CAAC,SAAS,EAAE,EACvE,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAC/B,CAAC;gBACJ,CAAC;gBACD,MAAM,GAAG,CAAC;YACZ,CAAC;YAED,OAAO,YAAY,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5D,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,GAAG;YACpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;gBACvC,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE;gBAC/C,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;aAC3B,CAAC,CAAC;YACH,IAAI,GAAG,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC9B,OAAO,YAAY,CAAC,GAAG,EAAE,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QACpD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,6EAA6E;AAE7E,SAAS,mBAAmB,CAAC,KAAuB;IAClD,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC5D,MAAM,IAAI,eAAe,CAAC,eAAe,EAAE,6CAA6C,CAAC,CAAC;IAC5F,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;QAChE,MAAM,IAAI,eAAe,CAAC,eAAe,EAAE,+CAA+C,CAAC,CAAC;IAC9F,CAAC;IACD,yEAAyE;IACzE,iEAAiE;IACjE,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAChC,wEAAwE;QACxE,uEAAuE;QACvE,MAAM,YAAY,GAAY,KAAK,CAAC,OAAO,CAAC;QAC5C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,eAAe,CAAC,eAAe,EAAE,gDAAgD,CAAC,CAAC;QAC/F,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YAC7B,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,CAAoB,EAAE,GAAW;IACvD,IAAI,CAAC,CAAC,CAAC,SAAS,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;QACpD,MAAM,IAAI,eAAe,CACvB,eAAe,EACf,iCAAiC,MAAM,CAAC,GAAG,CAAC,yBAAyB,CACtE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;QACtD,MAAM,IAAI,eAAe,CACvB,eAAe,EACf,iCAAiC,MAAM,CAAC,GAAG,CAAC,0BAA0B,CACvE,CAAC;IACJ,CAAC;IACD,sEAAsE;IACtE,oEAAoE;IACpE,MAAM,OAAO,GAAY,CAAC,CAAC,OAAO,CAAC;IACnC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACpD,MAAM,IAAI,eAAe,CACvB,eAAe,EACf,iCAAiC,MAAM,CAAC,GAAG,CAAC,6BAA6B,CAC1E,CAAC;IACJ,CAAC;AACH,CAAC;AAkCD,SAAS,MAAM,CAAC,GAAmB;IACjC,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,KAAK,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAA4B;QACnD,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,aAAa,EAAE,GAAG,CAAC,aAAa;QAChC,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;KACvC,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,GAAuB;IACxC,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,OAAO,EAAE,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAA4B;QACvD,eAAe,EAAE,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAA6B;QACxE,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,UAAU,EAAE,GAAG,CAAC,UAAU;KAC3B,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,IAAmC;IACrD,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,YAAY,CAAC,GAAmB,EAAE,OAAmC;IAC5E,OAAO;QACL,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC;QAChB,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC;QACrB,aAAa,EAAE,oBAAoB,CAAC,OAAO,CAAC;KAC7C,CAAC;AACJ,CAAC;AAED,4EAA4E;AAE5E;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAmC;IACtE,MAAM,MAAM,GAAqB;QAC/B,KAAK,EAAE,OAAO,CAAC,MAAM;QACrB,MAAM,EAAE,CAAC;QACT,MAAM,EAAE,CAAC;QACT,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,CAAC;KACZ,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,MAAM,GAAI,CAAC,CAAC,OAAgC,CAAC,MAAM,CAAC;QAC1D,QAAQ,MAAM,EAAE,CAAC;YACf,KAAK,QAAQ;gBACX,MAAM,CAAC,MAAM,EAAE,CAAC;gBAChB,MAAM;YACR,KAAK,QAAQ;gBACX,MAAM,CAAC,MAAM,EAAE,CAAC;gBAChB,MAAM;YACR,KAAK,OAAO;gBACV,MAAM,CAAC,KAAK,EAAE,CAAC;gBACf,MAAM;YACR,KAAK,SAAS;gBACZ,MAAM,CAAC,OAAO,EAAE,CAAC;gBACjB,MAAM;YACR,KAAK,WAAW;gBACd,MAAM,CAAC,QAAQ,EAAE,CAAC;gBAClB,MAAM;YACR,kEAAkE;QACpE,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QATicketStore — Prisma implementation against Derwin's QATicket table.
|
|
3
|
+
*
|
|
4
|
+
* QAP-018B (Sprint 2). Implements the QATicketStore contract from @derwinjs/sdk
|
|
5
|
+
* (see packages/sdk/src/types/qa-ticket-store.ts) against the @derwinjs/db
|
|
6
|
+
* Prisma client. Ships together with QAP-018 (the cross-link adapter) as
|
|
7
|
+
* the load-bearing proof of the architecture pivot (ADR-0008).
|
|
8
|
+
*
|
|
9
|
+
* Tenant isolation: every method scopes by projectId in the WHERE clause.
|
|
10
|
+
* App-layer guards in Sprint 2 ahead of full Supabase RLS migration in
|
|
11
|
+
* Sprint 3 / QAP-024. The negative path (wrong-project read) returns null /
|
|
12
|
+
* throws not_found rather than the wrong tenant's row — defense-in-depth
|
|
13
|
+
* against tenant enumeration.
|
|
14
|
+
*
|
|
15
|
+
* dashboardUrl generation: createQATicket inserts the row first, then updates
|
|
16
|
+
* with the deep-link URL built from dashboardBaseUrl + the new id. This is
|
|
17
|
+
* one extra round-trip vs. building the URL pre-insert with a known id, but
|
|
18
|
+
* keeps cuid generation server-side (Prisma defaults).
|
|
19
|
+
*/
|
|
20
|
+
import { type PrismaClient } from './prisma.js';
|
|
21
|
+
import { type QATicketStore } from '@derwinjs/sdk';
|
|
22
|
+
export interface PrismaQATicketStoreConfig {
|
|
23
|
+
/** Generated Prisma client. Pass an instance per process. */
|
|
24
|
+
prisma: PrismaClient;
|
|
25
|
+
/**
|
|
26
|
+
* Base URL for the Derwin dashboard. Used to build the per-ticket deep-link
|
|
27
|
+
* URL stored in QATicket.dashboardUrl and embedded in cross-link stub bodies.
|
|
28
|
+
* Trailing slashes are stripped.
|
|
29
|
+
*
|
|
30
|
+
* Examples: 'https://derwin.vercel.app', 'http://localhost:3000'.
|
|
31
|
+
*/
|
|
32
|
+
dashboardBaseUrl: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function createPrismaQATicketStore(config: PrismaQATicketStoreConfig): QATicketStore;
|
|
35
|
+
//# sourceMappingURL=qa-ticket-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qa-ticket-store.d.ts","sourceRoot":"","sources":["../src/qa-ticket-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAML,KAAK,aAAa,EAGnB,MAAM,eAAe,CAAC;AAIvB,MAAM,WAAW,yBAAyB;IACxC,6DAA6D;IAC7D,MAAM,EAAE,YAAY,CAAC;IACrB;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAID,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,yBAAyB,GAAG,aAAa,CA6O1F"}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QATicketStore — Prisma implementation against Derwin's QATicket table.
|
|
3
|
+
*
|
|
4
|
+
* QAP-018B (Sprint 2). Implements the QATicketStore contract from @derwinjs/sdk
|
|
5
|
+
* (see packages/sdk/src/types/qa-ticket-store.ts) against the @derwinjs/db
|
|
6
|
+
* Prisma client. Ships together with QAP-018 (the cross-link adapter) as
|
|
7
|
+
* the load-bearing proof of the architecture pivot (ADR-0008).
|
|
8
|
+
*
|
|
9
|
+
* Tenant isolation: every method scopes by projectId in the WHERE clause.
|
|
10
|
+
* App-layer guards in Sprint 2 ahead of full Supabase RLS migration in
|
|
11
|
+
* Sprint 3 / QAP-024. The negative path (wrong-project read) returns null /
|
|
12
|
+
* throws not_found rather than the wrong tenant's row — defense-in-depth
|
|
13
|
+
* against tenant enumeration.
|
|
14
|
+
*
|
|
15
|
+
* dashboardUrl generation: createQATicket inserts the row first, then updates
|
|
16
|
+
* with the deep-link URL built from dashboardBaseUrl + the new id. This is
|
|
17
|
+
* one extra round-trip vs. building the URL pre-insert with a known id, but
|
|
18
|
+
* keeps cuid generation server-side (Prisma defaults).
|
|
19
|
+
*/
|
|
20
|
+
import { Prisma } from './prisma.js';
|
|
21
|
+
import { QATicketStoreError, } from '@derwinjs/sdk';
|
|
22
|
+
// ─── Factory ─────────────────────────────────────────────────────────────
|
|
23
|
+
export function createPrismaQATicketStore(config) {
|
|
24
|
+
const { prisma } = config;
|
|
25
|
+
const dashboardBaseUrl = config.dashboardBaseUrl.replace(/\/$/, '');
|
|
26
|
+
function buildDashboardUrl(ticketId) {
|
|
27
|
+
return `${dashboardBaseUrl}/qa/tickets/${ticketId}`;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
async createQATicket(input) {
|
|
31
|
+
validateCreateInput(input);
|
|
32
|
+
// Insert row with dashboardUrl null; populate after the cuid is generated
|
|
33
|
+
// so the URL embeds the canonical ticket id.
|
|
34
|
+
let created;
|
|
35
|
+
try {
|
|
36
|
+
created = await prisma.qATicket.create({
|
|
37
|
+
data: {
|
|
38
|
+
projectId: input.projectId,
|
|
39
|
+
qaRunId: input.qaRunId,
|
|
40
|
+
title: input.title,
|
|
41
|
+
surface: input.surface,
|
|
42
|
+
classification: input.classification,
|
|
43
|
+
severity: input.severity,
|
|
44
|
+
riskTier: input.riskTier,
|
|
45
|
+
status: input.status ?? 'OPEN',
|
|
46
|
+
reproSteps: input.reproSteps,
|
|
47
|
+
suspectedRootCause: input.suspectedRootCause,
|
|
48
|
+
blastRadius: input.blastRadius,
|
|
49
|
+
proposedFixApproach: input.proposedFixApproach,
|
|
50
|
+
originatingTicketRef: input.originatingTicketRef,
|
|
51
|
+
externalRef: input.externalRef,
|
|
52
|
+
externalSystem: input.externalSystem,
|
|
53
|
+
autoMergeEligible: input.autoMergeEligible ?? false,
|
|
54
|
+
autoMergeRationale: input.autoMergeRationale,
|
|
55
|
+
crossLinkRefs: [],
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2003') {
|
|
61
|
+
throw new QATicketStoreError('fk_violation', 'Unknown projectId or qaRunId — foreign key violation', { projectId: input.projectId, qaRunId: input.qaRunId, prismaCode: e.code });
|
|
62
|
+
}
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
const dashboardUrl = buildDashboardUrl(created.id);
|
|
66
|
+
const finalRow = await prisma.qATicket.update({
|
|
67
|
+
where: { id: created.id },
|
|
68
|
+
data: { dashboardUrl },
|
|
69
|
+
});
|
|
70
|
+
const ticket = mapTicket(finalRow);
|
|
71
|
+
return {
|
|
72
|
+
ticket,
|
|
73
|
+
ref: { id: ticket.id, projectId: ticket.projectId },
|
|
74
|
+
dashboardUrl,
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
async updateQATicket(ref, patch) {
|
|
78
|
+
// Tenant-scope guard via findFirst — projectId mismatch reports not_found
|
|
79
|
+
// rather than letting Prisma's update succeed by id alone.
|
|
80
|
+
const existing = await prisma.qATicket.findFirst({
|
|
81
|
+
where: { id: ref.id, projectId: ref.projectId },
|
|
82
|
+
});
|
|
83
|
+
if (!existing) {
|
|
84
|
+
throw new QATicketStoreError('not_found', `Ticket ${ref.id} not found in project ${ref.projectId}`);
|
|
85
|
+
}
|
|
86
|
+
const data = {};
|
|
87
|
+
if (patch.status !== undefined)
|
|
88
|
+
data.status = patch.status;
|
|
89
|
+
if (patch.finalBucket !== undefined)
|
|
90
|
+
data.finalBucket = patch.finalBucket;
|
|
91
|
+
if (patch.bucketReason !== undefined)
|
|
92
|
+
data.bucketReason = patch.bucketReason;
|
|
93
|
+
if (patch.autoMergeEligible !== undefined) {
|
|
94
|
+
data.autoMergeEligible = patch.autoMergeEligible;
|
|
95
|
+
}
|
|
96
|
+
if (patch.autoMergeRationale !== undefined) {
|
|
97
|
+
data.autoMergeRationale = patch.autoMergeRationale;
|
|
98
|
+
}
|
|
99
|
+
if (patch.crossLinkRefs !== undefined) {
|
|
100
|
+
data.crossLinkRefs = patch.crossLinkRefs;
|
|
101
|
+
}
|
|
102
|
+
if (patch.closedAt !== undefined) {
|
|
103
|
+
data.closedAt = patch.closedAt;
|
|
104
|
+
}
|
|
105
|
+
// Optional audit field (QAP-019C). Stored when supplied; null is a
|
|
106
|
+
// valid "clear the field" signal. undefined means "don't change".
|
|
107
|
+
if (patch.resolvedBy !== undefined) {
|
|
108
|
+
data.resolvedBy = patch.resolvedBy;
|
|
109
|
+
}
|
|
110
|
+
// Side-effect: terminal status or finalBucket transition closes the ticket
|
|
111
|
+
// automatically if the caller didn't set closedAt explicitly and it isn't
|
|
112
|
+
// already closed.
|
|
113
|
+
const willCloseImplicitly = existing.closedAt === null &&
|
|
114
|
+
patch.closedAt === undefined &&
|
|
115
|
+
((patch.status !== undefined && isTerminalStatus(patch.status)) ||
|
|
116
|
+
patch.finalBucket !== undefined);
|
|
117
|
+
if (willCloseImplicitly) {
|
|
118
|
+
data.closedAt = new Date();
|
|
119
|
+
}
|
|
120
|
+
const updated = await prisma.qATicket.update({
|
|
121
|
+
where: { id: ref.id },
|
|
122
|
+
data,
|
|
123
|
+
});
|
|
124
|
+
return mapTicket(updated);
|
|
125
|
+
},
|
|
126
|
+
async getQATicket(ref) {
|
|
127
|
+
const row = await prisma.qATicket.findFirst({
|
|
128
|
+
where: { id: ref.id, projectId: ref.projectId },
|
|
129
|
+
});
|
|
130
|
+
return row ? mapTicket(row) : null;
|
|
131
|
+
},
|
|
132
|
+
async listQATickets(filter) {
|
|
133
|
+
if (!filter.projectId) {
|
|
134
|
+
throw new QATicketStoreError('invalid_input', 'projectId is required for listQATickets — there is no cross-tenant list query');
|
|
135
|
+
}
|
|
136
|
+
const where = {
|
|
137
|
+
projectId: filter.projectId,
|
|
138
|
+
};
|
|
139
|
+
if (filter.status !== undefined) {
|
|
140
|
+
where.status = Array.isArray(filter.status) ? { in: filter.status } : filter.status;
|
|
141
|
+
}
|
|
142
|
+
if (filter.finalBucket !== undefined) {
|
|
143
|
+
where.finalBucket = Array.isArray(filter.finalBucket)
|
|
144
|
+
? { in: filter.finalBucket }
|
|
145
|
+
: filter.finalBucket;
|
|
146
|
+
}
|
|
147
|
+
if (filter.classification !== undefined) {
|
|
148
|
+
where.classification = Array.isArray(filter.classification)
|
|
149
|
+
? { in: filter.classification }
|
|
150
|
+
: filter.classification;
|
|
151
|
+
}
|
|
152
|
+
if (filter.surface !== undefined) {
|
|
153
|
+
where.surface = Array.isArray(filter.surface) ? { in: filter.surface } : filter.surface;
|
|
154
|
+
}
|
|
155
|
+
if (filter.severity !== undefined) {
|
|
156
|
+
where.severity = Array.isArray(filter.severity) ? { in: filter.severity } : filter.severity;
|
|
157
|
+
}
|
|
158
|
+
if (filter.riskTier !== undefined) {
|
|
159
|
+
where.riskTier = Array.isArray(filter.riskTier) ? { in: filter.riskTier } : filter.riskTier;
|
|
160
|
+
}
|
|
161
|
+
// Age window: createdAt newer than (now - max), older than (now - min).
|
|
162
|
+
// ageHoursMin = "at least this old"; ageHoursMax = "no older than this".
|
|
163
|
+
if (filter.ageHoursMin !== undefined || filter.ageHoursMax !== undefined) {
|
|
164
|
+
const now = Date.now();
|
|
165
|
+
const createdAt = {};
|
|
166
|
+
if (filter.ageHoursMin !== undefined) {
|
|
167
|
+
createdAt.lte = new Date(now - filter.ageHoursMin * 3_600_000);
|
|
168
|
+
}
|
|
169
|
+
if (filter.ageHoursMax !== undefined) {
|
|
170
|
+
createdAt.gte = new Date(now - filter.ageHoursMax * 3_600_000);
|
|
171
|
+
}
|
|
172
|
+
where.createdAt = createdAt;
|
|
173
|
+
}
|
|
174
|
+
const limit = Math.min(Math.max(filter.limit ?? 50, 1), 200);
|
|
175
|
+
// Cursor pagination — fetch limit + 1 to determine hasMore
|
|
176
|
+
const findArgs = {
|
|
177
|
+
where,
|
|
178
|
+
orderBy: { createdAt: 'desc' },
|
|
179
|
+
take: limit + 1,
|
|
180
|
+
};
|
|
181
|
+
if (filter.cursor) {
|
|
182
|
+
findArgs.cursor = { id: filter.cursor };
|
|
183
|
+
findArgs.skip = 1;
|
|
184
|
+
}
|
|
185
|
+
const rows = await prisma.qATicket.findMany(findArgs);
|
|
186
|
+
const hasMore = rows.length > limit;
|
|
187
|
+
const visibleRows = hasMore ? rows.slice(0, limit) : rows;
|
|
188
|
+
const tickets = visibleRows.map(mapTicket);
|
|
189
|
+
const lastTicket = tickets[tickets.length - 1];
|
|
190
|
+
const nextCursor = hasMore && lastTicket ? lastTicket.id : null;
|
|
191
|
+
// Cheap total estimate. Acceptable at Phase 1-4 scale; revisit for
|
|
192
|
+
// approximate counts via pg_class.reltuples if this gets slow.
|
|
193
|
+
const totalEstimate = await prisma.qATicket.count({ where });
|
|
194
|
+
return { tickets, nextCursor, totalEstimate };
|
|
195
|
+
},
|
|
196
|
+
async attachArtifact(ref, artifact) {
|
|
197
|
+
const ticket = await prisma.qATicket.findFirst({
|
|
198
|
+
where: { id: ref.id, projectId: ref.projectId },
|
|
199
|
+
});
|
|
200
|
+
if (!ticket) {
|
|
201
|
+
throw new QATicketStoreError('not_found', `Ticket ${ref.id} not found in project ${ref.projectId}`);
|
|
202
|
+
}
|
|
203
|
+
await prisma.auditArtifact.create({
|
|
204
|
+
data: {
|
|
205
|
+
projectId: ref.projectId,
|
|
206
|
+
qaTicketId: ref.id,
|
|
207
|
+
artifactType: artifact.artifactType,
|
|
208
|
+
stage: artifact.stage,
|
|
209
|
+
// Storage backend defaults to filesystem in Sprint 2; Sprint 3 (QAP-025)
|
|
210
|
+
// wires Supabase Storage as the production backend per ADR-0010.
|
|
211
|
+
storageBackend: 'filesystem',
|
|
212
|
+
storageKey: artifact.storageKey,
|
|
213
|
+
contentType: artifact.contentType,
|
|
214
|
+
sizeBytes: artifact.sizeBytes,
|
|
215
|
+
// contentHash placeholder — Sprint 3 storage adapter computes from blob.
|
|
216
|
+
contentHash: '',
|
|
217
|
+
meta: {},
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
// Re-fetch the ticket with relations resolved for the caller.
|
|
221
|
+
const updated = await prisma.qATicket.findFirstOrThrow({
|
|
222
|
+
where: { id: ref.id, projectId: ref.projectId },
|
|
223
|
+
});
|
|
224
|
+
return mapTicket(updated);
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
229
|
+
/**
|
|
230
|
+
* Convert a Prisma QATicket row to the SDK value type. Json columns are
|
|
231
|
+
* narrowed via cast through unknown — Prisma's JsonValue is too loose to
|
|
232
|
+
* directly assign to typed shapes, but the schema invariant guarantees the
|
|
233
|
+
* shape on read paths that only come through this store.
|
|
234
|
+
*/
|
|
235
|
+
function mapTicket(row) {
|
|
236
|
+
return {
|
|
237
|
+
id: row.id,
|
|
238
|
+
projectId: row.projectId,
|
|
239
|
+
qaRunId: row.qaRunId,
|
|
240
|
+
externalRef: row.externalRef,
|
|
241
|
+
externalSystem: row.externalSystem,
|
|
242
|
+
originatingTicketRef: row.originatingTicketRef,
|
|
243
|
+
crossLinkRefs: (row.crossLinkRefs ?? []),
|
|
244
|
+
dashboardUrl: row.dashboardUrl,
|
|
245
|
+
surface: row.surface,
|
|
246
|
+
classification: row.classification,
|
|
247
|
+
severity: row.severity,
|
|
248
|
+
riskTier: row.riskTier,
|
|
249
|
+
status: row.status,
|
|
250
|
+
title: row.title,
|
|
251
|
+
reproSteps: row.reproSteps,
|
|
252
|
+
suspectedRootCause: row.suspectedRootCause,
|
|
253
|
+
blastRadius: row.blastRadius,
|
|
254
|
+
proposedFixApproach: row.proposedFixApproach,
|
|
255
|
+
autoMergeEligible: row.autoMergeEligible,
|
|
256
|
+
autoMergeRationale: row.autoMergeRationale,
|
|
257
|
+
finalBucket: row.finalBucket,
|
|
258
|
+
bucketReason: row.bucketReason,
|
|
259
|
+
createdAt: row.createdAt,
|
|
260
|
+
updatedAt: row.updatedAt,
|
|
261
|
+
closedAt: row.closedAt,
|
|
262
|
+
// QAP-019C — surfaced on reads after Phase 4 schema migration. `??` keeps
|
|
263
|
+
// the impl resilient against deep-mock fixtures that omit the field.
|
|
264
|
+
resolvedBy: row.resolvedBy ?? null,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function isTerminalStatus(s) {
|
|
268
|
+
return s === 'CLOSED_RESOLVED' || s === 'CLOSED_WONTFIX' || s === 'REGRESSED_REVERTED';
|
|
269
|
+
}
|
|
270
|
+
function validateCreateInput(input) {
|
|
271
|
+
if (!input.projectId) {
|
|
272
|
+
throw new QATicketStoreError('invalid_input', 'projectId is required');
|
|
273
|
+
}
|
|
274
|
+
if (!input.qaRunId) {
|
|
275
|
+
throw new QATicketStoreError('invalid_input', 'qaRunId is required');
|
|
276
|
+
}
|
|
277
|
+
if (!input.title.trim()) {
|
|
278
|
+
throw new QATicketStoreError('invalid_input', 'title is required');
|
|
279
|
+
}
|
|
280
|
+
if (!input.classification.trim()) {
|
|
281
|
+
throw new QATicketStoreError('invalid_input', 'classification is required');
|
|
282
|
+
}
|
|
283
|
+
if (!Array.isArray(input.reproSteps) || input.reproSteps.length === 0) {
|
|
284
|
+
throw new QATicketStoreError('invalid_input', 'reproSteps must be a non-empty array');
|
|
285
|
+
}
|
|
286
|
+
if (!input.suspectedRootCause.trim()) {
|
|
287
|
+
throw new QATicketStoreError('invalid_input', 'suspectedRootCause is required');
|
|
288
|
+
}
|
|
289
|
+
if (!input.proposedFixApproach.trim()) {
|
|
290
|
+
throw new QATicketStoreError('invalid_input', 'proposedFixApproach is required');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=qa-ticket-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qa-ticket-store.js","sourceRoot":"","sources":["../src/qa-ticket-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,EAAqB,MAAM,aAAa,CAAC;AACxD,OAAO,EACL,kBAAkB,GAQnB,MAAM,eAAe,CAAC;AAiBvB,4EAA4E;AAE5E,MAAM,UAAU,yBAAyB,CAAC,MAAiC;IACzE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAC1B,MAAM,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAEpE,SAAS,iBAAiB,CAAC,QAAgB;QACzC,OAAO,GAAG,gBAAgB,eAAe,QAAQ,EAAE,CAAC;IACtD,CAAC;IAED,OAAO;QACL,KAAK,CAAC,cAAc,CAAC,KAAK;YACxB,mBAAmB,CAAC,KAAK,CAAC,CAAC;YAE3B,0EAA0E;YAC1E,6CAA6C;YAC7C,IAAI,OAAO,CAAC;YACZ,IAAI,CAAC;gBACH,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;oBACrC,IAAI,EAAE;wBACJ,SAAS,EAAE,KAAK,CAAC,SAAS;wBAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;wBACtB,KAAK,EAAE,KAAK,CAAC,KAAK;wBAClB,OAAO,EAAE,KAAK,CAAC,OAAO;wBACtB,cAAc,EAAE,KAAK,CAAC,cAAc;wBACpC,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,MAAM;wBAC9B,UAAU,EAAE,KAAK,CAAC,UAA8C;wBAChE,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;wBAC5C,WAAW,EAAE,KAAK,CAAC,WAA+C;wBAClE,mBAAmB,EAAE,KAAK,CAAC,mBAAmB;wBAC9C,oBAAoB,EAAE,KAAK,CAAC,oBAAoB;wBAChD,WAAW,EAAE,KAAK,CAAC,WAAW;wBAC9B,cAAc,EAAE,KAAK,CAAC,cAAc;wBACpC,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,IAAI,KAAK;wBACnD,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;wBAC5C,aAAa,EAAE,EAAE;qBAClB;iBACF,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAI,CAAC,YAAY,MAAM,CAAC,6BAA6B,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAC5E,MAAM,IAAI,kBAAkB,CAC1B,cAAc,EACd,sDAAsD,EACtD,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,IAAI,EAAE,CAC3E,CAAC;gBACJ,CAAC;gBACD,MAAM,CAAC,CAAC;YACV,CAAC;YAED,MAAM,YAAY,GAAG,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACnD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC5C,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE;gBACzB,IAAI,EAAE,EAAE,YAAY,EAAE;aACvB,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;YACnC,OAAO;gBACL,MAAM;gBACN,GAAG,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE;gBACnD,YAAY;aACb,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,cAAc,CAAC,GAAG,EAAE,KAAK;YAC7B,0EAA0E;YAC1E,2DAA2D;YAC3D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAC/C,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE;aAChD,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,kBAAkB,CAC1B,WAAW,EACX,UAAU,GAAG,CAAC,EAAE,yBAAyB,GAAG,CAAC,SAAS,EAAE,CACzD,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,GAA+B,EAAE,CAAC;YAE5C,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;YAC3D,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;gBAAE,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;YAC1E,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS;gBAAE,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;YAC7E,IAAI,KAAK,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;gBAC1C,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC,iBAAiB,CAAC;YACnD,CAAC;YACD,IAAI,KAAK,CAAC,kBAAkB,KAAK,SAAS,EAAE,CAAC;gBAC3C,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC,kBAAkB,CAAC;YACrD,CAAC;YACD,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;gBACtC,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,aAAiD,CAAC;YAC/E,CAAC;YACD,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACjC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;YACjC,CAAC;YACD,mEAAmE;YACnE,kEAAkE;YAClE,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBACnC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;YACrC,CAAC;YAED,2EAA2E;YAC3E,0EAA0E;YAC1E,kBAAkB;YAClB,MAAM,mBAAmB,GACvB,QAAQ,CAAC,QAAQ,KAAK,IAAI;gBAC1B,KAAK,CAAC,QAAQ,KAAK,SAAS;gBAC5B,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,gBAAgB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBAC7D,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC;YACrC,IAAI,mBAAmB,EAAE,CAAC;gBACxB,IAAI,CAAC,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC;YAC7B,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC3C,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE;gBACrB,IAAI;aACL,CAAC,CAAC;YACH,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,GAAG;YACnB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAC1C,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE;aAChD,CAAC,CAAC;YACH,OAAO,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACrC,CAAC;QAED,KAAK,CAAC,aAAa,CAAC,MAAM;YACxB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;gBACtB,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,+EAA+E,CAChF,CAAC;YACJ,CAAC;YAED,MAAM,KAAK,GAA8B;gBACvC,SAAS,EAAE,MAAM,CAAC,SAAS;aAC5B,CAAC;YAEF,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAChC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;YACtF,CAAC;YACD,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;gBACrC,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC;oBACnD,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,WAAW,EAAE;oBAC5B,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC;YACzB,CAAC;YACD,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;gBACxC,KAAK,CAAC,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC;oBACzD,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,cAAc,EAAE;oBAC/B,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC;YAC5B,CAAC;YACD,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBACjC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;YAC1F,CAAC;YACD,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAClC,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC9F,CAAC;YACD,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAClC,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC9F,CAAC;YAED,wEAAwE;YACxE,yEAAyE;YACzE,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;gBACzE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACvB,MAAM,SAAS,GAA0B,EAAE,CAAC;gBAC5C,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;oBACrC,SAAS,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;gBACjE,CAAC;gBACD,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;oBACrC,SAAS,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;gBACjE,CAAC;gBACD,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;YAC9B,CAAC;YAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAE7D,2DAA2D;YAC3D,MAAM,QAAQ,GAAgC;gBAC5C,KAAK;gBACL,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;gBAC9B,IAAI,EAAE,KAAK,GAAG,CAAC;aAChB,CAAC;YACF,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,QAAQ,CAAC,MAAM,GAAG,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;gBACxC,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC;YACpB,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACtD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YACpC,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC1D,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC3C,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC/C,MAAM,UAAU,GAAG,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YAEhE,mEAAmE;YACnE,+DAA+D;YAC/D,MAAM,aAAa,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YAE7D,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC;QAChD,CAAC;QAED,KAAK,CAAC,cAAc,CAAC,GAAG,EAAE,QAAQ;YAChC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAC7C,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE;aAChD,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,kBAAkB,CAC1B,WAAW,EACX,UAAU,GAAG,CAAC,EAAE,yBAAyB,GAAG,CAAC,SAAS,EAAE,CACzD,CAAC;YACJ,CAAC;YAED,MAAM,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC;gBAChC,IAAI,EAAE;oBACJ,SAAS,EAAE,GAAG,CAAC,SAAS;oBACxB,UAAU,EAAE,GAAG,CAAC,EAAE;oBAClB,YAAY,EAAE,QAAQ,CAAC,YAAY;oBACnC,KAAK,EAAE,QAAQ,CAAC,KAAK;oBACrB,yEAAyE;oBACzE,iEAAiE;oBACjE,cAAc,EAAE,YAAY;oBAC5B,UAAU,EAAE,QAAQ,CAAC,UAAU;oBAC/B,WAAW,EAAE,QAAQ,CAAC,WAAW;oBACjC,SAAS,EAAE,QAAQ,CAAC,SAAS;oBAC7B,yEAAyE;oBACzE,WAAW,EAAE,EAAE;oBACf,IAAI,EAAE,EAAE;iBACT;aACF,CAAC,CAAC;YAEH,8DAA8D;YAC9D,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC;gBACrD,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE;aAChD,CAAC,CAAC;YACH,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,4EAA4E;AAE5E;;;;;GAKG;AACH,SAAS,SAAS,CAAC,GAAqD;IACtE,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,oBAAoB,EAAE,GAAG,CAAC,oBAAoB;QAC9C,aAAa,EAAE,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAA8B;QACrE,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,UAAU,EAAE,GAAG,CAAC,UAAoC;QACpD,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;QAC1C,WAAW,EAAE,GAAG,CAAC,WAAqC;QACtD,mBAAmB,EAAE,GAAG,CAAC,mBAAmB;QAC5C,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;QACxC,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;QAC1C,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,0EAA0E;QAC1E,qEAAqE;QACrE,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,IAAI;KACnC,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAe;IACvC,OAAO,CAAC,KAAK,iBAAiB,IAAI,CAAC,KAAK,gBAAgB,IAAI,CAAC,KAAK,oBAAoB,CAAC;AACzF,CAAC;AAED,SAAS,mBAAmB,CAAC,KAA0B;IACrD,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QACrB,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,uBAAuB,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,qBAAqB,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QACxB,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,mBAAmB,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC;QACjC,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,4BAA4B,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,sCAAsC,CAAC,CAAC;IACxF,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,IAAI,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,gCAAgC,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,iCAAiC,CAAC,CAAC;IACnF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
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 { type PrismaClient } from './prisma.js';
|
|
22
|
+
import { type QAUniformity, type QAUniformityStatus, type QAUniformityStore } from '@derwinjs/sdk';
|
|
23
|
+
export interface PrismaQAUniformityStoreConfig {
|
|
24
|
+
/** Generated Prisma client. Pass an instance per process. */
|
|
25
|
+
prisma: PrismaClient;
|
|
26
|
+
}
|
|
27
|
+
export declare function createPrismaQAUniformityStore(config: PrismaQAUniformityStoreConfig): QAUniformityStore;
|
|
28
|
+
interface PrismaQAUniformityRow {
|
|
29
|
+
id: string;
|
|
30
|
+
projectId: string;
|
|
31
|
+
qaRunId: string | null;
|
|
32
|
+
pagePath: string;
|
|
33
|
+
ruleName: string;
|
|
34
|
+
ruleCategory: string;
|
|
35
|
+
status: QAUniformityStatus;
|
|
36
|
+
violationDetail: string | null;
|
|
37
|
+
createdAt: Date;
|
|
38
|
+
}
|
|
39
|
+
export declare function mapUniformity(row: PrismaQAUniformityRow): QAUniformity;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=qa-uniformity-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qa-uniformity-store.d.ts","sourceRoot":"","sources":["../src/qa-uniformity-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAEL,KAAK,YAAY,EAMjB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EAIvB,MAAM,eAAe,CAAC;AAIvB,MAAM,WAAW,6BAA6B;IAC5C,6DAA6D;IAC7D,MAAM,EAAE,YAAY,CAAC;CACtB;AASD,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,6BAA6B,GACpC,iBAAiB,CAyNnB;AAkFD,UAAU,qBAAqB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,qBAAqB,GAAG,YAAY,CAYtE"}
|