// pagespeed-audit findings document v1 — generated from /docs/findings.schema.json. Do not edit by hand. // Join runs on run.url + run.date + run.strategy; join findings across runs on findings[].slug, never on findings[].id. export interface AdjudicationCounts { confirmed: number; false_positive: number; deprioritized: number; platform_blocked: number; readout: number; } export interface RunInfo { run_id: string; generated_at: string; skill_version: string; site: string; url: string; final_url?: string | null; strategy: "mobile" | "desktop"; date: string; lighthouse_version?: string | null; data_sources?: string[]; field_available: boolean; binding_constraint: string; missing_from_psi?: string | null; adjudication_counts: AdjudicationCounts; /** True when the page sits behind a login and was measured signed in (test account or session cookie the site owner provided), with local Lighthouse under PageSpeed Insights' conditions instead of PageSpeed Insights itself */ signed_in?: boolean; /** Public naming layer applied to this run's report, e.g. 'noise-silence/four-verdicts@1' (Real=confirmed, Wrong=false_positive, Trivial=deprioritized, Locked=platform_blocked) */ framework?: string; /** Noise & Silence framework: (false_positive + deprioritized + platform_blocked) / adjudicated flags, readout excluded */ noise_rate?: number | null; } export interface MetricRow { metric: "LCP" | "INP" | "CLS" | "FCP" | "TTFB"; source: "field_page" | "field_origin" | "lab"; value: number | null; unit?: "ms" | ""; category?: string | null; good?: number | null; poor?: number | null; } export interface HistoryRow { date: string; metric: "LCP" | "INP" | "CLS" | "FCP" | "TTFB"; p75: number | null; unit?: "ms" | ""; } export interface QuickFix { lang?: string; code?: string; note?: string; } export interface Adjudication { audit_id: string; disposition: "confirmed" | "false_positive" | "deprioritized" | "platform_blocked" | "readout"; claim: string; evidence: string; finding_ref?: string | null; metric_savings?: Record | null; has_quick_fix?: boolean; fix?: QuickFix | null; } export interface Coupling { finding: string; kind: "invalidates" | "invalidated_by" | "depends_on" | "required_by" | "co-requisite" | "interacts"; note?: string | null; /** true = generated from the other finding's declaration, not authored here */ mirrored?: boolean; } export interface FindingFix { lang?: string; before?: string | null; after?: string; note?: string | null; } export interface Impact { narrative: string; arithmetic?: string | null; metric?: "LCP" | "INP" | "CLS" | "FCP" | "TTFB" | null; unit?: "ms" | "" | null; min?: number | null; max?: number | null; direction?: "decrease" | "increase" | null; confidence?: "HIGH" | "MEDIUM-HIGH" | "MEDIUM" | "LOW" | null; } export interface Finding { /** Positional in-report anchor, re-assigned every run (F1, F2, …) and the target of adjudications[].finding_ref. NOT stable across runs — never join on it. Use slug for that. */ id: string; /** Stable cross-run identifier for the PROBLEM CLASS, not the wording: the same underlying problem on the same site produces the same slug in every future audit, even when title, prose, severity or measured numbers change. Never encodes run-specific data (no values, dates, run numbers, ordering, Lighthouse version). Unique within a run; a class that legitimately recurs is disambiguated with a stable discriminator derived from the resource itself, e.g. 'render-blocking-css:fonts.googleapis.com' — never a positional counter. Join findings across runs on run.url (or run.final_url) + findings[].slug. Authoring vocabulary and the fallback rule live in SKILL.md. */ slug: string; title: string; severity: "CRITICAL" | "HIGH" | "MEDIUM" | "LOW"; confidence: "HIGH" | "MEDIUM-HIGH" | "MEDIUM" | "LOW"; effort?: string | null; tags?: string[]; evidence?: string; /** What the defect was VERIFIED to affect, in the words of what was tested: 'origin-wide (verified on / /pricing /blog)' or 'this route only (tested / only)'. Never an inference from a single route. */ scope?: string | null; /** Human-readable falsification note. When the graded arrays below are present this is their flattened rendering ('Measured: … | Reasoned (inference): … | Verify before shipping: …'), so a v1 consumer that only reads this field keeps working. */ falsification?: string | null; /** Claims the audit OBSERVED — header, byte count, snippet, response. Optional; added additively to @1. */ falsification_measured?: string[] | null; /** Claims the audit INFERRED from those observations. Presenting an inference as a confirmed check overstates the evidence grade, so the two are carried apart. Optional; added additively to @1. */ falsification_reasoned?: string[] | null; /** The concrete test that settles a reasoned inference. REQUIRED (validate_findings) whenever falsification_reasoned is non-empty on a finding that ships a fix. */ verify_before_shipping?: string | null; /** Interactions with other findings in this run: one finding's fix changing a precondition another rests on, a prerequisite, or a co-requisite. Mirrored onto the other finding by the renderer and re-checked for symmetry by validate_findings — an interaction stated on only one side is an error. Optional; added additively to @1. */ coupling?: Coupling[] | null; chain?: string | null; fix?: FindingFix | null; impact: Impact; sequencing?: string | null; /** Noise & Silence: true = platform-owned / outside operator control. A locked finding carries NO fix; the renderer shows the redirect instead, and validate_findings rejects a locked finding that has a fix. */ locked?: boolean | null; /** For a locked finding: the controllable equivalent (merchant-side lever) or an explicit stop-trying note shown in place of a fix. */ redirect?: string | null; } export interface Deliverable { title: string; lang?: string; code: string; } export interface DoneWellItem { item: string; evidence?: string | null; } export interface PlanItem { bucket: "today" | "sprint" | "architectural"; position: number; item: string; } export interface TagEvidence { kind: "duplicate" | "orphan" | "unused" | "owner"; note: string; } /** Present when the tag's vendor accepts the same events from a server, so the browser tag can be removed. removes_* is that tag's own cost; stays_in_browser names the tag manager that keeps its cost. */ export interface TagServerSide { replacement: string; removes_transfer_bytes?: number | null; removes_main_thread_ms?: number | null; stays_in_browser?: string[]; tradeoff?: string; } export interface ConversionUplift { low?: number; central?: number; high?: number; } export interface MonthlyRevenue { low?: number; central?: number; high?: number; } /** A revenue range for a measured LCP saving: only on a Real (confirmed) tag whose removal was measured, priced with the published elasticity corridor; monthly_revenue only when the customer supplied their revenue. */ export interface Money { lcp_saving_ms: number; baseline_lcp_ms?: number | null; baseline?: string | null; lcp_saving_at_baseline_ms?: number | null; conversion_uplift: ConversionUplift; monthly_revenue?: MonthlyRevenue | null; currency?: string | null; model: string; assumptions?: string; } export interface TagLedgerRow { name: string; category?: string | null; owner?: string | null; hosts?: string[]; transfer_bytes?: number | null; main_thread_ms?: number | null; blocking_ms?: number | null; loaded_by?: string[]; pre_consent?: boolean | null; measured_gains?: Record | null; verdict: "keep" | "defer" | "offload" | "remove-candidate"; owner_decides?: boolean; protected?: boolean; reason: string; fix?: string | null; evidence?: TagEvidence[]; /** Present when the tag's vendor accepts the same events from a server, so the browser tag can be removed. removes_* is that tag's own cost; stays_in_browser names the tag manager that keeps its cost. */ server_side?: TagServerSide | null; /** The tag's Four Verdict: confirmed (Real), false_positive (Wrong), deprioritized (Trivial), platform_blocked (Locked). Only a confirmed tag carries a fix. */ disposition?: "confirmed" | "false_positive" | "deprioritized" | "platform_blocked" | null; /** Where the effort goes instead, on a tag that is not Real. */ redirect?: string | null; /** The store platform that injects a Locked tag. */ platform?: string | null; /** A revenue range for a measured LCP saving: only on a Real (confirmed) tag whose removal was measured, priced with the published elasticity corridor; monthly_revenue only when the customer supplied their revenue. */ money?: Money | null; } export interface ConversionUplift { low?: number; central?: number; high?: number; } export interface MonthlyRevenue { low?: number; central?: number; high?: number; } /** A revenue range for a measured LCP saving: only on a Real (confirmed) tag whose removal was measured, priced with the published elasticity corridor; monthly_revenue only when the customer supplied their revenue. */ export interface Money { lcp_saving_ms: number; baseline_lcp_ms?: number | null; baseline?: string | null; lcp_saving_at_baseline_ms?: number | null; conversion_uplift: ConversionUplift; monthly_revenue?: MonthlyRevenue | null; currency?: string | null; model: string; assumptions?: string; } /** Tag ledger totals (optional, present with tags): cost, verdict and Four Verdict counts, the tag Noise Rate, and the combined money range of the priced Real tags. */ export interface TagTotal { tags?: number | null; transfer_bytes?: number | null; main_thread_ms?: number | null; share_of_main_thread?: number | null; by_verdict?: Record; by_disposition?: Record; noise_rate?: number | null; /** A revenue range for a measured LCP saving: only on a Real (confirmed) tag whose removal was measured, priced with the published elasticity corridor; monthly_revenue only when the customer supplied their revenue. */ money?: Money | null; } /** Machine-readable twin of the audit report. Long-form arrays (metrics, history, adjudications, findings, plan) map 1:1 onto SQL tables; join runs on run.url + run.date + run.strategy, join flags across runs on adjudications[].audit_id (stable Lighthouse IDs), and join findings across runs on findings[].slug (stable problem-class ID; findings[].id is positional and re-assigned every run). */ export interface FindingsDocument { schema: "pagespeed-audit/findings@1"; run: RunInfo; metrics: MetricRow[]; history?: HistoryRow[]; adjudications: Adjudication[]; findings: Finding[]; deliverables?: Deliverable[]; done_well?: DoneWellItem[]; plan?: PlanItem[]; /** Tag ledger (optional): one row per third-party tag with its lab cost and a verdict. Consent, security and payment tags are protected and always keep; a remove-candidate carries evidence or owner_decides. Each tag also carries its Four Verdict (disposition); money appears only for a measured LCP saving. */ tags?: TagLedgerRow[]; /** Tag ledger totals (optional, present with tags): cost, verdict and Four Verdict counts, the tag Noise Rate, and the combined money range of the priced Real tags. */ tag_totals?: TagTotal; }