This page documents what the ScanoraAI engine actually does, signal-by-signal. Every audit draws 23 verified signals — broken down as 10 third-party APIs + 5 in-house parsers + 8 in-process probes. That math (10 + 5 + 8 = 23) is the canonical “23 verified sources” count referenced everywhere on this site, and the breakdown below lets you count it yourself. AI translates every finding into plain-language explanations, but never produces a score. Every claim here corresponds to code you can read or output you can verify yourself. If you find a discrepancy, that's a bug — please tell us.
The 23 signals — count them yourself
10 third-party APIs
5 in-house parsers
8 in-process probes
Items 1–10 are third-party APIs — independent public services. Items 11–15 are in-house parsers — checks we run on the page bytes we already fetched. Items 16–23 are in-process probes — checks we compute by inspecting the live page during the scan. All 23 together = the canonical "23 verified sources" count used throughout the site.
Scope note: Wappalyzer tech-stack detection runs via HTTP probe (server-side fetch). Technologies that are only loaded or revealed by a browser executing JavaScript — such as client-side analytics tags, dynamically injected frameworks, or SPA hydration signals — may not appear in results. Browser-based Wappalyzer will always detect more than a server-side probe.
Every finding, score, and source claim in ScanoraAI maps to one of seven canonical evidence labels. The label answers a single question: how was this number produced?
We adopted this vocabulary in v8.1 (May 2026) after an audit review flagged that phrases like “verified”, “estimated”, “AI-generated”, and “never estimated” had drifted in meaning across the product. The labels are now shared across the report UI, the methodology page, and the engine's typed contracts.
Lab measurement from a provider measurement API. Reproducible — re-run and compare.
Real-user data (CrUX, RUM). Sample-based — not synthetic.
Synthetic measurement under controlled throttling. Reproducible; does not reflect real users.
Deterministic parse of page bytes, headers, HTML, or structured data by our in-house parsers.
Derived from observed signals via published rules. Documented heuristic, not measurement.
LLM-generated prose explaining a measured or observed finding. Never the source of a score.
Provider failed or signal does not apply. Shown explicitly, never silently dropped.
Implementation: src/lib/evidence-labels.ts (shared module) and src/components/EvidenceLabel.tsx (UI chip). See /self-audit for the labels applied to a real audit run.
Every audit goes through these stages, in order. Each stage has a kill-switch — if a stage fails clearly (DNS doesn't resolve, the URL serves a parking page, etc.), we stop and tell you instead of producing a bogus score.
audit.gate.reject with the named reason.mobilePerfScore and used for the mobileScore metric. CrUX field data is extracted separately when Google has enough Chrome traffic for your site.Performance scores come from Google's PageSpeed Insights API v5, which runs the same Lighthouse engine you can run yourself at pagespeed.web.dev. We do not use a custom Lighthouse fork or modified scoring.
Lighthouse is non-deterministic — even Google's own tool returns scores that vary by ±5 points between runs on the same URL. That's why we run it 5 times and use the median, and why our scores will sometimes differ slightly from a single re-run on pagespeed.web.dev.
Desktop vs mobile scores: the main category scores (SEO, Performance, UX, Design) are driven by the desktop Lighthouse run. The mobile performance score is surfaced separately as mobilePerfScore in the report payload and shown in the audit results as your “Mobile Performance” score. Google’s ranking algorithm uses mobile Core Web Vitals — if your mobile score differs significantly from your desktop score, that gap is worth prioritising.
The Chrome User Experience Report (CrUX) is real-user data Google publishes for sites with enough Chrome traffic. When CrUX data exists for a URL we show it explicitly and label it “p75 real users” — that's the 75th-percentile experience over the last 28 days, the standard Web Vitals threshold.
CrUX requires a minimum traffic threshold. Roughly 1 in 4 sites we audit don't meet it. When CrUX data is unavailable we show a clear “Not enough real-world traffic” panel — we never invent placeholder field values.
We also flag lab/field divergence: if Lighthouse's lab measurement is more than 20 points off the CrUX field measurement, that's a signal worth knowing about (typically the homepage was tested on a fast pipe but real users hit slower mobile networks).
These come from the HTTP probe — we evaluate the homepage HTML directly:
What we actually crawl, and what we don't: every audit deeply analyses the homepage, then up to 9 more inner pages — discovered from the most-linked internal anchors on the homepage, with a sitemap.xml fallback when the homepage is link-sparse (SPAs, hero-only landing pages). Hard cap: 10 pages total, server-enforced. The actual page count varies — content-rich sites usually hit 10, single-page apps with no sitemap audit just the one. Every result page shows the live “Audited N pages”count from your specific run. The robots/sitemap source additionally HEAD-samples up to 10 evenly-spaced URLs from the sitemap to spot broken links — a sample, not an exhaustive check. Signals that say “may be missing on inner pages” reflect this bounded scope honestly. We do not crawl every URL on a 5,000-page site. For an exhaustive full-site crawl, use Screaming Frog or Sitebulb — they do that job properly.
Every check the engine runs, its source, the maximum penalty it can apply, and its confidence level. Verified = measured directly by Lighthouse or a third-party provider. Estimated = derived from structural HTML analysis.
title-missingNo <title> tag on the page.
title-too-longTitle exceeds 60 characters — truncated in SERPs.
title-too-shortTitle under 10 characters — not descriptive.
meta-description-missingNo meta description tag.
meta-description-longMeta description over 160 characters.
h1-missingNo H1 tag on the page.
multiple-h1More than one H1 — dilutes primary keyword signal.
canonical-missingNo canonical link tag — risk of duplicate content.
canonical-self-mismatchCanonical points to a different URL than the page.
robots-noindexPage has noindex directive — excluded from search.
sitemap-missingNo sitemap.xml found at /sitemap.xml or in robots.txt.
robots-txt-missingNo robots.txt found.
og-tags-missingNo Open Graph tags — poor social sharing previews.
schema-missingNo JSON-LD structured data detected.
lh-seo-scoreLighthouse SEO category score below threshold.
lcp-slowLCP > 4s (poor). Largest Contentful Paint.
lcp-needs-workLCP 2.5–4s (needs improvement).
fcp-slowFCP > 3s. First Contentful Paint.
tbt-highTBT > 600ms. Total Blocking Time.
cls-highCLS > 0.25. Cumulative Layout Shift.
ttfb-slowServer response time > 800ms.
no-response-compressionNo gzip/brotli Content-Encoding header.
no-cache-headersNo Cache-Control, ETag, or Last-Modified.
blocking-scripts-in-head3+ non-async/defer scripts in <head>.
render-blocking-resourcesLighthouse detected render-blocking resources.
unused-cssSignificant unused CSS bytes.
unused-javascriptSignificant unused JavaScript bytes.
unoptimised-imagesImages not in modern format (WebP/AVIF) or oversized.
missing-image-dimensionsImages missing width/height — causes layout shift.
lh-performance-scoreLighthouse performance score below threshold.
missing-html-langNo lang attribute on <html>. WCAG 3.1.1 Level A.
missing-viewport-metaNo viewport meta tag — broken mobile layout.
images-missing-altImages without alt text (count-weighted).
lh-accessibility-scoreLighthouse accessibility score below threshold.
axe-violationsWCAG 2.1 AA violations detected by axe-core.
color-contrastText/background contrast ratio below 4.5:1.
tap-targets-smallTouch targets smaller than 48×48px.
form-labels-missingForm inputs without associated labels.
focus-visible-missingInteractive elements lack visible focus indicator.
heading-order-brokenHeading hierarchy skips levels (H1→H3).
no-httpsSite not served over HTTPS.
mixed-contentHTTPS page loads HTTP sub-resources.
ssl-grade-lowTLS grade below A (deprecated protocols, weak ciphers).
ssl-expiredTLS certificate expired or expiring within 14 days.
hsts-missingNo Strict-Transport-Security header.
csp-missingNo Content-Security-Policy header.
x-frame-missingNo X-Frame-Options header — clickjacking risk.
safe-browsing-threatURL flagged in Google Safe Browsing threat lists.
mozilla-obs-grade-lowMozilla Observatory grade below B.
lh-best-practices-scoreLighthouse best practices score below threshold.
console-errorsJavaScript errors logged to console.
deprecated-apisDeprecated browser APIs in use.
doctype-missingNo <!DOCTYPE html> declaration.
w3c-errorsHTML spec violations (duplicate IDs, unclosed tags).
carbon-highPage CO₂ per view above 1g (not on green hosting).
favicon-missingNo favicon detected.
tech-outdatedDetected CMS/framework version with known CVEs.
Penalties are additive within a category and capped at the category floor (8 points minimum). The exact penalty applied to any signal depends on severity multipliers — see the Scoring section below. This table reflects engine v10.0. Rule IDs are stable across versions; penalties may be tuned.
Engine 6.0.0 added four probe-derived signals that fire only when the probe field is explicitly populated. An undefined field means “unknown” — it never fires a false negative. All four carry confidence: 'verified' and appear in structural-only mode.
Content-Encoding response header is absent — the server is not gzip/brotli compressing HTML.<script> tags appear in <head>, blocking the parser before first paint.<html> element has no lang attribute. Required by WCAG 3.1.1 Level A — screen readers need this to select the correct language engine.Cache-Control nor a validator (ETag / Last-Modified). Browsers cannot reuse the response without re-fetching.These signals were added because they are directly observable from a single HTTP response — no Lighthouse run required — and they represent real, fixable problems that affect a large fraction of audited sites.
Engine 7.0 adds seven independent third-party sources that run in parallel alongside the Lighthouse probe. Each source is free, has no API key requirement (except Safe Browsing which reuses your existing PSI key), and produces results you can independently verify by pasting your domain into the provider's own UI.
fromCache=on, maxAge=24h) — a fresh SSL Labs scan takes 60–180 seconds and would block the audit. If no cached result exists, we trigger a background scan and return null for this provider in the current audit (you'll see “TLS depth check pending — re-run in a few minutes”). This is distinct from our in-process TLS cert check (see below), which runs on every audit regardless. Verify at ssllabs.com/ssltest.HTTP 403 — and the report will display a one-click enable link. It's a free API (10,000 queries/day on the free tier) and takes about two minutes to enable.Verify at Google Transparency Report.Engine v8.0 added eight more in-process and lightweight sources that run on every audit:
width/height attributes (CLS risk), lazy-load coverage, modern-format opportunity (WebP/AVIF vs JPEG/PNG), andfetchpriority misuse on LCP images. No network request — runs on the HTML we already fetched.@type, empty required fields, and malformed JSON. Under 50ms, no network request.18 schema types with full required-property validation:
Schema types not in this list produce an informational signal with zero score deduction and a link to Google Rich Results Test — we never silently skip or claim full coverage.
robots.txt and sitemap.xml. HEAD-samples up to 10 evenly-spaced sitemap URLs to spot broken links. Flags disallow-all, missing sitemap, and sitemap/robots mismatches. ~2–5s with sampling.Engine v10.0 adds eight in-process probes (JS-shell detection, rendered-meta extraction, TLS certificate-chain inspection, CDN detection, cookie security audit, broken link sampling, exposed file detection, mixed content detection) that produce findings the sources above cannot. Each is its own evidence source: it has distinct inputs, distinct outputs, and surfaces failures the other 15 sources miss (10 third-party + 5 in-house parsers, listed above).
<div id="app"> to crawlers. That matters because Google's initial crawl, most LLM training crawlers, social-preview generators, and many SEO tools index the raw response — not the rendered DOM — especially for new pages that haven't entered the rendering queue yet.title, meta[name=description], link[rel=canonical], and Open Graph tags.<head> — it sees a populated title and gives you a green check. Our in-process content-SEO source reads only the raw HTML — it sees a default <title>React App</title> and flags a missing title. Neither one alone tells you the real story: that the title exists, just not in the SSR payload Googlebot may or may not re-render depending on crawl budget. This source is the only one that surfaces the gap explicitly. getPeerCertificate(detailed) returns and runs chain-completeness rules SSL Labs grades but doesn't expose as machine-readable signals.Source-count breakdown (18 total):
We use “source” to mean any check that produces findings no other check on this list produces. By that definition every entry above is a source. If you prefer the stricter framing “independent API call to an external provider,” the count is 10 — and the other 8 are documented in-house parsers and probes that run on every audit. Both framings are honest; we picked the broader one because the evidence behaviour is what actually drives the report.
Honesty contract: if a provider call fails (network error, rate limit, cache miss), we return null for that provider — never “passed”. The report shows which providers ran and which were unavailable. Provider signals carry confidence: 'verified' only when the upstream call succeeded and returned a populated field.
Both desktop and mobile Lighthouse strategies run on every audit (5 runs each, in parallel). The desktop run drives the main category scores (SEO, Performance, UX, Design) and the overall score. The mobile performance score is surfaced separately as mobilePerfScore in the report and shown in the audit results UI.
Overall = mean(SEO, Design, UX, Performance) ← all from desktop Lighthouse
Mobile Performance score = separate field (mobilePerfScore)
← from mobile Lighthouse 5-run medianRationale: desktop scores are more stable (lower variance) and are what most site owners compare against their own PageSpeed Insights runs. The mobile score is surfaced prominently because Google's ranking algorithm uses mobile Core Web Vitals — a large gap between your desktop and mobile scores is a high-priority finding.
Why not weight mobile ×1.6 in the overall? The previous methodology documented a 1.6× mobile weighting. This was removed in v8.2 because it produced an overall score that diverged from what users see when they run PageSpeed Insights themselves (which shows desktop and mobile separately). Showing both scores explicitly is more honest than blending them into a single weighted number that neither matches.
For each Critical and Medium finding, the engine generates a 2–4 sentence explanation that:
banner.jpg is 3.8 MB and is your LCP element” — not “large images slow your page.”aiGenerated: true flag and cites the model used. It is an enhancement to the verified finding — it never replaces the score deduction or the upstream source citation.Grounding check: before storing an explanation, we verify it references at least one number or technology name from the input context. Generic explanations that don't reference your specific data are discarded and the original finding is kept unchanged.
The multi-page audit runs the full single-page pipeline on up to 10 pages of a site and aggregates the results. It does not replace a full-site crawler — it is a bounded, honest sample.
sitemap.xml.Honesty contract: we report exactly how many pages were audited, not “the whole site.” We never extrapolate from 10 pages to 10,000. The UI shows a discovery note explaining which pages were found and how.
Each signal carries a penalty (0–25 points) and lands in a category: SEO, Performance, UX/Accessibility, or Design. Each category starts at 100. The probe and Lighthouse layers populate real findings on top, deducting points per active signal. The final per-category score blends Lighthouse (when available) with the structural signals at the rates below, then clamps to [8, 96]:
Performance = 70% Lighthouse perf + 30% structural SEO = 70% Lighthouse SEO + 30% structural UX/Accessibility = 70% Lighthouse a11y + 30% structural Design = 50% Lighthouse practices + 50% structural Overall = mean(perf, SEO, UX, design) ← desktop Lighthouse Mobile perf score = separate field (mobilePerfScore) ← mobile Lighthouse Quick-win bonus = +2 priority for fixes ≤ 5 min
When Lighthouse fails or is unavailable for the audit, the affected categories carry an “unverified” flag in the report payload (dataSources.<cat>.verified = false) and the UI shows a clearly-labelled notice in place of fabricated numbers. We do not synthesise category scores from a URL hash — that was removed in engine 6.0.0.
Recommendations are ranked by:
priority = penalty × severityMultiplier × confidenceBoost ÷ effortMultiplier
The multiplier values are fixed constants — not tuned per-run or per-model:
severityMultiplier critical → 2.0 high → 1.5 medium → 1.0 low → 0.6 info → 0.3 confidenceBoost verified (Lighthouse / axe-core measured) → 1.2 estimated (structural inference) → 1.0 inferred (heuristic / single-source) → 0.8 effortMultiplier trivial (< 5 min, e.g. add alt text) → 0.5 ← quick-win boost low (< 30 min, e.g. add meta tag) → 0.8 medium (< 2 h, e.g. compress images) → 1.0 high (< 1 day, e.g. lazy-load JS) → 1.4 complex (> 1 day, e.g. CLS layout fix) → 2.0
All four factors are deterministic — same inputs always produce the same priority order. We do not use an LLM to rank issues; the AI chat is purely an explanatory layer over the deterministic findings.
Worked example — “Missing alt text on 14 images”:
Signal: missing-alt-text
Penalty: 12 points
Severity: high → severityMultiplier = 1.5
Source: axe-core → confidenceBoost = 1.2 (verified)
Effort: trivial → effortMultiplier = 0.5 (< 5 min)
priority = 12 × 1.5 × 1.2 ÷ 0.5
= 12 × 1.5 = 18
= 18 × 1.2 = 21.6
= 21.6 ÷ 0.5 = 43.2 ← ranked #1 in most audits
Compare: "Render-blocking JS" (penalty 18, high, verified, high effort)
priority = 18 × 1.5 × 1.2 ÷ 1.4 = 23.1 ← ranked lower despite higher penaltyThe trivial effort divisor (0.5) intentionally surfaces quick wins above harder fixes of equal impact — because a fix you can ship in 5 minutes delivers value faster than one that takes a day.
PSI sometimes blips. We handle it without lying about the data:
ScanoraAI is a public URL scanner. That means we must prevent it from being used as a proxy for SSRF attacks, internal network probing, or denial-of-service amplification. Here is what we enforce:
https:// and http:// URLs are accepted. File, FTP, data, and other schemes are rejected at input validation.admin@scanoraai.com with subject line “Security disclosure”. We aim to respond within 48 hours.Three independent ways:
pagespeed.web.dev with your URL pre-filled. Compare the scores. They will be highly similar (accounting for normal run-to-run variance).curl against the same endpoint with your own API key and diff the responses.(url, scannedAt, category scores, signal IDs).scanoraai.com itself and publish the full output — scores, source timestamps, findings, and AI explanations — at /self-audit. We don't hide our own issues. The homepage currently scores 72 on mobile performance due to a large JS bundle — that's a known issue we are actively fixing.Saying we are accurate is not the same as proving it. We are committing in public to publishing measurable accuracy data — false-positive and false-negative rates against a fixed corpus of test sites — on a quarterly cadence.
What that means in practice:
This section is intentionally a roadmap commitment, not a finished number. The audit engine currently passes a 644-test internal suite covering signal detection, scoring math, and provider failure handling. The benchmark corpus is the next layer up — testing the engine as a black box against ground-truth issues. We'd rather under-promise here than publish cherry-picked numbers.
Every material change to scoring, signals, data sources, or honesty disclosures is recorded here. We don't silently retune weights — if a check changes how it deducts points, or a new data source goes live, it shows up in this list. Versions follow the audit engine (separate from the marketing-site version).
JS-shell detection (engine v8.3): the probe now identifies JS-framework sites (Next.js, Nuxt, Gatsby, Remix, SvelteKit, Create React App, Angular) by inspecting the raw HTML for framework-specific markers. When a shell is detected, jsRendered=true is set on the probe.
Rendered meta extraction (engine v8.3): Lighthouse/PSI runs a full Chromium render and exposes post-JS-execution signals via its SEO and accessibility audits (document-title, meta-description, canonical, viewport, is-crawlable, hreflang, structured-data, image-alt, html-has-lang, tap-targets). These are now extracted as renderedMeta on the LighthouseMetrics object.
Rendered override (engine v8.3): when jsRendered=true and PSI renderedMeta is available, the engine overrides raw-HTML-derived signals (pageTitle, metaDescription, hasCanonical, hasOgTags, hasSchema, hasViewportMeta, robotsNoindex, hasAltTextOnAllImages) with the Lighthouse-rendered values. This closes the gap where React/Next.js/Nuxt sites were incorrectly flagged for missing meta tags that were actually present after JS execution.
No new dependencies required: the rendered signals come from the PSI/Lighthouse API that already runs on every audit. Zero additional latency. The jsRendered flag is surfaced in the audit report so users can see when their site was detected as a JS framework.
Mobile Lighthouse run (engine v8.2): both desktop and mobile strategies now run on every audit (5 runs each, in parallel). Desktop drives the main category scores for consistency with what users see in PageSpeed Insights. Mobile performance score is surfaced separately as mobilePerfScore in the report payload and shown in the audit results UI. Closes the gap where a user could get a high desktop score but a poor mobile score without being told.
Source count corrected from 15 to 18: the orchestrator runs 17 providers (techDetector, Wappalyzer, WP Stack Health, CrUX, TLS cert, DNS health, image audit, Wayback, content-SEO, schema validator, robots/sitemap, Safe Browsing, Mozilla Observatory, W3C, SSL Labs, Website Carbon, axe-core) plus PSI/Lighthouse = 18 total. All marketing copy updated site-wide.
axe-core jsdom limitations disclosed on methodology page: CSS not computed (colour-contrast violations not detected), JavaScript-rendered content not evaluated, focus management not testable. Lighthouse accessibility audit (real Chrome headless) noted as the complement that catches contrast and dynamic issues.
SSL Labs cache behaviour documented: always uses fromCache=on&maxAge=24h. Cache miss triggers a background scan and returns null for this provider in the current audit. Distinct from in-process TLS cert health check (which always runs).
WordPress Stack Health scope clarified: only reports on what is provably detectable from served HTML. Inactive plugins, premium plugins not on wordpress.org, and security-obscured installs will not appear. Confidence levels (verified vs inferred) documented.
Evidence-label vocabulary: every finding, score, and source claim now maps to one of 7 canonical categories — measured / field / lab / observed / inferred / ai_explained / unavailable. Replaces ad-hoc phrases like "verified" / "estimated" / "AI-generated" / "never estimated" that the v8.1 audit review flagged as overclaim-prone. Shared module at src/lib/evidence-labels.ts; reusable EvidenceLabel UI chip; visible on /sample-report and across audit results.
Lighthouse run-variance display: median scores now ship with a 5-run distribution band (min, max, spread, confidence: high / medium / low). A 14-point spread on mobile = medium confidence; the report tells you to re-run on PageSpeed before optimising. Hides nothing — synthetic noise is real and now visible.
Root-cause grouping: shared upstream issues are linked across findings. The "oversized JavaScript" group, for example, drives LCP, TBT, mobile performance, transfer size, and carbon. Lead finding contributes full penalty; followers contribute reduced share to avoid double-counting. Surfaced in /sample-report and in audit results.
Ranking-claim cleanup: removed the "same Lighthouse engine Google uses for ranking" phrasing. New canonical wording: "Lighthouse scores are diagnostic; Core Web Vitals from CrUX field data are the actual ranking signal — page experience can influence search performance." Lighthouse is a tool, not a ranking signal.
axe-core / WCAG cleanup: every reference to automated accessibility checks now states "Manual review is still required for full accessibility conformance." axe-core catches roughly 57% of WCAG violation types automatically (Deque's published number); the rest require human review of keyboard traversal, focus order, screen-reader semantics, and cognitive accessibility.
Privacy-wording cleanup: "Zero data retention" / "URL never stored" removed. New canonical statement: "No account required for public scans. Reports retained 90 days if you create one. Raw provider payloads stored only for verification then deleted. No PII collected, no tracking cookies, no data sold or shared." Resolves the contradiction between zero-retention claims and 90-day report storage.
Source-count unification: every page now uses the canonical phrase "18 evidence sources: 10 external providers + 5 in-house parsers." Replaces drift across "7 verified sources", "8 sources", "10 third-party providers", and "18 verified sources" that the audit review flagged.
Source-count drift sweep: site-wide audit found four UI components still hard-coding stale counts after the canonical phrase was introduced — TrustLogosStrip, WhyScanoraSection, the audit-results header, and the sample-report SOURCES table. All four now read from a single SOURCE_COUNT_PHRASE constant in src/lib/evidence-labels.ts so future count changes happen in one place. Verified by grep across pages/ and components/ and a final smoke test against ma.tt + news.ycombinator.com (17 providers wired, 30 + 35 signals, dataSources.verified across all 5 categories).
Stack-aware fix badges: verified-fix blocks now show "WordPress-specific" / "Next.js-specific" (indigo) or "Generic HTML" (slate) badges with hover tooltips explaining which variant was selected and why. Previously a tiny "Stack: X" chip was too quiet to signal tailoring.
Carbon context layer: raw grams of CO₂ per page view are now paired with (1) Website Carbon's global-average anchor (0.36 g/view, ~50 M-site dataset), (2) a per-URL trend vs. stored history (null when no prior reading exists — no fabricated context), and (3) a deterministic severity-graded one-line recommendation keyed off cleanerThan percentile and green-host status.
/transparency 0/0/0/0/0 bug: when no self-audit file existed, the page rendered literal zero ScoreBars that read as "we scored 0/100 across the board." Fixed via a pending: true discriminator — the API now returns pending when no real data exists, and the page renders an explicit "First self-audit pending" panel instead.
CrUX absence: FieldLabToggle now always renders an explicit notice when loadingExperience.available === false, explaining the 28-day Chrome opted-in traffic threshold. Previously the toggle silently showed nothing.
carbon-context: new /private/carbon-history/ store (per-URL, capped at 20 entries) feeds the "vs. your past audits" trend line. Isolated from the existing score-history store so the audit-history API is unaffected.
Expanded from 8 to 18 data sources: PSI/Lighthouse (4-key rotation + circuit breaker + 5-run parallel median/variance), axe-core, Mozilla Observatory v2, SSL Labs, Safe Browsing, W3C Validator, Website Carbon, Wappalyzer, plus 3 in-house providers: content-SEO, schema validator, robots/sitemap deep audit.
Pre-verified fix library: 25 top signals × 4 stacks (WordPress, Next.js, Shopify, generic) → canonical exactFix blocks. UI shows verified-fix block below AI prose. Fixes are human-curated, not LLM-generated.
AI honesty stack: constrained JSON output schema rejects empty fields and ungrounded citations. Gemini 2.5 Flash primary, GPT-4o Mini fallback. AI prose is clearly labelled as AI-generated; verified-fix blocks are labelled as human-curated.
Schema validator honesty: exports SUPPORTED_SCHEMA_TYPES (18 types). Unsupported schema types produce a single grouped schema-unsupported-types Info signal with scoreDeduction: 0 and a pointer to Google Rich Results Test, rather than silently failing or claiming full coverage.
Trust UI: ScoreDriversPanel, ScoreStabilityPanel, FieldLabToggle, ThirdPartySourcesPanel, raw PSI route, /methodology v8.3 badge. Per-category dataSources provenance surfaced in every audit result.
ConfidenceLabel gains UNVERIFIED tier. Providers map internal Info severity to Low at the boundary so the Severity union stays clean: Critical | Medium | Low | Pass.
Site-audit mode (/api/site-audit): bounded crawl ≤10 pages, 3 s politeness delay, 12-min watchdog, 2 audits/IP/10 min rate limit. Separate from single-page mode (/api/audit).
Robots/sitemap deep audit: hand-rolled XML parser (<50 ms on 50 k URLs), auto-descends sitemapindex, HEAD-samples up to 10 URLs to verify reachability.
Content-SEO provider: thin content detection, keyword-in-H1/H2, heading hierarchy, anchor-text quality, multiple-H1 check.
Schema.org validator: @graph descent, multi-type arrays, required-property table for 18 schema types.
Stable signal-ID contract established. Signal IDs are now permanent public identifiers — changes to an existing signal's behaviour are recorded in this changelog rather than silently applied.
PSI/Lighthouse integration with 4-key rotation and circuit breaker. Mobile performance weighted higher than desktop to reflect real-world user distribution.
Mozilla Observatory v2, SSL Labs, Google Safe Browsing, W3C Validator, Website Carbon, Wappalyzer added as third-party sources. All results attributed to their source in the UI.
scoreDeduction: 0 allowed for Info-level signals. Transparency signals (e.g. carbon footprint, schema coverage) can appear in results without affecting the score, so users see the full picture without being penalised for things outside their control.
Auth rate-limiting (5/min/IP per action) and audit rate-limiting established. Sign-out exempted from rate limits.
Earlier internal iterations (pre-v6) are not listed because they predate the public commitment to a stable signal contract. v6.0 is the first version whose signal IDs and penalty caps match what runs in production today.
The full audit methodology is also published as JSON at /.well-known/audit-methodology.json for tools that want to verify our claims programmatically.
Run an audit