import type { ComponentBands } from '@variance-authority/core/attribute'; import type { AccessibilitySnapshot, RenderIdentity } from '@variance-authority/core/format'; import type { FindingRecord } from './finding-record.js'; import type { NotObserved } from './not-observed.js'; import type { RegionRecord } from './region-record.js'; import type { CompositionReport } from './composition.js'; import type { IgnoreLedger, SensitivityLedger } from './declarations.js'; import type { ReachReport } from './reach.js'; import type { PresentationSignalRecord } from './presentation-record.js'; import type { ChurnRecord, DriftRecord, FlakinessRecord } from './history-records.js'; import type { VariationRecord } from './variation.js'; import type { JourneysReport } from './journeys.js'; import type { LexiconReport } from './lexicon.js'; /** * The run report — the artifact an agent actually reads. * * The observation pipeline produces values in memory and then the process ends. * That is fine for a test and useless for the thing this is for: an agent asked * to *fix* something arrives afterwards, in a different process, with no access * to whatever was in scope when the change was sensed. * * So the run writes one of these, and the MCP tools query it. Keeping the file * the contract — rather than having the tools re-run the pipeline — is what makes * the two halves independent: a run can be produced by CI on a pinned machine * and queried on a laptop, and the tools can be tested against a report nobody * rendered. * * Deliberately not the raw `Observation`. That carries a `ChangeMask` and base64 * PNGs, which would make a 300-subject report hundreds of megabytes of data no * reader ever looks at. What is kept is what a sentence needs. */ export interface RunReport { readonly runVersion: 1; /** ISO 8601. Supplied by the caller — nothing in this package reads a clock. */ readonly at: string; readonly identity: RenderIdentity; readonly retention: 'durable' | 'ephemeral'; /** What the run was comparing, in the author's words. Carried into every answer. */ readonly intent?: string; readonly observations: readonly ObservationRecord[]; /** * Subjects the run planned and has no observation for. **Absent is not empty.** * * Lives here rather than only on the CLI's own superset because the answers an * agent gets are computed from *this* type, and a field the tools cannot see is * a field the tools cannot be contradicted by. A run that planned 300 subjects, * failed on 50 and found the other 250 unchanged produces a report in which * every observation is clean and the conclusion "nothing to review" is false; * the only thing that can refuse that sentence is this list. * * Optional because a report written by something other than `variance run` will * not carry it, and the difference has to survive: `undefined` means the writer * never said what it skipped, which is not the same claim as "it skipped * nothing" and must never be printed as one. Costs every reader an extra state * to handle, which is cheaper than the state it prevents collapsing. */ readonly notObserved?: readonly NotObserved[]; /** * Which run this was, and at which commit. * * Present when the run could name itself — `--run` and `--commit`, or the pair * the surrounding CI exports. Absent on a laptop, and absent is not a default * anybody may fill in: an invented id cannot be joined back to anything that * shipped, and it would silently become a denominator. * * Carried in the artifact because acceptance happens *later*, in a different * command reading this file. A reviewer approves a subject **in a build**, and * without the build's id there is nothing for that approval to point at — which * is why every observation a run records is unapproved and why drift summed * nothing until this field existed. */ readonly run?: { readonly id: string; readonly commit: string; }; /** * What a history record already knew about the subjects this run found * unstable, keyed by subject. * * Two readings put a *floor* under flakiness and can never put a ceiling on it * (`ObservationRecord.unstable`). This is the other instrument: how often the * subject has read differently before, and — the part that decides what anybody * does next — whether it has happened since the last few sweeps. "Unstable in 6 * of 20" and "6 times, none in the last 9 sweeps" are opposite instructions. * * It lives on this type rather than only on the CLI's superset for the reason * `notObserved` does: the answers an agent gets are computed from *this* type, * and a field the tools cannot see is a field they cannot be contradicted by. * * **Absent is not "this has never happened."** It means no store answered — * because none is configured, or because one could not be reached — and the * reason is in `warnings` where it can be printed. A surface that renders a * missing entry as "first occurrence" has invented the one fact this record * exists to supply. */ readonly flakiness?: Readonly>; /** * How often each component this run named as a cause has changed before, * keyed by component. * * The instrument a comparison structurally cannot be. A diff answers *what * moved*; this answers *how often this moves*, which is the difference between * "review this" and "this component has been rewritten eleven times this * quarter and nobody has asked why". No threshold reaches it, because the * quantity is a count across runs and a one-run-at-a-time tool keeps none. * * **Absent is not "it has never changed."** It means no store answered, and the * reason is in `warnings`. */ readonly churn?: Readonly>; /** * Design tokens whose value moved in this run, and what they have drifted to * across every approved change in the window. * * The finding no comparison reaches. A button gains 2px, eleven times, each * approved correctly by somebody looking at one diff — and the 22px is a number * no review ever saw, because the quantity that would catch it is a sum and a * single run holds none. Present only for tokens that moved *here*, so it is * empty on almost every run and is the whole story on the one where it is not. */ readonly drift?: Readonly>; /** * The suite compared to *itself*, at this one commit — see * [`composition.ts`](./composition.ts). * * The only section of this report with no baseline in it. Absent when the * collection produced no semantic snapshots to join, which is a real state and * not an empty graph: a suite that shares nothing and a suite nobody could ask * are different claims. */ readonly composition?: CompositionReport; /** * Every name the run held for each subject, for the question asked before a * subject has a name. See `lexicon.ts` for why it is written by the run and * why the values are raw. Absent under the same conditions as `composition`. */ readonly lexicon?: LexiconReport; /** * Subjects that declared themselves variations of another subject, and what * the variation *is*. * * The other section with no baseline in it, and the answer to a question the * category has never had one for: a story added behind a feature flag is a new * subject, so its first run is `new`, its diff is empty, and what the flag * actually does to the page is visible only by opening two pictures side by * side. A subject that names a parent is compared against that parent *in the * same run*, so the difference is a value with an identity — and the identity * is what lets a reviewer be told that the difference is the one they already * approved, on a run where both subjects changed. * * Every declared variation appears, including the ones that could not be * computed: a parent nobody observed is a variation that was not measured, and * dropping it would make a broken link look like a subject with nothing to say. * * Absent when no subject in the run declared a parent. Never present and empty. */ readonly variations?: readonly VariationRecord[]; /** * What this commit reaches, from the diff and the file graph — see * [`reach.ts`](./reach.ts). * * The third axis. `observations` is a subject against its past, `composition` * is the suite against itself, and this is the suite against the *edit* — which * components the changed files can possibly have moved, and by which chain. * Crossed against the verdicts, it is what lets a report say that an edit * reached a subject and changed nothing, or that a subject moved with nothing * in the commit reaching it. * * Absent when the run was given no ref to diff against, or when no file graph * was scanned. Present with `subjects` absent is the run saying it *has* a diff * and could not attribute it, which is a different fact and a louder one. */ readonly reach?: ReachReport; /** * What this run narrowed by, and what it could have narrowed by. * * A run that observed everything is the default and is not a failure, so this * is not a scolding — it is the coordinate. The execution index records the * commit it was written at, which is its position in time and space; the * distance from there to the tree on disk is what `--since` would have spent, * and a reader that cannot see the coordinate cannot decide whether spending * it is worth anything. * * Both halves are optional and mean different things absent. No `since` is a * run that observed everything it planned. No `index` is either no recorded * execution index or one with no position — nothing to diff from, so nothing * to offer — and never *the index is current*, which is `changed: 0`. */ readonly narrowing?: { /** The ref `--since` named. */ readonly since?: string; /** Where the recorded execution index stands, and how far the tree is from it. */ readonly index?: { readonly commit: string; /** Files differing between that commit and the working tree. */ readonly changed: number; }; }; /** * What each declaration did, and the audit an ignore is only safe to have * because of — see [`declarations.ts`](./declarations.ts). Kept in the record * because *has this mask grown over a regression?* is asked months later, by * somebody else. Never empty: absent is *the config named no such rule*. */ readonly ignores?: IgnoreLedger; /** The same, for rules that relax an assertion rather than remove a region. */ readonly sensitivities?: SensitivityLedger; /** * Where this run's subjects parted in the source, read off the execution * journal the build's probes wrote — the shape is in `journeys.ts`. * * **Absent is not "nobody parted."** It is *no journal*: a build carrying no * probes, which is most builds. Present with `found` empty is the pool named * in `whole` agreeing everywhere, which is a finding about the suite. */ readonly journeys?: JourneysReport; } export interface ObservationRecord { readonly subject: string; /** * `ignored` is green and is not `unchanged`. * * Pixels differed and every one of them landed inside a subtree the operator * excluded (spec 0024). Kept as its own word so a report can be asked how much * of its green was earned and how much was declared — a question `unchanged` * absorbs and can never answer again. */ readonly verdict: 'unchanged' | 'changed' | 'new' | 'ignored'; readonly because: string; /** * Differing pixels at the policy the run isolated on, **after** exclusions. * * What was ignored is in {@link ObservationRecord.ignored} rather than folded * in here, so the two numbers cannot be added by accident and a subject with a * mask over half of it does not read as a subject that barely moved. */ readonly changedPixels: number; /** Independently observed boundaries. A missing member was not measured. */ readonly signals?: { readonly document?: 'unchanged' | 'changed'; /** * Set when the two images were painted under different render identities: * `recipe` when only this tool's recipe digests moved, `machine` for anything * else. The cause of whatever the comparison found, never a reason to skip * it (ADR-0090). */ readonly identity?: 'recipe' | 'machine'; /** * Each identity field that moved, as `field before → after`, stored side * first. Present with {@link identity}; the key a review groups an * avalanche by. */ readonly moved?: readonly string[]; /** * `unobservable` when the subject occupies no pixels — there was nothing to * measure, as against `unchanged`, which is a measurement that found nothing * moved. A reader that saw `unchanged` would take the image as evidence it * never was. */ readonly pixels?: 'unchanged' | 'changed' | 'unobservable'; readonly accessibility?: { readonly verdict: 'unchanged' | 'changed'; readonly before?: AccessibilitySnapshot; readonly after?: AccessibilitySnapshot; }; /** Rendered relationship consequences, independent of render impact and verdict. */ readonly presentation?: PresentationSignalRecord; /** * `unrecorded` when the run collected a snapshot and the baseline kept none * that unpacks. An unchanged subject carrying it is re-stamped by an * accepting run, so the baseline can say where its nodes were the day * something changes. */ readonly snapshot?: 'unrecorded'; /** * Set when the baseline was read from where the layout before * `baselines.path` kept it: `from` that path, `to` the one the template * renders. An unchanged subject carrying it is re-stamped by an accepting * run, which moves the baseline and shows the move as a rename. */ readonly relocated?: { readonly from: string; readonly to: string; }; }; /** * What the operator's ignores took out of this comparison. * * Present whenever the subject had an excluded subtree at all, including when * nothing was absorbed — `pixels: 0` with `boxes: 2` is a rule that caught * nothing this run, which is the state that turns an ignore into a blind spot * and therefore the one a report must be able to state. */ readonly ignored?: { readonly pixels: number; readonly boxes: number; /** Boxes covering no changed pixel: the raster half of a dead ignore. */ readonly inert: number; /** Pixels each rule absorbed here. A rule present with `0` caught nothing. */ readonly byRule: Readonly>; }; /** * The sensitivity that absorbed this subject, when one did. * * A subject reported `ignored` has two possible reasons and they are not * interchangeable: every differing pixel fell inside an excluded subtree, or * every band that moved was one this subject is not asserted on. The first is * in {@link ObservationRecord.ignored}; this is the second, and keeping them * apart is what lets a run be asked how much of its green came from a mask and * how much from a declared level. */ readonly relaxed?: { readonly rule: string; readonly level: string; readonly bands: readonly string[]; }; /** * Every component whose hashes moved here, and the bands it moved in. * * A region is a box the pixels drew, named from where that box landed, so it * fails where a change is most worth explaining: an edit that reflows its * neighbours merges into one blob, the blob fits no component, and the name * that comes back is the document root. These entries never looked at a pixel, * so they still say `Button — geometry, token` where the only region is called * `Anonymous`, and a component here that no region names is a difference lost. * * **Absent is not empty.** Omitted means no baseline hashes were there to * compare; `[]` means both sides were read and every digest matched. */ readonly moved?: readonly ComponentBands[]; readonly regions: readonly RegionRecord[]; /** Regions found but not recorded, with their pixels. Never silently dropped. */ readonly truncated?: { readonly regions: number; readonly pixels: number; }; readonly missingFonts?: readonly string[]; /** * Defects found in *this* render, with no baseline consulted. * * The other half of what a run knows, and the half a comparison structurally * cannot produce: a control that never had an accessible name compares equal * to itself forever, so approving the first baseline approves the defect. * Recorded per subject rather than per run because they are attributed the * same way a region is — a component, a place, a file — and because the same * component appearing in six subjects is six chances to notice it. * * They do not affect the verdict. A tool that blocks a merge on day one over * findings nobody asked for gets switched off in week one; a project that * wants them enforced writes `blocking: ['a11y']` in its policy, which covers * both these and the regressions found by comparison. * * **Empty is not absent.** `[]` means this render was inspected and was clean; * omitted means nothing inspected it, because the collector supplied no * snapshot. Printing the second as the first tells a reader the component is * fine on the authority of something that never looked at it — the same * collapse `notObserved` exists to prevent. */ readonly findings?: readonly FindingRecord[]; /** * What a second collection, in a world nothing else had touched, said. * * Present only on a subject the run called `changed`, and only when the * collector can build such a world — `collectAlone` in `commands/run.ts` is * optional, because a collector holding one page open across every subject * cannot, and saying so is better than being assumed to have one. * * `reproduced: false` is the finding this whole path exists for: the change is * gone when nothing else has run, so the baseline was right and the *session* * moved this subject. That is a defect in the suite rather than in the * component, and it must never be promoted — see `accept`, which refuses it. * * **Absent is not `reproduced: true`.** Omitted means nothing re-collected * this subject, and reading that as "it reproduces" is how a false regression * gets promoted with a confirmation attached to it. */ readonly alone?: { /** `true` when the difference survived a world nothing else had touched. */ readonly reproduced: boolean; readonly because: string; }; /** * The subject was read twice in one world and the two readings disagreed. * * The other second pass, and it answers the opposite question to * {@link ObservationRecord.alone}: that one rebuilds the world and holds time, * this one holds the world and lets time pass. A subject that fails this one is * not a change and not a leak — it is a reading that cannot be taken twice, and * everything the comparison said about it was said about one of the two. * * `accept` refuses it, for the reason it refuses order dependence: promoting a * reading that will not reproduce makes the instability the baseline, and the * next run compares against a coin flip somebody approved. * * **Absent means this run's two readings agreed — it does not mean stable.** A * subject that reads differently one time in fifty passes twice-in-a-row forty * nine runs out of fifty. Two readings put a floor under flakiness; nothing here * puts a ceiling on it, and a reader who treats the absent field as a * certificate has been told something this field never said. It is also absent * on every subject the run did not call `changed`, which is almost all of them. */ readonly unstable?: { /** * Components whose own content differed between the two readings, with the * file that declares each one where the source index could name it. * * Empty when the collector supplied no snapshot, which is a different state * from *no component was responsible* — see the `because`, which says which * of the two it is rather than leaving an empty list to be read as an answer. */ readonly components: readonly { readonly name: string; /** `file:line`. Absent when no source index resolved the name. */ readonly file?: string; }[]; /** Frequency bands the disagreement fell in, e.g. `content`, `geometry`. */ readonly bands: readonly string[]; /** * The declaration that put every band of this movement outside what the * subject is asserted on. * * **Present means this is not a finding.** It does not gate, `accept` does * not refuse it, and the summary files it under a different heading — because * a route declared `layout` has said, in the config, that it does not assert * on what the page is painted with, and a clock inside it is then a fact * about the page rather than a defect in it. Stability is required inside the * boundary the subject declares, and demanding it outside would make the * declaration worthless: every route-level test would go red over the exact * movement it was written to ignore. * * Recorded rather than dropped, for the reason `ignored` is a word of its own * and never `unchanged` (ADR-0026): a suite has to be answerable about how * much of its green came from a declaration. `bands` above still lists * everything that moved, so the register can say what was absorbed and by * which rule. */ readonly absorbed?: { readonly rule: string; readonly level: string; }; readonly because: string; }; /** Where the images went, when the run kept them. Relative to the report. */ readonly images?: { readonly before?: string; readonly after?: string; readonly diff?: string; /** * The change at its own size: each of the largest regions cropped with the * rows around it, before beside after with the change marked. Absent * when no region was isolated, and then `before` and `after` are the picture. */ readonly change?: string; /** * The candidate's sidecar — document digest, component hashes, ARIA tree. * * Written for every subject, and for a subject that occupies no pixels it is * the *whole* candidate: there is no `after` to promote, and refusing on that * ground would leave such a subject observable and permanently unbaselinable. * Never an image; a reader painting the record must use `after`. */ readonly record?: string; }; } export type { FindingRecord } from './finding-record.js'; export type { NotObserved, NotObservedKind } from './not-observed.js'; export type { PresentationEffectEvidence, PresentationEffectRecord, PresentationEffectTransition, PresentationInformationRecord, PresentationSignalRecord, } from './presentation-record.js'; export type { ChurnRecord, DriftRecord, FlakinessRecord } from './history-records.js'; export type { RegionRecord } from './region-record.js'; export type { VariationRecord, VariationStep } from './variation.js'; //# sourceMappingURL=format.d.ts.map