Audit rules

lumen’s audit engine ships 36 built-in rules across meta, content, links, technical, performance, accessibility and social categories. Rules are small, evidence-first, and plugin-shaped — the audit engine accepts additional rules via the plugin registry.

Severity model

Three severities, no vibes:

SeverityWeightMeaning
error10wrong or broken for search
warning3likely costing you something
info0worth knowing, not a failure

failThreshold (in lumen.config.json) picks the lowest severity that fails the run — error by default.

Scoring

Per-page score = 100 minus the weight of that page’s issues, floored at 0. Report score = the mean over audited pages. A site where nothing was actually audited scores 0 — honest, not generous.

The 36 rules

Rule idDefaultCategoriesFires when
title-missingerrormetano <title> or empty after trim
title-lengthwarningmetatitle longer than 65 or shorter than 15 characters
description-missingerrormetano meta description or empty
description-lengthwarningmetadescription longer than 165 or shorter than 50 characters
h1-missingerrorcontentzero <h1> elements
h1-multipleinfocontentmore than one <h1>
canonical-presentinfometano <link rel="canonical"> (multiple canonicals raise to warning)
lang-attrwarningcontent<html lang> missing or not a plausible BCP-47 primary tag
viewport-metawarningtechnicalno <meta name="viewport">
image-alt-coveragewarningcontent, accessibilityany <img> without an alt attribute (alt="" counts as present)
redirect-chainwarninglinks, technicalpage needed two or more redirect hops to resolve
robots-noindexinfometa, technicalnoindex via meta robots or X-Robots-Tag header
status-errorerrortechnicalfetched page status >= 400
insecure-httpwarningtechnicalpage served over plain http:
mixed-contenterrortechnicalhttp: subresources on an https: page
response-latencywarningperformancemeasured response time above the threshold (default 1500 ms)
og-tags-missinginfosocialany of og:title / og:description / og:image absent
hreflang-presentinfometano <link rel="alternate" hreflang> entry (multiple locales are normal)
duplicate-contentwarningcontentpage body byte-identical to another crawled page (unhashed pages are unknown, never judged)
structured-data-invaliderrortechnical, integrityJSON-LD that does not parse, or @context missing / not schema.org (parse errors only — no rich-result claims)
structured-data-required-propswarningtechnical, integritya common schema.org type is missing required properties (vendored, versioned table — the version is in the report)
canonical-target-invaliderrorlinks, integritya canonical target fetched this run is non-200, redirects, noindex, or cross-host (an unfetched target is reported unknown, never judged)
sitemap-invalidwarningtechnical, integrityretained sitemap evidence is malformed, claims >50k URLs, lists a wrong host, or exceeds lumen's 2 MB evidence cap
sitemap-url-nonindexablewarningintegritysitemap URLs sampled in this crawl return non-200, noindex, or redirect elsewhere (the sample size is stated)
robots-invalidwarningtechnical, integrityunknown robots.txt directives, an accidental blanket Disallow: / on a public target, or a declared sitemap that failed to fetch
hreflang-reciprocitywarningmeta, integrityhreflang present but defective: invalid codes, missing self-reference, or missing return tags (among crawled pages)
orphan-pageinfointegritya sitemap URL no crawled page links to (only when the crawl completed)
meta-refresh-redirectwarningtechnical, integrity<meta http-equiv="refresh">
security-headersinfotechnical, integrityon https: HSTS, X-Content-Type-Options, or CSP missing (presence only — not a security grade)
twitter-card-missinginfosocialtwitter:card absent while Open Graph tags are present
thin-contentinfocontent, integrityvisible text below the word threshold (default 200 — the threshold used is in the message)
ai-crawler-accessinfoai-searchalways (site rule) — reports the robots.txt matrix for the vendored AI user-agent list; never recommends a training policy
llms-txtinfoai-searchalways (site rule) — checks /llms.txt presence and llmstxt.org shape (a proposal with unproven impact)
content-in-raw-htmlwarningai-searchSPA-shell signature with few visible words in the server HTML (HEURISTIC — primary content probably needs JavaScript)

The ai-search rules report facts: which AI crawlers your robots.txt allows (from a vendored, dated list — theasOf date and source are in the report), whether/llms.txt exists in the llmstxt.org shape, and whether your server HTML contains the primary content. They do notclaim any ranking effect: llms.txt is an unproven proposal, AI-crawler access is your editorial choice, and the JS-content signal is a labeled heuristic. lumen never recommends a training policy — blocking or allowing training bots is entirely yours.

Length thresholds (title, description) and the latency threshold are configurable; the thin-content word floor is configurable too; everything else is fixed behavior. Crawl rules (broken-internal-link, redirect-chain) only fire on evidence gathered during the crawl itself — a link target that was never fetched is reported as unknown, not invented.

Overrides

Per-rule severity overrides live in severityOverrides in lumen.config.json. An unknown rule id is a config error listing the valid ids — see configuration.

Issue + incomplete reports

An Issue looks like this (evidence snippets are escaped):

{
  "ruleId": "title-length",
  "severity": "warning",
  "message": "title is 72 characters (max 65)",
  "evidence": { "selector": "head > title", "snippet": "<title>Acme Garden Tools — hand-forged broadf…</title>" },
  "fixHint": "shorten the title to 65 characters or fewer"
}

Interrupted runs are labeled rather than dressed up as finished:

{
  "id": "example-2026-08-29t10-31-04z",
  "incomplete": true,
  "summary": { "pagesAudited": 37, "pagesSkipped": 3 },
  "stopReason": "time_budget"
}