@variance-authority/report 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 (53) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +21 -0
  3. package/README.md +258 -0
  4. package/dist/changelog-message.d.ts +51 -0
  5. package/dist/changelog-message.js +244 -0
  6. package/dist/changelog-message.js.map +1 -0
  7. package/dist/changelog.d.ts +231 -0
  8. package/dist/changelog.js +96 -0
  9. package/dist/changelog.js.map +1 -0
  10. package/dist/cluster.d.ts +112 -0
  11. package/dist/cluster.js +109 -0
  12. package/dist/cluster.js.map +1 -0
  13. package/dist/composition.d.ts +248 -0
  14. package/dist/composition.js +33 -0
  15. package/dist/composition.js.map +1 -0
  16. package/dist/declarations.d.ts +266 -0
  17. package/dist/declarations.js +212 -0
  18. package/dist/declarations.js.map +1 -0
  19. package/dist/file.d.ts +28 -0
  20. package/dist/file.js +151 -0
  21. package/dist/file.js.map +1 -0
  22. package/dist/finding-record.d.ts +64 -0
  23. package/dist/finding-record.js +14 -0
  24. package/dist/finding-record.js.map +1 -0
  25. package/dist/findings.d.ts +157 -0
  26. package/dist/findings.js +227 -0
  27. package/dist/findings.js.map +1 -0
  28. package/dist/format.d.ts +444 -0
  29. package/dist/format.js +2 -0
  30. package/dist/format.js.map +1 -0
  31. package/dist/history-records.d.ts +112 -0
  32. package/dist/history-records.js +16 -0
  33. package/dist/history-records.js.map +1 -0
  34. package/dist/index.d.ts +45 -0
  35. package/dist/index.js +35 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/intent.d.ts +168 -0
  38. package/dist/intent.js +214 -0
  39. package/dist/intent.js.map +1 -0
  40. package/dist/presentation-record.d.ts +66 -0
  41. package/dist/presentation-record.js +9 -0
  42. package/dist/presentation-record.js.map +1 -0
  43. package/dist/promotion.d.ts +86 -0
  44. package/dist/promotion.js +104 -0
  45. package/dist/promotion.js.map +1 -0
  46. package/dist/reach.d.ts +154 -0
  47. package/dist/reach.js +47 -0
  48. package/dist/reach.js.map +1 -0
  49. package/dist/variation.d.ts +58 -0
  50. package/dist/variation.js +2 -0
  51. package/dist/variation.js.map +1 -0
  52. package/mark.svg +30 -0
  53. package/package.json +48 -0
