@setsquare/review-sdk 0.0.1

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 (181) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +57 -0
  3. package/README.md +338 -0
  4. package/THIRD-PARTY-NOTICES.md +61 -0
  5. package/dist/autoscan.d.ts +25 -0
  6. package/dist/autoscan.d.ts.map +1 -0
  7. package/dist/autoscan.js +332 -0
  8. package/dist/autoscan.js.map +1 -0
  9. package/dist/axe.d.ts +96 -0
  10. package/dist/axe.d.ts.map +1 -0
  11. package/dist/axe.js +210 -0
  12. package/dist/axe.js.map +1 -0
  13. package/dist/bin/setsquare.d.ts +3 -0
  14. package/dist/bin/setsquare.d.ts.map +1 -0
  15. package/dist/bin/setsquare.js +21 -0
  16. package/dist/bin/setsquare.js.map +1 -0
  17. package/dist/brand.d.ts +84 -0
  18. package/dist/brand.d.ts.map +1 -0
  19. package/dist/brand.js +335 -0
  20. package/dist/brand.js.map +1 -0
  21. package/dist/checks/dynamic.d.ts +57 -0
  22. package/dist/checks/dynamic.d.ts.map +1 -0
  23. package/dist/checks/dynamic.js +184 -0
  24. package/dist/checks/dynamic.js.map +1 -0
  25. package/dist/checks/rule-pack.d.ts +34 -0
  26. package/dist/checks/rule-pack.d.ts.map +1 -0
  27. package/dist/checks/rule-pack.js +270 -0
  28. package/dist/checks/rule-pack.js.map +1 -0
  29. package/dist/checks/versions.d.ts +28 -0
  30. package/dist/checks/versions.d.ts.map +1 -0
  31. package/dist/checks/versions.js +45 -0
  32. package/dist/checks/versions.js.map +1 -0
  33. package/dist/ci.d.ts +37 -0
  34. package/dist/ci.d.ts.map +1 -0
  35. package/dist/ci.js +199 -0
  36. package/dist/ci.js.map +1 -0
  37. package/dist/contract.d.ts +334 -0
  38. package/dist/contract.d.ts.map +1 -0
  39. package/dist/contract.js +35 -0
  40. package/dist/contract.js.map +1 -0
  41. package/dist/dedupe.d.ts +79 -0
  42. package/dist/dedupe.d.ts.map +1 -0
  43. package/dist/dedupe.js +102 -0
  44. package/dist/dedupe.js.map +1 -0
  45. package/dist/document.d.ts +29 -0
  46. package/dist/document.d.ts.map +1 -0
  47. package/dist/document.js +94 -0
  48. package/dist/document.js.map +1 -0
  49. package/dist/envelope.d.ts +124 -0
  50. package/dist/envelope.d.ts.map +1 -0
  51. package/dist/envelope.js +337 -0
  52. package/dist/envelope.js.map +1 -0
  53. package/dist/escape.d.ts +14 -0
  54. package/dist/escape.d.ts.map +1 -0
  55. package/dist/escape.js +20 -0
  56. package/dist/escape.js.map +1 -0
  57. package/dist/findings.d.ts +47 -0
  58. package/dist/findings.d.ts.map +1 -0
  59. package/dist/findings.js +229 -0
  60. package/dist/findings.js.map +1 -0
  61. package/dist/fixture.d.ts +7 -0
  62. package/dist/fixture.d.ts.map +1 -0
  63. package/dist/fixture.js +237 -0
  64. package/dist/fixture.js.map +1 -0
  65. package/dist/group.d.ts +214 -0
  66. package/dist/group.d.ts.map +1 -0
  67. package/dist/group.js +709 -0
  68. package/dist/group.js.map +1 -0
  69. package/dist/html-report.d.ts +68 -0
  70. package/dist/html-report.d.ts.map +1 -0
  71. package/dist/html-report.js +649 -0
  72. package/dist/html-report.js.map +1 -0
  73. package/dist/human.d.ts +65 -0
  74. package/dist/human.d.ts.map +1 -0
  75. package/dist/human.js +111 -0
  76. package/dist/human.js.map +1 -0
  77. package/dist/index.d.ts +34 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +33 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/infer.d.ts +65 -0
  82. package/dist/infer.d.ts.map +1 -0
  83. package/dist/infer.js +67 -0
  84. package/dist/infer.js.map +1 -0
  85. package/dist/insights.d.ts +140 -0
  86. package/dist/insights.d.ts.map +1 -0
  87. package/dist/insights.js +483 -0
  88. package/dist/insights.js.map +1 -0
  89. package/dist/install.d.ts +8 -0
  90. package/dist/install.d.ts.map +1 -0
  91. package/dist/install.js +114 -0
  92. package/dist/install.js.map +1 -0
  93. package/dist/links.d.ts +45 -0
  94. package/dist/links.d.ts.map +1 -0
  95. package/dist/links.js +45 -0
  96. package/dist/links.js.map +1 -0
  97. package/dist/mask.d.ts +85 -0
  98. package/dist/mask.d.ts.map +1 -0
  99. package/dist/mask.js +98 -0
  100. package/dist/mask.js.map +1 -0
  101. package/dist/observe.d.ts +51 -0
  102. package/dist/observe.d.ts.map +1 -0
  103. package/dist/observe.js +233 -0
  104. package/dist/observe.js.map +1 -0
  105. package/dist/options.d.ts +249 -0
  106. package/dist/options.d.ts.map +1 -0
  107. package/dist/options.js +185 -0
  108. package/dist/options.js.map +1 -0
  109. package/dist/output.d.ts +113 -0
  110. package/dist/output.d.ts.map +1 -0
  111. package/dist/output.js +231 -0
  112. package/dist/output.js.map +1 -0
  113. package/dist/perf.d.ts +11 -0
  114. package/dist/perf.d.ts.map +1 -0
  115. package/dist/perf.js +150 -0
  116. package/dist/perf.js.map +1 -0
  117. package/dist/reading-level.d.ts +73 -0
  118. package/dist/reading-level.d.ts.map +1 -0
  119. package/dist/reading-level.js +189 -0
  120. package/dist/reading-level.js.map +1 -0
  121. package/dist/reporter.d.ts +94 -0
  122. package/dist/reporter.d.ts.map +1 -0
  123. package/dist/reporter.js +709 -0
  124. package/dist/reporter.js.map +1 -0
  125. package/dist/rules.d.ts +55 -0
  126. package/dist/rules.d.ts.map +1 -0
  127. package/dist/rules.js +77 -0
  128. package/dist/rules.js.map +1 -0
  129. package/dist/runtime.d.ts +32 -0
  130. package/dist/runtime.d.ts.map +1 -0
  131. package/dist/runtime.js +67 -0
  132. package/dist/runtime.js.map +1 -0
  133. package/dist/sanitise.d.ts +116 -0
  134. package/dist/sanitise.d.ts.map +1 -0
  135. package/dist/sanitise.js +247 -0
  136. package/dist/sanitise.js.map +1 -0
  137. package/dist/scan.d.ts +60 -0
  138. package/dist/scan.d.ts.map +1 -0
  139. package/dist/scan.js +246 -0
  140. package/dist/scan.js.map +1 -0
  141. package/dist/screens.d.ts +98 -0
  142. package/dist/screens.d.ts.map +1 -0
  143. package/dist/screens.js +301 -0
  144. package/dist/screens.js.map +1 -0
  145. package/dist/settle.d.ts +65 -0
  146. package/dist/settle.d.ts.map +1 -0
  147. package/dist/settle.js +162 -0
  148. package/dist/settle.js.map +1 -0
  149. package/dist/shots.d.ts +109 -0
  150. package/dist/shots.d.ts.map +1 -0
  151. package/dist/shots.js +113 -0
  152. package/dist/shots.js.map +1 -0
  153. package/dist/style.d.ts +47 -0
  154. package/dist/style.d.ts.map +1 -0
  155. package/dist/style.js +154 -0
  156. package/dist/style.js.map +1 -0
  157. package/dist/suite.d.ts +218 -0
  158. package/dist/suite.d.ts.map +1 -0
  159. package/dist/suite.js +219 -0
  160. package/dist/suite.js.map +1 -0
  161. package/dist/summary.d.ts +268 -0
  162. package/dist/summary.d.ts.map +1 -0
  163. package/dist/summary.js +1308 -0
  164. package/dist/summary.js.map +1 -0
  165. package/dist/unscannable.d.ts +75 -0
  166. package/dist/unscannable.d.ts.map +1 -0
  167. package/dist/unscannable.js +111 -0
  168. package/dist/unscannable.js.map +1 -0
  169. package/dist/versions.d.ts +13 -0
  170. package/dist/versions.d.ts.map +1 -0
  171. package/dist/versions.js +39 -0
  172. package/dist/versions.js.map +1 -0
  173. package/dist/wcag-map.d.ts +19 -0
  174. package/dist/wcag-map.d.ts.map +1 -0
  175. package/dist/wcag-map.js +52 -0
  176. package/dist/wcag-map.js.map +1 -0
  177. package/dist/with-setsquare.d.ts +26 -0
  178. package/dist/with-setsquare.d.ts.map +1 -0
  179. package/dist/with-setsquare.js +70 -0
  180. package/dist/with-setsquare.js.map +1 -0
  181. package/package.json +81 -0
