@forkpoint/agent-lighthouse-core 1.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.mts +769 -136
- package/dist/index.d.ts +769 -136
- package/dist/index.js +35855 -19683
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +35748 -19617
- package/dist/index.mjs.map +1 -1
- package/migration-map.json +1183 -46
- package/package.json +3 -1
package/dist/index.d.mts
CHANGED
|
@@ -6,11 +6,28 @@ interface FetchOptions {
|
|
|
6
6
|
timeout?: number;
|
|
7
7
|
followRedirects?: boolean;
|
|
8
8
|
acceptHeader?: string;
|
|
9
|
-
method?: 'GET' | 'POST' | 'OPTIONS' | 'HEAD';
|
|
9
|
+
method?: 'GET' | 'POST' | 'OPTIONS' | 'HEAD' | 'DELETE';
|
|
10
10
|
body?: string;
|
|
11
11
|
contentType?: string;
|
|
12
|
+
/** Override the User-Agent header (e.g. to probe a site as a specific AI bot). */
|
|
13
|
+
userAgent?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Extra request headers (e.g. `MCP-Protocol-Version`). Applied before the
|
|
16
|
+
* fetcher's own headers, so a caller can add headers but cannot clobber the
|
|
17
|
+
* scanner User-Agent, the negotiated Accept, or credentials lifted out of the
|
|
18
|
+
* URL's userinfo.
|
|
19
|
+
*/
|
|
20
|
+
headers?: Record<string, string>;
|
|
12
21
|
/** External abort (e.g. the per-scan deadline). Combined with the per-request timeout. */
|
|
13
22
|
signal?: AbortSignal;
|
|
23
|
+
/**
|
|
24
|
+
* Read the response as bytes instead of text.
|
|
25
|
+
*
|
|
26
|
+
* `body` is a UTF-8 decoded string, which replaces every invalid sequence
|
|
27
|
+
* with U+FFFD — fatal for an image, whose provenance metadata is binary. With
|
|
28
|
+
* this flag the response arrives in `bytes` and `body` stays empty.
|
|
29
|
+
*/
|
|
30
|
+
binary?: boolean;
|
|
14
31
|
}
|
|
15
32
|
interface FetchResult {
|
|
16
33
|
url: string;
|
|
@@ -22,6 +39,21 @@ interface FetchResult {
|
|
|
22
39
|
totalMs: number;
|
|
23
40
|
contentType: string;
|
|
24
41
|
contentLength: number;
|
|
42
|
+
/** Raw response bytes. Present only when the request asked for `binary`. */
|
|
43
|
+
bytes?: Uint8Array;
|
|
44
|
+
/**
|
|
45
|
+
* Every redirect hop the fetcher walked, in order. Absent when the caller
|
|
46
|
+
* disabled redirects or the response was not a redirect.
|
|
47
|
+
*
|
|
48
|
+
* `finalUrl` alone cannot say whether a host change was permanent. A scan
|
|
49
|
+
* has to tell a domain migration (301/308) from a temporary hop to somebody
|
|
50
|
+
* else's domain, so the status of each hop is kept.
|
|
51
|
+
*/
|
|
52
|
+
redirectChain?: Array<{
|
|
53
|
+
status: number;
|
|
54
|
+
from: string;
|
|
55
|
+
to: string;
|
|
56
|
+
}>;
|
|
25
57
|
error?: string;
|
|
26
58
|
}
|
|
27
59
|
/**
|
|
@@ -35,10 +67,20 @@ declare function createFetcher(): {
|
|
|
35
67
|
|
|
36
68
|
interface WafProtection {
|
|
37
69
|
isBlocked: boolean;
|
|
38
|
-
provider?: 'akamai' | 'cloudflare' | 'datadome' | 'perimeterx' | 'imperva' | 'kasada' | 'aws-waf' | 'generic-waf' | 'connection-drop';
|
|
70
|
+
provider?: 'akamai' | 'cloudflare' | 'datadome' | 'perimeterx' | 'imperva' | 'kasada' | 'aws-waf' | 'generic-waf' | 'connection-drop' | 'rate-limited';
|
|
39
71
|
name: string;
|
|
40
72
|
reason: string;
|
|
41
73
|
statusCode?: number;
|
|
74
|
+
/**
|
|
75
|
+
* The scan was throttled, not refused.
|
|
76
|
+
*
|
|
77
|
+
* An HTTP 429 says "too many requests", which is a statement about this
|
|
78
|
+
* scan's request rate and not about who the site admits. An audit that
|
|
79
|
+
* cannot tell the two apart must not report a bot wall on the strength of
|
|
80
|
+
* one — a storefront that serves GPTBot perfectly well would be told its
|
|
81
|
+
* firewall is blocking AI crawlers.
|
|
82
|
+
*/
|
|
83
|
+
isRateLimit?: boolean;
|
|
42
84
|
}
|
|
43
85
|
/**
|
|
44
86
|
* Analyzes fetch results (homepage, root files, extra pages) to determine
|
|
@@ -89,13 +131,23 @@ interface AuditGuidance {
|
|
|
89
131
|
tags?: string[];
|
|
90
132
|
}
|
|
91
133
|
type ScoreDisplayMode = 'binary' | 'ternary' | 'informative';
|
|
92
|
-
/** Public sunset notice for a deprecated audit (see docs/evidence/sunset/
|
|
134
|
+
/** Public sunset notice for a deprecated audit (see docs/evidence/sunset/not-a-factor.md). */
|
|
93
135
|
interface DeprecationNotice {
|
|
94
136
|
/** One sentence: why this signal is not a factor. */
|
|
95
137
|
notice: string;
|
|
96
|
-
/** Public rationale URL (
|
|
138
|
+
/** Public rationale URL (not-a-factor.md anchor). */
|
|
97
139
|
link: string;
|
|
98
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Strength of the published evidence backing an audit, assigned in the audit's
|
|
143
|
+
* dossier per docs/evidence/policy.md. Only A and B carry scoring weight.
|
|
144
|
+
*/
|
|
145
|
+
type EvidenceGrade = 'A' | 'B' | 'C' | 'D';
|
|
146
|
+
/**
|
|
147
|
+
* How an audit participates in scoring. Derived from its evidence grade
|
|
148
|
+
* (spec §4): only `scored` audits move a category score.
|
|
149
|
+
*/
|
|
150
|
+
type AuditTier = 'scored' | 'informative' | 'experimental';
|
|
99
151
|
interface AuditMeta {
|
|
100
152
|
id: string;
|
|
101
153
|
category: string;
|
|
@@ -109,7 +161,31 @@ interface AuditMeta {
|
|
|
109
161
|
guidance?: AuditGuidance;
|
|
110
162
|
/** Present when the audit is sunset: shown as a notice, excluded from scores. */
|
|
111
163
|
deprecated?: DeprecationNotice;
|
|
164
|
+
/** Evidence grade from the audit's dossier (docs/evidence/policy.md). */
|
|
165
|
+
evidenceGrade?: EvidenceGrade;
|
|
166
|
+
/** Scoring tier derived from the grade (spec §4). */
|
|
167
|
+
tier?: AuditTier;
|
|
168
|
+
/** Repo-relative path to the audit's evidence dossier. */
|
|
169
|
+
dossier?: string;
|
|
170
|
+
/**
|
|
171
|
+
* Which classes of scan evidence this audit needs to say anything true.
|
|
172
|
+
*
|
|
173
|
+
* Declared per audit rather than inferred at runtime, and checked against
|
|
174
|
+
* what the source actually reads by `scripts/check-requires.mjs`. An audit
|
|
175
|
+
* that reads the sampled pages — directly or through a page-fed gatherer —
|
|
176
|
+
* needs all four keys; one that reads only root files needs the origin to
|
|
177
|
+
* have answered. The deliberate disagreements are the audits whose subject
|
|
178
|
+
* *is* the missing evidence, and they are listed in that script.
|
|
179
|
+
*/
|
|
180
|
+
requires?: EvidenceKey[];
|
|
112
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* A class of evidence a scan either obtained or did not.
|
|
184
|
+
*
|
|
185
|
+
* Declared here rather than in `scan-evidence.ts` so `AuditMeta` can name it
|
|
186
|
+
* without the audit layer importing the scan layer.
|
|
187
|
+
*/
|
|
188
|
+
type EvidenceKey = 'origin-reachable' | 'unblocked-fetches' | 'rendered-body' | 'sample-adequate';
|
|
113
189
|
interface AuditResult {
|
|
114
190
|
status: CheckStatus;
|
|
115
191
|
score: number;
|
|
@@ -126,6 +202,13 @@ interface AuditResult {
|
|
|
126
202
|
};
|
|
127
203
|
pageUrl?: string;
|
|
128
204
|
priority?: CheckPriority;
|
|
205
|
+
/**
|
|
206
|
+
* A fix written from what this scan actually found.
|
|
207
|
+
*
|
|
208
|
+
* Overrides `meta.guidance.fix` in the report, so an audit can name the
|
|
209
|
+
* offending section rather than repeat the generic advice.
|
|
210
|
+
*/
|
|
211
|
+
remediation?: string;
|
|
129
212
|
}
|
|
130
213
|
type CheckStatus = 'pass' | 'warn' | 'fail' | 'na';
|
|
131
214
|
type CheckPriority = 'critical' | 'high' | 'medium' | 'low';
|
|
@@ -142,6 +225,8 @@ interface CheckResult {
|
|
|
142
225
|
description: string;
|
|
143
226
|
status: CheckStatus;
|
|
144
227
|
score: number;
|
|
228
|
+
/** Evidence-derived weight copied from AuditMeta.weight (A=1.0, B=0.6, informative=0). */
|
|
229
|
+
weight?: number;
|
|
145
230
|
scoreDisplayMode: ScoreDisplayMode;
|
|
146
231
|
displayValue?: string;
|
|
147
232
|
explanation?: string;
|
|
@@ -154,11 +239,17 @@ interface CheckResult {
|
|
|
154
239
|
found?: string;
|
|
155
240
|
code?: string;
|
|
156
241
|
docsUrl?: string;
|
|
242
|
+
/** The published evidence dossier for this check, derived from its id. */
|
|
243
|
+
evidenceUrl?: string;
|
|
157
244
|
[key: string]: unknown;
|
|
158
245
|
};
|
|
159
246
|
tags?: string[];
|
|
160
247
|
/** Present when the audit is sunset: shown as a notice, excluded from scores. */
|
|
161
248
|
deprecated?: DeprecationNotice;
|
|
249
|
+
/** Evidence grade copied from AuditMeta.evidenceGrade. */
|
|
250
|
+
evidenceGrade?: EvidenceGrade;
|
|
251
|
+
/** Scoring tier copied from AuditMeta.tier. */
|
|
252
|
+
tier?: AuditTier;
|
|
162
253
|
}
|
|
163
254
|
interface CategoryResult {
|
|
164
255
|
id: string;
|
|
@@ -171,12 +262,31 @@ interface CategoryResult {
|
|
|
171
262
|
failCount: number;
|
|
172
263
|
}
|
|
173
264
|
type ScoreTier = 'agent-ready' | 'partially-ready' | 'needs-work' | 'not-ready';
|
|
265
|
+
/**
|
|
266
|
+
* Whether a verdict about this site can mean anything, and what is missing.
|
|
267
|
+
*
|
|
268
|
+
* A scan that fetched nothing still produced a number before this existed:
|
|
269
|
+
* `ridge.com` scored 43 and `westontable.com` 37 on scans that read no page,
|
|
270
|
+
* against a real store's 51. The numbers overlap, so a reader could not
|
|
271
|
+
* separate "mediocre site" from "we never saw this site". When `judgeable` is
|
|
272
|
+
* false the report carries no score at all — not a zero, not a low number.
|
|
273
|
+
*/
|
|
274
|
+
interface ScanValidity {
|
|
275
|
+
judgeable: boolean;
|
|
276
|
+
evidence: Record<EvidenceKey, boolean>;
|
|
277
|
+
reasons: Partial<Record<EvidenceKey, string>>;
|
|
278
|
+
/** Why the score was suppressed, when it was. */
|
|
279
|
+
unscoredReason?: string;
|
|
280
|
+
}
|
|
174
281
|
interface ScanReport {
|
|
175
282
|
scanId: string;
|
|
176
283
|
url: string;
|
|
177
284
|
domain: string;
|
|
178
|
-
|
|
179
|
-
|
|
285
|
+
/** Null when the scan obtained too little evidence to judge the site. */
|
|
286
|
+
overallScore: number | null;
|
|
287
|
+
/** Null exactly when `overallScore` is. */
|
|
288
|
+
scoreTier: ScoreTier | null;
|
|
289
|
+
scanValidity?: ScanValidity;
|
|
180
290
|
summary?: string;
|
|
181
291
|
categories: CategoryResult[];
|
|
182
292
|
topPasses: CheckResult[];
|
|
@@ -209,89 +319,38 @@ interface ReadinessVitals {
|
|
|
209
319
|
technical: number;
|
|
210
320
|
}
|
|
211
321
|
|
|
212
|
-
type PhaseId = 'fetch-root' | 'fetch-pages' | 'analyze' | 'audits' | 'report';
|
|
213
|
-
type ScanEvent = {
|
|
214
|
-
type: 'scan:start';
|
|
215
|
-
url: string;
|
|
216
|
-
fraction: number;
|
|
217
|
-
elapsedMs: number;
|
|
218
|
-
} | {
|
|
219
|
-
type: 'phase:start';
|
|
220
|
-
phase: PhaseId;
|
|
221
|
-
totalUnits: number;
|
|
222
|
-
fraction: number;
|
|
223
|
-
elapsedMs: number;
|
|
224
|
-
} | {
|
|
225
|
-
type: 'unit:done';
|
|
226
|
-
phase: PhaseId;
|
|
227
|
-
completed: number;
|
|
228
|
-
total: number;
|
|
229
|
-
label?: string;
|
|
230
|
-
fraction: number;
|
|
231
|
-
elapsedMs: number;
|
|
232
|
-
} | {
|
|
233
|
-
type: 'unit:fail';
|
|
234
|
-
phase: PhaseId;
|
|
235
|
-
label: string;
|
|
236
|
-
error: string;
|
|
237
|
-
fraction: number;
|
|
238
|
-
elapsedMs: number;
|
|
239
|
-
} | {
|
|
240
|
-
type: 'phase:done';
|
|
241
|
-
phase: PhaseId;
|
|
242
|
-
durationMs: number;
|
|
243
|
-
fraction: number;
|
|
244
|
-
elapsedMs: number;
|
|
245
|
-
} | {
|
|
246
|
-
type: 'scan:done';
|
|
247
|
-
durationMs: number;
|
|
248
|
-
score: number;
|
|
249
|
-
fraction: number;
|
|
250
|
-
elapsedMs: number;
|
|
251
|
-
};
|
|
252
|
-
/**
|
|
253
|
-
* How much of the overall scan fraction each phase owns. Sums to 1 so the
|
|
254
|
-
* fraction of a finished scan is exactly 1.
|
|
255
|
-
*/
|
|
256
|
-
declare const PHASE_WEIGHTS: Record<PhaseId, number>;
|
|
257
322
|
/**
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
323
|
+
* What a scan actually obtained, decided once, before any audit runs.
|
|
324
|
+
*
|
|
325
|
+
* An audit that runs on evidence the scan never got reports a verdict about
|
|
326
|
+
* the scanner, not about the site: "no structured data" when the fetch was
|
|
327
|
+
* refused, "0 words" when the origin never answered. This module names the
|
|
328
|
+
* classes of evidence a scan can be missing so those audits can be skipped
|
|
329
|
+
* with the reason attached instead of answering blind.
|
|
330
|
+
*
|
|
331
|
+
* Pure: no network, no IO. It reads what the orchestrator already has.
|
|
262
332
|
*/
|
|
263
|
-
declare class ProgressTracker {
|
|
264
|
-
private readonly onEvent;
|
|
265
|
-
private readonly startMs;
|
|
266
|
-
private doneWeight;
|
|
267
|
-
private phase;
|
|
268
|
-
private phaseStartMs;
|
|
269
|
-
private totalUnits;
|
|
270
|
-
private completedUnits;
|
|
271
|
-
private lastFraction;
|
|
272
|
-
constructor(onEvent: (event: ScanEvent) => void);
|
|
273
|
-
/** Fraction of the whole scan that is complete, in [0, 1]. Never decreases. */
|
|
274
|
-
get fraction(): number;
|
|
275
|
-
/** Stamp an event with the current fraction/elapsed and advance the floor. */
|
|
276
|
-
private stamp;
|
|
277
|
-
private elapsedMs;
|
|
278
|
-
scanStart(url: string): void;
|
|
279
|
-
phaseStart(phase: PhaseId, totalUnits: number): void;
|
|
280
|
-
/** Correct the current phase's unit total (e.g. discovery finds pages mid-phase). */
|
|
281
|
-
setPhaseTotal(totalUnits: number): void;
|
|
282
|
-
unitDone(label?: string): void;
|
|
283
|
-
/** A failed unit still counts as settled work so the phase can complete. */
|
|
284
|
-
unitFail(label: string, error: string): void;
|
|
285
|
-
phaseDone(): void;
|
|
286
|
-
scanDone(score: number): void;
|
|
287
|
-
}
|
|
288
333
|
|
|
289
|
-
interface
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
334
|
+
interface ScanEvidence {
|
|
335
|
+
met: Record<EvidenceKey, boolean>;
|
|
336
|
+
/** One sentence per unmet key, shown in the `na` stub and the trace. */
|
|
337
|
+
reasons: Partial<Record<EvidenceKey, string>>;
|
|
338
|
+
/** Page URL to "the served HTML carried text a non-JS consumer can read". */
|
|
339
|
+
renderedByPage: Record<string, boolean>;
|
|
340
|
+
usablePageTypes: Set<PageType>;
|
|
341
|
+
/** False when no verdict about this site can mean anything. */
|
|
342
|
+
judgeable: boolean;
|
|
293
343
|
}
|
|
294
|
-
|
|
344
|
+
interface ScanEvidenceInput {
|
|
345
|
+
requestedUrl: string;
|
|
346
|
+
homepageResult: FetchResult;
|
|
347
|
+
pages: PageContext[];
|
|
348
|
+
rootFiles: Record<string, FetchResult>;
|
|
349
|
+
wafProtection: WafProtection | null;
|
|
350
|
+
}
|
|
351
|
+
declare function buildScanEvidence(input: ScanEvidenceInput): ScanEvidence;
|
|
352
|
+
/** All requirements met. For test harnesses not exercising the gate. */
|
|
353
|
+
declare function allEvidenceMet(): ScanEvidence;
|
|
295
354
|
|
|
296
355
|
interface A11yNodeFinding {
|
|
297
356
|
target: string;
|
|
@@ -340,9 +399,25 @@ interface CheckContext {
|
|
|
340
399
|
baseUrl: string;
|
|
341
400
|
fetch: (options: FetchOptions) => Promise<FetchResult>;
|
|
342
401
|
wafProtection?: WafProtection;
|
|
402
|
+
/**
|
|
403
|
+
* What the scan actually obtained, decided once before any audit ran.
|
|
404
|
+
*
|
|
405
|
+
* Required, not optional. An optional field fails open, and a caller that
|
|
406
|
+
* forgets is exactly the silent-nothing verdict this exists to remove. Test
|
|
407
|
+
* harnesses that do not exercise the gate pass `allEvidenceMet()`.
|
|
408
|
+
*/
|
|
409
|
+
evidence: ScanEvidence;
|
|
343
410
|
}
|
|
344
411
|
type CheckFn = (ctx: CheckContext) => CheckResult | Promise<CheckResult>;
|
|
345
412
|
|
|
413
|
+
/**
|
|
414
|
+
* Where an audit's evidence dossier is published. A pure function of the id.
|
|
415
|
+
*
|
|
416
|
+
* Every audit id is `<category>/<slug>`, and the documentation site publishes
|
|
417
|
+
* one page per dossier at that path with a trailing slash (`trailingSlash:
|
|
418
|
+
* 'always'`), so no per-audit field is needed to reach the evidence.
|
|
419
|
+
*/
|
|
420
|
+
declare function evidenceUrl(id: string): string;
|
|
346
421
|
/**
|
|
347
422
|
* Base class for all audits. Follows the Lighthouse pattern:
|
|
348
423
|
* each audit is a class with a static `meta` descriptor and
|
|
@@ -351,6 +426,16 @@ type CheckFn = (ctx: CheckContext) => CheckResult | Promise<CheckResult>;
|
|
|
351
426
|
declare abstract class Audit {
|
|
352
427
|
static meta: AuditMeta;
|
|
353
428
|
private static readonly PRIORITY_MAP;
|
|
429
|
+
/**
|
|
430
|
+
* Split the fourth argument of `fail`/`warn` into a priority and a fix.
|
|
431
|
+
*
|
|
432
|
+
* The argument started as a priority token and grew a second use: audits
|
|
433
|
+
* pass a sentence describing the fix for what this scan found. A sentence is
|
|
434
|
+
* not a `CheckPriority`, and putting it there made `toCheckResult` throw on
|
|
435
|
+
* the schema — so a sentence is read as the remediation and the priority
|
|
436
|
+
* falls back to the audit's default.
|
|
437
|
+
*/
|
|
438
|
+
private static splitRecommendation;
|
|
354
439
|
/** Validate an audit result against the schema. */
|
|
355
440
|
protected validate(result: AuditResult): AuditResult;
|
|
356
441
|
/** Run the audit and return a result. */
|
|
@@ -381,7 +466,11 @@ declare abstract class Audit {
|
|
|
381
466
|
interface CategoryConfig {
|
|
382
467
|
id: string;
|
|
383
468
|
name: string;
|
|
384
|
-
/**
|
|
469
|
+
/**
|
|
470
|
+
* Evidence mass: the summed weight of the category's registered audits
|
|
471
|
+
* (spec §4). Relative, not a percentage — `calculateOverallScore` normalizes
|
|
472
|
+
* by the total mass, so a category with no scored audits weighs nothing.
|
|
473
|
+
*/
|
|
385
474
|
weight: number;
|
|
386
475
|
}
|
|
387
476
|
interface AuditRegistration {
|
|
@@ -395,7 +484,77 @@ interface ScanConfig {
|
|
|
395
484
|
/** categoryId → list of audit registrations */
|
|
396
485
|
audits: Record<string, AuditRegistration[]>;
|
|
397
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* Evidence mass per category: Σ of the member audits' weights (spec §4).
|
|
489
|
+
*
|
|
490
|
+
* This replaces the old hand-tuned `CATEGORY_WEIGHTS` map. A category earns
|
|
491
|
+
* influence over the overall score by carrying proven audits, so moving an
|
|
492
|
+
* audit between categories moves its mass with it and nothing else changes.
|
|
493
|
+
* Categories made entirely of informative/experimental audits have mass 0 and
|
|
494
|
+
* cannot move the overall score.
|
|
495
|
+
*/
|
|
496
|
+
declare const CATEGORY_MASS: Record<string, number>;
|
|
398
497
|
declare const defaultConfig: ScanConfig;
|
|
498
|
+
/** Every registered category id, in canonical report order. */
|
|
499
|
+
declare const CATEGORY_IDS: readonly string[];
|
|
500
|
+
/**
|
|
501
|
+
* Narrow a scan to a subset of the registry.
|
|
502
|
+
*
|
|
503
|
+
* Two independent filters: `categories` restricts which categories run at all,
|
|
504
|
+
* and `includeExperimental` decides whether experimental-tier audits are part
|
|
505
|
+
* of the run. Experimental audits are excluded unless asked for — they carry
|
|
506
|
+
* weight 0 either way, but running an unvalidated check on every scan is a
|
|
507
|
+
* decision the operator makes, not a default.
|
|
508
|
+
*
|
|
509
|
+
* Returns the same object when there is nothing to filter, so the common path
|
|
510
|
+
* allocates nothing.
|
|
511
|
+
*/
|
|
512
|
+
declare function filterConfig(config: ScanConfig, opts: {
|
|
513
|
+
categories?: string[];
|
|
514
|
+
includeExperimental?: boolean;
|
|
515
|
+
}): ScanConfig;
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* What one audit did, in a form that can be diffed.
|
|
519
|
+
*
|
|
520
|
+
* A report says what an audit concluded. Tracing bad logic needs more: which
|
|
521
|
+
* audits never ran and why, how long each took, and what evidence each verdict
|
|
522
|
+
* was drawn from. That is the difference between "this site failed
|
|
523
|
+
* `faqpage-schema`" and "`faqpage-schema` ran in 4ms against 0 sampled pages",
|
|
524
|
+
* and only the second one tells you where to look.
|
|
525
|
+
*
|
|
526
|
+
* One record per registered audit, every scan, no exceptions — an audit that
|
|
527
|
+
* crashed or was skipped is exactly the one worth seeing.
|
|
528
|
+
*/
|
|
529
|
+
interface AuditTrace {
|
|
530
|
+
id: string;
|
|
531
|
+
category: string;
|
|
532
|
+
/**
|
|
533
|
+
* `ran` — the audit executed and returned.
|
|
534
|
+
* `skipped` — no scanned page matched its `applicablePageTypes`.
|
|
535
|
+
* `gated` — the scan never obtained evidence the audit needs.
|
|
536
|
+
* `error` — it threw, or its result was rejected by the schema.
|
|
537
|
+
*/
|
|
538
|
+
outcome: 'ran' | 'skipped' | 'gated' | 'error';
|
|
539
|
+
status: CheckStatus;
|
|
540
|
+
score: number;
|
|
541
|
+
weight: number;
|
|
542
|
+
tier?: string;
|
|
543
|
+
evidenceGrade?: string;
|
|
544
|
+
/** Wall time inside `audit()`. Zero for an audit that never ran. */
|
|
545
|
+
durationMs: number;
|
|
546
|
+
displayValue?: string;
|
|
547
|
+
explanation?: string;
|
|
548
|
+
pageUrl?: string;
|
|
549
|
+
/** The structured evidence behind the verdict, as the report carries it. */
|
|
550
|
+
details?: CheckResult['details'];
|
|
551
|
+
}
|
|
552
|
+
/** Which outcome a finished check represents. */
|
|
553
|
+
declare function outcomeOf(check: CheckResult): AuditTrace['outcome'];
|
|
554
|
+
/** Build a trace record from a finished check. */
|
|
555
|
+
declare function traceFromCheck(check: CheckResult, durationMs: number): AuditTrace;
|
|
556
|
+
/** One trace as a log line: the shape a human scans, not the full record. */
|
|
557
|
+
declare function formatTrace(trace: AuditTrace): string;
|
|
399
558
|
|
|
400
559
|
interface AuditRunResult {
|
|
401
560
|
checks: CheckResult[];
|
|
@@ -414,6 +573,14 @@ type AuditProgressEvent = {
|
|
|
414
573
|
label: string;
|
|
415
574
|
error: string;
|
|
416
575
|
};
|
|
576
|
+
/**
|
|
577
|
+
* Called once per registered audit, with what it did.
|
|
578
|
+
*
|
|
579
|
+
* Separate from {@link AuditProgressEvent}, which exists to drive a progress
|
|
580
|
+
* bar and carries only a label. This carries the verdict and its evidence, and
|
|
581
|
+
* fires for skipped and errored audits too — those are the ones worth seeing.
|
|
582
|
+
*/
|
|
583
|
+
type AuditTraceHandler = (trace: AuditTrace) => void;
|
|
417
584
|
interface AuditPlan {
|
|
418
585
|
runnable: Array<{
|
|
419
586
|
reg: AuditRegistration;
|
|
@@ -421,23 +588,402 @@ interface AuditPlan {
|
|
|
421
588
|
}>;
|
|
422
589
|
skipped: CheckResult[];
|
|
423
590
|
}
|
|
591
|
+
/** How `planAudits` should treat audits the scan cannot feed. */
|
|
592
|
+
interface PlanOptions {
|
|
593
|
+
/**
|
|
594
|
+
* Skip audits whose `requires` the scan did not obtain.
|
|
595
|
+
*
|
|
596
|
+
* Off by default while the gate is calibrated: turning it on changes what
|
|
597
|
+
* every blocked or client-rendered scan reports, and that lands as one
|
|
598
|
+
* deliberate change rather than as a side effect of this one.
|
|
599
|
+
*/
|
|
600
|
+
enforceEvidence?: boolean;
|
|
601
|
+
}
|
|
424
602
|
/**
|
|
425
603
|
* Split a scan config into the audits that will actually execute and the
|
|
426
|
-
*
|
|
604
|
+
* `na` stubs for those it will not. Exported so the orchestrator can size the
|
|
427
605
|
* audits progress phase before running it.
|
|
428
606
|
*/
|
|
429
|
-
declare function planAudits(ctx: CheckContext, config: ScanConfig): AuditPlan;
|
|
607
|
+
declare function planAudits(ctx: CheckContext, config: ScanConfig, options?: PlanOptions): AuditPlan;
|
|
430
608
|
/**
|
|
431
609
|
* Execute all audits registered in the config against the scan context.
|
|
432
610
|
* Returns check results grouped into categories with weighted scores.
|
|
433
611
|
* Pass a precomputed `plan` (from {@link planAudits}) to avoid recomputing it.
|
|
434
612
|
*/
|
|
435
|
-
declare function runAudits(ctx: CheckContext, config: ScanConfig, onEvent?: (event: AuditProgressEvent) => void, plan?: AuditPlan): Promise<AuditRunResult>;
|
|
613
|
+
declare function runAudits(ctx: CheckContext, config: ScanConfig, onEvent?: (event: AuditProgressEvent) => void, plan?: AuditPlan, onTrace?: AuditTraceHandler): Promise<AuditRunResult>;
|
|
614
|
+
|
|
615
|
+
type PhaseId = 'fetch-root' | 'fetch-pages' | 'analyze' | 'audits' | 'report';
|
|
616
|
+
type ScanEvent = {
|
|
617
|
+
type: 'scan:start';
|
|
618
|
+
url: string;
|
|
619
|
+
fraction: number;
|
|
620
|
+
elapsedMs: number;
|
|
621
|
+
} | {
|
|
622
|
+
type: 'phase:start';
|
|
623
|
+
phase: PhaseId;
|
|
624
|
+
totalUnits: number;
|
|
625
|
+
fraction: number;
|
|
626
|
+
elapsedMs: number;
|
|
627
|
+
} | {
|
|
628
|
+
type: 'unit:done';
|
|
629
|
+
phase: PhaseId;
|
|
630
|
+
completed: number;
|
|
631
|
+
total: number;
|
|
632
|
+
label?: string;
|
|
633
|
+
fraction: number;
|
|
634
|
+
elapsedMs: number;
|
|
635
|
+
} | {
|
|
636
|
+
type: 'unit:fail';
|
|
637
|
+
phase: PhaseId;
|
|
638
|
+
label: string;
|
|
639
|
+
error: string;
|
|
640
|
+
fraction: number;
|
|
641
|
+
elapsedMs: number;
|
|
642
|
+
} | {
|
|
643
|
+
type: 'phase:done';
|
|
644
|
+
phase: PhaseId;
|
|
645
|
+
durationMs: number;
|
|
646
|
+
fraction: number;
|
|
647
|
+
elapsedMs: number;
|
|
648
|
+
} | {
|
|
649
|
+
type: 'scan:done';
|
|
650
|
+
durationMs: number;
|
|
651
|
+
/** Null when the scan obtained too little evidence to judge the site. */
|
|
652
|
+
score: number | null;
|
|
653
|
+
fraction: number;
|
|
654
|
+
elapsedMs: number;
|
|
655
|
+
};
|
|
656
|
+
/**
|
|
657
|
+
* How much of the overall scan fraction each phase owns. Sums to 1 so the
|
|
658
|
+
* fraction of a finished scan is exactly 1.
|
|
659
|
+
*/
|
|
660
|
+
declare const PHASE_WEIGHTS: Record<PhaseId, number>;
|
|
661
|
+
/**
|
|
662
|
+
* Turns scan milestones into {@link ScanEvent}s. Owns the fraction math
|
|
663
|
+
* (`Σ weights of done phases + current phase weight × completed/total`),
|
|
664
|
+
* clamps it to be monotonic non-decreasing, and stamps `fraction`/`elapsedMs`
|
|
665
|
+
* onto every event. Emission is synchronous — throttling is a consumer concern.
|
|
666
|
+
*/
|
|
667
|
+
declare class ProgressTracker {
|
|
668
|
+
private readonly onEvent;
|
|
669
|
+
private readonly startMs;
|
|
670
|
+
private doneWeight;
|
|
671
|
+
private phase;
|
|
672
|
+
private phaseStartMs;
|
|
673
|
+
private totalUnits;
|
|
674
|
+
private completedUnits;
|
|
675
|
+
private lastFraction;
|
|
676
|
+
constructor(onEvent: (event: ScanEvent) => void);
|
|
677
|
+
/** Fraction of the whole scan that is complete, in [0, 1]. Never decreases. */
|
|
678
|
+
get fraction(): number;
|
|
679
|
+
/** Stamp an event with the current fraction/elapsed and advance the floor. */
|
|
680
|
+
private stamp;
|
|
681
|
+
private elapsedMs;
|
|
682
|
+
scanStart(url: string): void;
|
|
683
|
+
phaseStart(phase: PhaseId, totalUnits: number): void;
|
|
684
|
+
/** Correct the current phase's unit total (e.g. discovery finds pages mid-phase). */
|
|
685
|
+
setPhaseTotal(totalUnits: number): void;
|
|
686
|
+
unitDone(label?: string): void;
|
|
687
|
+
/** A failed unit still counts as settled work so the phase can complete. */
|
|
688
|
+
unitFail(label: string, error: string): void;
|
|
689
|
+
phaseDone(): void;
|
|
690
|
+
scanDone(score: number | null): void;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
interface ScanOptions {
|
|
694
|
+
onEvent?: (event: ScanEvent) => void;
|
|
695
|
+
pages?: PageOverride[] | null;
|
|
696
|
+
signal?: AbortSignal;
|
|
697
|
+
/**
|
|
698
|
+
* Restrict the scan to these category ids. Unknown ids simply match nothing —
|
|
699
|
+
* validate them at the entry point so the operator hears about a typo.
|
|
700
|
+
*/
|
|
701
|
+
categories?: string[];
|
|
702
|
+
/**
|
|
703
|
+
* Include audits whose tier is `experimental`. Off by default, per
|
|
704
|
+
* docs/evidence/policy.md: an experimental check is behind a flag and never
|
|
705
|
+
* scored.
|
|
706
|
+
*/
|
|
707
|
+
includeExperimental?: boolean;
|
|
708
|
+
/**
|
|
709
|
+
* Called once per registered audit with what it did — including the ones
|
|
710
|
+
* that were skipped or failed. Use it to trace a verdict back to the
|
|
711
|
+
* evidence it was drawn from; see {@link AuditTrace}.
|
|
712
|
+
*/
|
|
713
|
+
onAuditTrace?: AuditTraceHandler;
|
|
714
|
+
/**
|
|
715
|
+
* Skip audits the scan could not feed, instead of running them blind.
|
|
716
|
+
*
|
|
717
|
+
* On by default. An audit whose required evidence the scan never obtained
|
|
718
|
+
* reports `na` tagged `skipped:no-evidence`, with the reason attached, and
|
|
719
|
+
* is never constructed. Set it to `false` to run every audit regardless —
|
|
720
|
+
* useful when measuring what the gate removes.
|
|
721
|
+
*/
|
|
722
|
+
enforceEvidenceGate?: boolean;
|
|
723
|
+
}
|
|
724
|
+
declare function runScan(url: string, options?: ScanOptions): Promise<ScanReport>;
|
|
725
|
+
|
|
726
|
+
type FetchClass = 'ok' | 'soft-404' | 'blocked' | 'missing' | 'error';
|
|
727
|
+
type ExpectedKind = 'text' | 'json' | 'xml' | 'html';
|
|
728
|
+
declare function stripBom(text: string): string;
|
|
729
|
+
declare function normalizeNewlines(text: string): string;
|
|
730
|
+
/**
|
|
731
|
+
* Classify a fetched root file honestly. `status === 200` alone is not
|
|
732
|
+
* "the file exists": SPAs and some CDNs return the HTML app shell (200)
|
|
733
|
+
* for any unknown path — a soft 404 that inflated v1 scores.
|
|
734
|
+
*
|
|
735
|
+
* The body is the primary evidence and outranks the Content-Type header. Plenty
|
|
736
|
+
* of real servers ship `/llms.txt` or `/sitemap.xml` as `text/html`; trusting
|
|
737
|
+
* the header there would condemn a genuine file as a soft 404.
|
|
738
|
+
*/
|
|
739
|
+
declare function classifyFetch(result: FetchResult | undefined, expected: ExpectedKind): FetchClass;
|
|
740
|
+
declare function isRealFile(result: FetchResult | undefined, expected: ExpectedKind): boolean;
|
|
741
|
+
|
|
742
|
+
interface RobotsRule {
|
|
743
|
+
type: 'allow' | 'disallow';
|
|
744
|
+
path: string;
|
|
745
|
+
}
|
|
746
|
+
interface RobotsGroup {
|
|
747
|
+
userAgent: string;
|
|
748
|
+
rules: RobotsRule[];
|
|
749
|
+
crawlDelay?: number;
|
|
750
|
+
/**
|
|
751
|
+
* Directives inside the group that are neither rules nor crawl-delay, in file
|
|
752
|
+
* order — Content-Signal, Request-rate, vendor extensions. Retained rather
|
|
753
|
+
* than dropped: a directive we do not model today is still evidence about
|
|
754
|
+
* what the operator declared. Absent when the group carries none.
|
|
755
|
+
*/
|
|
756
|
+
otherDirectives?: Array<{
|
|
757
|
+
name: string;
|
|
758
|
+
value: string;
|
|
759
|
+
}>;
|
|
760
|
+
}
|
|
761
|
+
/** A parsed robots.txt: its groups plus the file-level directives outside them. */
|
|
762
|
+
interface RobotsFile {
|
|
763
|
+
groups: RobotsGroup[];
|
|
764
|
+
/**
|
|
765
|
+
* Every `Sitemap:` value, in file order. RFC 9309 §2.2.3 makes this directive
|
|
766
|
+
* host-global and user-agent independent: it is not part of any group, which
|
|
767
|
+
* is exactly why a site can advertise a sitemap its own rules forbid.
|
|
768
|
+
*/
|
|
769
|
+
sitemaps: string[];
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Parse robots.txt into groups plus file-level directives.
|
|
773
|
+
*
|
|
774
|
+
* `parseRobots` returns this function's `groups` and nothing else, so the
|
|
775
|
+
* group-only view every existing audit uses and the whole-file view added for
|
|
776
|
+
* the Plan 5 discovery audits can never disagree about grouping.
|
|
777
|
+
*/
|
|
778
|
+
declare function parseRobotsFile(body: string): RobotsFile;
|
|
779
|
+
declare function parseRobots(body: string): RobotsGroup[];
|
|
780
|
+
/** Group UA "GPTBot/1.1" matches bot token "gptbot": compare the product token. */
|
|
781
|
+
declare function matchesUserAgent(groupUserAgent: string, botToken: string): boolean;
|
|
782
|
+
/**
|
|
783
|
+
* Does this bot have a group of its own?
|
|
784
|
+
*
|
|
785
|
+
* Under RFC 9309 §2.2.1 a crawler obeys the most specific matching group and
|
|
786
|
+
* ignores `*` entirely once a named group exists. So "the wildcard allows it"
|
|
787
|
+
* is not an answer for a bot with its own group, and this predicate is what
|
|
788
|
+
* lets an audit report which set of rules actually applied.
|
|
789
|
+
*/
|
|
790
|
+
declare function hasNamedGroup(groups: RobotsGroup[], botToken: string): boolean;
|
|
791
|
+
declare function groupsForBot(groups: RobotsGroup[], botToken: string): RobotsGroup[];
|
|
792
|
+
declare function isPathAllowed(groups: RobotsGroup[], botToken: string, path: string): boolean;
|
|
793
|
+
declare function isBlanketBlocked(groups: RobotsGroup[], botToken: string): boolean;
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* The AI crawlers whose published User-Agent strings can actually be sent.
|
|
797
|
+
*
|
|
798
|
+
* `Google-Extended` is deliberately absent: it is a robots.txt product token
|
|
799
|
+
* only, with no user agent of its own, so a UA probe for it would compare the
|
|
800
|
+
* site against a string that never appears in real traffic.
|
|
801
|
+
*/
|
|
802
|
+
declare const AI_CRAWLER_UAS: ReadonlyArray<{
|
|
803
|
+
token: string;
|
|
804
|
+
ua: string;
|
|
805
|
+
label: string;
|
|
806
|
+
}>;
|
|
807
|
+
/** A mainstream browser UA. The control half of every pair. */
|
|
808
|
+
declare const BASELINE_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36";
|
|
809
|
+
/**
|
|
810
|
+
* How a site answered a crawler UA, relative to the same request as a browser.
|
|
811
|
+
*
|
|
812
|
+
* The classes are deliberately distinct rather than one "blocked" verdict:
|
|
813
|
+
* a Cloudflare interstitial, a pay-per-crawl price, a proof-of-work wall and a
|
|
814
|
+
* rate limit each need a different remedy from the operator, and an opaque 403
|
|
815
|
+
* may even be correct impersonation defence.
|
|
816
|
+
*/
|
|
817
|
+
type BlockClass = 'ok' | 'cf-challenge' | 'pay-per-crawl' | 'anubis-pow' | 'rate-limited' | 'opaque-403' | 'soft-block' | 'transport-error';
|
|
818
|
+
interface UaProbe {
|
|
819
|
+
url: string;
|
|
820
|
+
token: string;
|
|
821
|
+
baselineStatus: number;
|
|
822
|
+
probeStatus: number;
|
|
823
|
+
blockClass: BlockClass;
|
|
824
|
+
/** Probe main-content length over baseline main-content length. */
|
|
825
|
+
textRatio: number;
|
|
826
|
+
/** The shortest decisive fact — a header line, a status, a body marker. */
|
|
827
|
+
evidence: string;
|
|
828
|
+
/** Main-content text of each side, kept so a caller can diff them word by word. */
|
|
829
|
+
baselineText: string;
|
|
830
|
+
probeText: string;
|
|
831
|
+
/** The raw baseline body, so a caller can resolve declared markup against the DOM a browser got. */
|
|
832
|
+
baselineBody: string;
|
|
833
|
+
/** The raw probe body, so a caller can look for challenge fingerprints the classifier does not model. */
|
|
834
|
+
probeBody: string;
|
|
835
|
+
/** Baseline response headers, so a caller can read a price or a payment challenge. */
|
|
836
|
+
baselineHeaders: Record<string, string>;
|
|
837
|
+
/** Probe response headers, for the same reason. */
|
|
838
|
+
probeHeaders: Record<string, string>;
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Compare one crawler-UA response against the browser baseline for the same URL.
|
|
842
|
+
*
|
|
843
|
+
* A non-2xx baseline yields `ok` for every probe: the scanner could not read the
|
|
844
|
+
* page either, so nothing observed is bot-specific and reporting it would be a
|
|
845
|
+
* finding about our own fetch.
|
|
846
|
+
*/
|
|
847
|
+
declare function classifyResponse(baseline: FetchResult, probe: FetchResult): {
|
|
848
|
+
blockClass: BlockClass;
|
|
849
|
+
textRatio: number;
|
|
850
|
+
evidence: string;
|
|
851
|
+
baselineText: string;
|
|
852
|
+
probeText: string;
|
|
853
|
+
baselineBody: string;
|
|
854
|
+
probeBody: string;
|
|
855
|
+
baselineHeaders: Record<string, string>;
|
|
856
|
+
probeHeaders: Record<string, string>;
|
|
857
|
+
};
|
|
858
|
+
/**
|
|
859
|
+
* Fetch each URL once as a browser, then once per crawler token, and classify
|
|
860
|
+
* the difference.
|
|
861
|
+
*
|
|
862
|
+
* The baseline is fetched once per URL and reused across tokens, so a six-token
|
|
863
|
+
* sweep costs seven requests per URL rather than twelve. URLs are `isSafeUrl`-
|
|
864
|
+
* gated because callers pass in addresses read out of site-controlled content.
|
|
865
|
+
*/
|
|
866
|
+
declare function probeUaParity(fetch: (options: FetchOptions) => Promise<FetchResult>, urls: string[], tokens: string[], opts?: {
|
|
867
|
+
signal?: AbortSignal;
|
|
868
|
+
}): Promise<UaProbe[]>;
|
|
869
|
+
|
|
870
|
+
/** One `<url>` row of a sitemap. */
|
|
871
|
+
interface SitemapEntry {
|
|
872
|
+
loc: string;
|
|
873
|
+
lastmod?: string;
|
|
874
|
+
}
|
|
875
|
+
/** The result of walking a site's advertised sitemap roots. */
|
|
876
|
+
interface SitemapTree {
|
|
877
|
+
entries: SitemapEntry[];
|
|
878
|
+
/** Child sitemaps actually fetched, in order. Empty for a flat sitemap. */
|
|
879
|
+
childSitemaps: string[];
|
|
880
|
+
/** How many `<lastmod>` values failed the W3C Datetime shape. */
|
|
881
|
+
malformedLastmod: number;
|
|
882
|
+
/** True when a cap stopped the walk, so `entries` is not the whole tree. */
|
|
883
|
+
truncated: boolean;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Does this value parse as a W3C Datetime, the format the sitemap protocol
|
|
887
|
+
* requires of `<lastmod>`?
|
|
888
|
+
*
|
|
889
|
+
* Accepts the date-only `YYYY-MM-DD` form and the full RFC 3339 timestamp.
|
|
890
|
+
* Deliberately strict about the shape before handing anything to `Date`, which
|
|
891
|
+
* would happily accept `1755859200` (epoch seconds, a common CMS bug) and
|
|
892
|
+
* every prose string a locale parser recognises.
|
|
893
|
+
*/
|
|
894
|
+
declare function isW3CDateTime(value: string): boolean;
|
|
895
|
+
/**
|
|
896
|
+
* Walk a site's sitemap roots and collect their `<url>` rows.
|
|
897
|
+
*
|
|
898
|
+
* Recursion stops after one level of `<sitemapindex>`: the depth of a nested
|
|
899
|
+
* index is chosen by the site being scanned, so following it arbitrarily is an
|
|
900
|
+
* unbounded walk driven by untrusted input. Every child URL is `isSafeUrl`- and
|
|
901
|
+
* same-host-gated before it is fetched, for the same reason.
|
|
902
|
+
*/
|
|
903
|
+
declare function collectSitemapEntries(fetch: (options: FetchOptions) => Promise<FetchResult>, roots: string[], opts?: {
|
|
904
|
+
maxChildren?: number;
|
|
905
|
+
maxEntries?: number;
|
|
906
|
+
signal?: AbortSignal;
|
|
907
|
+
}): Promise<SitemapTree>;
|
|
908
|
+
/**
|
|
909
|
+
* Take an even-strided sample of `n` entries.
|
|
910
|
+
*
|
|
911
|
+
* Deterministic rather than random: two audits sampling the same tree must
|
|
912
|
+
* probe the same URLs, or a finding from one cannot be lined up against a
|
|
913
|
+
* finding from the other.
|
|
914
|
+
*/
|
|
915
|
+
declare function sampleEntries(entries: SitemapEntry[], n: number): SitemapEntry[];
|
|
916
|
+
|
|
917
|
+
/** One `selector { declarations }` rule, with the at-rule it sits inside. */
|
|
918
|
+
interface CssRule {
|
|
919
|
+
/** The full selector list, verbatim, so a human can adjudicate a match. */
|
|
920
|
+
selector: string;
|
|
921
|
+
/** The declaration block, lowercased and whitespace-collapsed. */
|
|
922
|
+
declarations: string;
|
|
923
|
+
/** The enclosing at-rule prelude (`media print`, `supports (...)`), if any. */
|
|
924
|
+
atRule?: string;
|
|
925
|
+
/** Where the rule came from, for evidence. */
|
|
926
|
+
origin: 'inline' | string;
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Scan a stylesheet into flat selector/declaration pairs.
|
|
930
|
+
*
|
|
931
|
+
* Deliberately a scanner, not a parser: it performs no cascade, no specificity
|
|
932
|
+
* ordering and no media-query evaluation. It exists so an audit can ask "does
|
|
933
|
+
* any rule that could apply to this element declare display:none", and it
|
|
934
|
+
* reports the matched selector text so a human can check the answer. A full CSS
|
|
935
|
+
* engine would need a dependency the core package does not carry.
|
|
936
|
+
*/
|
|
937
|
+
declare function parseCssRules(source: string, origin?: string): CssRule[];
|
|
938
|
+
interface PageCss {
|
|
939
|
+
rules: CssRule[];
|
|
940
|
+
/** Stylesheet URLs on another origin, which are never fetched. */
|
|
941
|
+
skippedCrossOrigin: string[];
|
|
942
|
+
/** Stylesheet URLs fetched and scanned. */
|
|
943
|
+
fetched: string[];
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Collect every CSS rule a page's own markup and same-origin stylesheets carry.
|
|
947
|
+
*
|
|
948
|
+
* Cross-origin sheets are not fetched: a scan must not pull bytes from a third
|
|
949
|
+
* party on the scanned site's behalf. They are reported instead, so a result
|
|
950
|
+
* built on partial CSS says so rather than reading as complete.
|
|
951
|
+
*/
|
|
952
|
+
declare function collectPageCss(ctx: CheckContext, page: PageContext): Promise<PageCss>;
|
|
953
|
+
|
|
954
|
+
declare function pagesOfType(ctx: CheckContext, ...types: PageType[]): PageContext[];
|
|
955
|
+
interface PageJudgement {
|
|
956
|
+
page: PageContext;
|
|
957
|
+
ok: boolean;
|
|
958
|
+
detail?: string;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Run one judgement over a set of pages. v1 audits frequently judged only
|
|
962
|
+
* pages[0] and generalized to the whole site; this makes per-page judgement
|
|
963
|
+
* the path of least resistance. Callers MUST return notApplicable when
|
|
964
|
+
* `judged.length === 0` — an empty set proves nothing.
|
|
965
|
+
*/
|
|
966
|
+
declare function judgePages(pages: PageContext[], judge: (page: PageContext) => {
|
|
967
|
+
ok: boolean;
|
|
968
|
+
detail?: string;
|
|
969
|
+
}): {
|
|
970
|
+
judged: PageJudgement[];
|
|
971
|
+
passRate: number;
|
|
972
|
+
failures: PageJudgement[];
|
|
973
|
+
};
|
|
436
974
|
|
|
437
975
|
declare function parseHtml(html: string): CheerioAPI;
|
|
438
976
|
declare function extractJsonLd($: CheerioAPI): object[];
|
|
439
977
|
/**
|
|
440
|
-
*
|
|
978
|
+
* Top-level JSON-LD entities only: expands arrays and `@graph` members,
|
|
979
|
+
* propagating `@context` downward. Nested property objects (an Article's
|
|
980
|
+
* `publisher`, a Product's `offers`) are NOT hoisted — v1's deep flatten
|
|
981
|
+
* made audits "find" entities the page never declared at top level.
|
|
982
|
+
*/
|
|
983
|
+
declare function topLevelJsonLd(blocks: object[]): object[];
|
|
984
|
+
/**
|
|
985
|
+
* Every JSON-LD node including nested ones — for audits that legitimately
|
|
986
|
+
* search deep (e.g. AggregateRating nested under Product).
|
|
441
987
|
*
|
|
442
988
|
* Real-world JSON-LD nests the interesting types arbitrarily: a top-level
|
|
443
989
|
* `[{...}]` array (common on Shopify), an `@graph` container, and properties
|
|
@@ -447,7 +993,9 @@ declare function extractJsonLd($: CheerioAPI): object[];
|
|
|
447
993
|
* invisible — producing false "schema not found" verdicts. This walks every
|
|
448
994
|
* object and array value so type/property lookups see the whole graph.
|
|
449
995
|
*/
|
|
450
|
-
declare function
|
|
996
|
+
declare function allJsonLdNodes(blocks: object[]): object[];
|
|
997
|
+
/** @deprecated v1 name for the deep walk. Use topLevelJsonLd (structure) or allJsonLdNodes (deep search). Removed in v2. */
|
|
998
|
+
declare const flattenJsonLd: typeof allJsonLdNodes;
|
|
451
999
|
/**
|
|
452
1000
|
* Extract links from Markdown-ish text (llms.txt, READMEs).
|
|
453
1001
|
*
|
|
@@ -475,7 +1023,28 @@ declare function extractHeadings($: CheerioAPI): Array<{
|
|
|
475
1023
|
level: number;
|
|
476
1024
|
text: string;
|
|
477
1025
|
}>;
|
|
1026
|
+
/**
|
|
1027
|
+
* The text of the page's main content region.
|
|
1028
|
+
*
|
|
1029
|
+
* Answers what the main content says, so page chrome — navigation, headers,
|
|
1030
|
+
* footers, cookie banners — stays out of it. Among several <main> elements the
|
|
1031
|
+
* one holding the most text wins: themes ship empty or fragmented <main>
|
|
1032
|
+
* wrappers, and the first one is often a short stub. Falls back to the whole
|
|
1033
|
+
* <body> only when no <main> holds any text.
|
|
1034
|
+
*
|
|
1035
|
+
* For shell detection — did the server render any readable text at all — use
|
|
1036
|
+
* `getRenderedText` instead.
|
|
1037
|
+
*/
|
|
478
1038
|
declare function getMainContentText($: CheerioAPI): string;
|
|
1039
|
+
/**
|
|
1040
|
+
* The text of the served HTML body, minus script/style/noscript/template.
|
|
1041
|
+
*
|
|
1042
|
+
* Answers whether the server rendered any readable text at all, so page chrome
|
|
1043
|
+
* counts: a shell that ships only a nav and a footer is exactly what this has
|
|
1044
|
+
* to see. Use `getMainContentText` when the question is what the main content
|
|
1045
|
+
* says rather than whether anything was served.
|
|
1046
|
+
*/
|
|
1047
|
+
declare function getRenderedText($: CheerioAPI): string;
|
|
479
1048
|
declare function getWordCount($: CheerioAPI): number;
|
|
480
1049
|
declare function extractImages($: CheerioAPI): Array<{
|
|
481
1050
|
src: string;
|
|
@@ -486,6 +1055,9 @@ declare function extractImages($: CheerioAPI): Array<{
|
|
|
486
1055
|
loading?: string;
|
|
487
1056
|
role?: string;
|
|
488
1057
|
ariaHidden?: string;
|
|
1058
|
+
ariaLabel?: string;
|
|
1059
|
+
ariaLabelledby?: string;
|
|
1060
|
+
title?: string;
|
|
489
1061
|
}>;
|
|
490
1062
|
declare function extractForms($: CheerioAPI): Array<{
|
|
491
1063
|
action: string;
|
|
@@ -525,7 +1097,9 @@ declare const MAX_CONCURRENT_REQUESTS = 10;
|
|
|
525
1097
|
declare const SCANNER_USER_AGENT = "AgentLighthouse/1.0 (+https://github.com/ForkPoint/agent-lighthouse)";
|
|
526
1098
|
declare const TAG_SKIPPED_PAGE_TYPE = "skipped:page-type";
|
|
527
1099
|
declare const TAG_SCAN_ERROR = "scan-error";
|
|
528
|
-
|
|
1100
|
+
/** An audit the scan could not feed: it needs evidence this scan never got. */
|
|
1101
|
+
declare const TAG_SKIPPED_NO_EVIDENCE = "skipped:no-evidence";
|
|
1102
|
+
/** Display names for the 8 v2 categories. */
|
|
529
1103
|
declare const CATEGORY_NAMES: Record<string, string>;
|
|
530
1104
|
declare const READINESS_WEIGHTS: {
|
|
531
1105
|
readonly commerce: 0.4;
|
|
@@ -538,6 +1112,12 @@ declare const SCORE_TIER_LABELS: Record<ScoreTier, string>;
|
|
|
538
1112
|
declare function getTierLabel(tier: string | null): string;
|
|
539
1113
|
declare function getTierColor(tier: string | null): string;
|
|
540
1114
|
|
|
1115
|
+
/**
|
|
1116
|
+
* Spec §4 weight law: an audit's scoring weight is a pure function of its
|
|
1117
|
+
* evidence grade and tier. Only the `scored` tier carries weight, and only
|
|
1118
|
+
* grades A and B are proven enough to move a score.
|
|
1119
|
+
*/
|
|
1120
|
+
declare function weightForGrade(grade: EvidenceGrade, tier: AuditTier): number;
|
|
541
1121
|
/**
|
|
542
1122
|
* Single source of truth for "this check is advisory only".
|
|
543
1123
|
*
|
|
@@ -548,7 +1128,15 @@ declare function getTierColor(tier: string | null): string;
|
|
|
548
1128
|
*/
|
|
549
1129
|
declare function isInformative(check: Pick<CheckResult, 'scoreDisplayMode'>): boolean;
|
|
550
1130
|
declare function calculateCategoryScore(checks: CheckResult[]): number;
|
|
551
|
-
|
|
1131
|
+
/**
|
|
1132
|
+
* Assemble a category result.
|
|
1133
|
+
*
|
|
1134
|
+
* `mass` is the category's evidence mass — the summed weight of its registered
|
|
1135
|
+
* audits (`CATEGORY_MASS` in audit-config). It is passed in rather than looked
|
|
1136
|
+
* up so this module stays free of a dependency on the registry, which the
|
|
1137
|
+
* audits themselves import (for `weightForGrade`).
|
|
1138
|
+
*/
|
|
1139
|
+
declare function buildCategoryResult(id: string, checks: CheckResult[], mass?: number): CategoryResult;
|
|
552
1140
|
declare function calculateOverallScore(categories: CategoryResult[]): number;
|
|
553
1141
|
|
|
554
1142
|
/**
|
|
@@ -572,17 +1160,19 @@ declare const AuditResultSchema: z.ZodObject<{
|
|
|
572
1160
|
expected: z.ZodOptional<z.ZodString>;
|
|
573
1161
|
found: z.ZodOptional<z.ZodString>;
|
|
574
1162
|
code: z.ZodOptional<z.ZodString>;
|
|
575
|
-
}, "strip", z.
|
|
576
|
-
|
|
577
|
-
found
|
|
578
|
-
|
|
579
|
-
}, {
|
|
580
|
-
|
|
581
|
-
found
|
|
582
|
-
|
|
583
|
-
}
|
|
1163
|
+
}, "strip", z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, z.objectOutputType<{
|
|
1164
|
+
expected: z.ZodOptional<z.ZodString>;
|
|
1165
|
+
found: z.ZodOptional<z.ZodString>;
|
|
1166
|
+
code: z.ZodOptional<z.ZodString>;
|
|
1167
|
+
}, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip">, z.objectInputType<{
|
|
1168
|
+
expected: z.ZodOptional<z.ZodString>;
|
|
1169
|
+
found: z.ZodOptional<z.ZodString>;
|
|
1170
|
+
code: z.ZodOptional<z.ZodString>;
|
|
1171
|
+
}, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip">>>;
|
|
584
1172
|
pageUrl: z.ZodUnion<[z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodString]>, z.ZodString]>;
|
|
585
1173
|
priority: z.ZodOptional<z.ZodEnum<["critical", "high", "medium", "low"]>>;
|
|
1174
|
+
/** A fix written from what this scan found, which beats the generic one in meta.guidance. */
|
|
1175
|
+
remediation: z.ZodOptional<z.ZodString>;
|
|
586
1176
|
code: z.ZodOptional<z.ZodString>;
|
|
587
1177
|
}, "strip", z.ZodTypeAny, {
|
|
588
1178
|
status: "warn" | "pass" | "fail" | "na";
|
|
@@ -590,32 +1180,34 @@ declare const AuditResultSchema: z.ZodObject<{
|
|
|
590
1180
|
code?: string | undefined;
|
|
591
1181
|
found?: string | undefined;
|
|
592
1182
|
expected?: string | undefined;
|
|
593
|
-
details?: {
|
|
594
|
-
|
|
595
|
-
found
|
|
596
|
-
|
|
597
|
-
} | undefined;
|
|
1183
|
+
details?: z.objectOutputType<{
|
|
1184
|
+
expected: z.ZodOptional<z.ZodString>;
|
|
1185
|
+
found: z.ZodOptional<z.ZodString>;
|
|
1186
|
+
code: z.ZodOptional<z.ZodString>;
|
|
1187
|
+
}, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip"> | undefined;
|
|
598
1188
|
message?: string | undefined;
|
|
599
1189
|
displayValue?: string | undefined;
|
|
600
1190
|
explanation?: string | undefined;
|
|
601
1191
|
pageUrl?: string | undefined;
|
|
602
1192
|
priority?: "critical" | "high" | "medium" | "low" | undefined;
|
|
1193
|
+
remediation?: string | undefined;
|
|
603
1194
|
}, {
|
|
604
1195
|
status: "warn" | "pass" | "fail" | "na";
|
|
605
1196
|
score: number;
|
|
606
1197
|
code?: string | undefined;
|
|
607
1198
|
found?: string | undefined;
|
|
608
1199
|
expected?: string | undefined;
|
|
609
|
-
details?: {
|
|
610
|
-
|
|
611
|
-
found
|
|
612
|
-
|
|
613
|
-
} | undefined;
|
|
1200
|
+
details?: z.objectInputType<{
|
|
1201
|
+
expected: z.ZodOptional<z.ZodString>;
|
|
1202
|
+
found: z.ZodOptional<z.ZodString>;
|
|
1203
|
+
code: z.ZodOptional<z.ZodString>;
|
|
1204
|
+
}, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip"> | undefined;
|
|
614
1205
|
message?: string | undefined;
|
|
615
1206
|
displayValue?: string | undefined;
|
|
616
1207
|
explanation?: string | undefined;
|
|
617
1208
|
pageUrl?: string | undefined;
|
|
618
1209
|
priority?: "critical" | "high" | "medium" | "low" | undefined;
|
|
1210
|
+
remediation?: string | undefined;
|
|
619
1211
|
}>;
|
|
620
1212
|
declare const FixEffortSchema: z.ZodEnum<["trivial", "easy", "moderate", "complex"]>;
|
|
621
1213
|
declare const AuditGuidanceSchema: z.ZodObject<{
|
|
@@ -651,6 +1243,13 @@ declare const DeprecationNoticeSchema: z.ZodObject<{
|
|
|
651
1243
|
link: string;
|
|
652
1244
|
notice: string;
|
|
653
1245
|
}>;
|
|
1246
|
+
/** Evidence strength from the audit's dossier (docs/evidence/policy.md). */
|
|
1247
|
+
declare const EvidenceGradeSchema: z.ZodEnum<["A", "B", "C", "D"]>;
|
|
1248
|
+
/** Scoring participation tier (spec §4). */
|
|
1249
|
+
declare const AuditTierSchema: z.ZodEnum<["scored", "informative", "experimental"]>;
|
|
1250
|
+
/** v2 audit identity: `category/slug`, stable across releases (spec §6). */
|
|
1251
|
+
declare const AUDIT_ID_PATTERN: RegExp;
|
|
1252
|
+
declare const EvidenceKeySchema: z.ZodEnum<["origin-reachable", "unblocked-fetches", "rendered-body", "sample-adequate"]>;
|
|
654
1253
|
declare const AuditMetaSchema: z.ZodObject<{
|
|
655
1254
|
id: z.ZodString;
|
|
656
1255
|
category: z.ZodString;
|
|
@@ -693,6 +1292,10 @@ declare const AuditMetaSchema: z.ZodObject<{
|
|
|
693
1292
|
link: string;
|
|
694
1293
|
notice: string;
|
|
695
1294
|
}>>;
|
|
1295
|
+
evidenceGrade: z.ZodEnum<["A", "B", "C", "D"]>;
|
|
1296
|
+
tier: z.ZodEnum<["scored", "informative", "experimental"]>;
|
|
1297
|
+
dossier: z.ZodString;
|
|
1298
|
+
requires: z.ZodOptional<z.ZodArray<z.ZodEnum<["origin-reachable", "unblocked-fetches", "rendered-body", "sample-adequate"]>, "many">>;
|
|
696
1299
|
}, "strip", z.ZodTypeAny, {
|
|
697
1300
|
category: string;
|
|
698
1301
|
title: string;
|
|
@@ -702,6 +1305,9 @@ declare const AuditMetaSchema: z.ZodObject<{
|
|
|
702
1305
|
scoreDisplayMode: "binary" | "ternary" | "informative";
|
|
703
1306
|
weight: number;
|
|
704
1307
|
defaultPriority: "critical" | "high" | "medium" | "low";
|
|
1308
|
+
evidenceGrade: "A" | "B" | "C" | "D";
|
|
1309
|
+
tier: "informative" | "scored" | "experimental";
|
|
1310
|
+
dossier: string;
|
|
705
1311
|
applicablePageTypes?: string[] | undefined;
|
|
706
1312
|
guidance?: {
|
|
707
1313
|
impact: string;
|
|
@@ -715,6 +1321,7 @@ declare const AuditMetaSchema: z.ZodObject<{
|
|
|
715
1321
|
link: string;
|
|
716
1322
|
notice: string;
|
|
717
1323
|
} | undefined;
|
|
1324
|
+
requires?: ("origin-reachable" | "unblocked-fetches" | "rendered-body" | "sample-adequate")[] | undefined;
|
|
718
1325
|
}, {
|
|
719
1326
|
category: string;
|
|
720
1327
|
title: string;
|
|
@@ -724,6 +1331,9 @@ declare const AuditMetaSchema: z.ZodObject<{
|
|
|
724
1331
|
scoreDisplayMode: "binary" | "ternary" | "informative";
|
|
725
1332
|
weight: number;
|
|
726
1333
|
defaultPriority: "critical" | "high" | "medium" | "low";
|
|
1334
|
+
evidenceGrade: "A" | "B" | "C" | "D";
|
|
1335
|
+
tier: "informative" | "scored" | "experimental";
|
|
1336
|
+
dossier: string;
|
|
727
1337
|
applicablePageTypes?: string[] | undefined;
|
|
728
1338
|
guidance?: {
|
|
729
1339
|
impact: string;
|
|
@@ -737,6 +1347,7 @@ declare const AuditMetaSchema: z.ZodObject<{
|
|
|
737
1347
|
link: string;
|
|
738
1348
|
notice: string;
|
|
739
1349
|
} | undefined;
|
|
1350
|
+
requires?: ("origin-reachable" | "unblocked-fetches" | "rendered-body" | "sample-adequate")[] | undefined;
|
|
740
1351
|
}>;
|
|
741
1352
|
declare const CheckResultSchema: z.ZodObject<{
|
|
742
1353
|
id: z.ZodString;
|
|
@@ -757,17 +1368,20 @@ declare const CheckResultSchema: z.ZodObject<{
|
|
|
757
1368
|
found: z.ZodOptional<z.ZodString>;
|
|
758
1369
|
code: z.ZodOptional<z.ZodString>;
|
|
759
1370
|
docsUrl: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodString]>;
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
expected
|
|
769
|
-
|
|
770
|
-
|
|
1371
|
+
evidenceUrl: z.ZodOptional<z.ZodString>;
|
|
1372
|
+
}, "strip", z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, z.objectOutputType<{
|
|
1373
|
+
expected: z.ZodOptional<z.ZodString>;
|
|
1374
|
+
found: z.ZodOptional<z.ZodString>;
|
|
1375
|
+
code: z.ZodOptional<z.ZodString>;
|
|
1376
|
+
docsUrl: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodString]>;
|
|
1377
|
+
evidenceUrl: z.ZodOptional<z.ZodString>;
|
|
1378
|
+
}, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip">, z.objectInputType<{
|
|
1379
|
+
expected: z.ZodOptional<z.ZodString>;
|
|
1380
|
+
found: z.ZodOptional<z.ZodString>;
|
|
1381
|
+
code: z.ZodOptional<z.ZodString>;
|
|
1382
|
+
docsUrl: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodString]>;
|
|
1383
|
+
evidenceUrl: z.ZodOptional<z.ZodString>;
|
|
1384
|
+
}, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip">>>;
|
|
771
1385
|
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
772
1386
|
deprecated: z.ZodOptional<z.ZodObject<{
|
|
773
1387
|
notice: z.ZodString;
|
|
@@ -779,6 +1393,8 @@ declare const CheckResultSchema: z.ZodObject<{
|
|
|
779
1393
|
link: string;
|
|
780
1394
|
notice: string;
|
|
781
1395
|
}>>;
|
|
1396
|
+
evidenceGrade: z.ZodOptional<z.ZodEnum<["A", "B", "C", "D"]>>;
|
|
1397
|
+
tier: z.ZodOptional<z.ZodEnum<["scored", "informative", "experimental"]>>;
|
|
782
1398
|
}, "strip", z.ZodTypeAny, {
|
|
783
1399
|
status: "warn" | "pass" | "fail" | "na";
|
|
784
1400
|
category: string;
|
|
@@ -790,12 +1406,13 @@ declare const CheckResultSchema: z.ZodObject<{
|
|
|
790
1406
|
fix: string;
|
|
791
1407
|
description: string;
|
|
792
1408
|
scoreDisplayMode: "binary" | "ternary" | "informative";
|
|
793
|
-
details?: {
|
|
794
|
-
|
|
795
|
-
found
|
|
796
|
-
|
|
797
|
-
docsUrl
|
|
798
|
-
|
|
1409
|
+
details?: z.objectOutputType<{
|
|
1410
|
+
expected: z.ZodOptional<z.ZodString>;
|
|
1411
|
+
found: z.ZodOptional<z.ZodString>;
|
|
1412
|
+
code: z.ZodOptional<z.ZodString>;
|
|
1413
|
+
docsUrl: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodString]>;
|
|
1414
|
+
evidenceUrl: z.ZodOptional<z.ZodString>;
|
|
1415
|
+
}, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip"> | undefined;
|
|
799
1416
|
displayValue?: string | undefined;
|
|
800
1417
|
explanation?: string | undefined;
|
|
801
1418
|
pageUrl?: string | undefined;
|
|
@@ -804,6 +1421,8 @@ declare const CheckResultSchema: z.ZodObject<{
|
|
|
804
1421
|
link: string;
|
|
805
1422
|
notice: string;
|
|
806
1423
|
} | undefined;
|
|
1424
|
+
evidenceGrade?: "A" | "B" | "C" | "D" | undefined;
|
|
1425
|
+
tier?: "informative" | "scored" | "experimental" | undefined;
|
|
807
1426
|
}, {
|
|
808
1427
|
status: "warn" | "pass" | "fail" | "na";
|
|
809
1428
|
category: string;
|
|
@@ -815,12 +1434,13 @@ declare const CheckResultSchema: z.ZodObject<{
|
|
|
815
1434
|
fix: string;
|
|
816
1435
|
description: string;
|
|
817
1436
|
scoreDisplayMode: "binary" | "ternary" | "informative";
|
|
818
|
-
details?: {
|
|
819
|
-
|
|
820
|
-
found
|
|
821
|
-
|
|
822
|
-
docsUrl
|
|
823
|
-
|
|
1437
|
+
details?: z.objectInputType<{
|
|
1438
|
+
expected: z.ZodOptional<z.ZodString>;
|
|
1439
|
+
found: z.ZodOptional<z.ZodString>;
|
|
1440
|
+
code: z.ZodOptional<z.ZodString>;
|
|
1441
|
+
docsUrl: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodString]>;
|
|
1442
|
+
evidenceUrl: z.ZodOptional<z.ZodString>;
|
|
1443
|
+
}, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString, "many">]>, "strip"> | undefined;
|
|
824
1444
|
displayValue?: string | undefined;
|
|
825
1445
|
explanation?: string | undefined;
|
|
826
1446
|
pageUrl?: string | undefined;
|
|
@@ -829,6 +1449,8 @@ declare const CheckResultSchema: z.ZodObject<{
|
|
|
829
1449
|
link: string;
|
|
830
1450
|
notice: string;
|
|
831
1451
|
} | undefined;
|
|
1452
|
+
evidenceGrade?: "A" | "B" | "C" | "D" | undefined;
|
|
1453
|
+
tier?: "informative" | "scored" | "experimental" | undefined;
|
|
832
1454
|
}>;
|
|
833
1455
|
|
|
834
1456
|
declare function normalizeUrl(input: string): string;
|
|
@@ -883,4 +1505,15 @@ declare class Logger {
|
|
|
883
1505
|
}
|
|
884
1506
|
declare const logger: Logger;
|
|
885
1507
|
|
|
886
|
-
|
|
1508
|
+
/** The ACP CheckoutSession `links` enum, in spec order. */
|
|
1509
|
+
declare const ACP_LINK_TYPES: readonly ["terms_of_use", "privacy_policy", "return_policy", "shipping_policy", "contact_us", "about_us", "faq", "support"];
|
|
1510
|
+
type AcpLinkType = (typeof ACP_LINK_TYPES)[number];
|
|
1511
|
+
/**
|
|
1512
|
+
* The first same-page candidate URL for each ACP link type, in document order.
|
|
1513
|
+
*
|
|
1514
|
+
* Classification is by URL path first, anchor text second, so a link whose path
|
|
1515
|
+
* is opaque (`/p/12345`) is still classified when its label is not.
|
|
1516
|
+
*/
|
|
1517
|
+
declare function resolvePolicyLinks(ctx: CheckContext): Map<AcpLinkType, string>;
|
|
1518
|
+
|
|
1519
|
+
export { ACP_LINK_TYPES, AI_CRAWLER_UAS, AUDIT_ID_PATTERN, type AcpLinkType, type AgentLighthouseConfig, Audit, type AuditGuidance, AuditGuidanceSchema, type AuditMeta, AuditMetaSchema, type AuditPlan, type AuditProgressEvent, type AuditRegistration, type AuditResult, AuditResultSchema, type AuditRunResult, type AuditTier, AuditTierSchema, type AuditTrace, type AuditTraceHandler, BASELINE_UA, type BlockClass, CATEGORY_IDS, CATEGORY_MASS, CATEGORY_NAMES, type CategoryConfig, type CategoryResult, type CheckContext, type CheckFn, type CheckPriority, CheckPrioritySchema, type CheckRecommendation, type CheckResult, CheckResultSchema, type CheckStatus, CheckStatusSchema, type CssRule, DEFAULT_SCAN_LIMIT, type DeprecationNotice, DeprecationNoticeSchema, type EvidenceGrade, EvidenceGradeSchema, type EvidenceKey, EvidenceKeySchema, type ExpectedKind, type FetchClass, type FetchOptions, type FetchResult, type FieldStatus, type FixEffort, FixEffortSchema, MAX_CONCURRENT_REQUESTS, MAX_PAGES_PER_SCAN, MAX_RESPONSE_BODY_BYTES, PAGE_TYPE_LABELS, PHASE_WEIGHTS, PRESETS, type PageContext, type PageCss, type PageJudgement, type PageOverride, type PageType, type PhaseId, type PresetName, type PresetOptions, type ProductFieldVerification, ProgressTracker, READINESS_WEIGHTS, REQUEST_TIMEOUT_MS, type ReadinessVitals, type RobotsFile, type RobotsGroup, type RobotsRule, SCANNER_USER_AGENT, SCAN_TIMEOUT_MS, SCORE_TIER_LABELS, type ScanConfig, type ScanEvent, type ScanEvidence, type ScanOptions, type ScanReport, type ScanValidity, type ScoreDisplayMode, ScoreDisplayModeSchema, type ScoreTier, type SitemapEntry, type SitemapTree, TAG_SCAN_ERROR, TAG_SKIPPED_NO_EVIDENCE, TAG_SKIPPED_PAGE_TYPE, type UaProbe, type WafProtection, allEvidenceMet, allJsonLdNodes, buildCategoryResult, buildScanEvidence, calculateCategoryScore, calculateOverallScore, classifyFetch, classifyResponse, collectPageCss, collectSitemapEntries, createFetcher, defaultConfig, defineConfig, detectPageType, detectWafProtection, evidenceUrl, extractDomain, extractForms, extractHeadLinks, extractHeadings, extractImages, extractInternalLinks, extractJsonLd, extractMarkdownLinks, extractMetaTags, extractNavLinks, extractProductFieldVerification, extractScripts, extractStylesheetUrls, filterConfig, flattenJsonLd, formatTrace, getMainContentText, getPreset, getRenderedText, getScoreTier, getTierColor, getTierLabel, getWordCount, groupsForBot, hasNamedGroup, isBlanketBlocked, isInformative, isPathAllowed, isPrivateIp, isRealFile, isSafeUrl, isW3CDateTime, joinUrl, judgePages, loadConfigFile, logger, matchesUserAgent, normalizeNewlines, normalizeUrl, outcomeOf, pagesOfType, parseCssRules, parseHtml, parseRobots, parseRobotsFile, planAudits, probeUaParity, resolvePolicyLinks, runAudits, runScan, sampleEntries, stripBom, topLevelJsonLd, traceFromCheck, weightForGrade };
|