@@ -0,0 +1,444 @@
1
+ import type { AccessibilitySnapshot, ComponentBands, RenderIdentity } from '@variance-authority/core';
2
+ import type { FindingRecord } from './finding-record.js';
3
+ import type { CompositionReport } from './composition.js';
4
+ import type { IgnoreLedger, SensitivityLedger } from './declarations.js';
5
+ import type { ReachReport } from './reach.js';
6
+ import type { PresentationSignalRecord } from './presentation-record.js';
7
+ import type { ChurnRecord, DriftRecord, FlakinessRecord } from './history-records.js';
8
+ import type { VariationRecord } from './variation.js';
9
+ /**
10
+ * The run report — the artifact an agent actually reads.
11
+ *
12
+ * The observation pipeline produces values in memory and then the process ends.
13
+ * That is fine for a test and useless for the thing this is for: an agent asked
14
+ * to *fix* something arrives afterwards, in a different process, with no access
15
+ * to whatever was in scope when the change was sensed.
16
+ *
17
+ * So the run writes one of these, and the MCP tools query it. Keeping the file
18
+ * the contract — rather than having the tools re-run the pipeline — is what makes
19
+ * the two halves independent: a run can be produced by CI on a pinned machine
20
+ * and queried on a laptop, and the tools can be tested against a report nobody
21
+ * rendered.
22
+ *
23
+ * Deliberately not the raw `Observation`. That carries a `ChangeMask` and base64
24
+ * PNGs, which would make a 300-subject report hundreds of megabytes of data no
25
+ * reader ever looks at. What is kept is what a sentence needs.
26
+ */
27
+ export interface RunReport {
28
+ readonly runVersion: 1;
29
+ /** ISO 8601. Supplied by the caller — nothing in this package reads a clock. */
30
+ readonly at: string;
31
+ readonly identity: RenderIdentity;
32
+ readonly retention: 'durable' | 'ephemeral';
33
+ /** What the run was comparing, in the author's words. Carried into every answer. */
34
+ readonly intent?: string;
35
+ readonly observations: readonly ObservationRecord[];
36
+ /**
37
+ * Subjects the run planned and has no observation for. **Absent is not empty.**
38
+ *
39
+ * Lives here rather than only on the CLI's own superset because the answers an
40
+ * agent gets are computed from *this* type, and a field the tools cannot see is
41
+ * a field the tools cannot be contradicted by. A run that planned 300 subjects,
42
+ * failed on 50 and found the other 250 unchanged produces a report in which
43
+ * every observation is clean and the conclusion "nothing to review" is false;
44
+ * the only thing that can refuse that sentence is this list.
45
+ *
46
+ * Optional because a report written by something other than `variance run` will
47
+ * not carry it, and the difference has to survive: `undefined` means the writer
48
+ * never said what it skipped, which is not the same claim as "it skipped
49
+ * nothing" and must never be printed as one. Costs every reader an extra state
50
+ * to handle, which is cheaper than the state it prevents collapsing.
51
+ */
52
+ readonly notObserved?: readonly NotObserved[];
53
+ /**
54
+ * Which run this was, and at which commit.
55
+ *
56
+ * Present when the run could name itself — `--run` and `--commit`, or the pair
57
+ * the surrounding CI exports. Absent on a laptop, and absent is not a default
58
+ * anybody may fill in: an invented id cannot be joined back to anything that
59
+ * shipped, and it would silently become a denominator.
60
+ *
61
+ * Carried in the artifact because acceptance happens *later*, in a different
62
+ * command reading this file. A reviewer approves a subject **in a build**, and
63
+ * without the build's id there is nothing for that approval to point at — which
64
+ * is why every observation a run records is unapproved and why drift summed
65
+ * nothing until this field existed.
66
+ */
67
+ readonly run?: {
68
+ readonly id: string;
69
+ readonly commit: string;
70
+ };
71
+ /**
72
+ * What a history record already knew about the subjects this run found
73
+ * unstable, keyed by subject.
74
+ *
75
+ * Two readings put a *floor* under flakiness and can never put a ceiling on it
76
+ * (`ObservationRecord.unstable`). This is the other instrument: how often the
77
+ * subject has read differently before, and — the part that decides what anybody
78
+ * does next — whether it has happened since the last few sweeps. "Unstable in 6
79
+ * of 20" and "6 times, none in the last 9 sweeps" are opposite instructions.
80
+ *
81
+ * It lives on this type rather than only on the CLI's superset for the reason
82
+ * `notObserved` does: the answers an agent gets are computed from *this* type,
83
+ * and a field the tools cannot see is a field they cannot be contradicted by.
84
+ *
85
+ * **Absent is not "this has never happened."** It means no store answered —
86
+ * because none is configured, or because one could not be reached — and the
87
+ * reason is in `warnings` where it can be printed. A surface that renders a
88
+ * missing entry as "first occurrence" has invented the one fact this record
89
+ * exists to supply.
90
+ */
91
+ readonly flakiness?: Readonly<Record<string, FlakinessRecord>>;
92
+ /**
93
+ * How often each component this run named as a cause has changed before,
94
+ * keyed by component.
95
+ *
96
+ * The instrument a comparison structurally cannot be. A diff answers *what
97
+ * moved*; this answers *how often this moves*, which is the difference between
98
+ * "review this" and "this component has been rewritten eleven times this
99
+ * quarter and nobody has asked why". No threshold reaches it, because the
100
+ * quantity is a count across runs and a one-run-at-a-time tool keeps none.
101
+ *
102
+ * **Absent is not "it has never changed."** It means no store answered, and the
103
+ * reason is in `warnings`.
104
+ */
105
+ readonly churn?: Readonly<Record<string, ChurnRecord>>;
106
+ /**
107
+ * Design tokens whose value moved in this run, and what they have drifted to
108
+ * across every approved change in the window.
109
+ *
110
+ * The finding no comparison reaches. A button gains 2px, eleven times, each
111
+ * approved correctly by somebody looking at one diff — and the 22px is a number
112
+ * no review ever saw, because the quantity that would catch it is a sum and a
113
+ * single run holds none. Present only for tokens that moved *here*, so it is
114
+ * empty on almost every run and is the whole story on the one where it is not.
115
+ */
116
+ readonly drift?: Readonly<Record<string, DriftRecord>>;
117
+ /**
118
+ * The suite compared to *itself*, at this one commit — see
119
+ * [`composition.ts`](./composition.ts).
120
+ *
121
+ * The only section of this report with no baseline in it. Absent when the
122
+ * collection produced no semantic snapshots to join, which is a real state and
123
+ * not an empty graph: a suite that shares nothing and a suite nobody could ask
124
+ * are different claims.
125
+ */
126
+ readonly composition?: CompositionReport;
127
+ /**
128
+ * Subjects that declared themselves variations of another subject, and what
129
+ * the variation *is*.
130
+ *
131
+ * The other section with no baseline in it, and the answer to a question the
132
+ * category has never had one for: a story added behind a feature flag is a new
133
+ * subject, so its first run is `new`, its diff is empty, and what the flag
134
+ * actually does to the page is visible only by opening two pictures side by
135
+ * side. A subject that names a parent is compared against that parent *in the
136
+ * same run*, so the difference is a value with an identity — and the identity
137
+ * is what lets a reviewer be told that the difference is the one they already
138
+ * approved, on a run where both subjects changed.
139
+ *
140
+ * Every declared variation appears, including the ones that could not be
141
+ * computed: a parent nobody observed is a variation that was not measured, and
142
+ * dropping it would make a broken link look like a subject with nothing to say.
143
+ *
144
+ * Absent when no subject in the run declared a parent. Never present and empty.
145
+ */
146
+ readonly variations?: readonly VariationRecord[];
147
+ /**
148
+ * What this commit reaches, from the diff and the file graph — see
149
+ * [`reach.ts`](./reach.ts).
150
+ *
151
+ * The third axis. `observations` is a subject against its past, `composition`
152
+ * is the suite against itself, and this is the suite against the *edit* — which
153
+ * components the changed files can possibly have moved, and by which chain.
154
+ * Crossed against the verdicts, it is what lets a report say that an edit
155
+ * reached a subject and changed nothing, or that a subject moved with nothing
156
+ * in the commit reaching it.
157
+ *
158
+ * Absent when the run was given no ref to diff against, or when no file graph
159
+ * was scanned. Present with `subjects` absent is the run saying it *has* a diff
160
+ * and could not attribute it, which is a different fact and a louder one.
161
+ */
162
+ readonly reach?: ReachReport;
163
+ /**
164
+ * What this run narrowed by, and what it could have narrowed by.
165
+ *
166
+ * A run that observed everything is the default and is not a failure, so this
167
+ * is not a scolding — it is the coordinate. The execution index records the
168
+ * commit it was written at, which is its position in time and space; the
169
+ * distance from there to the tree on disk is what `--since` would have spent,
170
+ * and a reader that cannot see the coordinate cannot decide whether spending
171
+ * it is worth anything.
172
+ *
173
+ * Both halves are optional and mean different things absent. No `since` is a
174
+ * run that observed everything it planned. No `index` is either no recorded
175
+ * execution index or one with no position — nothing to diff from, so nothing
176
+ * to offer — and never *the index is current*, which is `changed: 0`.
177
+ */
178
+ readonly narrowing?: {
179
+ /** The ref `--since` named. */
180
+ readonly since?: string;
181
+ /** Where the recorded execution index stands, and how far the tree is from it. */
182
+ readonly index?: {
183
+ readonly commit: string;
184
+ /** Files differing between that commit and the working tree. */
185
+ readonly changed: number;
186
+ };
187
+ };
188
+ /**
189
+ * What each declaration did, and the audit an ignore is only safe to have
190
+ * because of — see [`declarations.ts`](./declarations.ts). Kept in the record
191
+ * because *has this mask grown over a regression?* is asked months later, by
192
+ * somebody else. Never empty: absent is *the config named no such rule*.
193
+ */
194
+ readonly ignores?: IgnoreLedger;
195
+ /** The same, for rules that relax an assertion rather than remove a region. */
196
+ readonly sensitivities?: SensitivityLedger;
197
+ }
198
+ /**
199
+ * Why a subject is in the report without an observation.
200
+ *
201
+ * Three kinds, because they mean different things about whether anyone should
202
+ * act. `excluded` is a decision the operator made and wrote down; `failed` is a
203
+ * hole in coverage, and collapsing the two makes every exclusion permanently red
204
+ * or a crash on subject 41 look chosen. `unreached` is neither: the run read the
205
+ * diff against what each baseline records having rendered and concluded this
206
+ * change cannot arrive. Calling that a decision reports one nobody made.
207
+ */
208
+ export type NotObservedKind = 'excluded' | 'failed' | 'unreached';
209
+ export interface NotObserved {
210
+ readonly subject: string;
211
+ readonly kind: NotObservedKind;
212
+ /** One sentence, ready to print, naming what was not looked at and why. */
213
+ readonly because: string;
214
+ }
215
+ export interface ObservationRecord {
216
+ readonly subject: string;
217
+ /**
218
+ * `ignored` is green and is not `unchanged`.
219
+ *
220
+ * Pixels differed and every one of them landed inside a subtree the operator
221
+ * excluded (spec 0024). Kept as its own word so a report can be asked how much
222
+ * of its green was earned and how much was declared — a question `unchanged`
223
+ * absorbs and can never answer again.
224
+ */
225
+ readonly verdict: 'unchanged' | 'changed' | 'new' | 'incomparable' | 'ignored';
226
+ readonly because: string;
227
+ /**
228
+ * Differing pixels at the policy the run isolated on, **after** exclusions.
229
+ *
230
+ * What was ignored is in {@link ObservationRecord.ignored} rather than folded
231
+ * in here, so the two numbers cannot be added by accident and a subject with a
232
+ * mask over half of it does not read as a subject that barely moved.
233
+ */
234
+ readonly changedPixels: number;
235
+ /** Independently observed boundaries. A missing member was not measured. */
236
+ readonly signals?: {
237
+ readonly document?: 'unchanged' | 'changed';
238
+ readonly pixels?: 'unchanged' | 'changed';
239
+ readonly accessibility?: {
240
+ readonly verdict: 'unchanged' | 'changed' | 'incomparable';
241
+ readonly before?: AccessibilitySnapshot;
242
+ readonly after?: AccessibilitySnapshot;
243
+ };
244
+ /** Rendered relationship consequences, independent of render impact and verdict. */
245
+ readonly presentation?: PresentationSignalRecord;
246
+ };
247
+ /**
248
+ * What the operator's ignores took out of this comparison.
249
+ *
250
+ * Present whenever the subject had an excluded subtree at all, including when
251
+ * nothing was absorbed — `pixels: 0` with `boxes: 2` is a rule that caught
252
+ * nothing this run, which is the state that turns an ignore into a blind spot
253
+ * and therefore the one a report must be able to state.
254
+ */
255
+ readonly ignored?: {
256
+ readonly pixels: number;
257
+ readonly boxes: number;
258
+ /** Boxes covering no changed pixel: the raster half of a dead ignore. */
259
+ readonly inert: number;
260
+ /** Pixels each rule absorbed here. A rule present with `0` caught nothing. */
261
+ readonly byRule: Readonly<Record<string, number>>;
262
+ };
263
+ /**
264
+ * The sensitivity that absorbed this subject, when one did.
265
+ *
266
+ * A subject reported `ignored` has two possible reasons and they are not
267
+ * interchangeable: every differing pixel fell inside an excluded subtree, or
268
+ * every band that moved was one this subject is not asserted on. The first is
269
+ * in {@link ObservationRecord.ignored}; this is the second, and keeping them
270
+ * apart is what lets a run be asked how much of its green came from a mask and
271
+ * how much from a declared level.
272
+ */
273
+ readonly relaxed?: {
274
+ readonly rule: string;
275
+ readonly level: string;
276
+ readonly bands: readonly string[];
277
+ };
278
+ /**
279
+ * Every component whose hashes moved here, and the bands it moved in.
280
+ *
281
+ * A region is a box the pixels drew, named from where that box landed, so it
282
+ * fails where a change is most worth explaining: an edit that reflows its
283
+ * neighbours merges into one blob, the blob fits no component, and the name
284
+ * that comes back is the document root. These entries never looked at a pixel,
285
+ * so they still say `Button — geometry, token` where the only region is called
286
+ * `Anonymous`, and a component here that no region names is a difference lost.
287
+ *
288
+ * **Absent is not empty.** Omitted means no baseline hashes were there to
289
+ * compare; `[]` means both sides were read and every digest matched.
290
+ */
291
+ readonly moved?: readonly ComponentBands[];
292
+ readonly regions: readonly RegionRecord[];
293
+ /** Regions found but not recorded, with their pixels. Never silently dropped. */
294
+ readonly truncated?: {
295
+ readonly regions: number;
296
+ readonly pixels: number;
297
+ };
298
+ readonly missingFonts?: readonly string[];
299
+ /**
300
+ * Defects found in *this* render, with no baseline consulted.
301
+ *
302
+ * The other half of what a run knows, and the half a comparison structurally
303
+ * cannot produce: a control that never had an accessible name compares equal
304
+ * to itself forever, so approving the first baseline approves the defect.
305
+ * Recorded per subject rather than per run because they are attributed the
306
+ * same way a region is — a component, a place, a file — and because the same
307
+ * component appearing in six subjects is six chances to notice it.
308
+ *
309
+ * They do not affect the verdict. A tool that blocks a merge on day one over
310
+ * findings nobody asked for gets switched off in week one; a project that
311
+ * wants them enforced writes `blocking: ['a11y']` in its policy, which covers
312
+ * both these and the regressions found by comparison.
313
+ *
314
+ * **Empty is not absent.** `[]` means this render was inspected and was clean;
315
+ * omitted means nothing inspected it, because the collector supplied no
316
+ * snapshot. Printing the second as the first tells a reader the component is
317
+ * fine on the authority of something that never looked at it — the same
318
+ * collapse `notObserved` exists to prevent.
319
+ */
320
+ readonly findings?: readonly FindingRecord[];
321
+ /**
322
+ * What a second collection, in a world nothing else had touched, said.
323
+ *
324
+ * Present only on a subject the run called `changed`, and only when the
325
+ * collector can build such a world — `collectAlone` in `commands/run.ts` is
326
+ * optional, because a collector holding one page open across every subject
327
+ * cannot, and saying so is better than being assumed to have one.
328
+ *
329
+ * `reproduced: false` is the finding this whole path exists for: the change is
330
+ * gone when nothing else has run, so the baseline was right and the *session*
331
+ * moved this subject. That is a defect in the suite rather than in the
332
+ * component, and it must never be promoted — see `accept`, which refuses it.
333
+ *
334
+ * **Absent is not `reproduced: true`.** Omitted means nothing re-collected
335
+ * this subject, and reading that as "it reproduces" is how a false regression
336
+ * gets promoted with a confirmation attached to it.
337
+ */
338
+ readonly alone?: {
339
+ /** `true` when the difference survived a world nothing else had touched. */
340
+ readonly reproduced: boolean;
341
+ readonly because: string;
342
+ };
343
+ /**
344
+ * The subject was read twice in one world and the two readings disagreed.
345
+ *
346
+ * The other second pass, and it answers the opposite question to
347
+ * {@link ObservationRecord.alone}: that one rebuilds the world and holds time,
348
+ * this one holds the world and lets time pass. A subject that fails this one is
349
+ * not a change and not a leak — it is a reading that cannot be taken twice, and
350
+ * everything the comparison said about it was said about one of the two.
351
+ *
352
+ * `accept` refuses it, for the reason it refuses order dependence: promoting a
353
+ * reading that will not reproduce makes the instability the baseline, and the
354
+ * next run compares against a coin flip somebody approved.
355
+ *
356
+ * **Absent means this run's two readings agreed — it does not mean stable.** A
357
+ * subject that reads differently one time in fifty passes twice-in-a-row forty
358
+ * nine runs out of fifty. Two readings put a floor under flakiness; nothing here
359
+ * puts a ceiling on it, and a reader who treats the absent field as a
360
+ * certificate has been told something this field never said. It is also absent
361
+ * on every subject the run did not call `changed`, which is almost all of them.
362
+ */
363
+ readonly unstable?: {
364
+ /**
365
+ * Components whose own content differed between the two readings, with the
366
+ * file that declares each one where the source index could name it.
367
+ *
368
+ * Empty when the collector supplied no snapshot, which is a different state
369
+ * from *no component was responsible* — see the `because`, which says which
370
+ * of the two it is rather than leaving an empty list to be read as an answer.
371
+ */
372
+ readonly components: readonly {
373
+ readonly name: string;
374
+ /** `file:line`. Absent when no source index resolved the name. */
375
+ readonly file?: string;
376
+ }[];
377
+ /** Frequency bands the disagreement fell in, e.g. `content`, `geometry`. */
378
+ readonly bands: readonly string[];
379
+ /**
380
+ * The declaration that put every band of this movement outside what the
381
+ * subject is asserted on.
382
+ *
383
+ * **Present means this is not a finding.** It does not gate, `accept` does
384
+ * not refuse it, and the summary files it under a different heading — because
385
+ * a route declared `layout` has said, in the config, that it does not assert
386
+ * on what the page is painted with, and a clock inside it is then a fact
387
+ * about the page rather than a defect in it. Stability is required inside the
388
+ * boundary the subject declares, and demanding it outside would make the
389
+ * declaration worthless: every route-level test would go red over the exact
390
+ * movement it was written to ignore.
391
+ *
392
+ * Recorded rather than dropped, for the reason `ignored` is a word of its own
393
+ * and never `unchanged` (ADR-0026): a suite has to be answerable about how
394
+ * much of its green came from a declaration. `bands` above still lists
395
+ * everything that moved, so the register can say what was absorbed and by
396
+ * which rule.
397
+ */
398
+ readonly absorbed?: {
399
+ readonly rule: string;
400
+ readonly level: string;
401
+ };
402
+ readonly because: string;
403
+ };
404
+ /** Where the images went, when the run kept them. Relative to the report. */
405
+ readonly images?: {
406
+ readonly before?: string;
407
+ readonly after?: string;
408
+ readonly diff?: string;
409
+ };
410
+ }
411
+ export interface RegionRecord {
412
+ readonly x: number;
413
+ readonly y: number;
414
+ readonly width: number;
415
+ readonly height: number;
416
+ readonly pixels: number;
417
+ readonly component?: string;
418
+ readonly path?: string;
419
+ /** Landmark phrase, e.g. `main → list item 2 of 3`. */
420
+ readonly where?: string;
421
+ readonly file?: string;
422
+ /**
423
+ * `true` when the semantic tier named this component a root of the change.
424
+ *
425
+ * The field that decides whether a report leads with the edit or with what the
426
+ * edit pushed around. See `rankRegions` — area alone gets this backwards.
427
+ */
428
+ readonly cause: boolean;
429
+ /** `true` when no box contained the region; a wrong scale or origin. */
430
+ readonly unattributed?: boolean;
431
+ /**
432
+ * The shape of this difference, with position and values removed.
433
+ *
434
+ * Printed so that writing a shape-scoped ignore is copying a digest out of the
435
+ * report rather than deriving one. Two regions with the same fingerprint are
436
+ * the same kind of thing happening, wherever on the canvas they landed.
437
+ */
438
+ readonly fingerprint?: string;
439
+ }
440
+ export type { FindingRecord } from './finding-record.js';
441
+ export type { PresentationEffectEvidence, PresentationEffectRecord, PresentationEffectTransition, PresentationInformationRecord, PresentationSignalRecord, } from './presentation-record.js';
442
+ export type { ChurnRecord, DriftRecord, FlakinessRecord } from './history-records.js';
443
+ export type { VariationRecord } from './variation.js';
444
+ //# sourceMappingURL=format.d.ts.map
package/dist/format.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n AccessibilitySnapshot,\n ComponentBands,\n RenderIdentity,\n} from '@variance-authority/core';\nimport type { FindingRecord } from './finding-record.js';\nimport type { CompositionReport } from './composition.js';\nimport type { IgnoreLedger, SensitivityLedger } from './declarations.js';\nimport type { ReachReport } from './reach.js';\nimport type { PresentationSignalRecord } from './presentation-record.js';\nimport type { ChurnRecord, DriftRecord, FlakinessRecord } from './history-records.js';\nimport type { VariationRecord } from './variation.js';\n\n/**\n * The run report — the artifact an agent actually reads.\n *\n * The observation pipeline produces values in memory and then the process ends.\n * That is fine for a test and useless for the thing this is for: an agent asked\n * to *fix* something arrives afterwards, in a different process, with no access\n * to whatever was in scope when the change was sensed.\n *\n * So the run writes one of these, and the MCP tools query it. Keeping the file\n * the contract — rather than having the tools re-run the pipeline — is what makes\n * the two halves independent: a run can be produced by CI on a pinned machine\n * and queried on a laptop, and the tools can be tested against a report nobody\n * rendered.\n *\n * Deliberately not the raw `Observation`. That carries a `ChangeMask` and base64\n * PNGs, which would make a 300-subject report hundreds of megabytes of data no\n * reader ever looks at. What is kept is what a sentence needs.\n */\n\nexport interface RunReport {\n readonly runVersion: 1;\n /** ISO 8601. Supplied by the caller — nothing in this package reads a clock. */\n readonly at: string;\n readonly identity: RenderIdentity;\n readonly retention: 'durable' | 'ephemeral';\n /** What the run was comparing, in the author's words. Carried into every answer. */\n readonly intent?: string;\n readonly observations: readonly ObservationRecord[];\n /**\n * Subjects the run planned and has no observation for. **Absent is not empty.**\n *\n * Lives here rather than only on the CLI's own superset because the answers an\n * agent gets are computed from *this* type, and a field the tools cannot see is\n * a field the tools cannot be contradicted by. A run that planned 300 subjects,\n * failed on 50 and found the other 250 unchanged produces a report in which\n * every observation is clean and the conclusion \"nothing to review\" is false;\n * the only thing that can refuse that sentence is this list.\n *\n * Optional because a report written by something other than `variance run` will\n * not carry it, and the difference has to survive: `undefined` means the writer\n * never said what it skipped, which is not the same claim as \"it skipped\n * nothing\" and must never be printed as one. Costs every reader an extra state\n * to handle, which is cheaper than the state it prevents collapsing.\n */\n readonly notObserved?: readonly NotObserved[];\n\n /**\n * Which run this was, and at which commit.\n *\n * Present when the run could name itself — `--run` and `--commit`, or the pair\n * the surrounding CI exports. Absent on a laptop, and absent is not a default\n * anybody may fill in: an invented id cannot be joined back to anything that\n * shipped, and it would silently become a denominator.\n *\n * Carried in the artifact because acceptance happens *later*, in a different\n * command reading this file. A reviewer approves a subject **in a build**, and\n * without the build's id there is nothing for that approval to point at — which\n * is why every observation a run records is unapproved and why drift summed\n * nothing until this field existed.\n */\n readonly run?: {\n readonly id: string;\n readonly commit: string;\n };\n\n /**\n * What a history record already knew about the subjects this run found\n * unstable, keyed by subject.\n *\n * Two readings put a *floor* under flakiness and can never put a ceiling on it\n * (`ObservationRecord.unstable`). This is the other instrument: how often the\n * subject has read differently before, and — the part that decides what anybody\n * does next — whether it has happened since the last few sweeps. \"Unstable in 6\n * of 20\" and \"6 times, none in the last 9 sweeps\" are opposite instructions.\n *\n * It lives on this type rather than only on the CLI's superset for the reason\n * `notObserved` does: the answers an agent gets are computed from *this* type,\n * and a field the tools cannot see is a field they cannot be contradicted by.\n *\n * **Absent is not \"this has never happened.\"** It means no store answered —\n * because none is configured, or because one could not be reached — and the\n * reason is in `warnings` where it can be printed. A surface that renders a\n * missing entry as \"first occurrence\" has invented the one fact this record\n * exists to supply.\n */\n readonly flakiness?: Readonly<Record<string, FlakinessRecord>>;\n\n /**\n * How often each component this run named as a cause has changed before,\n * keyed by component.\n *\n * The instrument a comparison structurally cannot be. A diff answers *what\n * moved*; this answers *how often this moves*, which is the difference between\n * \"review this\" and \"this component has been rewritten eleven times this\n * quarter and nobody has asked why\". No threshold reaches it, because the\n * quantity is a count across runs and a one-run-at-a-time tool keeps none.\n *\n * **Absent is not \"it has never changed.\"** It means no store answered, and the\n * reason is in `warnings`.\n */\n readonly churn?: Readonly<Record<string, ChurnRecord>>;\n\n /**\n * Design tokens whose value moved in this run, and what they have drifted to\n * across every approved change in the window.\n *\n * The finding no comparison reaches. A button gains 2px, eleven times, each\n * approved correctly by somebody looking at one diff — and the 22px is a number\n * no review ever saw, because the quantity that would catch it is a sum and a\n * single run holds none. Present only for tokens that moved *here*, so it is\n * empty on almost every run and is the whole story on the one where it is not.\n */\n readonly drift?: Readonly<Record<string, DriftRecord>>;\n\n /**\n * The suite compared to *itself*, at this one commit — see\n * [`composition.ts`](./composition.ts).\n *\n * The only section of this report with no baseline in it. Absent when the\n * collection produced no semantic snapshots to join, which is a real state and\n * not an empty graph: a suite that shares nothing and a suite nobody could ask\n * are different claims.\n */\n readonly composition?: CompositionReport;\n\n /**\n * Subjects that declared themselves variations of another subject, and what\n * the variation *is*.\n *\n * The other section with no baseline in it, and the answer to a question the\n * category has never had one for: a story added behind a feature flag is a new\n * subject, so its first run is `new`, its diff is empty, and what the flag\n * actually does to the page is visible only by opening two pictures side by\n * side. A subject that names a parent is compared against that parent *in the\n * same run*, so the difference is a value with an identity — and the identity\n * is what lets a reviewer be told that the difference is the one they already\n * approved, on a run where both subjects changed.\n *\n * Every declared variation appears, including the ones that could not be\n * computed: a parent nobody observed is a variation that was not measured, and\n * dropping it would make a broken link look like a subject with nothing to say.\n *\n * Absent when no subject in the run declared a parent. Never present and empty.\n */\n readonly variations?: readonly VariationRecord[];\n\n /**\n * What this commit reaches, from the diff and the file graph — see\n * [`reach.ts`](./reach.ts).\n *\n * The third axis. `observations` is a subject against its past, `composition`\n * is the suite against itself, and this is the suite against the *edit* — which\n * components the changed files can possibly have moved, and by which chain.\n * Crossed against the verdicts, it is what lets a report say that an edit\n * reached a subject and changed nothing, or that a subject moved with nothing\n * in the commit reaching it.\n *\n * Absent when the run was given no ref to diff against, or when no file graph\n * was scanned. Present with `subjects` absent is the run saying it *has* a diff\n * and could not attribute it, which is a different fact and a louder one.\n */\n readonly reach?: ReachReport;\n\n /**\n * What this run narrowed by, and what it could have narrowed by.\n *\n * A run that observed everything is the default and is not a failure, so this\n * is not a scolding — it is the coordinate. The execution index records the\n * commit it was written at, which is its position in time and space; the\n * distance from there to the tree on disk is what `--since` would have spent,\n * and a reader that cannot see the coordinate cannot decide whether spending\n * it is worth anything.\n *\n * Both halves are optional and mean different things absent. No `since` is a\n * run that observed everything it planned. No `index` is either no recorded\n * execution index or one with no position — nothing to diff from, so nothing\n * to offer — and never *the index is current*, which is `changed: 0`.\n */\n readonly narrowing?: {\n /** The ref `--since` named. */\n readonly since?: string;\n /** Where the recorded execution index stands, and how far the tree is from it. */\n readonly index?: {\n readonly commit: string;\n /** Files differing between that commit and the working tree. */\n readonly changed: number;\n };\n };\n\n /**\n * What each declaration did, and the audit an ignore is only safe to have\n * because of — see [`declarations.ts`](./declarations.ts). Kept in the record\n * because *has this mask grown over a regression?* is asked months later, by\n * somebody else. Never empty: absent is *the config named no such rule*.\n */\n readonly ignores?: IgnoreLedger;\n /** The same, for rules that relax an assertion rather than remove a region. */\n readonly sensitivities?: SensitivityLedger;\n}\n\n/**\n * Why a subject is in the report without an observation.\n *\n * Three kinds, because they mean different things about whether anyone should\n * act. `excluded` is a decision the operator made and wrote down; `failed` is a\n * hole in coverage, and collapsing the two makes every exclusion permanently red\n * or a crash on subject 41 look chosen. `unreached` is neither: the run read the\n * diff against what each baseline records having rendered and concluded this\n * change cannot arrive. Calling that a decision reports one nobody made.\n */\nexport type NotObservedKind = 'excluded' | 'failed' | 'unreached';\n\nexport interface NotObserved {\n readonly subject: string;\n readonly kind: NotObservedKind;\n /** One sentence, ready to print, naming what was not looked at and why. */\n readonly because: string;\n}\n\nexport interface ObservationRecord {\n readonly subject: string;\n\n /**\n * `ignored` is green and is not `unchanged`.\n *\n * Pixels differed and every one of them landed inside a subtree the operator\n * excluded (spec 0024). Kept as its own word so a report can be asked how much\n * of its green was earned and how much was declared — a question `unchanged`\n * absorbs and can never answer again.\n */\n readonly verdict: 'unchanged' | 'changed' | 'new' | 'incomparable' | 'ignored';\n readonly because: string;\n\n /**\n * Differing pixels at the policy the run isolated on, **after** exclusions.\n *\n * What was ignored is in {@link ObservationRecord.ignored} rather than folded\n * in here, so the two numbers cannot be added by accident and a subject with a\n * mask over half of it does not read as a subject that barely moved.\n */\n readonly changedPixels: number;\n\n /** Independently observed boundaries. A missing member was not measured. */\n readonly signals?: {\n readonly document?: 'unchanged' | 'changed';\n readonly pixels?: 'unchanged' | 'changed';\n readonly accessibility?: {\n readonly verdict: 'unchanged' | 'changed' | 'incomparable';\n readonly before?: AccessibilitySnapshot;\n readonly after?: AccessibilitySnapshot;\n };\n /** Rendered relationship consequences, independent of render impact and verdict. */\n readonly presentation?: PresentationSignalRecord;\n };\n\n /**\n * What the operator's ignores took out of this comparison.\n *\n * Present whenever the subject had an excluded subtree at all, including when\n * nothing was absorbed — `pixels: 0` with `boxes: 2` is a rule that caught\n * nothing this run, which is the state that turns an ignore into a blind spot\n * and therefore the one a report must be able to state.\n */\n readonly ignored?: {\n readonly pixels: number;\n readonly boxes: number;\n /** Boxes covering no changed pixel: the raster half of a dead ignore. */\n readonly inert: number;\n /** Pixels each rule absorbed here. A rule present with `0` caught nothing. */\n readonly byRule: Readonly<Record<string, number>>;\n };\n /**\n * The sensitivity that absorbed this subject, when one did.\n *\n * A subject reported `ignored` has two possible reasons and they are not\n * interchangeable: every differing pixel fell inside an excluded subtree, or\n * every band that moved was one this subject is not asserted on. The first is\n * in {@link ObservationRecord.ignored}; this is the second, and keeping them\n * apart is what lets a run be asked how much of its green came from a mask and\n * how much from a declared level.\n */\n readonly relaxed?: {\n readonly rule: string;\n readonly level: string;\n readonly bands: readonly string[];\n };\n\n /**\n * Every component whose hashes moved here, and the bands it moved in.\n *\n * A region is a box the pixels drew, named from where that box landed, so it\n * fails where a change is most worth explaining: an edit that reflows its\n * neighbours merges into one blob, the blob fits no component, and the name\n * that comes back is the document root. These entries never looked at a pixel,\n * so they still say `Button — geometry, token` where the only region is called\n * `Anonymous`, and a component here that no region names is a difference lost.\n *\n * **Absent is not empty.** Omitted means no baseline hashes were there to\n * compare; `[]` means both sides were read and every digest matched.\n */\n readonly moved?: readonly ComponentBands[];\n readonly regions: readonly RegionRecord[];\n /** Regions found but not recorded, with their pixels. Never silently dropped. */\n readonly truncated?: { readonly regions: number; readonly pixels: number };\n readonly missingFonts?: readonly string[];\n\n /**\n * Defects found in *this* render, with no baseline consulted.\n *\n * The other half of what a run knows, and the half a comparison structurally\n * cannot produce: a control that never had an accessible name compares equal\n * to itself forever, so approving the first baseline approves the defect.\n * Recorded per subject rather than per run because they are attributed the\n * same way a region is — a component, a place, a file — and because the same\n * component appearing in six subjects is six chances to notice it.\n *\n * They do not affect the verdict. A tool that blocks a merge on day one over\n * findings nobody asked for gets switched off in week one; a project that\n * wants them enforced writes `blocking: ['a11y']` in its policy, which covers\n * both these and the regressions found by comparison.\n *\n * **Empty is not absent.** `[]` means this render was inspected and was clean;\n * omitted means nothing inspected it, because the collector supplied no\n * snapshot. Printing the second as the first tells a reader the component is\n * fine on the authority of something that never looked at it — the same\n * collapse `notObserved` exists to prevent.\n */\n readonly findings?: readonly FindingRecord[];\n\n /**\n * What a second collection, in a world nothing else had touched, said.\n *\n * Present only on a subject the run called `changed`, and only when the\n * collector can build such a world — `collectAlone` in `commands/run.ts` is\n * optional, because a collector holding one page open across every subject\n * cannot, and saying so is better than being assumed to have one.\n *\n * `reproduced: false` is the finding this whole path exists for: the change is\n * gone when nothing else has run, so the baseline was right and the *session*\n * moved this subject. That is a defect in the suite rather than in the\n * component, and it must never be promoted — see `accept`, which refuses it.\n *\n * **Absent is not `reproduced: true`.** Omitted means nothing re-collected\n * this subject, and reading that as \"it reproduces\" is how a false regression\n * gets promoted with a confirmation attached to it.\n */\n readonly alone?: {\n /** `true` when the difference survived a world nothing else had touched. */\n readonly reproduced: boolean;\n readonly because: string;\n };\n\n /**\n * The subject was read twice in one world and the two readings disagreed.\n *\n * The other second pass, and it answers the opposite question to\n * {@link ObservationRecord.alone}: that one rebuilds the world and holds time,\n * this one holds the world and lets time pass. A subject that fails this one is\n * not a change and not a leak — it is a reading that cannot be taken twice, and\n * everything the comparison said about it was said about one of the two.\n *\n * `accept` refuses it, for the reason it refuses order dependence: promoting a\n * reading that will not reproduce makes the instability the baseline, and the\n * next run compares against a coin flip somebody approved.\n *\n * **Absent means this run's two readings agreed — it does not mean stable.** A\n * subject that reads differently one time in fifty passes twice-in-a-row forty\n * nine runs out of fifty. Two readings put a floor under flakiness; nothing here\n * puts a ceiling on it, and a reader who treats the absent field as a\n * certificate has been told something this field never said. It is also absent\n * on every subject the run did not call `changed`, which is almost all of them.\n */\n readonly unstable?: {\n /**\n * Components whose own content differed between the two readings, with the\n * file that declares each one where the source index could name it.\n *\n * Empty when the collector supplied no snapshot, which is a different state\n * from *no component was responsible* — see the `because`, which says which\n * of the two it is rather than leaving an empty list to be read as an answer.\n */\n readonly components: readonly {\n readonly name: string;\n /** `file:line`. Absent when no source index resolved the name. */\n readonly file?: string;\n }[];\n /** Frequency bands the disagreement fell in, e.g. `content`, `geometry`. */\n readonly bands: readonly string[];\n\n /**\n * The declaration that put every band of this movement outside what the\n * subject is asserted on.\n *\n * **Present means this is not a finding.** It does not gate, `accept` does\n * not refuse it, and the summary files it under a different heading — because\n * a route declared `layout` has said, in the config, that it does not assert\n * on what the page is painted with, and a clock inside it is then a fact\n * about the page rather than a defect in it. Stability is required inside the\n * boundary the subject declares, and demanding it outside would make the\n * declaration worthless: every route-level test would go red over the exact\n * movement it was written to ignore.\n *\n * Recorded rather than dropped, for the reason `ignored` is a word of its own\n * and never `unchanged` (ADR-0026): a suite has to be answerable about how\n * much of its green came from a declaration. `bands` above still lists\n * everything that moved, so the register can say what was absorbed and by\n * which rule.\n */\n readonly absorbed?: {\n readonly rule: string;\n readonly level: string;\n };\n\n readonly because: string;\n };\n\n /** Where the images went, when the run kept them. Relative to the report. */\n readonly images?: {\n readonly before?: string;\n readonly after?: string;\n readonly diff?: string;\n };\n}\n\nexport interface RegionRecord {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n readonly pixels: number;\n readonly component?: string;\n readonly path?: string;\n /** Landmark phrase, e.g. `main → list item 2 of 3`. */\n readonly where?: string;\n readonly file?: string;\n /**\n * `true` when the semantic tier named this component a root of the change.\n *\n * The field that decides whether a report leads with the edit or with what the\n * edit pushed around. See `rankRegions` — area alone gets this backwards.\n */\n readonly cause: boolean;\n /** `true` when no box contained the region; a wrong scale or origin. */\n readonly unattributed?: boolean;\n\n /**\n * The shape of this difference, with position and values removed.\n *\n * Printed so that writing a shape-scoped ignore is copying a digest out of the\n * report rather than deriving one. Two regions with the same fingerprint are\n * the same kind of thing happening, wherever on the canvas they landed.\n */\n readonly fingerprint?: string;\n}\n\n// Re-exported so a reader importing the report's shape gets the shapes its\n// fields are made of, without having to know which file each one was argued in.\nexport type { FindingRecord } from './finding-record.js';\nexport type {\n PresentationEffectEvidence,\n PresentationEffectRecord,\n PresentationEffectTransition,\n PresentationInformationRecord,\n PresentationSignalRecord,\n} from './presentation-record.js';\nexport type { ChurnRecord, DriftRecord, FlakinessRecord } from './history-records.js';\nexport type { VariationRecord } from './variation.js';\n"]}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * What the store answered, flattened into the report.
3
+ *
4
+ * Three shapes, and each is a copy of something `@variance-authority/history`
5
+ * already has. Deliberately: a report is opened by a reader that has a JSON file
6
+ * and nothing else, and importing the history package to name the type of a
7
+ * field would make reading a run report require installing a database client.
8
+ *
9
+ * They are together because they answer one question between them — *what does
10
+ * this suite's past say about what it just did* — and apart from `format.ts`
11
+ * because that question is the only one in the report a run cannot answer on its
12
+ * own. A run with no history configured has none of these, and its report is not
13
+ * missing anything it could have produced.
14
+ */
15
+ /** What one token has drifted to, and how far it travelled getting there. */
16
+ export interface DriftRecord {
17
+ readonly from: string;
18
+ readonly to: string;
19
+ /** Value changes behind it. One is not drift; this is never below two. */
20
+ readonly steps: number;
21
+ readonly firstAt: string;
22
+ readonly lastAt: string;
23
+ /**
24
+ * The arithmetic, when every value was the same kind of quantity.
25
+ *
26
+ * Absent for a colour, a font stack, or a mixed set of units — and absent means
27
+ * *not measurable*, never zero. `because` says which.
28
+ */
29
+ readonly quantity?: {
30
+ readonly unit: string;
31
+ readonly net: number;
32
+ /** The largest single step: the most any one review could have seen. */
33
+ readonly largestStep: number;
34
+ /** Sum of the absolute steps, which exceeds `|net|` whenever it changed direction. */
35
+ readonly travel: number;
36
+ };
37
+ /** One sentence, ready to print, from the package that owns the arithmetic. */
38
+ readonly because: string;
39
+ }
40
+ /**
41
+ * How often one component's own code has changed, over a window.
42
+ *
43
+ * A structural copy of `@variance-authority/history`'s `Churn`, for the reason
44
+ * {@link FlakinessRecord} is one: this package requires nothing, and a report
45
+ * reader must not have to install a history client to open a file.
46
+ */
47
+ export interface ChurnRecord {
48
+ /** Runs in the window, quiet ones included. The denominator. */
49
+ readonly runs: number;
50
+ /** Runs in which this component caused an **approved** change in any band. */
51
+ readonly changedRuns: number;
52
+ /**
53
+ * Runs in which only this component's geometry moved — it was *displaced* by an
54
+ * edit somewhere else. Reported, never summed: accumulating displacement makes
55
+ * the widest container in the application the thing that keeps changing, in
56
+ * every run, forever.
57
+ */
58
+ readonly collateralRuns: number;
59
+ /** Runs carrying a change to this component that nobody approved. */
60
+ readonly rejectedRuns: number;
61
+ readonly firstAt?: string;
62
+ readonly lastAt?: string;
63
+ /** One sentence, ready to print, from the package that owns the arithmetic. */
64
+ readonly because: string;
65
+ }
66
+ /**
67
+ * How often one subject has failed to read the same way twice.
68
+ *
69
+ * A structural copy of `@variance-authority/history`'s `Flakiness` rather than an
70
+ * import of it: this package requires nothing, and a report reader must not have
71
+ * to install a history client to open a file. The two are kept in step by the
72
+ * writer — `cli`, which imports both — and the fields that could drift are the
73
+ * ones with a rule attached, restated here so a reader of the artifact meets it.
74
+ */
75
+ export interface FlakinessRecord {
76
+ /** Distinct runs recorded in the window, whatever they examined. */
77
+ readonly runs: number;
78
+ /**
79
+ * Distinct runs that read **every** subject twice, and the only honest
80
+ * denominator: an ordinary run asks a subject whether it agrees with itself
81
+ * only after calling it `changed`, so a green subject's silence in one is not
82
+ * evidence of anything.
83
+ */
84
+ readonly sweeps: number;
85
+ /** Distinct runs in which this subject read differently and was not absorbed. */
86
+ readonly occurrences: number;
87
+ /**
88
+ * Runs whose instability fell entirely in bands this subject does not assert
89
+ * on — working as declared, never a finding, counted so a rule that absorbs
90
+ * something forever can still be asked about.
91
+ */
92
+ readonly absorbedRuns: number;
93
+ /** Occurrences per sweep. **Absent when no sweep has run**, and never zero. */
94
+ readonly rate?: number;
95
+ /**
96
+ * Sweeps recorded since the most recent occurrence. Counted in sweeps rather
97
+ * than in days, so a suite that stopped running does not look increasingly
98
+ * fixed the longer nobody looks at it.
99
+ */
100
+ readonly sweepsSince: number;
101
+ /** What read differently, loudest first. Empty when nothing could be named. */
102
+ readonly causes: readonly {
103
+ readonly component?: string;
104
+ readonly band?: string;
105
+ readonly runs: number;
106
+ }[];
107
+ readonly firstAt?: string;
108
+ readonly lastAt?: string;
109
+ /** One sentence, ready to print, from the package that owns the arithmetic. */
110
+ readonly because: string;
111
+ }
112
+ //# sourceMappingURL=history-records.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * What the store answered, flattened into the report.
3
+ *
4
+ * Three shapes, and each is a copy of something `@variance-authority/history`
5
+ * already has. Deliberately: a report is opened by a reader that has a JSON file
6
+ * and nothing else, and importing the history package to name the type of a
7
+ * field would make reading a run report require installing a database client.
8
+ *
9
+ * They are together because they answer one question between them — *what does
10
+ * this suite's past say about what it just did* — and apart from `format.ts`
11
+ * because that question is the only one in the report a run cannot answer on its
12
+ * own. A run with no history configured has none of these, and its report is not
13
+ * missing anything it could have produced.
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=history-records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-records.js","sourceRoot":"","sources":["../src/history-records.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG","sourcesContent":["/**\n * What the store answered, flattened into the report.\n *\n * Three shapes, and each is a copy of something `@variance-authority/history`\n * already has. Deliberately: a report is opened by a reader that has a JSON file\n * and nothing else, and importing the history package to name the type of a\n * field would make reading a run report require installing a database client.\n *\n * They are together because they answer one question between them — *what does\n * this suite's past say about what it just did* — and apart from `format.ts`\n * because that question is the only one in the report a run cannot answer on its\n * own. A run with no history configured has none of these, and its report is not\n * missing anything it could have produced.\n */\n\n/** What one token has drifted to, and how far it travelled getting there. */\nexport interface DriftRecord {\n readonly from: string;\n readonly to: string;\n /** Value changes behind it. One is not drift; this is never below two. */\n readonly steps: number;\n readonly firstAt: string;\n readonly lastAt: string;\n\n /**\n * The arithmetic, when every value was the same kind of quantity.\n *\n * Absent for a colour, a font stack, or a mixed set of units — and absent means\n * *not measurable*, never zero. `because` says which.\n */\n readonly quantity?: {\n readonly unit: string;\n readonly net: number;\n /** The largest single step: the most any one review could have seen. */\n readonly largestStep: number;\n /** Sum of the absolute steps, which exceeds `|net|` whenever it changed direction. */\n readonly travel: number;\n };\n\n /** One sentence, ready to print, from the package that owns the arithmetic. */\n readonly because: string;\n}\n\n/**\n * How often one component's own code has changed, over a window.\n *\n * A structural copy of `@variance-authority/history`'s `Churn`, for the reason\n * {@link FlakinessRecord} is one: this package requires nothing, and a report\n * reader must not have to install a history client to open a file.\n */\nexport interface ChurnRecord {\n /** Runs in the window, quiet ones included. The denominator. */\n readonly runs: number;\n\n /** Runs in which this component caused an **approved** change in any band. */\n readonly changedRuns: number;\n\n /**\n * Runs in which only this component's geometry moved — it was *displaced* by an\n * edit somewhere else. Reported, never summed: accumulating displacement makes\n * the widest container in the application the thing that keeps changing, in\n * every run, forever.\n */\n readonly collateralRuns: number;\n\n /** Runs carrying a change to this component that nobody approved. */\n readonly rejectedRuns: number;\n\n readonly firstAt?: string;\n readonly lastAt?: string;\n\n /** One sentence, ready to print, from the package that owns the arithmetic. */\n readonly because: string;\n}\n\n/**\n * How often one subject has failed to read the same way twice.\n *\n * A structural copy of `@variance-authority/history`'s `Flakiness` rather than an\n * import of it: this package requires nothing, and a report reader must not have\n * to install a history client to open a file. The two are kept in step by the\n * writer — `cli`, which imports both — and the fields that could drift are the\n * ones with a rule attached, restated here so a reader of the artifact meets it.\n */\nexport interface FlakinessRecord {\n /** Distinct runs recorded in the window, whatever they examined. */\n readonly runs: number;\n\n /**\n * Distinct runs that read **every** subject twice, and the only honest\n * denominator: an ordinary run asks a subject whether it agrees with itself\n * only after calling it `changed`, so a green subject's silence in one is not\n * evidence of anything.\n */\n readonly sweeps: number;\n\n /** Distinct runs in which this subject read differently and was not absorbed. */\n readonly occurrences: number;\n\n /**\n * Runs whose instability fell entirely in bands this subject does not assert\n * on — working as declared, never a finding, counted so a rule that absorbs\n * something forever can still be asked about.\n */\n readonly absorbedRuns: number;\n\n /** Occurrences per sweep. **Absent when no sweep has run**, and never zero. */\n readonly rate?: number;\n\n /**\n * Sweeps recorded since the most recent occurrence. Counted in sweeps rather\n * than in days, so a suite that stopped running does not look increasingly\n * fixed the longer nobody looks at it.\n */\n readonly sweepsSince: number;\n\n /** What read differently, loudest first. Empty when nothing could be named. */\n readonly causes: readonly {\n readonly component?: string;\n readonly band?: string;\n readonly runs: number;\n }[];\n\n readonly firstAt?: string;\n readonly lastAt?: string;\n\n /** One sentence, ready to print, from the package that owns the arithmetic. */\n readonly because: string;\n}\n"]}