@@ -0,0 +1,334 @@
1
+ import type { AxeFinding, AxeScan, PageRuntime, Perf, ScanDocument, RuleInapplicable, RulePass, RunIngestRequest, UnscannedRow } from '@setsquare/schema';
2
+ import type { WcagTarget } from './rules.js';
3
+ import type { DeclaredAxes } from '@setsquare/schema/conformance';
4
+ import type { OverheadFacts, SuiteFacts } from './suite.js';
5
+ /**
6
+ * The fixture ↔ reporter contract.
7
+ *
8
+ * The seam is CAPTURE vs INTERPRETATION, and it is drawn here rather than
9
+ * anywhere convenient. The fixture runs in N worker processes and observes; the
10
+ * reporter runs once in the main process and decides. Everything that is a
11
+ * policy — dedupe, precedence, surface normalisation, persona and theme
12
+ * resolution, envelope assembly, output — belongs to the reporter, because a
13
+ * policy implemented in two places is a policy that will diverge.
14
+ *
15
+ * That is why a `ScanRecord` carries raw observations (`observed`) and not
16
+ * conclusions. The fixture can see things the reporter never can — the live DOM,
17
+ * a `data-theme` attribute mid-test, the order navigations actually happened in
18
+ * — so it records them faithfully and forms no opinion about what they mean.
19
+ *
20
+ * Sanitisation is the single exception to "the fixture decides nothing", and it
21
+ * is deliberate: page-derived strings must be capped and constrained BEFORE they
22
+ * cross the attachment boundary, because an attachment is also written into
23
+ * Playwright's own HTML report and trace. Letting an unbounded hostile selector
24
+ * out of the worker and cleaning it up later would mean it had already been
25
+ * written somewhere we do not control. Sanitisation is a pure function, so it
26
+ * carries no state across the seam.
27
+ */
28
+ /** Attachment name. Namespaced so it cannot collide with a customer's own. */
29
+ export declare const SCAN_RECORD_ATTACHMENT = "setsquare.scan-record";
30
+ /**
31
+ * Bumped when the record shape changes incompatibly.
32
+ *
33
+ * Fixture and reporter are always the same install, so this is not a
34
+ * compatibility mechanism in the wire-format sense — it is a loud failure when
35
+ * a stale build lingers in a customer's `node_modules` after an upgrade, which
36
+ * would otherwise present as mysteriously missing evidence.
37
+ */
38
+ export declare const SCAN_RECORD_VERSION = 1;
39
+ /** A single main-frame navigation, as the fixture saw it happen. */
40
+ export interface ObservedNavigation {
41
+ url: string;
42
+ /** Milliseconds since the context was created. Relative, so no clock is trusted. */
43
+ atMs: number;
44
+ /**
45
+ * How the URL came to change. `pushState`/`popstate` never fire a real
46
+ * navigation, and a trigger that listens only for `framenavigated` misses
47
+ * every SPA route change — which is most of a modern app.
48
+ */
49
+ kind: 'navigation' | 'pushState' | 'replaceState' | 'popstate';
50
+ }
51
+ /** What the fixture saw, with no interpretation applied. */
52
+ export interface ObservedContext {
53
+ /** Playwright project name. The reporter maps this to persona/theme; the fixture does not. */
54
+ project: string;
55
+ engine: 'chromium' | 'firefox' | 'webkit';
56
+ /** Every main-frame URL this page held, in order. */
57
+ navigations: ObservedNavigation[];
58
+ viewport?: {
59
+ width: number;
60
+ height: number;
61
+ };
62
+ /**
63
+ * `data-theme` (and friends) as read from the live document. The reporter
64
+ * ranks this BELOW an explicit config statement — an in-test toggle is
65
+ * truthful, but a customer telling us outranks us sniffing.
66
+ */
67
+ observedTheme?: string;
68
+ }
69
+ /**
70
+ * What the fixture saw a page do, attached at teardown.
71
+ *
72
+ * Per page rather than per test because a single test may open several — the
73
+ * reference implementation drives two sessions in one spec — and collapsing them
74
+ * would attribute one page's evidence to another's context.
75
+ */
76
+ export interface ObservationRecord {
77
+ recordVersion: typeof SCAN_RECORD_VERSION;
78
+ kind: 'observation';
79
+ observed: ObservedContext;
80
+ /**
81
+ * Surfaces this page reached and could not scan, aggregated by reason.
82
+ *
83
+ * Absent means this page recorded no ledger events — NOT that nothing went
84
+ * unscanned, which is what an empty array would claim. The reporter sums these
85
+ * across records, which it must do for run groups anyway.
86
+ */
87
+ unscanned?: UnscannedRow[];
88
+ /** Events dropped past the ledger's distinct-row cap. Absent means none were. */
89
+ unscannedOmitted?: number;
90
+ }
91
+ /** Where a scan happened, as the page reported it at that moment. */
92
+ export interface ScanPageFacts {
93
+ project: string;
94
+ engine: ObservedContext['engine'];
95
+ url: string;
96
+ viewport?: {
97
+ width: number;
98
+ height: number;
99
+ };
100
+ observedTheme?: string;
101
+ }
102
+ /**
103
+ * What the customer said about a scan they asked for.
104
+ *
105
+ * Separate from what we observed, and deliberately so: an explicit statement
106
+ * outranks inference everywhere, and keeping the two apart is what lets the
107
+ * reporter apply that precedence instead of guessing which is which.
108
+ */
109
+ export interface DeclaredContext {
110
+ surface: string;
111
+ state?: string;
112
+ theme?: string;
113
+ persona?: string;
114
+ }
115
+ /**
116
+ * The result of one `setsquare.scan()` call — attached from the test body rather than
117
+ * from teardown, because that is where the customer asked for it.
118
+ *
119
+ * Carries its own `axeVersion` and `rulesetDigest`. Per scan, not per run: a
120
+ * sharded run can in principle be produced by runners with drifted dependencies,
121
+ * and a finding whose ruleset we cannot name is a finding we cannot defend.
122
+ */
123
+ export interface ScanResultRecord {
124
+ recordVersion: typeof SCAN_RECORD_VERSION;
125
+ kind: 'scan';
126
+ page: ScanPageFacts;
127
+ declared: DeclaredContext;
128
+ /** Groups scans of one page load, so perf is not counted once per state. */
129
+ visitRef: string;
130
+ axeVersion: string;
131
+ rulesetDigest: string;
132
+ /** The conformance level assessed. Stated, never inferred from what was found. */
133
+ wcagTarget: WcagTarget;
134
+ findings: AxeFinding[];
135
+ passes: RulePass[];
136
+ /** Rules that ran and found nothing to check — EARL's inapplicable. */
137
+ inapplicable?: RuleInapplicable[];
138
+ /** Present on the FIRST scan of a page load only. Absent is valid and common. */
139
+ perf?: Perf;
140
+ /**
141
+ * What the page did while we were on it. First scan of a page load only, for
142
+ * the same reason `perf` is: these are facts about a LOAD, not about a scan.
143
+ */
144
+ runtime?: PageRuntime;
145
+ /** Document-level values for cross-surface consistency. */
146
+ document?: ScanDocument;
147
+ /**
148
+ * What this scan cost us, in milliseconds.
149
+ *
150
+ * Local-only — it never reaches the wire, because it is a measurement about US
151
+ * rather than about the customer's application, and the next run reproduces it.
152
+ * It exists so a run can state its own overhead instead of the customer having
153
+ * to wonder, which is a trust signal and pre-empts the "your SDK slowed our CI"
154
+ * story.
155
+ *
156
+ * Time SPENT, never time ADDED: these are summed across workers and scanning is
157
+ * concurrent with the customer's own tests, so the total is not wall clock added
158
+ * to the run. Nothing may render it as a delta or a percentage — that needs a
159
+ * control run, which is what `SETSQUARE_DISABLED` is for.
160
+ */
161
+ cost?: {
162
+ totalMs: number;
163
+ axeMs: number;
164
+ probesMs?: number;
165
+ };
166
+ /** How the page was judged ready. Absent on explicit scans — no heuristic ran. */
167
+ settle?: AxeScan['settle'];
168
+ }
169
+ export type ScanRecord = ObservationRecord | ScanResultRecord;
170
+ /**
171
+ * The disk envelope's sidecar.
172
+ *
173
+ * Local-only facts live here and NOWHERE in `run.json`, which is the ingest
174
+ * request body byte-for-byte. That separation is what lets the SDK's own test
175
+ * suite validate the artifact against the generated JSON Schema and mean it: the
176
+ * file we write is the file the platform would have received, so "minus nothing"
177
+ * is a checkable claim rather than a description.
178
+ */
179
+ /**
180
+ * What the merge can honestly say about a run group's coverage.
181
+ *
182
+ * `complete` is only claimable against a DECLARED census (`SETSQUARE_RUN_PARTS`):
183
+ * without one, an invocation that crashed before it ever announced itself is
184
+ * invisible, and "no invocation known to be in flight" is not "complete".
185
+ * `unknown` is that sentence said out loud. `partial` means evidence is known
186
+ * to be absent — absent, never clean.
187
+ */
188
+ export type GroupCoverage = 'complete' | 'partial' | 'unknown';
189
+ /** One invocation's contribution, as the merged sidecar accounts for it. */
190
+ export interface RunGroupPartMeta {
191
+ partId: string;
192
+ lane?: string;
193
+ /** The label used to namespace this part's testIds in the merged envelope. */
194
+ label: string;
195
+ startedAt: string;
196
+ generatedAt: string;
197
+ durationMs: number;
198
+ /** Playwright's own verdict for the invocation. `interrupted` taints the merge. */
199
+ playwrightStatus: 'passed' | 'failed' | 'timedout' | 'interrupted' | 'unknown';
200
+ tests: number;
201
+ scans: number;
202
+ plannedTests?: number;
203
+ /** That invocation's own Playwright HTML report, when it made one. */
204
+ reportPath?: string;
205
+ }
206
+ /**
207
+ * The group accounting block of a merged run's sidecar.
208
+ *
209
+ * Local-only, like everything else in the sidecar. The envelope stays the pure
210
+ * wire body; which invocations produced it is provenance, and provenance lives
211
+ * here.
212
+ */
213
+ export interface RunGroupMeta {
214
+ /** The orchestrator's id, after filesystem sanitisation. */
215
+ id: string;
216
+ /** The declared invocation count, or null when none was declared. */
217
+ expectedParts: number | null;
218
+ /** Invocations that announced themselves (wrote a begun marker). */
219
+ begun: number;
220
+ /** Invocations whose results made it into this merge. */
221
+ completed: number;
222
+ /** partIds that began but have no readable part — crashed, still running, or torn. */
223
+ missing: string[];
224
+ /** partIds excluded because they are hours older than the newest part. */
225
+ stale: string[];
226
+ coverage: GroupCoverage;
227
+ /** Wall-clock span from the earliest start to the latest finish across parts. */
228
+ spanMs: number;
229
+ /** Content hash of the run.json this sidecar describes. A mismatch means a torn pair. */
230
+ runJsonSha256: string;
231
+ parts: RunGroupPartMeta[];
232
+ }
233
+ export interface RunMeta {
234
+ /** Never a run id. The server assigns those, and no server was involved (P1-17.7). */
235
+ runId: null;
236
+ /** Said out loud, because a JSON file on disk has no other way to say it. */
237
+ submitted: false;
238
+ sdkVersion: string;
239
+ generatedAt: string;
240
+ outputDir: string;
241
+ envelopePath: string;
242
+ durationMs: number;
243
+ counts: {
244
+ tests: number;
245
+ pages: number;
246
+ scans: number;
247
+ /**
248
+ * How many tests the run intended to execute, when Playwright told us.
249
+ *
250
+ * Local-only for now, in the sidecar rather than the envelope. "82 of 112
251
+ * tests never ran" is genuinely evidence-relevant and will want a home on the
252
+ * wire eventually — but that is a schema change with a both-runtimes bar
253
+ * behind it, and the local path can be honest today without one.
254
+ */
255
+ plannedTests?: number;
256
+ };
257
+ /**
258
+ * The Playwright config this run came from, relative to `outputDir`'s base.
259
+ *
260
+ * Local-only, and it exists so a repro command can name the right `-c`. A
261
+ * monorepo with several configs writes several `.setsquare` directories, and a
262
+ * consumer reading one of them otherwise has no way to know which config
263
+ * produced it — which makes every derived `playwright test` invocation a guess.
264
+ *
265
+ * Absent when Playwright gave us no `configFile` (an inline config), in which
266
+ * case the honest output omits `-c` rather than inventing a path.
267
+ */
268
+ configFile?: string;
269
+ /**
270
+ * What the run cost us, and what it spent that cost on.
271
+ *
272
+ * **Time SPENT, never time ADDED.** These are summed across workers while
273
+ * scanning runs concurrently with the customer's own tests, so the total is not
274
+ * wall clock added to the suite. Nothing may render a delta or a percentage from
275
+ * it: a delta needs a control run, which is what `SETSQUARE_DISABLED=1` exists
276
+ * to provide. Printing an unmeasurable delta would be the exact sin `perf.ts`
277
+ * was written to prevent.
278
+ */
279
+ overhead?: OverheadFacts;
280
+ /**
281
+ * Suite-shaped facts from Playwright's own result object.
282
+ *
283
+ * Local-only because these describe the RUN's mechanics rather than the
284
+ * customer's application. `workerIndex` and `failure` do reach the wire, because
285
+ * they are per-test evidence a platform would trend; these aggregates are
286
+ * derivable from them and are precomputed only so the three renderers cannot
287
+ * disagree.
288
+ */
289
+ suite?: SuiteFacts;
290
+ /**
291
+ * Which axes this run's evidence actually spans, and which it never touched.
292
+ *
293
+ * `declaredAxes` comes from the customer's own theme/persona maps and project
294
+ * list, and is the ONLY real denominator available on any axis — "4 personas
295
+ * configured, 1 with evidence" is defensible where anything derived from
296
+ * observed values alone is not. Absent means we could not read the config, and
297
+ * renderers must then suppress the line rather than reporting N = M.
298
+ *
299
+ * `unmeasuredAxes` names Playwright `use` options that would create an evidence
300
+ * axis and were not set by any project — `forcedColors`, `reducedMotion`,
301
+ * `colorScheme`. Saying "those axes are unmeasured in this run" is true, costs
302
+ * nothing, and is a great deal more honest than the alternative of quietly
303
+ * scanning one setting and calling it coverage.
304
+ */
305
+ coverage?: {
306
+ /**
307
+ * Keyed by AXIS NAME (`theme`, `persona`, `state`), matching
308
+ * `conformance.ts`'s own vocabulary so the value can be handed straight to
309
+ * `axisCoverage` without translation. An earlier draft used plural nouns and
310
+ * had to be reconciled — two shapes for one concept is precisely the drift
311
+ * this codebase keeps single sources of truth to avoid.
312
+ */
313
+ declaredAxes?: DeclaredAxes;
314
+ /**
315
+ * Playwright project names.
316
+ *
317
+ * Kept apart from `declaredAxes` because a project is NOT an axis — it is the
318
+ * provenance a theme or persona is resolved FROM. Filing it under axes would
319
+ * make "3 of 5 projects have evidence" look like a coverage figure on the
320
+ * grid, which it is not.
321
+ */
322
+ declaredProjects?: string[];
323
+ unmeasuredAxes: string[];
324
+ };
325
+ /** Anything the SDK could not determine, named rather than guessed. */
326
+ unknowns: string[];
327
+ /** Warnings raised during the run. The SDK never fails a build; it says so here. */
328
+ warnings: string[];
329
+ /** Present only on merged run-group artifacts. See `RunGroupMeta`. */
330
+ group?: RunGroupMeta;
331
+ }
332
+ /** Convenience alias — the wire body, exactly as `packages/schema` defines it. */
333
+ export type RunEnvelope = RunIngestRequest;
334
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The fixture ↔ reporter contract.
3
+ *
4
+ * The seam is CAPTURE vs INTERPRETATION, and it is drawn here rather than
5
+ * anywhere convenient. The fixture runs in N worker processes and observes; the
6
+ * reporter runs once in the main process and decides. Everything that is a
7
+ * policy — dedupe, precedence, surface normalisation, persona and theme
8
+ * resolution, envelope assembly, output — belongs to the reporter, because a
9
+ * policy implemented in two places is a policy that will diverge.
10
+ *
11
+ * That is why a `ScanRecord` carries raw observations (`observed`) and not
12
+ * conclusions. The fixture can see things the reporter never can — the live DOM,
13
+ * a `data-theme` attribute mid-test, the order navigations actually happened in
14
+ * — so it records them faithfully and forms no opinion about what they mean.
15
+ *
16
+ * Sanitisation is the single exception to "the fixture decides nothing", and it
17
+ * is deliberate: page-derived strings must be capped and constrained BEFORE they
18
+ * cross the attachment boundary, because an attachment is also written into
19
+ * Playwright's own HTML report and trace. Letting an unbounded hostile selector
20
+ * out of the worker and cleaning it up later would mean it had already been
21
+ * written somewhere we do not control. Sanitisation is a pure function, so it
22
+ * carries no state across the seam.
23
+ */
24
+ /** Attachment name. Namespaced so it cannot collide with a customer's own. */
25
+ export const SCAN_RECORD_ATTACHMENT = 'setsquare.scan-record';
26
+ /**
27
+ * Bumped when the record shape changes incompatibly.
28
+ *
29
+ * Fixture and reporter are always the same install, so this is not a
30
+ * compatibility mechanism in the wire-format sense — it is a loud failure when
31
+ * a stale build lingers in a customer's `node_modules` after an upgrade, which
32
+ * would otherwise present as mysteriously missing evidence.
33
+ */
34
+ export const SCAN_RECORD_VERSION = 1;
35
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AAE9D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC"}
@@ -0,0 +1,79 @@
1
+ import type { AxeScan } from '@setsquare/schema';
2
+ /**
3
+ * Which scan survives when several describe the same thing.
4
+ *
5
+ * This file exists because of one observation about the reference
6
+ * implementation, made before any code was written: **Eos's `warmup` project
7
+ * visits every entry route in the application first**, purely to pay
8
+ * cold-compile costs. Under the dedupe the phase plan originally specified —
9
+ * keyed on URL, first-wins — every surface's evidence in the run would have come
10
+ * from that shallow warm-up pass, and every real spec's scan would have been
11
+ * discarded. Not an edge case: the default outcome on the target we chose.
12
+ *
13
+ * So the rule is keyed on the full context tuple, and it is BEST-wins rather than
14
+ * first-wins.
15
+ */
16
+ export interface DedupeInput {
17
+ scan: AxeScan;
18
+ /** The Playwright project that produced it. */
19
+ project: string;
20
+ /** True when the project exists to prepare the suite rather than to test it. */
21
+ infrastructure: boolean;
22
+ /** Order encountered, used only to break ties deterministically. */
23
+ index: number;
24
+ }
25
+ /**
26
+ * Identity of a scan for dedupe purposes: WHERE it looked, in WHAT context.
27
+ *
28
+ * The full tuple, not the URL alone. Two scans of one surface under different
29
+ * themes are different evidence — collapsing them reports a themed surface as
30
+ * clean when only one of its themes was — and the same is true of state and
31
+ * persona.
32
+ *
33
+ * The *where* is the ROUTE PATTERN, not the surface name, and that distinction
34
+ * was bought with a bug. The surface name is something we choose: an explicit
35
+ * `setsquare.scan('hostile-page')` and the auto-scan of the very same URL produced
36
+ * `hostile-page` and `hostile`, two different strings, so both survived and every
37
+ * count in the run was doubled. Keying on the name lets our own naming decide
38
+ * what counts as the same page — and it gets it wrong precisely when a customer
39
+ * has told us something, which is the one case that must work.
40
+ *
41
+ * The route pattern is the page's identity as the application defines it, and
42
+ * parameterised routes are normalised into it already (`/items/42/edit` →
43
+ * `/items/:id/edit`), so it collapses exactly what should collapse. The surface
44
+ * name then comes from whichever scan WINS, which is how an explicit label ends
45
+ * up on the row instead of competing with an inferred one for a place in the list.
46
+ */
47
+ export declare function contextKey(scan: AxeScan): string;
48
+ /**
49
+ * Collapse scans to one per context, keeping the best.
50
+ *
51
+ * Ties are broken by LAST-encountered, which is just as deterministic as first
52
+ * and materially more correct: a later scan of the same context has had longer to
53
+ * settle. Output order still follows first encounter, so two runs of the same
54
+ * suite produce byte-identical, diffable envelopes.
55
+ */
56
+ export declare function dedupeScans(inputs: DedupeInput[]): {
57
+ kept: AxeScan[];
58
+ dropped: number;
59
+ };
60
+ /**
61
+ * Projects that exist to prepare the suite rather than to test it.
62
+ *
63
+ * Derived from the customer's OWN dependency graph rather than from a
64
+ * configuration list they would have to remember to write: in Playwright, a
65
+ * project named in another project's `dependencies` is by definition setup for
66
+ * it. On the reference implementation this identifies `preflight`, `setup`,
67
+ * `auth` and `warmup` with no configuration at all — which is the difference
68
+ * between a rule that works on the first run and one that works after someone
69
+ * reads the docs.
70
+ *
71
+ * A suite where every project depends on every other would mark them all as
72
+ * infrastructure; that is degenerate enough not to be worth guarding, and the
73
+ * effect would only be to make the scores uniform again.
74
+ */
75
+ export declare function infrastructureProjects(projects: readonly {
76
+ name: string;
77
+ dependencies?: readonly string[];
78
+ }[]): Set<string>;
79
+ //# sourceMappingURL=dedupe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe.d.ts","sourceRoot":"","sources":["../src/dedupe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,cAAc,EAAE,OAAO,CAAC;IACxB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAGhD;AA6BD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG;IAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAoBvF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,GACtE,GAAG,CAAC,MAAM,CAAC,CAQb"}
package/dist/dedupe.js ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Identity of a scan for dedupe purposes: WHERE it looked, in WHAT context.
3
+ *
4
+ * The full tuple, not the URL alone. Two scans of one surface under different
5
+ * themes are different evidence — collapsing them reports a themed surface as
6
+ * clean when only one of its themes was — and the same is true of state and
7
+ * persona.
8
+ *
9
+ * The *where* is the ROUTE PATTERN, not the surface name, and that distinction
10
+ * was bought with a bug. The surface name is something we choose: an explicit
11
+ * `setsquare.scan('hostile-page')` and the auto-scan of the very same URL produced
12
+ * `hostile-page` and `hostile`, two different strings, so both survived and every
13
+ * count in the run was doubled. Keying on the name lets our own naming decide
14
+ * what counts as the same page — and it gets it wrong precisely when a customer
15
+ * has told us something, which is the one case that must work.
16
+ *
17
+ * The route pattern is the page's identity as the application defines it, and
18
+ * parameterised routes are normalised into it already (`/items/42/edit` →
19
+ * `/items/:id/edit`), so it collapses exactly what should collapse. The surface
20
+ * name then comes from whichever scan WINS, which is how an explicit label ends
21
+ * up on the row instead of competing with an inferred one for a place in the list.
22
+ */
23
+ export function contextKey(scan) {
24
+ const { surface, urlPattern, theme, persona, state } = scan.context;
25
+ return [urlPattern ?? surface, theme ?? '', persona ?? '', state ?? ''].join('\u0000');
26
+ }
27
+ /**
28
+ * How good a scan is as evidence for its context, higher being better.
29
+ *
30
+ * Deliberately NOT "how many findings it has". Ranking by finding count would
31
+ * make the noisiest scan win, which rewards scanning a page mid-render — the
32
+ * exact failure mode the settle heuristic exists to prevent.
33
+ */
34
+ function score(input) {
35
+ let value = 0;
36
+ // A customer who called `setsquare.scan('job-editor', { state: … })` knows something
37
+ // we could never infer. It outranks anything automatic.
38
+ if (input.scan.context.surfaceSource === 'explicit')
39
+ value += 1000;
40
+ // A spec that tests the surface beats a project that merely warmed it.
41
+ if (!input.infrastructure)
42
+ value += 100;
43
+ // A scan that reached the page quietly beats one the ceiling cut short.
44
+ if (!input.scan.settle?.settleTimedOut)
45
+ value += 10;
46
+ // Richer context is more useful evidence, all else equal.
47
+ if (input.scan.context.persona)
48
+ value += 1;
49
+ if (input.scan.context.theme)
50
+ value += 1;
51
+ return value;
52
+ }
53
+ /**
54
+ * Collapse scans to one per context, keeping the best.
55
+ *
56
+ * Ties are broken by LAST-encountered, which is just as deterministic as first
57
+ * and materially more correct: a later scan of the same context has had longer to
58
+ * settle. Output order still follows first encounter, so two runs of the same
59
+ * suite produce byte-identical, diffable envelopes.
60
+ */
61
+ export function dedupeScans(inputs) {
62
+ const best = new Map();
63
+ for (const input of inputs) {
64
+ const key = contextKey(input.scan);
65
+ const incumbent = best.get(key);
66
+ // >= rather than >: on equal evidence the LATER scan wins.
67
+ //
68
+ // Later means more settled. A page whose content arrives after its first
69
+ // quiet window gets scanned twice — once as a shell, once complete — and both
70
+ // scans score identically, so a first-wins tie-break would enshrine the shell
71
+ // and report a clean page that is not. Measured on the fixture site: /slow
72
+ // reported zero violations until this line changed direction.
73
+ if (!incumbent || score(input) >= score(incumbent))
74
+ best.set(key, input);
75
+ }
76
+ const kept = [...best.values()].sort((a, b) => a.index - b.index).map((input) => input.scan);
77
+ return { kept, dropped: inputs.length - kept.length };
78
+ }
79
+ /**
80
+ * Projects that exist to prepare the suite rather than to test it.
81
+ *
82
+ * Derived from the customer's OWN dependency graph rather than from a
83
+ * configuration list they would have to remember to write: in Playwright, a
84
+ * project named in another project's `dependencies` is by definition setup for
85
+ * it. On the reference implementation this identifies `preflight`, `setup`,
86
+ * `auth` and `warmup` with no configuration at all — which is the difference
87
+ * between a rule that works on the first run and one that works after someone
88
+ * reads the docs.
89
+ *
90
+ * A suite where every project depends on every other would mark them all as
91
+ * infrastructure; that is degenerate enough not to be worth guarding, and the
92
+ * effect would only be to make the scores uniform again.
93
+ */
94
+ export function infrastructureProjects(projects) {
95
+ const infrastructure = new Set();
96
+ for (const project of projects) {
97
+ for (const dependency of project.dependencies ?? [])
98
+ infrastructure.add(dependency);
99
+ }
100
+ return infrastructure;
101
+ }
102
+ //# sourceMappingURL=dedupe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../src/dedupe.ts"],"names":[],"mappings":"AA2BA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,UAAU,CAAC,IAAa;IACtC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;IACpE,OAAO,CAAC,UAAU,IAAI,OAAO,EAAE,KAAK,IAAI,EAAE,EAAE,OAAO,IAAI,EAAE,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACzF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,KAAK,CAAC,KAAkB;IAC/B,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,qFAAqF;IACrF,wDAAwD;IACxD,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,KAAK,UAAU;QAAE,KAAK,IAAI,IAAI,CAAC;IAEnE,uEAAuE;IACvE,IAAI,CAAC,KAAK,CAAC,cAAc;QAAE,KAAK,IAAI,GAAG,CAAC;IAExC,wEAAwE;IACxE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc;QAAE,KAAK,IAAI,EAAE,CAAC;IAEpD,0DAA0D;IAC1D,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,KAAK,IAAI,CAAC,CAAC;IAC3C,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK;QAAE,KAAK,IAAI,CAAC,CAAC;IAEzC,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,MAAqB;IAC/C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE5C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEhC,2DAA2D;QAC3D,EAAE;QACF,yEAAyE;QACzE,8EAA8E;QAC9E,8EAA8E;QAC9E,2EAA2E;QAC3E,8DAA8D;QAC9D,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE7F,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;AACxD,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAAuE;IAEvE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;IAEzC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,YAAY,IAAI,EAAE;YAAE,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACtF,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { Page } from '@playwright/test';
2
+ import type { ScanDocument } from '@setsquare/schema';
3
+ /**
4
+ * Document-level facts, read in one round-trip alongside the theme.
5
+ *
6
+ * The envelope has always carried rule OUTCOMES — `document-title`,
7
+ * `html-has-lang`, `bypass` — and never the VALUES, which is why no existing
8
+ * artefact can say "these three surfaces all claim to be Dashboard". On its own
9
+ * each of those pages has a perfectly good title; the finding only exists across
10
+ * surfaces, which is the class of thing only a multi-surface run can see.
11
+ *
12
+ * Folded into the theme read rather than added as a second `page.evaluate`: one
13
+ * protocol round-trip per scan instead of two, on a path that already runs for
14
+ * every scan in the suite.
15
+ */
16
+ export interface DocumentFacts {
17
+ theme?: string;
18
+ document?: ScanDocument;
19
+ }
20
+ /**
21
+ * Read theme and document facts from the live page.
22
+ *
23
+ * Returns `{}` on any failure. Every field is independently optional, and absence
24
+ * always means "not captured", never a negative finding — `documentConsistency`
25
+ * relies on that to keep an uncaptured surface out of `skipLinkAbsent`, which
26
+ * would otherwise manufacture findings out of our own gaps.
27
+ */
28
+ export declare function readDocumentFacts(page: Page): Promise<DocumentFacts>;
29
+ //# sourceMappingURL=document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB;AA6BD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,CAwE1E"}