@oscharko-dev/keiko-evidence 0.2.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/dist/.tsbuildinfo +1 -0
- package/dist/aggregate.d.ts +4 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/aggregate.js +21 -0
- package/dist/build.d.ts +3 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +227 -0
- package/dist/connected-context-evidence.d.ts +47 -0
- package/dist/connected-context-evidence.d.ts.map +1 -0
- package/dist/connected-context-evidence.js +197 -0
- package/dist/errors.d.ts +3 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +4 -0
- package/dist/index-api.d.ts +15 -0
- package/dist/index-api.d.ts.map +1 -0
- package/dist/index-api.js +136 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/persist.d.ts +9 -0
- package/dist/persist.d.ts.map +1 -0
- package/dist/persist.js +40 -0
- package/dist/promptEnhancement/index.d.ts +7 -0
- package/dist/promptEnhancement/index.d.ts.map +1 -0
- package/dist/promptEnhancement/index.js +10 -0
- package/dist/promptEnhancement/manifestSchema.d.ts +71 -0
- package/dist/promptEnhancement/manifestSchema.d.ts.map +1 -0
- package/dist/promptEnhancement/manifestSchema.js +307 -0
- package/dist/promptEnhancement/redaction.d.ts +17 -0
- package/dist/promptEnhancement/redaction.d.ts.map +1 -0
- package/dist/promptEnhancement/redaction.js +66 -0
- package/dist/promptEnhancement/store.d.ts +64 -0
- package/dist/promptEnhancement/store.d.ts.map +1 -0
- package/dist/promptEnhancement/store.js +409 -0
- package/dist/qualityIntelligence/candidatesArtifact.d.ts +74 -0
- package/dist/qualityIntelligence/candidatesArtifact.d.ts.map +1 -0
- package/dist/qualityIntelligence/candidatesArtifact.js +258 -0
- package/dist/qualityIntelligence/companionStore.d.ts +37 -0
- package/dist/qualityIntelligence/companionStore.d.ts.map +1 -0
- package/dist/qualityIntelligence/companionStore.js +158 -0
- package/dist/qualityIntelligence/figmaSnapshot/schema.d.ts +123 -0
- package/dist/qualityIntelligence/figmaSnapshot/schema.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaSnapshot/schema.js +163 -0
- package/dist/qualityIntelligence/figmaSnapshot/store.d.ts +144 -0
- package/dist/qualityIntelligence/figmaSnapshot/store.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaSnapshot/store.js +898 -0
- package/dist/qualityIntelligence/index.d.ts +18 -0
- package/dist/qualityIntelligence/index.d.ts.map +1 -0
- package/dist/qualityIntelligence/index.js +21 -0
- package/dist/qualityIntelligence/manifestSchema.d.ts +154 -0
- package/dist/qualityIntelligence/manifestSchema.d.ts.map +1 -0
- package/dist/qualityIntelligence/manifestSchema.js +70 -0
- package/dist/qualityIntelligence/redaction.d.ts +10 -0
- package/dist/qualityIntelligence/redaction.d.ts.map +1 -0
- package/dist/qualityIntelligence/redaction.js +103 -0
- package/dist/qualityIntelligence/retention.d.ts +71 -0
- package/dist/qualityIntelligence/retention.d.ts.map +1 -0
- package/dist/qualityIntelligence/retention.js +287 -0
- package/dist/qualityIntelligence/retentionPolicy.d.ts +10 -0
- package/dist/qualityIntelligence/retentionPolicy.d.ts.map +1 -0
- package/dist/qualityIntelligence/retentionPolicy.js +38 -0
- package/dist/qualityIntelligence/store.d.ts +95 -0
- package/dist/qualityIntelligence/store.d.ts.map +1 -0
- package/dist/qualityIntelligence/store.js +483 -0
- package/dist/redaction.d.ts +2 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +4 -0
- package/dist/report.d.ts +17 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +50 -0
- package/dist/retention.d.ts +4 -0
- package/dist/retention.d.ts.map +1 -0
- package/dist/retention.js +95 -0
- package/dist/runid.d.ts +2 -0
- package/dist/runid.d.ts.map +1 -0
- package/dist/runid.js +4 -0
- package/dist/side-file.d.ts +9 -0
- package/dist/side-file.d.ts.map +1 -0
- package/dist/side-file.js +102 -0
- package/dist/store.d.ts +8 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +332 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1 -0
- package/dist/workflow-evidence.d.ts +36 -0
- package/dist/workflow-evidence.d.ts.map +1 -0
- package/dist/workflow-evidence.js +158 -0
- package/package.json +32 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// Quality Intelligence generated-candidate artifact (Issue #274/#280, Epic #270, ADR-0023 D8).
|
|
2
|
+
//
|
|
3
|
+
// The run manifest (`<runId>.qi.json`) carries only counts + refs. The reviewable, exportable
|
|
4
|
+
// product — the generated test-case bodies — is persisted here as a companion artifact
|
|
5
|
+
// `<runId>.candidates.json`. Bodies are redacted (every string leaf) BEFORE persist so a candidate
|
|
6
|
+
// that echoed a secret-shaped source string cannot reach disk, preview, or export unredacted
|
|
7
|
+
// (Issue #284). Stored as plain rows (branded IDs collapse to strings on the wire).
|
|
8
|
+
import { QualityIntelligence } from "@oscharko-dev/keiko-contracts";
|
|
9
|
+
import { createNodeContainedJsonArtifactStore, } from "./companionStore.js";
|
|
10
|
+
import { EvidenceReadError } from "../errors.js";
|
|
11
|
+
export const QUALITY_INTELLIGENCE_CANDIDATES_SCHEMA_VERSION = 1;
|
|
12
|
+
// Exported (but intentionally NOT re-exported from the package barrel) so the run-deletion path
|
|
13
|
+
// can reference the evidence-owned companion suffix without duplicating the literal.
|
|
14
|
+
export const CANDIDATES_SUFFIX = ".candidates.json";
|
|
15
|
+
const cloneQualityVerdict = (verdict) => ({
|
|
16
|
+
verdict: verdict.verdict,
|
|
17
|
+
score: verdict.score,
|
|
18
|
+
dimensions: verdict.dimensions.map((dimension) => ({ ...dimension })),
|
|
19
|
+
overallRationale: verdict.overallRationale,
|
|
20
|
+
});
|
|
21
|
+
const toRow = (candidate) => {
|
|
22
|
+
const qualityVerdict = candidate.qualityVerdict;
|
|
23
|
+
return {
|
|
24
|
+
id: String(candidate.id),
|
|
25
|
+
title: candidate.title,
|
|
26
|
+
preconditions: [...candidate.preconditions],
|
|
27
|
+
steps: [...candidate.steps],
|
|
28
|
+
expectedResults: [...candidate.expectedResults],
|
|
29
|
+
priority: candidate.priority,
|
|
30
|
+
riskClass: candidate.riskClass,
|
|
31
|
+
tags: [...candidate.tags],
|
|
32
|
+
status: candidate.status,
|
|
33
|
+
derivedFromAtomIds: candidate.derivedFromAtomIds.map(String),
|
|
34
|
+
...(qualityVerdict !== undefined
|
|
35
|
+
? { qualityVerdict: cloneQualityVerdict(qualityVerdict) }
|
|
36
|
+
: {}),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const PRIORITIES = new Set(QualityIntelligence.QUALITY_INTELLIGENCE_PRIORITIES);
|
|
40
|
+
const RISK_CLASSES = new Set(QualityIntelligence.QUALITY_INTELLIGENCE_RISK_CLASSES);
|
|
41
|
+
const TEST_CASE_STATUSES = new Set(QualityIntelligence.QUALITY_INTELLIGENCE_TEST_CASE_STATUSES);
|
|
42
|
+
const TEST_QUALITY_DIMENSIONS = new Set(QualityIntelligence.TEST_QUALITY_RUBRIC_DIMENSIONS);
|
|
43
|
+
const isObjectRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
44
|
+
const isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
|
|
45
|
+
const isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
46
|
+
const isString = (value) => typeof value === "string";
|
|
47
|
+
const isPriority = (value) => typeof value === "string" && PRIORITIES.has(value);
|
|
48
|
+
const isRiskClass = (value) => typeof value === "string" && RISK_CLASSES.has(value);
|
|
49
|
+
const isTestCaseStatus = (value) => typeof value === "string" && TEST_CASE_STATUSES.has(value);
|
|
50
|
+
const isQualityVerdictValue = (value) => value === "strong" || value === "weak";
|
|
51
|
+
const isScore = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100;
|
|
52
|
+
const isDimensionScore = (value) => isScore(value) && Number.isInteger(value);
|
|
53
|
+
const isRubricDimensionName = (value) => typeof value === "string" && TEST_QUALITY_DIMENSIONS.has(value);
|
|
54
|
+
function isRubricDimension(value) {
|
|
55
|
+
return (isObjectRecord(value) &&
|
|
56
|
+
isRubricDimensionName(value.name) &&
|
|
57
|
+
isDimensionScore(value.score) &&
|
|
58
|
+
isString(value.rationale));
|
|
59
|
+
}
|
|
60
|
+
function isCandidateQualityVerdict(value) {
|
|
61
|
+
return (isObjectRecord(value) &&
|
|
62
|
+
isQualityVerdictValue(value.verdict) &&
|
|
63
|
+
isScore(value.score) &&
|
|
64
|
+
Array.isArray(value.dimensions) &&
|
|
65
|
+
value.dimensions.every(isRubricDimension) &&
|
|
66
|
+
isString(value.overallRationale));
|
|
67
|
+
}
|
|
68
|
+
const isEditedBy = (value) => value === "human" || value === "api";
|
|
69
|
+
// An edited list field persists as an array of non-blank strings — the edit route rejects blank
|
|
70
|
+
// ("") items before persist (editRoutes.ts isListField). Empty arrays are accepted on read so a
|
|
71
|
+
// legitimately-cleared preconditions/tags list still loads: strict on write, fail-open on read. The
|
|
72
|
+
// route additionally enforces minItems:1 for steps/expectedResults, but that is a write-time domain
|
|
73
|
+
// rule (a body must have at least one step); on read we stay deliberately fail-open for every list
|
|
74
|
+
// field here — this validator only gates the provenance log shape, never the candidate row itself
|
|
75
|
+
// (rows carry the merged effective value, validated separately by isStringArray).
|
|
76
|
+
const isListOfNonBlankStrings = (value) => Array.isArray(value) && value.every((item) => typeof item === "string" && item.length > 0);
|
|
77
|
+
const EDITABLE_FIELD_VALIDATORS = {
|
|
78
|
+
title: isNonEmptyString,
|
|
79
|
+
preconditions: isListOfNonBlankStrings,
|
|
80
|
+
steps: isListOfNonBlankStrings,
|
|
81
|
+
expectedResults: isListOfNonBlankStrings,
|
|
82
|
+
priority: isPriority,
|
|
83
|
+
riskClass: isRiskClass,
|
|
84
|
+
tags: isListOfNonBlankStrings,
|
|
85
|
+
};
|
|
86
|
+
function isEditableFields(value) {
|
|
87
|
+
if (!isObjectRecord(value))
|
|
88
|
+
return false;
|
|
89
|
+
const keys = Object.keys(value);
|
|
90
|
+
return keys.every((key) => EDITABLE_FIELD_VALIDATORS[key]?.(value[key]) ?? false);
|
|
91
|
+
}
|
|
92
|
+
const CANDIDATE_ROW_VALIDATORS = [
|
|
93
|
+
(row) => isNonEmptyString(row.id),
|
|
94
|
+
(row) => isNonEmptyString(row.title),
|
|
95
|
+
(row) => isStringArray(row.preconditions),
|
|
96
|
+
(row) => isStringArray(row.steps),
|
|
97
|
+
(row) => isStringArray(row.expectedResults),
|
|
98
|
+
(row) => isPriority(row.priority),
|
|
99
|
+
(row) => isRiskClass(row.riskClass),
|
|
100
|
+
(row) => isStringArray(row.tags),
|
|
101
|
+
(row) => isTestCaseStatus(row.status),
|
|
102
|
+
(row) => isStringArray(row.derivedFromAtomIds),
|
|
103
|
+
(row) => row.qualityVerdict === undefined || isCandidateQualityVerdict(row.qualityVerdict),
|
|
104
|
+
];
|
|
105
|
+
function isCandidateRow(value) {
|
|
106
|
+
return isObjectRecord(value) && CANDIDATE_ROW_VALIDATORS.every((validate) => validate(value));
|
|
107
|
+
}
|
|
108
|
+
function isEditedRevision(value) {
|
|
109
|
+
if (!isObjectRecord(value))
|
|
110
|
+
return false;
|
|
111
|
+
const provenance = value.provenance;
|
|
112
|
+
return (isNonEmptyString(value.candidateId) &&
|
|
113
|
+
isObjectRecord(provenance) &&
|
|
114
|
+
isNonEmptyString(provenance.editedAt) &&
|
|
115
|
+
isEditedBy(provenance.editedBy) &&
|
|
116
|
+
isNonEmptyString(provenance.editorLabel) &&
|
|
117
|
+
isEditableFields(value.editedFields));
|
|
118
|
+
}
|
|
119
|
+
function invalidArtifact(message) {
|
|
120
|
+
throw new EvidenceReadError(message);
|
|
121
|
+
}
|
|
122
|
+
function readArtifactStringField(record, key, message) {
|
|
123
|
+
const value = record[key];
|
|
124
|
+
if (!isNonEmptyString(value))
|
|
125
|
+
invalidArtifact(message);
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
function readArtifactCandidates(record) {
|
|
129
|
+
const { candidates } = record;
|
|
130
|
+
if (!Array.isArray(candidates) || !candidates.every(isCandidateRow)) {
|
|
131
|
+
invalidArtifact("QI candidates companion schema invalid: candidates[] has an invalid row");
|
|
132
|
+
}
|
|
133
|
+
return candidates;
|
|
134
|
+
}
|
|
135
|
+
function readArtifactEditedRevisions(record) {
|
|
136
|
+
const { editedRevisions } = record;
|
|
137
|
+
if (editedRevisions === undefined)
|
|
138
|
+
return undefined;
|
|
139
|
+
if (!Array.isArray(editedRevisions) || !editedRevisions.every(isEditedRevision)) {
|
|
140
|
+
invalidArtifact("QI candidates companion schema invalid: editedRevisions[] has an invalid revision");
|
|
141
|
+
}
|
|
142
|
+
return editedRevisions;
|
|
143
|
+
}
|
|
144
|
+
// Strict-schema gate on read: reject any artifact whose version literal drifts so a stale or
|
|
145
|
+
// tampered file fails closed instead of surfacing a wrong shape to the BFF.
|
|
146
|
+
const parseArtifact = (value) => {
|
|
147
|
+
if (!isObjectRecord(value)) {
|
|
148
|
+
invalidArtifact("QI candidates companion schema invalid: expected an object");
|
|
149
|
+
}
|
|
150
|
+
const record = value;
|
|
151
|
+
if (record.qiCandidatesSchemaVersion !== QUALITY_INTELLIGENCE_CANDIDATES_SCHEMA_VERSION) {
|
|
152
|
+
invalidArtifact("QI candidates companion schema invalid: unsupported schema version");
|
|
153
|
+
}
|
|
154
|
+
const runId = readArtifactStringField(record, "runId", "QI candidates companion schema invalid: runId must be a string");
|
|
155
|
+
const generatedAt = readArtifactStringField(record, "generatedAt", "QI candidates companion schema invalid: generatedAt must be a string");
|
|
156
|
+
const candidates = readArtifactCandidates(record);
|
|
157
|
+
const editedRevisions = readArtifactEditedRevisions(record);
|
|
158
|
+
return {
|
|
159
|
+
qiCandidatesSchemaVersion: QUALITY_INTELLIGENCE_CANDIDATES_SCHEMA_VERSION,
|
|
160
|
+
runId,
|
|
161
|
+
generatedAt,
|
|
162
|
+
candidates,
|
|
163
|
+
...(editedRevisions !== undefined ? { editedRevisions } : {}),
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
const storeFor = (evidenceDir) => createNodeContainedJsonArtifactStore(evidenceDir, CANDIDATES_SUFFIX, { parse: parseArtifact });
|
|
167
|
+
/**
|
|
168
|
+
* Persist the generated candidate bodies for a run. Redacts every string leaf first, then writes
|
|
169
|
+
* the companion artifact atomically. Returns the on-disk location.
|
|
170
|
+
*/
|
|
171
|
+
export const recordQualityIntelligenceCandidates = (input) => {
|
|
172
|
+
const rows = input.candidates.map(toRow);
|
|
173
|
+
const redactedRows = input.redact(rows);
|
|
174
|
+
const redactedEditedRevisions = input.editedRevisions === undefined
|
|
175
|
+
? undefined
|
|
176
|
+
: input.redact(input.editedRevisions);
|
|
177
|
+
const artifact = {
|
|
178
|
+
qiCandidatesSchemaVersion: QUALITY_INTELLIGENCE_CANDIDATES_SCHEMA_VERSION,
|
|
179
|
+
runId: input.runId,
|
|
180
|
+
generatedAt: input.generatedAt,
|
|
181
|
+
candidates: redactedRows,
|
|
182
|
+
...(redactedEditedRevisions !== undefined ? { editedRevisions: redactedEditedRevisions } : {}),
|
|
183
|
+
};
|
|
184
|
+
return storeFor(input.evidenceDir).record(input.runId, artifact);
|
|
185
|
+
};
|
|
186
|
+
export const loadQualityIntelligenceCandidates = (runId, options) => storeFor(options.evidenceDir).load(runId);
|
|
187
|
+
export const deleteQualityIntelligenceCandidates = (runId, options) => storeFor(options.evidenceDir).delete(runId);
|
|
188
|
+
const EDITABLE_KEYS = [
|
|
189
|
+
"title",
|
|
190
|
+
"preconditions",
|
|
191
|
+
"steps",
|
|
192
|
+
"expectedResults",
|
|
193
|
+
"priority",
|
|
194
|
+
"riskClass",
|
|
195
|
+
"tags",
|
|
196
|
+
];
|
|
197
|
+
const hasEditedField = (fields) => EDITABLE_KEYS.some((key) => fields[key] !== undefined);
|
|
198
|
+
const mergeRow = (row, fields) => ({
|
|
199
|
+
...row,
|
|
200
|
+
...(fields.title !== undefined ? { title: fields.title } : {}),
|
|
201
|
+
...(fields.preconditions !== undefined ? { preconditions: [...fields.preconditions] } : {}),
|
|
202
|
+
...(fields.steps !== undefined ? { steps: [...fields.steps] } : {}),
|
|
203
|
+
...(fields.expectedResults !== undefined ? { expectedResults: [...fields.expectedResults] } : {}),
|
|
204
|
+
...(fields.priority !== undefined ? { priority: fields.priority } : {}),
|
|
205
|
+
...(fields.riskClass !== undefined ? { riskClass: fields.riskClass } : {}),
|
|
206
|
+
...(fields.tags !== undefined ? { tags: [...fields.tags] } : {}),
|
|
207
|
+
});
|
|
208
|
+
const sameStringArray = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
|
|
209
|
+
const sameRow = (left, right) => left.id === right.id &&
|
|
210
|
+
left.title === right.title &&
|
|
211
|
+
sameStringArray(left.preconditions, right.preconditions) &&
|
|
212
|
+
sameStringArray(left.steps, right.steps) &&
|
|
213
|
+
sameStringArray(left.expectedResults, right.expectedResults) &&
|
|
214
|
+
left.priority === right.priority &&
|
|
215
|
+
left.riskClass === right.riskClass &&
|
|
216
|
+
sameStringArray(left.tags, right.tags) &&
|
|
217
|
+
left.status === right.status &&
|
|
218
|
+
sameStringArray(left.derivedFromAtomIds, right.derivedFromAtomIds);
|
|
219
|
+
export const applyQualityIntelligenceCandidateEdit = (input) => {
|
|
220
|
+
if (!hasEditedField(input.editedFields))
|
|
221
|
+
return { ok: false, reason: "no-edited-fields" };
|
|
222
|
+
const store = storeFor(input.evidenceDir);
|
|
223
|
+
const artifact = store.load(input.runId);
|
|
224
|
+
if (artifact === undefined)
|
|
225
|
+
return { ok: false, reason: "artifact-not-found" };
|
|
226
|
+
const existing = artifact.candidates.find((row) => row.id === input.candidateId);
|
|
227
|
+
if (existing === undefined)
|
|
228
|
+
return { ok: false, reason: "candidate-not-found" };
|
|
229
|
+
const redactedFields = input.redact(input.editedFields);
|
|
230
|
+
const updatedRow = mergeRow(existing, redactedFields);
|
|
231
|
+
if (sameRow(existing, updatedRow)) {
|
|
232
|
+
return { ok: true, candidate: existing, changed: false };
|
|
233
|
+
}
|
|
234
|
+
const candidates = artifact.candidates.map((row) => row.id === input.candidateId ? updatedRow : row);
|
|
235
|
+
// Redact the provenance label before persist: `editorLabel` is the one user-controlled free-text
|
|
236
|
+
// field of the provenance (the edit route derives it from the request body) and is stored as a
|
|
237
|
+
// string leaf of the candidates artifact — never write an unredacted label to disk. This restores
|
|
238
|
+
// parity with recordQualityIntelligenceCandidates (which redacts the whole editedRevisions[]) and
|
|
239
|
+
// upholds the file-level "every string leaf redacted before persist" invariant. Only the label is
|
|
240
|
+
// routed through the redactor: `editedAt` (machine ISO timestamp) and `editedBy` (closed enum) are
|
|
241
|
+
// server-set, carry no secret, and must stay byte-exact so the strict read validator still accepts
|
|
242
|
+
// them. `candidateId` likewise stays the matched row id so the revision keeps referencing its row.
|
|
243
|
+
const redactedProvenance = {
|
|
244
|
+
...input.provenance,
|
|
245
|
+
editorLabel: input.redact(input.provenance.editorLabel),
|
|
246
|
+
};
|
|
247
|
+
const revision = {
|
|
248
|
+
candidateId: input.candidateId,
|
|
249
|
+
provenance: redactedProvenance,
|
|
250
|
+
editedFields: redactedFields,
|
|
251
|
+
};
|
|
252
|
+
store.record(input.runId, {
|
|
253
|
+
...artifact,
|
|
254
|
+
candidates,
|
|
255
|
+
editedRevisions: [...(artifact.editedRevisions ?? []), revision],
|
|
256
|
+
});
|
|
257
|
+
return { ok: true, candidate: updatedRow, changed: true };
|
|
258
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type WorkspaceFs } from "@oscharko-dev/keiko-workspace";
|
|
2
|
+
export interface ContainedJsonArtifactStore<T> {
|
|
3
|
+
readonly record: (runId: string, value: T) => string;
|
|
4
|
+
readonly load: (runId: string) => T | undefined;
|
|
5
|
+
readonly delete: (runId: string) => boolean;
|
|
6
|
+
readonly location: (runId: string) => string;
|
|
7
|
+
}
|
|
8
|
+
export interface ContainedJsonArtifactStoreOptions<T> {
|
|
9
|
+
readonly fs?: WorkspaceFs;
|
|
10
|
+
readonly randomSuffix?: () => string;
|
|
11
|
+
/** Validates + narrows a parsed JSON value; return `undefined` to reject a corrupt artifact. */
|
|
12
|
+
readonly parse: (value: unknown) => T | undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build a node-backed contained JSON artifact store for one `suffix` (e.g. `.candidates.json`).
|
|
16
|
+
* `record` overwrites in place (companions are mutable, unlike the write-once manifest): it writes
|
|
17
|
+
* a fresh atomic temp and renames over any existing file.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createNodeContainedJsonArtifactStore<T>(evidenceDir: string, suffix: string, options: ContainedJsonArtifactStoreOptions<T>): ContainedJsonArtifactStore<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Idempotently delete ONE QI companion artifact `<runId><suffix>` from the contained `qi/` dir.
|
|
22
|
+
*
|
|
23
|
+
* Used by the run-deletion path (`deleteQualityIntelligenceRun`) to clean up companion artifacts
|
|
24
|
+
* that live alongside the run manifest. EXACT-suffix matching is mandatory: a non-leading `.` is a
|
|
25
|
+
* legal runId character (`assertValidRunId`), so `run-1` and `run-1.2` can coexist and a prefix
|
|
26
|
+
* (`startsWith`) sweep would let deleting `run-1` destroy `run-1.2`'s companion. By deriving the
|
|
27
|
+
* full `${runId}${suffix}` name from the validated run id, the delete is collision-free,
|
|
28
|
+
* realpath-contained at the base, and symlink-refusing (`deleteArtifactFile` lstat-gates `isFile`,
|
|
29
|
+
* which is false for a symlink). Returns true iff a regular single-link file was removed.
|
|
30
|
+
*
|
|
31
|
+
* Intentionally NOT re-exported from the package barrel — it is an internal seam consumed only by
|
|
32
|
+
* the deletion path, so the published surface stays unchanged.
|
|
33
|
+
*/
|
|
34
|
+
export declare function deleteQualityIntelligenceCompanionArtifact(evidenceDir: string, runId: string, suffix: string, options?: {
|
|
35
|
+
readonly fs?: WorkspaceFs;
|
|
36
|
+
}): boolean;
|
|
37
|
+
//# sourceMappingURL=companionStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"companionStore.d.ts","sourceRoot":"","sources":["../../src/qualityIntelligence/companionStore.ts"],"names":[],"mappings":"AAuBA,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAQzF,MAAM,WAAW,0BAA0B,CAAC,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAC9C;AAED,MAAM,WAAW,iCAAiC,CAAC,CAAC;IAClD,QAAQ,CAAC,EAAE,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,MAAM,CAAC;IACrC,gGAAgG;IAChG,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,GAAG,SAAS,CAAC;CACnD;AA2GD;;;;GAIG;AACH,wBAAgB,oCAAoC,CAAC,CAAC,EACpD,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,iCAAiC,CAAC,CAAC,CAAC,GAC5C,0BAA0B,CAAC,CAAC,CAAC,CAwB/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0CAA0C,CACxD,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,WAAW,CAAA;CAAO,GAC1C,OAAO,CAIT"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Quality Intelligence companion-artifact store (Issue #274/#280/#282, Epic #270, ADR-0023 D7+D8).
|
|
2
|
+
//
|
|
3
|
+
// Generic contained JSON artifact store that lives ALONGSIDE the immutable run manifest under
|
|
4
|
+
// `<evidenceDir>/qi/`, keyed by `<runId><suffix>`. The run manifest (`<runId>.qi.json`) stays the
|
|
5
|
+
// integrity-hashed, write-once evidence record; companion artifacts carry the MUTABLE product
|
|
6
|
+
// surfaces the manifest deliberately does not (generated candidate bodies for review/export, and
|
|
7
|
+
// the human review/lifecycle state). Suffix isolation keeps `listQualityIntelligenceRuns` (which
|
|
8
|
+
// only counts `.qi.json`) blind to companions.
|
|
9
|
+
//
|
|
10
|
+
// Same safety discipline as the manifest store: realpath-contained base, validated runId-derived
|
|
11
|
+
// filename, atomic O_EXCL temp + rename, 0o700 dir / 0o600 file intent.
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
import { chmodSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
14
|
+
import { join, resolve } from "node:path";
|
|
15
|
+
import { resolveWithinWorkspace } from "@oscharko-dev/keiko-workspace";
|
|
16
|
+
import { nodeWorkspaceFs } from "@oscharko-dev/keiko-workspace/internal/fs";
|
|
17
|
+
import { assertValidRunId } from "@oscharko-dev/keiko-security";
|
|
18
|
+
import { EvidenceReadError, EvidenceWriteError } from "../errors.js";
|
|
19
|
+
import { QI_SUBDIR } from "./store.js";
|
|
20
|
+
const QI_DIR_MODE = 0o700;
|
|
21
|
+
function realBaseForWrite(baseDir, fs) {
|
|
22
|
+
try {
|
|
23
|
+
mkdirSync(baseDir, { recursive: true, mode: QI_DIR_MODE });
|
|
24
|
+
return fs.realPath(baseDir);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw new EvidenceWriteError(`cannot create QI companion directory: ${error instanceof Error ? error.message : "unknown"}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function realBaseForRead(baseDir, fs) {
|
|
31
|
+
if (!fs.exists(baseDir))
|
|
32
|
+
return undefined;
|
|
33
|
+
try {
|
|
34
|
+
return fs.realPath(baseDir);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new EvidenceReadError(`cannot read QI companion directory: ${error instanceof Error ? error.message : "unknown"}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function lexicalArtifactPath(runId, suffix, realBase) {
|
|
41
|
+
assertValidRunId(runId);
|
|
42
|
+
const name = `${runId}${suffix}`;
|
|
43
|
+
return resolveWithinWorkspace(realBase, name);
|
|
44
|
+
}
|
|
45
|
+
function isSingleLinkRegularFile(path, fs) {
|
|
46
|
+
try {
|
|
47
|
+
const stat = fs.stat(path);
|
|
48
|
+
return stat.isFile && (stat.hardLinkCount ?? 1) <= 1;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw new EvidenceReadError(`cannot inspect QI companion: ${error instanceof Error ? error.message : "unknown"}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function assertWritableArtifactEntry(target, fs) {
|
|
55
|
+
const entry = lstatSync(target, { throwIfNoEntry: false });
|
|
56
|
+
if (entry === undefined)
|
|
57
|
+
return;
|
|
58
|
+
if (!entry.isFile() || !isSingleLinkRegularFile(target, fs)) {
|
|
59
|
+
throw new EvidenceWriteError("cannot overwrite a non-ledger QI companion artifact");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function atomicWrite(target, json, randomSuffix) {
|
|
63
|
+
const temp = `${target}.${randomSuffix()}.tmp`;
|
|
64
|
+
try {
|
|
65
|
+
writeFileSync(temp, json, { encoding: "utf8", flag: "wx" });
|
|
66
|
+
try {
|
|
67
|
+
chmodSync(temp, 0o600);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// non-fatal: not all filesystems support chmod (e.g. Windows)
|
|
71
|
+
}
|
|
72
|
+
renameSync(temp, target);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
rmSync(temp, { force: true });
|
|
76
|
+
throw new EvidenceWriteError(`QI companion write failed: ${error instanceof Error ? error.message : "unknown"}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function readArtifactFile(baseDir, fs, suffix, parse, runId) {
|
|
80
|
+
assertValidRunId(runId);
|
|
81
|
+
const realBase = realBaseForRead(baseDir, fs);
|
|
82
|
+
if (realBase === undefined)
|
|
83
|
+
return undefined;
|
|
84
|
+
const target = join(realBase, `${runId}${suffix}`);
|
|
85
|
+
if (lstatSync(target, { throwIfNoEntry: false })?.isFile() !== true)
|
|
86
|
+
return undefined;
|
|
87
|
+
if (!isSingleLinkRegularFile(target, fs))
|
|
88
|
+
return undefined;
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(readFileSync(target, "utf8"));
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
throw new EvidenceReadError(`QI companion is not valid JSON: ${error instanceof Error ? error.message : "unknown"}`);
|
|
95
|
+
}
|
|
96
|
+
return parse(parsed);
|
|
97
|
+
}
|
|
98
|
+
function deleteArtifactFile(baseDir, fs, suffix, runId) {
|
|
99
|
+
assertValidRunId(runId);
|
|
100
|
+
const realBase = realBaseForRead(baseDir, fs);
|
|
101
|
+
if (realBase === undefined)
|
|
102
|
+
return false;
|
|
103
|
+
const target = lexicalArtifactPath(runId, suffix, realBase);
|
|
104
|
+
if (lstatSync(target, { throwIfNoEntry: false })?.isFile() !== true)
|
|
105
|
+
return false;
|
|
106
|
+
if (!isSingleLinkRegularFile(target, fs))
|
|
107
|
+
return false;
|
|
108
|
+
rmSync(target, { force: true });
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Build a node-backed contained JSON artifact store for one `suffix` (e.g. `.candidates.json`).
|
|
113
|
+
* `record` overwrites in place (companions are mutable, unlike the write-once manifest): it writes
|
|
114
|
+
* a fresh atomic temp and renames over any existing file.
|
|
115
|
+
*/
|
|
116
|
+
export function createNodeContainedJsonArtifactStore(evidenceDir, suffix, options) {
|
|
117
|
+
const baseDir = join(evidenceDir, QI_SUBDIR);
|
|
118
|
+
const fs = options.fs ?? nodeWorkspaceFs;
|
|
119
|
+
const randomSuffix = options.randomSuffix ?? randomUUID;
|
|
120
|
+
return {
|
|
121
|
+
record: (runId, value) => {
|
|
122
|
+
assertValidRunId(runId);
|
|
123
|
+
const realBase = realBaseForWrite(baseDir, fs);
|
|
124
|
+
const target = lexicalArtifactPath(runId, suffix, realBase);
|
|
125
|
+
assertWritableArtifactEntry(target, fs);
|
|
126
|
+
atomicWrite(target, JSON.stringify(value), randomSuffix);
|
|
127
|
+
return target;
|
|
128
|
+
},
|
|
129
|
+
load: (runId) => readArtifactFile(baseDir, fs, suffix, options.parse, runId),
|
|
130
|
+
delete: (runId) => deleteArtifactFile(baseDir, fs, suffix, runId),
|
|
131
|
+
location: (runId) => {
|
|
132
|
+
assertValidRunId(runId);
|
|
133
|
+
const realBase = realBaseForRead(baseDir, fs);
|
|
134
|
+
return realBase === undefined
|
|
135
|
+
? join(resolve(baseDir), `${runId}${suffix}`)
|
|
136
|
+
: lexicalArtifactPath(runId, suffix, realBase);
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Idempotently delete ONE QI companion artifact `<runId><suffix>` from the contained `qi/` dir.
|
|
142
|
+
*
|
|
143
|
+
* Used by the run-deletion path (`deleteQualityIntelligenceRun`) to clean up companion artifacts
|
|
144
|
+
* that live alongside the run manifest. EXACT-suffix matching is mandatory: a non-leading `.` is a
|
|
145
|
+
* legal runId character (`assertValidRunId`), so `run-1` and `run-1.2` can coexist and a prefix
|
|
146
|
+
* (`startsWith`) sweep would let deleting `run-1` destroy `run-1.2`'s companion. By deriving the
|
|
147
|
+
* full `${runId}${suffix}` name from the validated run id, the delete is collision-free,
|
|
148
|
+
* realpath-contained at the base, and symlink-refusing (`deleteArtifactFile` lstat-gates `isFile`,
|
|
149
|
+
* which is false for a symlink). Returns true iff a regular single-link file was removed.
|
|
150
|
+
*
|
|
151
|
+
* Intentionally NOT re-exported from the package barrel — it is an internal seam consumed only by
|
|
152
|
+
* the deletion path, so the published surface stays unchanged.
|
|
153
|
+
*/
|
|
154
|
+
export function deleteQualityIntelligenceCompanionArtifact(evidenceDir, runId, suffix, options = {}) {
|
|
155
|
+
const baseDir = join(evidenceDir, QI_SUBDIR);
|
|
156
|
+
const fs = options.fs ?? nodeWorkspaceFs;
|
|
157
|
+
return deleteArtifactFile(baseDir, fs, suffix, runId);
|
|
158
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export declare const FIGMA_SNAPSHOT_SCHEMA_VERSION: 1;
|
|
2
|
+
/** A reference to one rendered screen image written as a binary side-file. */
|
|
3
|
+
export interface FigmaSnapshotImageRef {
|
|
4
|
+
readonly mimeType: "image/png";
|
|
5
|
+
/** Path RELATIVE to the per-run side-file subdir. */
|
|
6
|
+
readonly relativePath: string;
|
|
7
|
+
readonly sha256: string;
|
|
8
|
+
readonly byteLength: number;
|
|
9
|
+
}
|
|
10
|
+
/** Why a screen was excluded from the snapshot. The `render-fetch-failed:<CODE>` variant carries
|
|
11
|
+
* the FigmaConnectorErrorCode suffix when the download threw a coded error, letting retention
|
|
12
|
+
* and metrics distinguish misconfigured egress from an unclassified network flake.
|
|
13
|
+
*/
|
|
14
|
+
export type FigmaSnapshotSkipReason = "render-url-missing" | "render-url-blocked" | "render-screen-cap-exceeded" | "render-fetch-failed" | `render-fetch-failed:${string}` | "render-empty" | "render-oversized";
|
|
15
|
+
export interface FigmaSnapshotSkippedScreenRow {
|
|
16
|
+
readonly screenId: string;
|
|
17
|
+
readonly reason: FigmaSnapshotSkipReason;
|
|
18
|
+
}
|
|
19
|
+
export interface FigmaSnapshotScreenRow {
|
|
20
|
+
readonly screenId: string;
|
|
21
|
+
/** Opaque serialised Screen-IR (#752). Design content — kept, not redacted away. */
|
|
22
|
+
readonly irJson: unknown;
|
|
23
|
+
readonly image: FigmaSnapshotImageRef;
|
|
24
|
+
readonly integrityHash: string;
|
|
25
|
+
}
|
|
26
|
+
/** A screen whose structural Screen-IR is persisted but whose PNG render is absent. */
|
|
27
|
+
export interface FigmaSnapshotStructuralScreenRow {
|
|
28
|
+
readonly screenId: string;
|
|
29
|
+
/** Why the screen has no render side-file. Mirrors the matching skippedScreens row. */
|
|
30
|
+
readonly reason: FigmaSnapshotSkipReason;
|
|
31
|
+
/** Opaque serialised Screen-IR (#752). Design content — kept, not redacted away. */
|
|
32
|
+
readonly irJson: unknown;
|
|
33
|
+
readonly integrityHash: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A raw inter-screen transition carried for the navigation/flow graph (#811). OPTIONAL and additive:
|
|
37
|
+
* a record without `links` (e.g. an older snapshot) is still valid and the navigation derivation
|
|
38
|
+
* degrades to zero nav items. NOT part of any integrity hash — `links` is non-identity design
|
|
39
|
+
* metadata, so adding it does not change the drift hash (#735). Node ids + trigger are design content
|
|
40
|
+
* (already redaction-safe); no token, secret, or outbound URL ever reaches this shape.
|
|
41
|
+
*/
|
|
42
|
+
export interface FigmaSnapshotLinkRow {
|
|
43
|
+
readonly sourceNodeId: string;
|
|
44
|
+
readonly trigger: string;
|
|
45
|
+
readonly targetNodeId: string;
|
|
46
|
+
}
|
|
47
|
+
/** Token-free provenance carried for audit. `fetchedAt` is audit-only and NOT in any hash. */
|
|
48
|
+
export interface FigmaSnapshotProvenanceRow {
|
|
49
|
+
readonly fileKey: string;
|
|
50
|
+
readonly nodeId: string;
|
|
51
|
+
readonly version: string | undefined;
|
|
52
|
+
readonly fetchedAt: string;
|
|
53
|
+
}
|
|
54
|
+
export interface FigmaSnapshotRedactionSummary {
|
|
55
|
+
readonly totalStringsScanned: number;
|
|
56
|
+
readonly stringsRedacted: number;
|
|
57
|
+
readonly patternsMatched: Readonly<Record<string, number>>;
|
|
58
|
+
}
|
|
59
|
+
/** Tamper-evidence for optional #752 artifacts that are hash-neutral for drift identity. */
|
|
60
|
+
export interface FigmaSnapshotArtifactHashes {
|
|
61
|
+
readonly links?: string;
|
|
62
|
+
readonly tokens?: string;
|
|
63
|
+
readonly metrics?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface FigmaSnapshotAugmentationMetrics {
|
|
66
|
+
readonly deterministic: number;
|
|
67
|
+
readonly modelAugmented: number;
|
|
68
|
+
readonly modelAugmentedShare: number;
|
|
69
|
+
}
|
|
70
|
+
export interface FigmaSnapshotNavGraphMetrics {
|
|
71
|
+
readonly screens: number;
|
|
72
|
+
readonly transitions: number;
|
|
73
|
+
}
|
|
74
|
+
export interface FigmaSnapshotA11yMetrics {
|
|
75
|
+
readonly findings: number;
|
|
76
|
+
}
|
|
77
|
+
/** Numeric-only operational metrics from Issue #760. No ids, names, links, text, or token. */
|
|
78
|
+
export interface FigmaSnapshotMetrics {
|
|
79
|
+
readonly reductionRatio: number;
|
|
80
|
+
readonly screenCount: number;
|
|
81
|
+
readonly renderCount: number;
|
|
82
|
+
readonly designTokenCount: number;
|
|
83
|
+
readonly augmentation: FigmaSnapshotAugmentationMetrics;
|
|
84
|
+
readonly navGraph?: FigmaSnapshotNavGraphMetrics;
|
|
85
|
+
readonly a11y?: FigmaSnapshotA11yMetrics;
|
|
86
|
+
}
|
|
87
|
+
export interface FigmaSnapshotRecord {
|
|
88
|
+
readonly figmaSnapshotSchemaVersion: typeof FIGMA_SNAPSHOT_SCHEMA_VERSION;
|
|
89
|
+
readonly runId: string;
|
|
90
|
+
readonly provenance: FigmaSnapshotProvenanceRow;
|
|
91
|
+
readonly screens: readonly FigmaSnapshotScreenRow[];
|
|
92
|
+
readonly skippedScreens: readonly FigmaSnapshotSkippedScreenRow[];
|
|
93
|
+
/**
|
|
94
|
+
* Structural IR for screens without a PNG render. Optional for older snapshots. New snapshots
|
|
95
|
+
* write one row for each skipped screen whose IR was available, so downstream QI can still use
|
|
96
|
+
* the JSON even when rendering is capped or degraded.
|
|
97
|
+
*/
|
|
98
|
+
readonly structuralScreens?: readonly FigmaSnapshotStructuralScreenRow[];
|
|
99
|
+
/** Raw inter-screen transitions for the navigation/flow graph (#811). Optional + additive. */
|
|
100
|
+
readonly links?: readonly FigmaSnapshotLinkRow[];
|
|
101
|
+
/**
|
|
102
|
+
* The deterministic design-tokens artifact (#752) — colours, typography, spacing, radius — kept as
|
|
103
|
+
* an opaque serialised value (like {@link FigmaSnapshotScreenRow.irJson}) so design-to-code (#755)
|
|
104
|
+
* can consume the tokens from the STORED snapshot without re-deriving them (the structural style
|
|
105
|
+
* fields they come from are pruned out of the lean per-screen IR). OPTIONAL + additive: a record
|
|
106
|
+
* without `tokens` (an older snapshot) is still valid and code-gen emits an empty token table. NOT
|
|
107
|
+
* part of any integrity hash — design tokens are non-identity design metadata, so adding them does
|
|
108
|
+
* not change the drift hash (#735). Design content (no token/secret/outbound URL reaches this shape).
|
|
109
|
+
*/
|
|
110
|
+
readonly tokens?: unknown;
|
|
111
|
+
/** Durable numeric operational metrics (#760), safe to expose and reload with the snapshot. */
|
|
112
|
+
readonly metrics?: FigmaSnapshotMetrics;
|
|
113
|
+
/** Separate integrity hashes for optional hash-neutral artifacts (`links`/`tokens`) when present. */
|
|
114
|
+
readonly artifactHashes?: FigmaSnapshotArtifactHashes;
|
|
115
|
+
readonly integrityHash: string;
|
|
116
|
+
readonly redactionSummary: FigmaSnapshotRedactionSummary;
|
|
117
|
+
}
|
|
118
|
+
export interface FigmaSnapshotValidationResult {
|
|
119
|
+
readonly ok: boolean;
|
|
120
|
+
readonly reason: string | undefined;
|
|
121
|
+
}
|
|
122
|
+
export declare function validateFigmaSnapshotRecord(value: unknown): FigmaSnapshotValidationResult;
|
|
123
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/qualityIntelligence/figmaSnapshot/schema.ts"],"names":[],"mappings":"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"}
|