@forkpoint/agent-lighthouse-core 3.1.0 → 4.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/dist/index.d.mts CHANGED
@@ -2,12 +2,13 @@ import { Dispatcher } from 'undici';
2
2
  import { CheerioAPI } from 'cheerio';
3
3
  import { z } from 'zod';
4
4
 
5
+ declare function isSafeUrl(url: string): Promise<boolean>;
5
6
  interface FetchOptions {
6
7
  url: string;
7
8
  timeout?: number;
8
9
  followRedirects?: boolean;
9
10
  acceptHeader?: string;
10
- method?: 'GET' | 'POST' | 'OPTIONS' | 'HEAD' | 'DELETE';
11
+ method?: "GET" | "POST" | "OPTIONS" | "HEAD" | "DELETE";
11
12
  body?: string;
12
13
  contentType?: string;
13
14
  /** Override the User-Agent header (e.g. to probe a site as a specific AI bot). */
@@ -57,11 +58,6 @@ interface FetchResult {
57
58
  }>;
58
59
  error?: string;
59
60
  }
60
- /**
61
- * Validates that a URL is safe to fetch (http/https scheme, non-private IP).
62
- * Use before fetching any URL extracted from scanned site content.
63
- */
64
- declare function isSafeUrl(url: string): Promise<boolean>;
65
61
  interface FetcherOptions {
66
62
  /**
67
63
  * undici dispatcher for every request this fetcher makes.
@@ -89,6 +85,10 @@ interface FetcherOptions {
89
85
  * is the origin.
90
86
  */
91
87
  maxConcurrent?: number;
88
+ /**
89
+ * Extra HTTP headers sent with every request from this fetcher.
90
+ */
91
+ headers?: Record<string, string>;
92
92
  }
93
93
  /**
94
94
  * A dispatcher that opens at most `connections` sockets per origin.
@@ -105,7 +105,7 @@ declare function createFetcher(fetcherOptions?: FetcherOptions): {
105
105
 
106
106
  interface WafProtection {
107
107
  isBlocked: boolean;
108
- provider?: 'akamai' | 'cloudflare' | 'datadome' | 'perimeterx' | 'imperva' | 'kasada' | 'aws-waf' | 'generic-waf' | 'connection-drop' | 'rate-limited';
108
+ provider?: "akamai" | "cloudflare" | "datadome" | "perimeterx" | "imperva" | "kasada" | "aws-waf" | "generic-waf" | "connection-drop" | "rate-limited";
109
109
  name: string;
110
110
  reason: string;
111
111
  statusCode?: number;
@@ -127,7 +127,7 @@ interface WafProtection {
127
127
  */
128
128
  declare function detectWafProtection(targetUrl: string, homepageResult: FetchResult | null, rootFiles: Record<string, FetchResult>, scannedPagesCount: number): WafProtection | null;
129
129
 
130
- type PageType = 'homepage' | 'category' | 'product' | 'content';
130
+ type PageType = "homepage" | "category" | "product" | "content";
131
131
  declare const PAGE_TYPE_LABELS: Record<PageType, string>;
132
132
  /**
133
133
  * A user-supplied page to scan with an explicit type.
@@ -137,7 +137,7 @@ interface PageOverride {
137
137
  pageType: PageType;
138
138
  }
139
139
  /** Per-field presence in a product page's structured data. */
140
- type FieldStatus = 'found' | 'partial' | 'missing';
140
+ type FieldStatus = "found" | "partial" | "missing";
141
141
  /**
142
142
  * Field-level verification of a product page, read directly from its structured
143
143
  * data (JSON-LD + Microdata + RDFa).
@@ -153,7 +153,7 @@ interface ProductFieldVerification {
153
153
  reviewCount: FieldStatus;
154
154
  sourceUrl?: string;
155
155
  }
156
- type FixEffort = 'trivial' | 'easy' | 'moderate' | 'complex';
156
+ type FixEffort = "trivial" | "easy" | "moderate" | "complex";
157
157
  interface AuditGuidance {
158
158
  /** Customer-facing explanation of business impact when this audit fails */
159
159
  impact: string;
@@ -168,7 +168,7 @@ interface AuditGuidance {
168
168
  /** Tags for filtering/grouping in the report UI */
169
169
  tags?: string[];
170
170
  }
171
- type ScoreDisplayMode = 'binary' | 'ternary' | 'informative';
171
+ type ScoreDisplayMode = "binary" | "ternary" | "informative";
172
172
  /** Public sunset notice for a deprecated audit (see docs/evidence/sunset/not-a-factor.md). */
173
173
  interface DeprecationNotice {
174
174
  /** One sentence: why this signal is not a factor. */
@@ -180,12 +180,12 @@ interface DeprecationNotice {
180
180
  * Strength of the published evidence backing an audit, assigned in the audit's
181
181
  * dossier per docs/evidence/policy.md. Only A and B carry scoring weight.
182
182
  */
183
- type EvidenceGrade = 'A' | 'B' | 'C' | 'D';
183
+ type EvidenceGrade = "A" | "B" | "C" | "D";
184
184
  /**
185
185
  * How an audit participates in scoring. Derived from its evidence grade
186
186
  * (spec §4): only `scored` audits move a category score.
187
187
  */
188
- type AuditTier = 'scored' | 'informative' | 'experimental';
188
+ type AuditTier = "scored" | "informative" | "experimental";
189
189
  interface AuditMeta {
190
190
  id: string;
191
191
  category: string;
@@ -194,6 +194,7 @@ interface AuditMeta {
194
194
  description: string;
195
195
  scoreDisplayMode: ScoreDisplayMode;
196
196
  weight: number;
197
+ pageTypes?: PageType[];
197
198
  applicablePageTypes?: PageType[];
198
199
  defaultPriority: CheckPriority;
199
200
  guidance?: AuditGuidance;
@@ -223,7 +224,7 @@ interface AuditMeta {
223
224
  * Declared here rather than in `scan-evidence.ts` so `AuditMeta` can name it
224
225
  * without the audit layer importing the scan layer.
225
226
  */
226
- type EvidenceKey = 'origin-reachable' | 'unblocked-fetches' | 'rendered-body' | 'sample-adequate';
227
+ type EvidenceKey = "origin-reachable" | "unblocked-fetches" | "rendered-body" | "sample-adequate";
227
228
  interface AuditResult {
228
229
  status: CheckStatus;
229
230
  score: number;
@@ -248,8 +249,8 @@ interface AuditResult {
248
249
  */
249
250
  remediation?: string;
250
251
  }
251
- type CheckStatus = 'pass' | 'warn' | 'fail' | 'na';
252
- type CheckPriority = 'critical' | 'high' | 'medium' | 'low';
252
+ type CheckStatus = "pass" | "warn" | "fail" | "na";
253
+ type CheckPriority = "critical" | "high" | "medium" | "low";
253
254
  interface CheckRecommendation {
254
255
  priority: CheckPriority;
255
256
  description: string;
@@ -298,8 +299,10 @@ interface CategoryResult {
298
299
  passCount: number;
299
300
  warnCount: number;
300
301
  failCount: number;
302
+ registryMass?: number;
303
+ assessedMass?: number;
301
304
  }
302
- type ScoreTier = 'agent-ready' | 'partially-ready' | 'needs-work' | 'not-ready';
305
+ type ScoreTier = "agent-ready" | "partially-ready" | "needs-work" | "not-ready";
303
306
  /**
304
307
  * Whether a verdict about this site can mean anything, and what is missing.
305
308
  *
@@ -349,6 +352,39 @@ interface ScanReport {
349
352
  readinessVitals?: ReadinessVitals;
350
353
  productFields?: ProductFieldVerification;
351
354
  wafProtection?: WafProtection;
355
+ originEvidence?: {
356
+ origin: string;
357
+ version: string;
358
+ readAt: string;
359
+ cached: boolean;
360
+ };
361
+ conditions?: ScanConditions;
362
+ }
363
+ interface ScanConditions {
364
+ url: string;
365
+ pageType: {
366
+ type: PageType;
367
+ source: "declared" | "detected";
368
+ };
369
+ origin: {
370
+ origin: string;
371
+ version: string;
372
+ readAt: string;
373
+ cached: boolean;
374
+ };
375
+ coverage: {
376
+ registryMass: number;
377
+ assessedMass: number;
378
+ pageMass: number;
379
+ originMass: number;
380
+ gatedMass: number;
381
+ };
382
+ unscored: {
383
+ totalCount: number;
384
+ informativeCount: number;
385
+ gatedCount: number;
386
+ reasons: Record<string, number>;
387
+ };
352
388
  }
353
389
  interface ReadinessVitals {
354
390
  commerce: number;
@@ -357,6 +393,54 @@ interface ReadinessVitals {
357
393
  technical: number;
358
394
  }
359
395
 
396
+ interface OriginCacheOptions {
397
+ bypassOriginCache?: boolean;
398
+ headers?: Record<string, string>;
399
+ }
400
+ interface OriginEvidence {
401
+ origin: string;
402
+ version: string;
403
+ readAt: string;
404
+ rootFiles: Record<string, FetchResult>;
405
+ originHomepage?: FetchResult;
406
+ }
407
+ /**
408
+ * Derives a canonical anonymous origin cache key.
409
+ *
410
+ * Strips any userinfo/credentials from the URL so credentials never leak into
411
+ * cache keys. Request headers other than credentials are folded in as a
412
+ * fingerprint: a scan that sets a bot user agent must not share a slot with
413
+ * a default scan, since the origin may answer the two differently.
414
+ */
415
+ declare function computeOriginCacheKey(url: string, version?: string, headers?: Record<string, string>): string;
416
+ /**
417
+ * Determines whether the scan must bypass the shared origin cache.
418
+ * Authenticated scans (URL credentials, Authorization header, cookies) or explicit bypass flags bypass the shared cache.
419
+ */
420
+ declare function shouldBypassOriginCache(url: string, options?: OriginCacheOptions): boolean;
421
+ /**
422
+ * A bounded, TTL-evicting store of origin evidence.
423
+ *
424
+ * Expired entries are swept on every write, and the oldest entry is dropped
425
+ * when the store is full, so a process that scans many origins once each
426
+ * cannot grow without limit. A read of an expired key evicts it as well.
427
+ */
428
+ declare class OriginCache {
429
+ private cache;
430
+ private ttlMs;
431
+ private maxEntries;
432
+ constructor(ttlMs?: number, maxEntries?: number);
433
+ /** Drop every entry whose TTL has passed. */
434
+ sweep(now?: number): void;
435
+ get(key: string): OriginEvidence | undefined;
436
+ has(key: string): boolean;
437
+ set(key: string, evidence: OriginEvidence, ttlMs?: number): void;
438
+ delete(key: string): boolean;
439
+ clear(): void;
440
+ get size(): number;
441
+ }
442
+ declare const defaultOriginCache: OriginCache;
443
+
360
444
  /**
361
445
  * What a scan actually obtained, decided once, before any audit runs.
362
446
  *
@@ -394,7 +478,7 @@ interface A11yNodeFinding {
394
478
  target: string;
395
479
  summary: string;
396
480
  }
397
- type A11yStatus = 'pass' | 'fail' | 'incomplete' | 'inapplicable';
481
+ type A11yStatus = "pass" | "fail" | "incomplete" | "inapplicable";
398
482
  interface A11yRuleResult {
399
483
  status: A11yStatus;
400
484
  nodes: A11yNodeFinding[];
@@ -405,6 +489,7 @@ type A11yPageResult = Record<string, A11yRuleResult>;
405
489
  interface PageContext {
406
490
  url: string;
407
491
  pageType: PageType;
492
+ pageTypeSource?: "declared" | "detected";
408
493
  fetchResult: FetchResult;
409
494
  $: CheerioAPI;
410
495
  /** Parsed JSON-LD blocks only (used by JSON-LD-specific audits). */
@@ -437,6 +522,15 @@ interface CheckContext {
437
522
  baseUrl: string;
438
523
  fetch: (options: FetchOptions) => Promise<FetchResult>;
439
524
  wafProtection?: WafProtection;
525
+ /**
526
+ * The object per-scan gatherer caches key on.
527
+ *
528
+ * The runner hands each audit a scoped copy of this context, and a copy has
529
+ * a new identity. Without this stamp every gatherer `WeakMap` would miss
530
+ * once per audit and repeat its fetch. The runner sets it on each copy;
531
+ * an unstamped context is its own owner. See `gatherers/cache-owner.ts`.
532
+ */
533
+ cacheOwner?: object;
440
534
  /**
441
535
  * What the scan actually obtained, decided once before any audit ran.
442
536
  *
@@ -445,6 +539,16 @@ interface CheckContext {
445
539
  * harnesses that do not exercise the gate pass `allEvidenceMet()`.
446
540
  */
447
541
  evidence: ScanEvidence;
542
+ /**
543
+ * Origin evidence metadata and cached homepage response.
544
+ */
545
+ originEvidence?: {
546
+ origin: string;
547
+ version: string;
548
+ readAt: string;
549
+ cached: boolean;
550
+ originHomepage?: FetchResult;
551
+ };
448
552
  }
449
553
  type CheckFn = (ctx: CheckContext) => CheckResult | Promise<CheckResult>;
450
554
 
@@ -498,7 +602,7 @@ declare abstract class Audit {
498
602
  [key: string]: unknown;
499
603
  } | string, pageUrl?: string): AuditResult;
500
604
  /** Merge static meta with the runtime result to produce a CheckResult. */
501
- toCheckResult(rawResult: AuditResult): CheckResult;
605
+ toCheckResult(rawResult: AuditResult, overrideDisplayMode?: ScoreDisplayMode): CheckResult;
502
606
  }
503
607
 
504
608
  interface CategoryConfig {
@@ -573,7 +677,7 @@ interface AuditTrace {
573
677
  * `gated` — the scan never obtained evidence the audit needs.
574
678
  * `error` — it threw, or its result was rejected by the schema.
575
679
  */
576
- outcome: 'ran' | 'skipped' | 'gated' | 'error';
680
+ outcome: "ran" | "skipped" | "gated" | "error";
577
681
  status: CheckStatus;
578
682
  score: number;
579
683
  weight: number;
@@ -585,10 +689,10 @@ interface AuditTrace {
585
689
  explanation?: string;
586
690
  pageUrl?: string;
587
691
  /** The structured evidence behind the verdict, as the report carries it. */
588
- details?: CheckResult['details'];
692
+ details?: CheckResult["details"];
589
693
  }
590
694
  /** Which outcome a finished check represents. */
591
- declare function outcomeOf(check: CheckResult): AuditTrace['outcome'];
695
+ declare function outcomeOf(check: CheckResult): AuditTrace["outcome"];
592
696
  /** Build a trace record from a finished check. */
593
697
  declare function traceFromCheck(check: CheckResult, durationMs: number): AuditTrace;
594
698
  /** One trace as a log line: the shape a human scans, not the full record. */
@@ -604,10 +708,10 @@ interface AuditRunResult {
604
708
  * ProgressTracker, which owns counting and fraction/elapsed stamping.
605
709
  */
606
710
  type AuditProgressEvent = {
607
- type: 'unit:done';
711
+ type: "unit:done";
608
712
  label: string;
609
713
  } | {
610
- type: 'unit:fail';
714
+ type: "unit:fail";
611
715
  label: string;
612
716
  error: string;
613
717
  };
@@ -620,32 +724,37 @@ type AuditProgressEvent = {
620
724
  */
621
725
  type AuditTraceHandler = (trace: AuditTrace) => void;
622
726
  interface AuditPlan {
623
- runnable: Array<{
624
- reg: AuditRegistration;
625
- categoryId: string;
626
- }>;
727
+ runnable: RunnableAudit[];
627
728
  skipped: CheckResult[];
628
729
  }
629
730
  /** How `planAudits` should treat audits the scan cannot feed. */
630
731
  interface PlanOptions {
631
732
  /**
632
- * Skip audits whose `requires` the scan did not obtain.
733
+ * Skip audits whose `requires` the scan did not obtain. **Defaults to true.**
633
734
  *
634
- * The orchestrator turns this on for every scan
635
- * (`enforceEvidenceGate ?? true`), so an audit's `requires` decides what a
636
- * blocked or client-rendered scan reports. It defaults to off in this option
637
- * bag only so a caller planning audits directly gets the unfiltered set;
638
- * passing `false` is the measurement escape hatch for comparing a gated run
639
- * against an ungated one.
735
+ * An audit's `requires` decides what a blocked or client-rendered scan
736
+ * reports, so a caller that omits this option gets the gated set — the same
737
+ * set a scan gets. A production diagnostic may pass `false` to bypass only
738
+ * these per-audit `requires` checks. It is never the default, and it never
739
+ * bypasses the unconditional unread-scan guard.
640
740
  */
641
741
  enforceEvidence?: boolean;
642
742
  }
743
+ interface RunnableAudit {
744
+ reg: AuditRegistration;
745
+ categoryId: string;
746
+ scopedPages?: PageContext[];
747
+ scoreDisplayMode?: ScoreDisplayMode;
748
+ }
643
749
  /**
644
750
  * Split a scan config into the audits that will actually execute and the
645
751
  * `na` stubs for those it will not. Exported so the orchestrator can size the
646
752
  * audits progress phase before running it.
647
753
  */
648
- declare function planAudits(ctx: CheckContext, config: ScanConfig, options?: PlanOptions): AuditPlan;
754
+ declare function planAudits(ctx: CheckContext, config: ScanConfig, options?: PlanOptions): {
755
+ runnable: RunnableAudit[];
756
+ skipped: CheckResult[];
757
+ };
649
758
  /**
650
759
  * Execute all audits registered in the config against the scan context.
651
760
  * Returns check results grouped into categories with weighted scores.
@@ -653,20 +762,20 @@ declare function planAudits(ctx: CheckContext, config: ScanConfig, options?: Pla
653
762
  */
654
763
  declare function runAudits(ctx: CheckContext, config: ScanConfig, onEvent?: (event: AuditProgressEvent) => void, plan?: AuditPlan, onTrace?: AuditTraceHandler): Promise<AuditRunResult>;
655
764
 
656
- type PhaseId = 'fetch-root' | 'fetch-pages' | 'analyze' | 'audits' | 'report';
765
+ type PhaseId = "fetch-root" | "fetch-pages" | "analyze" | "audits" | "report";
657
766
  type ScanEvent = {
658
- type: 'scan:start';
767
+ type: "scan:start";
659
768
  url: string;
660
769
  fraction: number;
661
770
  elapsedMs: number;
662
771
  } | {
663
- type: 'phase:start';
772
+ type: "phase:start";
664
773
  phase: PhaseId;
665
774
  totalUnits: number;
666
775
  fraction: number;
667
776
  elapsedMs: number;
668
777
  } | {
669
- type: 'unit:done';
778
+ type: "unit:done";
670
779
  phase: PhaseId;
671
780
  completed: number;
672
781
  total: number;
@@ -674,20 +783,20 @@ type ScanEvent = {
674
783
  fraction: number;
675
784
  elapsedMs: number;
676
785
  } | {
677
- type: 'unit:fail';
786
+ type: "unit:fail";
678
787
  phase: PhaseId;
679
788
  label: string;
680
789
  error: string;
681
790
  fraction: number;
682
791
  elapsedMs: number;
683
792
  } | {
684
- type: 'phase:done';
793
+ type: "phase:done";
685
794
  phase: PhaseId;
686
795
  durationMs: number;
687
796
  fraction: number;
688
797
  elapsedMs: number;
689
798
  } | {
690
- type: 'scan:done';
799
+ type: "scan:done";
691
800
  durationMs: number;
692
801
  /** Null when the scan obtained too little evidence to judge the site. */
693
802
  score: number | null;
@@ -734,6 +843,8 @@ declare class ProgressTracker {
734
843
  interface ScanOptions {
735
844
  onEvent?: (event: ScanEvent) => void;
736
845
  pages?: PageOverride[] | null;
846
+ /** Explicitly declared page type for the target URL. */
847
+ pageType?: PageType;
737
848
  signal?: AbortSignal;
738
849
  /**
739
850
  * Restrict the scan to these category ids. Unknown ids simply match nothing —
@@ -757,48 +868,41 @@ interface ScanOptions {
757
868
  *
758
869
  * On by default. An audit whose required evidence the scan never obtained
759
870
  * reports `na` tagged `skipped:no-evidence`, with the reason attached, and
760
- * is never constructed. Set it to `false` to run every audit regardless —
761
- * useful when measuring what the gate removes.
871
+ * is never constructed. Set it to `false` to bypass only the per-audit
872
+ * `requires` checks. The unread-scan guard remains unconditional, so a scan
873
+ * that read no attributable site response still runs no audit.
762
874
  */
763
875
  enforceEvidenceGate?: boolean;
764
876
  /**
765
877
  * undici dispatcher for every request this scan makes.
766
- *
767
- * Omitted, the scan uses the fetcher's shared agent, which opens as many
768
- * connections per origin as the scan asks for. A caller scanning origins that
769
- * did not invite it — the nightly corpus job — passes a bounded agent so the
770
- * ~28 root-file requests the scan issues at once do not arrive at a stranger's
771
- * WAF as a burst.
772
878
  */
773
879
  dispatcher?: Dispatcher;
774
880
  /**
775
881
  * How many requests this scan may have in flight at once.
776
- *
777
- * Omitted, the scan issues every request as it reaches the fetcher. A caller
778
- * scanning origins that did not invite it sets this alongside `dispatcher`:
779
- * the dispatcher bounds the sockets, and this bounds what is issued, so the
780
- * requests the scan holds back wait outside the per-request deadline and
781
- * outside `ttfbMs` instead of inside undici's queue. Without it a bounded
782
- * scan reports its own queueing as the origin being slow or unreachable.
783
882
  */
784
883
  maxConcurrent?: number;
884
+ /**
885
+ * Extra HTTP request headers to pass on all fetches.
886
+ */
887
+ headers?: Record<string, string>;
888
+ /**
889
+ * Explicitly bypass the shared origin evidence cache for this scan.
890
+ */
891
+ bypassOriginCache?: boolean;
892
+ /**
893
+ * Optional custom OriginCache instance (defaults to defaultOriginCache).
894
+ */
895
+ originCache?: OriginCache;
785
896
  /**
786
897
  * A `robots.txt` response the caller already has, used instead of fetching
787
898
  * it again.
788
- *
789
- * For a caller that must read `robots.txt` before it decides to scan at all.
790
- * The nightly corpus job is one: it asks permission first, and without this
791
- * every site it visits gets two requests for the one file its owner watches.
792
- * It must be the response to `<baseUrl>/robots.txt` fetched with this
793
- * scanner's own user agent — anything else and the scan judges a file it was
794
- * not served.
795
899
  */
796
900
  robotsTxt?: FetchResult;
797
901
  }
798
902
  declare function runScan(url: string, options?: ScanOptions): Promise<ScanReport>;
799
903
 
800
- type FetchClass = 'ok' | 'soft-404' | 'blocked' | 'missing' | 'error';
801
- type ExpectedKind = 'text' | 'json' | 'xml' | 'html';
904
+ type FetchClass = "ok" | "soft-404" | "blocked" | "missing" | "error";
905
+ type ExpectedKind = "text" | "json" | "xml" | "html";
802
906
  declare function stripBom(text: string): string;
803
907
  declare function normalizeNewlines(text: string): string;
804
908
  /**
@@ -814,7 +918,7 @@ declare function classifyFetch(result: FetchResult | undefined, expected: Expect
814
918
  declare function isRealFile(result: FetchResult | undefined, expected: ExpectedKind): boolean;
815
919
 
816
920
  interface RobotsRule {
817
- type: 'allow' | 'disallow';
921
+ type: "allow" | "disallow";
818
922
  path: string;
819
923
  }
820
924
  interface RobotsGroup {
@@ -888,7 +992,7 @@ declare const BASELINE_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) App
888
992
  * rate limit each need a different remedy from the operator, and an opaque 403
889
993
  * may even be correct impersonation defence.
890
994
  */
891
- type BlockClass = 'ok' | 'cf-challenge' | 'pay-per-crawl' | 'anubis-pow' | 'rate-limited' | 'opaque-403' | 'soft-block' | 'transport-error';
995
+ type BlockClass = "ok" | "cf-challenge" | "pay-per-crawl" | "anubis-pow" | "rate-limited" | "opaque-403" | "soft-block" | "transport-error";
892
996
  interface UaProbe {
893
997
  url: string;
894
998
  token: string;
@@ -955,6 +1059,10 @@ interface SitemapTree {
955
1059
  malformedLastmod: number;
956
1060
  /** True when a cap stopped the walk, so `entries` is not the whole tree. */
957
1061
  truncated: boolean;
1062
+ /** Sitemap files that answered 200 and parsed as a urlset or sitemapindex. */
1063
+ readableFiles: string[];
1064
+ /** Sitemap files that answered 200 but were neither. A soft-404 lands here. */
1065
+ malformedFiles: string[];
958
1066
  }
959
1067
  /**
960
1068
  * Does this value parse as a W3C Datetime, the format the sitemap protocol
@@ -969,6 +1077,11 @@ declare function isW3CDateTime(value: string): boolean;
969
1077
  /**
970
1078
  * Walk a site's sitemap roots and collect their `<url>` rows.
971
1079
  *
1080
+ * Every root in `roots` is read: a robots.txt that declares three sitemaps
1081
+ * has three, and stopping at the first would judge a third of the site as
1082
+ * the whole. `fallbackRoots` are the conventional paths, probed in order only
1083
+ * when no declared root parsed, and the first one that does ends the probe.
1084
+ *
972
1085
  * Recursion stops after one level of `<sitemapindex>`: the depth of a nested
973
1086
  * index is chosen by the site being scanned, so following it arbitrarily is an
974
1087
  * unbounded walk driven by untrusted input. Every child URL is `isSafeUrl`- and
@@ -978,6 +1091,7 @@ declare function collectSitemapEntries(fetch: (options: FetchOptions) => Promise
978
1091
  maxChildren?: number;
979
1092
  maxEntries?: number;
980
1093
  signal?: AbortSignal;
1094
+ fallbackRoots?: string[];
981
1095
  }): Promise<SitemapTree>;
982
1096
  /**
983
1097
  * Take an even-strided sample of `n` entries.
@@ -997,7 +1111,7 @@ interface CssRule {
997
1111
  /** The enclosing at-rule prelude (`media print`, `supports (...)`), if any. */
998
1112
  atRule?: string;
999
1113
  /** Where the rule came from, for evidence. */
1000
- origin: 'inline' | string;
1114
+ origin: "inline" | string;
1001
1115
  }
1002
1116
  /**
1003
1117
  * Scan a stylesheet into flat selector/declaration pairs.
@@ -1162,11 +1276,20 @@ declare function extractStylesheetUrls($: CheerioAPI): string[];
1162
1276
  */
1163
1277
  declare function detectPageType(url: string, $: CheerioAPI, jsonLd: object[], meta: Record<string, string>, isFirstPage: boolean): PageType;
1164
1278
 
1165
- declare const DEFAULT_SCAN_LIMIT = 5;
1166
- declare const MAX_PAGES_PER_SCAN = 6;
1279
+ declare const DEFAULT_SCAN_LIMIT = 1;
1280
+ declare const MAX_PAGES_PER_SCAN = 1;
1281
+ declare const ORIGIN_EVIDENCE_VERSION = "v1";
1282
+ declare const DEFAULT_ORIGIN_CACHE_TTL_MS: number;
1283
+ /**
1284
+ * How many origins the shared cache holds at once. Each entry carries every
1285
+ * root file of one origin, so a long-lived process (the MCP server, a looped
1286
+ * live run) needs a ceiling, not only a TTL.
1287
+ */
1288
+ declare const DEFAULT_ORIGIN_CACHE_MAX_ENTRIES = 256;
1167
1289
  declare const REQUEST_TIMEOUT_MS = 10000;
1168
1290
  declare const SCAN_TIMEOUT_MS = 60000;
1169
1291
  declare const MAX_RESPONSE_BODY_BYTES: number;
1292
+ /** @deprecated Unused internally; retained for published API stability. Scheduled for removal in v4. */
1170
1293
  declare const MAX_CONCURRENT_REQUESTS = 10;
1171
1294
  declare const SCANNER_USER_AGENT = "AgentLighthouse/1.0 (+https://github.com/ForkPoint/agent-lighthouse)";
1172
1295
  declare const TAG_SKIPPED_PAGE_TYPE = "skipped:page-type";
@@ -1200,7 +1323,7 @@ declare function weightForGrade(grade: EvidenceGrade, tier: AuditTier): number;
1200
1323
  * fails/passes or readiness vitals. Every surface that ranks or scores checks
1201
1324
  * filters through this predicate so the rule cannot drift per package.
1202
1325
  */
1203
- declare function isInformative(check: Pick<CheckResult, 'scoreDisplayMode'>): boolean;
1326
+ declare function isInformative(check: Pick<CheckResult, "scoreDisplayMode">): boolean;
1204
1327
  declare function calculateCategoryScore(checks: CheckResult[]): number;
1205
1328
  /**
1206
1329
  * Assemble a category result.
@@ -1332,6 +1455,7 @@ declare const AuditMetaSchema: z.ZodObject<{
1332
1455
  description: z.ZodString;
1333
1456
  scoreDisplayMode: z.ZodEnum<["binary", "ternary", "informative"]>;
1334
1457
  weight: z.ZodNumber;
1458
+ pageTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1335
1459
  applicablePageTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1336
1460
  defaultPriority: z.ZodEnum<["critical", "high", "medium", "low"]>;
1337
1461
  guidance: z.ZodOptional<z.ZodObject<{
@@ -1382,6 +1506,7 @@ declare const AuditMetaSchema: z.ZodObject<{
1382
1506
  evidenceGrade: "A" | "B" | "C" | "D";
1383
1507
  tier: "informative" | "scored" | "experimental";
1384
1508
  dossier: string;
1509
+ pageTypes?: string[] | undefined;
1385
1510
  applicablePageTypes?: string[] | undefined;
1386
1511
  guidance?: {
1387
1512
  impact: string;
@@ -1408,6 +1533,7 @@ declare const AuditMetaSchema: z.ZodObject<{
1408
1533
  evidenceGrade: "A" | "B" | "C" | "D";
1409
1534
  tier: "informative" | "scored" | "experimental";
1410
1535
  dossier: string;
1536
+ pageTypes?: string[] | undefined;
1411
1537
  applicablePageTypes?: string[] | undefined;
1412
1538
  guidance?: {
1413
1539
  impact: string;
@@ -1526,13 +1652,128 @@ declare const CheckResultSchema: z.ZodObject<{
1526
1652
  evidenceGrade?: "A" | "B" | "C" | "D" | undefined;
1527
1653
  tier?: "informative" | "scored" | "experimental" | undefined;
1528
1654
  }>;
1655
+ declare const PageTypeSchema: z.ZodEnum<["homepage", "category", "product", "content"]>;
1656
+ declare const ScanConditionsSchema: z.ZodObject<{
1657
+ url: z.ZodString;
1658
+ pageType: z.ZodObject<{
1659
+ type: z.ZodEnum<["homepage", "category", "product", "content"]>;
1660
+ source: z.ZodEnum<["declared", "detected"]>;
1661
+ }, "strip", z.ZodTypeAny, {
1662
+ type: "homepage" | "category" | "product" | "content";
1663
+ source: "declared" | "detected";
1664
+ }, {
1665
+ type: "homepage" | "category" | "product" | "content";
1666
+ source: "declared" | "detected";
1667
+ }>;
1668
+ origin: z.ZodObject<{
1669
+ origin: z.ZodString;
1670
+ version: z.ZodString;
1671
+ readAt: z.ZodString;
1672
+ cached: z.ZodBoolean;
1673
+ }, "strip", z.ZodTypeAny, {
1674
+ origin: string;
1675
+ version: string;
1676
+ readAt: string;
1677
+ cached: boolean;
1678
+ }, {
1679
+ origin: string;
1680
+ version: string;
1681
+ readAt: string;
1682
+ cached: boolean;
1683
+ }>;
1684
+ coverage: z.ZodObject<{
1685
+ registryMass: z.ZodNumber;
1686
+ assessedMass: z.ZodNumber;
1687
+ pageMass: z.ZodNumber;
1688
+ originMass: z.ZodNumber;
1689
+ gatedMass: z.ZodNumber;
1690
+ }, "strip", z.ZodTypeAny, {
1691
+ registryMass: number;
1692
+ assessedMass: number;
1693
+ pageMass: number;
1694
+ originMass: number;
1695
+ gatedMass: number;
1696
+ }, {
1697
+ registryMass: number;
1698
+ assessedMass: number;
1699
+ pageMass: number;
1700
+ originMass: number;
1701
+ gatedMass: number;
1702
+ }>;
1703
+ unscored: z.ZodObject<{
1704
+ totalCount: z.ZodNumber;
1705
+ informativeCount: z.ZodNumber;
1706
+ gatedCount: z.ZodNumber;
1707
+ reasons: z.ZodRecord<z.ZodString, z.ZodNumber>;
1708
+ }, "strip", z.ZodTypeAny, {
1709
+ reasons: Record<string, number>;
1710
+ totalCount: number;
1711
+ informativeCount: number;
1712
+ gatedCount: number;
1713
+ }, {
1714
+ reasons: Record<string, number>;
1715
+ totalCount: number;
1716
+ informativeCount: number;
1717
+ gatedCount: number;
1718
+ }>;
1719
+ }, "strip", z.ZodTypeAny, {
1720
+ url: string;
1721
+ origin: {
1722
+ origin: string;
1723
+ version: string;
1724
+ readAt: string;
1725
+ cached: boolean;
1726
+ };
1727
+ pageType: {
1728
+ type: "homepage" | "category" | "product" | "content";
1729
+ source: "declared" | "detected";
1730
+ };
1731
+ coverage: {
1732
+ registryMass: number;
1733
+ assessedMass: number;
1734
+ pageMass: number;
1735
+ originMass: number;
1736
+ gatedMass: number;
1737
+ };
1738
+ unscored: {
1739
+ reasons: Record<string, number>;
1740
+ totalCount: number;
1741
+ informativeCount: number;
1742
+ gatedCount: number;
1743
+ };
1744
+ }, {
1745
+ url: string;
1746
+ origin: {
1747
+ origin: string;
1748
+ version: string;
1749
+ readAt: string;
1750
+ cached: boolean;
1751
+ };
1752
+ pageType: {
1753
+ type: "homepage" | "category" | "product" | "content";
1754
+ source: "declared" | "detected";
1755
+ };
1756
+ coverage: {
1757
+ registryMass: number;
1758
+ assessedMass: number;
1759
+ pageMass: number;
1760
+ originMass: number;
1761
+ gatedMass: number;
1762
+ };
1763
+ unscored: {
1764
+ reasons: Record<string, number>;
1765
+ totalCount: number;
1766
+ informativeCount: number;
1767
+ gatedCount: number;
1768
+ };
1769
+ }>;
1529
1770
 
1530
1771
  declare function normalizeUrl(input: string): string;
1531
1772
  declare function joinUrl(base: string, path: string): string;
1532
1773
  declare function extractDomain(url: string): string;
1533
1774
  declare function isPrivateIp(ip: string): boolean;
1534
1775
 
1535
- type PresetName = 'ecommerce' | 'saas' | 'content' | 'quick' | 'full';
1776
+ type PresetName = "ecommerce" | "saas" | "content" | "quick" | "full";
1536
1777
  interface PresetOptions {
1537
1778
  name: PresetName;
1538
1779
  description: string;
@@ -1555,7 +1796,7 @@ interface AgentLighthouseConfig {
1555
1796
  /** Per-category minimum score assertions */
1556
1797
  assertCategories?: Record<string, number>;
1557
1798
  /** Output report formats (terminal, html, json, md) */
1558
- output?: Array<'terminal' | 'html' | 'json' | 'md'>;
1799
+ output?: Array<"terminal" | "html" | "json" | "md">;
1559
1800
  /** Output directory for reports */
1560
1801
  outputDir?: string;
1561
1802
  /** Maximum number of pages to discover & scan */
@@ -1568,7 +1809,7 @@ declare function defineConfig(config: AgentLighthouseConfig): AgentLighthouseCon
1568
1809
  */
1569
1810
  declare function loadConfigFile(customPath?: string): AgentLighthouseConfig;
1570
1811
 
1571
- type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug';
1812
+ type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
1572
1813
  declare class Logger {
1573
1814
  level: LogLevel;
1574
1815
  private shouldLog;
@@ -1590,4 +1831,4 @@ type AcpLinkType = (typeof ACP_LINK_TYPES)[number];
1590
1831
  */
1591
1832
  declare function resolvePolicyLinks(ctx: CheckContext): Map<AcpLinkType, string>;
1592
1833
 
1593
- 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 FetcherOptions, 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, boundedDispatcher, 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 };
1834
+ 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_ORIGIN_CACHE_MAX_ENTRIES, DEFAULT_ORIGIN_CACHE_TTL_MS, DEFAULT_SCAN_LIMIT, type DeprecationNotice, DeprecationNoticeSchema, type EvidenceGrade, EvidenceGradeSchema, type EvidenceKey, EvidenceKeySchema, type ExpectedKind, type FetchClass, type FetchOptions, type FetchResult, type FetcherOptions, type FieldStatus, type FixEffort, FixEffortSchema, MAX_CONCURRENT_REQUESTS, MAX_PAGES_PER_SCAN, MAX_RESPONSE_BODY_BYTES, ORIGIN_EVIDENCE_VERSION, OriginCache, type OriginCacheOptions, type OriginEvidence, PAGE_TYPE_LABELS, PHASE_WEIGHTS, PRESETS, type PageContext, type PageCss, type PageJudgement, type PageOverride, type PageType, PageTypeSchema, 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 ScanConditions, ScanConditionsSchema, 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, boundedDispatcher, buildCategoryResult, buildScanEvidence, calculateCategoryScore, calculateOverallScore, classifyFetch, classifyResponse, collectPageCss, collectSitemapEntries, computeOriginCacheKey, createFetcher, defaultConfig, defaultOriginCache, 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, shouldBypassOriginCache, stripBom, topLevelJsonLd, traceFromCheck, weightForGrade };