# Rubric changelog

Rubric versions, what changed, and the corpus impact of each change.

### v2.1 — 2026-10-02

Rubric v2.1 makes Compliance & Trust **opt-in** and scores it **rule by rule** (T7.55; owner
decision 8; AItoolsplan.md "How playbooks change the P audits"). Spec version unchanged (llms.txt
v2). Fairness and gaming review: `docs/research/rubric-v2.1-review-2026-10-02.md`. Before and after:
`corpus/derived/rescore-2026-10-02-v2.1.md`.

**Rule changes.**
- **The gate is a declaration.** `playbook_matched` is true when the covering llms.txt declares an
  industry and a declared playbook can be scored. Syntax: one `Industry:` (or `Industries:`) line
  before the first `##` section (plain, `> ` blockquote, list item or bold key), in `## Identity`,
  or in a section whose heading starts with "Compliance"; values separated by `,` `;` `|` or `+`;
  each a playbook slug or display name compared without case, spaces or punctuation
  (`Industry: Housing, General business`). At most 3 (as in a playbook combination); further known
  values are reported as `over_limit`, values we don't know as `unknown`; neither is scored. The
  first such line counts; code fences are not read. Parsed as `declared_industry` (parse-core).
- **Only signed-off rules score.** A declared playbook is scored when it is `publishable` and has a
  signed-off (`status: rule`) blocking or high rule in a P1–P4 category with a matcher. Every
  shipped playbook is still "to verify" today, so a declaration is recorded and reported ("its
  playbook awaits reviewer sign-off") but the category stays Not applicable.
- **The domain list is retired.** v2.0 matched 7 homebuilders and brokerages by domain
  (`khov|tollbrothers|taylormorrison|tripointehomes|lgihomes|compass|opendoor` → `real-estate`).
  No domain opens the gate in v2.1.
- **Profile.** A scored industry declaration (an `industry`-kind playbook) selects `regulated`.
  The `REGULATED` domain regex is **kept as the fallback for sites that declare no scored industry**:
  retiring it would have moved 22 undeclared sites to another profile, 17 of them down (up to
  −1.9, wegmans.com and metlife.com D → F), which would make not declaring cost points.
- **P1–P4 measure coverage** with a matcher per rule (`tools/playbook-matchers.mjs`, 325 rules, every
  blocking/high P1–P4 rule of every playbook, signed off or not), severity-weighted (blocking 2, high
  1); P3 (and P1, P2, P4) Not applicable where the scored playbooks define no rule of that kind; P4
  half credit for a trigger without a route. The phrase proxies (`compliance_language`,
  `never_confirm`) no longer score anything (the parse flags stay for research).
- **P5** reads a quoted or labelled not-advice disclaimer addressed to agents (was 0.7 for any
  compliance phrase). **P6** is measured (citation preference 0.5 + content licensing for AI use 0.5;
  was a constant 0) and leaves the known-constants list (`CONSTANT_SCORED` is now R1, R2, R3).
- **AGENTS.md counts.** A rule is found in the llms.txt or AGENTS.md; each file is matched on its
  own and the breakdown keeps each file's coverage (`scans.audits.compliance`) with the scored
  rules on P1–P4 (`rules`: short name, severity, credit), so the report is a gap list.
- **Draft re-score (T10.4).** `score-draft` takes the playbooks: an llms.txt draft that declares or
  drops an industry re-gates P1–P6 and the profile, recombined with the scan's AGENTS.md coverage;
  an AGENTS.md draft re-checks P1–P6 against the scan's llms.txt coverage. A v2.0 scan is refused
  (`rubric_changed`) as before, so no draft mixes two rubrics.

**Fairness.** Undeclared sites are scored exactly as v2.0 scored unmatched sites: the category is
removed and its weight redistributed, never zero. Declaring is opt-in to a stricter check: it can
lower a score until the file covers the playbook (simulated: the 7 former domain-list sites would
each score lower declared than undeclared today), which is the intended incentive (declare when
your file carries the rules), stated on the audit pages. The one site that loses points in this
rescore, www.khov.com (−0.9, stays C), lost credit it got from a domain list, not for not
declaring; it regains the category by declaring once the housing playbook is signed off.

**Corpus impact** (157 domains: the 156 of the published corpus plus www.khov.com, archived by a
live scan on 2026-07-29; `corpus/derived/scores-2026-10-02-v2.0.ndjson` → `…-v2.1.ndjson`, the
same parse, `corpus/derived/parsed-2026-10-02.ndjson`):
- No file declares an industry, so P1–P6 apply to none (v2.0: 7).
- Mean 57.63 → 58.09 (157); 57.51 → 57.98 over the 156. 6 up, 1 down, 150 unchanged. Grades
  A/B/C/D/F 0/1/12/55/89 → 0/1/13/56/87: taylormorrison.com D → C (62.6 → 78.3),
  tripointehomes.com F → D (50.9 → 63.6), compass.com F → D (49.4 → 61.7); opendoor.com +11.7,
  lgihomes.com +10.8, tollbrothers.com +10.6 (all F). Each had a phrase-proxy compliance score of
  0 at the regulated weight of 20. www.khov.com 75.9 → 75.0 (C): its proxy score was 79.5.
- No profile, cap or non-P audit changes anywhere.

### v2.0 — 2026-09-28

Rubric v2.0 judges files against **llms.txt v2** (llmstxt.org, modified 2026-08-10). Every
scorecard and breakdown now records `spec_version: "llms.txt v2 (2026-08-10)"` next to
`rubric_version`. Why: `llmsupdate926.md`. Interfaces: `tools/RUBRIC-V2-CONTRACT.md`.

**Owner decisions.** Numbered 2.0 (a break, aligned with the spec), not 1.2. E5 is retired from
scoring; capture of `llms-full.txt` / `llms-ctx*.txt` continues for research. The agent test
(A10) is parked until the paid tier. Subpath scans are allowed in the free tier, with the same
quota.

**Rule changes.**
- **R1: any covering file is full credit.** v2: "A file covers the URLs under its path, and where
  more than one file applies, agents should use the most specific one." R1 is 1.0 for the most
  specific covering file, root or path (was 0.5 at a subpath). Which file covers the scanned URL
  is recorded in `<date>.llms.meta.json` and shown as R1's evidence and the scorecard's
  `evidence.covering_path`; no meta file means the root file.
- **`no_file` cap:** only when no file covers the URL at its path or any ancestor, up to the
  root. The scan decides this before scoring (the engine only runs on a file).
- **S5 retired** (`## Optional` present). v2 took away Optional's special meaning. Spec is now
  S1–S4 at 25 each (were 20).
- **C4 is budget only.** A file over 8k tokens with `## Optional` no longer gets 0.85; it gets the
  same budget score as any file its size.
- **C8 (new, Curation, weight 20, gate: file has links):** share of the file's links that point to
  LLM-friendly content, meaning a `.md`/`.txt` URL, or a page declaring a markdown alternate.
  C1–C7 scaled by 0.8 (to 16/16/8/12/12/8/8) to keep their ratios.
- **E3:** a page counts as having a markdown twin in either v2 form (`page.html.md` or `page.md`;
  `index.html.md` or `index.md`), declared alternates first. That is a change to the sampler; the
  engine still scores E3 as the sampled hit rate.
- **E5 retired** (`llms-full.txt`). v2 removed context expansion.
- **E8 (new, Ecosystem, weight 9, gate: pages checked):** pages declare
  `rel="alternate" type="text/markdown"` (0.5) and `rel="describedby"` (0.5), from `<head>` or the
  `Link:` header, averaged over the homepage and up to 3 sampled pages that answered 200
  (`<date>.pages.json`). A link to a markdown version samples the page it mirrors
  (`/docs/method.md` → `/docs/method`), so a file that links only to `.md` URLs still gets its
  pages checked. UNVERIFIED when no pages were checked. E8 takes E5's 9, so E1–E7 keep
  their weights. The §4.6 table now matches the registry (22/18/18/13/9/11/9).
- **WebMCP** counts as a machine surface for A5/A6 (new parser flag `mentions_webmcp`: "WebMCP",
  `navigator.modelContext`, `document.modelContext`, `toolname=`).
- **Self-blocking (R5, E1, `self_blocking` cap) follows RFC 9309.** Before, a reader counted as
  blocked only on a literal `Disallow: /` under the last `User-agent` line. Now it is blocked when
  `tools/robots.mjs` `isAllowed` says it may not fetch the covering `llms.txt`. So consecutive
  `User-agent` lines share their rules, an `Allow: /llms.txt` carve-out is honoured (not blocked),
  `Disallow: /llms.txt` or a `Disallow` over the file's subpath is caught, and a reader with no
  group of its own falls back to `User-agent: *`. **`User-agent: *` + `Disallow: /` counts as
  self-blocking:** the file is unreadable to every reader, which is exactly what R5 ("AI crawlers
  permitted to fetch it") and the cap describe. The reader set and the `named` count are
  unchanged.
- **Still constant** (the §5 ratchet in `tools/test/score.test.mjs`): R1, R2, R3, A4, A6, A9, P6.
  R1 stays constant by design, since every covering file now earns 1.0. C8 and E8 launch measured,
  and the ratchet asserts that both apply and vary.

**Corpus impact** (156 domains; the same capture as v1.1, which reproduces exactly from the v1.1
engine; `corpus/derived/scores-2026-09-28-v2.0.ndjson` against `…-v1.1.ndjson`):
- Mean total 60.42 → 57.96 (−2.46): 38 up, 101 down, 17 unchanged. Grades: 6 up, 31 down
  (A/B/C/D/F 0/3/17/67/69 → 0/1/13/55/87). Most of the downward moves are D → F near 60.
- **C8 accounts for the drop** (mean −2.49 on its own). It applies to 145 files, with a mean
  of 0.33: 75 have no `.md`/`.txt` links (0), 47 score ≥ 0.5 and 19 score 1.0. Docs sites that
  link markdown gain (mintlify.com and datadoghq.com +4.5, platform.claude.com +4.3). Brand sites
  linking HTML pages lose up to ~6 (siemens.com −6.3, target.com −6.2, kaggle.com −6.2).
  retool.com and zapier.com fall from B to C.
- **S5 and C4.** S5 applied to 38 files and credited 8, so 30 long files without `## Optional`
  gain Spec points (Spec mean +2.75). C4 changes on the 8 files that had the escape: 3 up
  (bun.sh, docs.perplexity.ai, together.ai: the budget alone scores above 0.85) and 5 down
  (posthog.com to 0.3, docs.stripe.com to 0.55). Net effect on the mean: +0.27.
- **E5.** Applied to 38 files, credited 13. Net effect on the mean: +0.08.
- **Self-blocking.** 3 → 4 capped. The new one is khov-dev (www-dev.khov.com, the staging host):
  `User-agent: *` / `Disallow: /` makes its file unreadable to every reader, so it falls from 72.8
  to 25. The other three stay capped, and no site loses its cap: R5 and E1 change on khov-dev
  only.
- **R1, E8, WebMCP: no corpus effect yet.** Every archived file came from `/llms.txt` and none has
  a `llms.meta.json`. No `pages.json` exists, so E8 is UNVERIFIED on all 156. The one file that
  mentions WebMCP (zapier.com) was already credited for MCP.
- **Rank agreement with the Edition 1 judgments** (Kendall tau, n = 55, method of
  `tools/render-rankings.mjs`): 0.374 → 0.335. Without khov-dev it is 0.369 → 0.364. Nearly all of
  the fall is khov-dev, which Edition 1 judged on content (77) while v2.0 caps it for being
  unreadable. Retiring S5 and the C4 escape costs 0.016 of the rest, because Edition 1's judges
  worked from v1 and praised `## Optional`. C8 adds 0.002 (a C8 weight of 10 would score 0.325).

**Measurement fixes (2026-09-29).** Two engine gaps found while writing the audit FAQs
(`docs-accuracy.md`); no weights changed, and `rubric_version` stays 2.0 (for A7.3–A7.5 too, by
owner decision 11.6). The only gate changes are E3's `md_twins_sampled`, which now receives a
sample, E4's `jsonld_checked` and E6's `canonical_checked`, which are now computed from the page
capture, and A6's `has_actions` and A4's `multi_journey`, which now read an action surface and
evidence of multi-step work (all below).
- **A7 accepts `## FAQs`.** The heading test was `/\bfaq\b|frequently asked/i`, so a section
  named "FAQs" scored 0.2; it is now `/\bfaqs?\b|frequently asked/i`.
- **E1/R5 naming reads a versioned `User-agent`.** `User-agent: GPTBot/1.0` now names GPTBot:
  the `/…` suffix is dropped before matching, as the RFC 9309 blocking rule (`tools/robots.mjs`)
  already did, so naming and blocking read the same line the same way. The scan progress line
  (`worker/src/progress.mjs`) follows suit and, like the engine, ignores a trailing `#` comment.
- **Live scans fetch `/CLAUDE.md` and `/SKILL.md`.** Live capture fetched only `/AGENTS.md`, so a
  site with only a `CLAUDE.md` scored 0 on E7 on a scan but 0.6 in the research corpus. It now
  fetches all three, as `corpus/capture.sh` does, with the same acceptance rule (HTML, non-200 and
  under 20 bytes rejected) and archives them under the same names. The scan progress line names
  `CLAUDE.md` / `SKILL.md` only when found.
- **Corpus impact: none** (`node tools/audit-stats.mjs`, 156 domains: every figure unchanged).
  No archived file names a section "FAQs" (A7 full marks stay 5 of 156, 3.2%), and no archived
  robots.txt puts a version on an AI crawler's `User-agent` (E1 full marks stay 30 of 148,
  20.3%).
- **Docs follow the engine.** The per-audit docs and the §3.3 and §4 tables now state the
  engine's actual formula and gate for every audit, marking the rubric's intended measure as
  *Planned* where the engine uses a proxy or a constant.
- **A URL without a trailing slash can be a directory.** Resolution treated the last path
  segment of `https://x.com/docs` as a page and never tried `/docs/llms.txt`. A last segment with
  no file extension is now also tried as a directory, most specific first (`/docs/guide` tries
  `/docs/guide/llms.txt`, `/docs/llms.txt`, `/llms.txt`); one with an extension (`/docs/page.html`)
  stays a page. The cap of five candidates, root always last, is unchanged; a scan of an
  extension-less deep URL makes at most one more request.
- **401 is turned away, not Unverified.** A 401 on the llms.txt fetch means authentication is
  required: the file is not publicly retrievable, which is a fact about access, like a 403, not a
  measurement failure. The scan outcome is now BLOCKED ("turned away at the door", copy now
  covers a login) instead of UNVERIFIED (`worker/src/score-bridge.mjs` `classifyOutcome`).
  Neither outcome is scored, so no total changes; only the report the site sees does.
- **A robots.txt 4xx means no rules (RFC 9309 §2.3.1.3).** A robots.txt that answers 4xx is
  "unavailable", and crawlers may access everything. R5 and E1 were UNVERIFIED for it; they now
  score as a robots.txt with no rules: nothing blocked, no AI crawler named, so 0.5 each and no
  `self_blocking` cap. The engine only does this when the capture recorded the 4xx: the scan
  (`worker/src/score-bridge.mjs`) and `corpus/capture.sh` write `<date>.robots.meta.json`
  (`{ url, status }`), which `robotsPolicy` reads. No record, a 5xx, no response, or a 429 (a
  rate limit on our request; Google also treats it as a server error) stays UNVERIFIED. When an
  archive holds both, the newer day wins, and on the same day the file we read does.
- **A short robots.txt is archived.** The capture rule "a 200 under 20 bytes is empty" threw away
  complete robots.txt files such as `User-agent:*` / `Allow:` (19 bytes). robots.txt now only
  needs a 200 and a non-HTML body (`worker/src/accept.mjs`, `corpus/capture.sh`); an empty 200
  robots.txt is also a file with no rules and scores 0.5 on R5 and E1.
- **Markdown versions (E3) are measured, live and in the corpus (A5.1).** E3 was UNVERIFIED on
  every scan because nothing passed the twin sample to the scorer (`mdTwins: null`). The sampler
  (`tools/sample-md-twins.mjs` `sampleTwins`) now runs on every live scan (`worker/src/capture.mjs`
  `fetchMdTwins`, after the pages capture so declared alternates are tried first; LLMSRank-User,
  like the rest of the scan) and writes `<date>.md-twins.json`, which the engine reads for E3
  (`tools/score.mjs` `mdTwinSample`). Cost per scan: 3 pages in parallel, at most 4 candidate URLs
  each (HEAD, a ranged GET only where HEAD is refused), 5 s per request, 10 s for the whole sample;
  what the budget cuts off is unverified, never a miss. Scoring is found ÷ pages answered
  definitively (200, 404, 410); a 403, 429, timeout or robots.txt block leaves the page out, and
  zero definitive pages is UNVERIFIED. A file with no URLs at all is now NOT_APPLICABLE. The scan
  progress line reads "markdown versions · 2 of 3 found".
- **A linked page served as markdown is its own markdown version.** A link such as
  `https://www.pinecone.io/product/md/` that answers `text/markdown` was scored a miss, because a
  directory URL only tried `index.html.md` and `index.md`. The page capture (`<date>.pages.json`)
  now records the content type of a non-HTML page, and a page that answered `text/markdown` or
  `text/plain` counts itself first. No extra requests.
- **Corpus impact of E3** (`node tools/audit-stats.mjs`, 156 domains; twins sampled 2026-09-29 by
  LLMSRankBot behind robots.txt, 6 sites at a time, `corpus/files/<domain>/2026-09-29.md-twins.json`
  and `corpus/derived/md-twins-2026-09-29.ndjson`; declared alternates read from the page heads,
  not archived as pages.json, so E8 stays as it was): E3 applies to 123 sites, is UNVERIFIED on 31
  (17 with no same-site page links the parser reads, 14 where every check was blocked, rate-limited
  or timed out) and NOT_APPLICABLE on 2. 42 of the 123 (34.1%) score 1.0, 70 (56.9%) score 0, mean
  0.388. 22 hits came through a declared alternate or a page served as markdown. Totals: mean 58.0
  → 57.5, median 59.4 → 58.5; 44 up, 72 down, 40 unchanged; grades 10 up, 14 down (D 55 → 51, F 87
  → 91). The largest moves are developer.civitai.com +13.0 (F → D) and jasper.ai −4.3.
- **Corpus impact of these three: none** (`node tools/audit-stats.mjs`, 156 domains: every figure
  unchanged). No corpus llms.txt fetch recorded a 401 (`<date>.meta.json` `http_status`: 153
  × 200, 4 × 403, 1 malformed), and the 2026-07-19 capture recorded no status for a robots.txt it didn't archive, so the
  8 domains with no archived robots.txt stay UNVERIFIED on R5 and E1 until they are recaptured.
- **JSON-LD agreement (E4) is measured (A5.2).** E4 was UNVERIFIED on every scan because page
  markup wasn't captured. The page capture that E8 already runs (`tools/capture-pages.mjs`, live
  and corpus) now also records each page's identity JSON-LD (`tools/jsonld.mjs` `extractJsonLd`):
  only `@type`, `name`, `alternateName`, `legalName`, `url` and `sameAs`, at most 8 entities per
  page, stored in `pages.json` (`jsonld`, format in `tools/RUBRIC-V2-CONTRACT.md`). No extra
  requests. The read still stops at `</head>` when the head carries identity JSON-LD; otherwise it
  continues into the body, to `</html>`, within the same 256 KB cap, because many sites emit
  JSON-LD there (43 of the 103 corpus sites with identity JSON-LD have it only in the body,
  stripe.com and vercel.com among them): reading the head alone would have scored them 0.
  `<link>` relations are still read from the head only. A block that doesn't parse counts as
  absent and never fails the capture. Rule: 0.4 identity present + 0.4 a name agrees with the H1
  + 0.2 a url on the file's site (§4.6, with the reasons for the thresholds). The gate
  (`jsonld_checked`) needs a page that answered 200 and was read for JSON-LD, and, for a 0, the
  homepage read to the end. A `pages.json` captured before this change has no `jsonld`, so E4
  stays UNVERIFIED for it while E8 still scores.
- **Corpus impact of E4 and E8** (`node tools/audit-stats.mjs`, 156 domains; pages captured
  2026-09-29 by LLMSRankBot behind robots.txt, 6 sites at a time, one
  `corpus/files/<domain>/2026-09-29.pages.json` per domain with an archived llms.txt, 157 written).
  The corpus had no `pages.json` until now, so **E8 is measured across the corpus for the first
  time**: it applies to 142 sites and is UNVERIFIED on 14 (every page answered 403 or 429, robots.txt
  disallowed them, or nothing is archived). None earns full marks, 97 (68.3%) score 0 and 27 score
  0.5; mean 0.129. 40 sites declare a `text/markdown` alternate on at least one page and 6 declare
  `describedby`. The same captures feed C8's declared alternates: 12 sites move, C8 mean 0.329 →
  0.338, full marks 19 → 20. **E4** applies to 123 sites and is UNVERIFIED on 33 (the same 14, plus
  19 whose homepage was cut at the 256 KB cap or didn't load and no page showed identity). 94 of
  the 123 (76.4%) score 1.0, 20 (16.3%) score 0 and 9 are partial (5 × 0.8, 3 × 0.6, 1 × 0.4);
  mean 0.815. Totals: E8 (with C8) alone takes the mean from 57.50 to 56.78 (125 sites down), E4
  then adds 0.70 (97 up); together mean 57.50 → 57.48 (57.5 as published), median 58.5 → 58.3; 77
  up, 53 down, 26 unchanged; grades 5 up and 5 down, so the counts are unchanged (B 1, C 13, D 51,
  F 91). The largest moves are ollama.com +4.3, n8n.io +3.6 (F → D) and developer.civitai.com −5.6
  (D → F).
- **Canonical agreement (E6) is measured (A5.3).** E6 was UNVERIFIED on every scan. The page
  capture now also records each page's canonical (`canonical`: the head `<link rel="canonical">`,
  else the `Link:` header, resolved to an absolute URL; `tools/links.mjs` `canonicalOf`), and
  the engine compares it with the URL the llms.txt links (`tools/canonical.mjs`): 1 equal, 0.5 the
  same page in another form, 0 a canonical elsewhere; pages with no canonical are left out (rule
  and reasons in §4.6). No extra requests. **No corpus recapture was needed:** the 2026-09-29
  `pages.json` already keeps every head `<link>` element (and the raw `Link:` header), so
  `canonicalOf` derives the canonical from those, resolved against the page URL, and `pages.json`
  stays write-once; only new captures (and live scans) store `canonical`. The one loss: a head with
  more than 100 `<link>` elements is cut at 100 in the capture, so a canonical after the hundredth
  is missed and the page is left out (18 of 502 corpus pages reach the cap).
- **Corpus impact of E6** (`node tools/audit-stats.mjs`, 156 domains, same 2026-09-29 captures).
  E6 applies to 111 sites and is UNVERIFIED on 45: 14 where no page loaded (the sites E8 also
  leaves out) and 31 where a page loaded but none of the sampled linked pages declared a canonical
  (a homepage-only capture, or no `rel=canonical`). Of the 330 linked pages compared, 308 are equal, 1
  is the same page in another form and 21 point elsewhere (dell.com's links keep tracking
  parameters and pluralise a path; docs.windsurf.com links pages whose canonical is on docs.devin.ai;
  suno.com/explore canonicalises to the homepage). 96 sites (86.5%) score 1.0, 3 (2.7%) score 0, 12
  are partial; mean 0.928. Total movement: mean 57.48 → 57.94 (57.5 → 57.9 as published), median
  58.3 → 59.1; 98 sites up, 8 down, 50 unchanged; grades 4 F → D and 2 D → C (F 91 → 87, D 51 → 53,
  C 13 → 15; B 1). The largest moves are cox.com and americanexpress.com +1.5; the falls are
  under 1 point and are sites whose linked pages all point elsewhere (decagon.ai and
  docs.windsurf.com, −0.8; lmstudio.ai −0.7).
- **Entity identity (A8) is measured from the file (A5.4).** A8 scored 0.6 with a version or
  date stamp and 0.2 without, so identity itself was never read and nothing could score above 0.6.
  The parser (`tools/parse-core.mjs` `identitySignals`, synced to the browser copy) now sets four
  flags from the file's prose, never its link lines or code: `identity_statement`,
  `alternate_names`, `identifiers` and `glossary`. Rule: 0.4 an identity statement + 0.3
  alternate names or identifiers + 0.3 a glossary; 0 when the file states none (rule and reasons
  in §4.4). The version stamp left A8 (it stays in C7), the jargon gate was dropped (no
  deterministic test for jargon), and page JSON-LD does not corroborate (E4 scores it, and in the
  corpus the legal names it declares appear in files only incidentally). Deterministic, no model.
  The corpus parse (`corpus/derived/parsed-2026-07-19.ndjson`) gains the four flags, computed by
  the current parser from the same archived files; nothing else in it changes, and a fresh
  `parse-llms --all` differs from the old output only in these flags.
- **Precision of the A8 signals** (every corpus match read by hand, 156 files): identity statement
  20 files, all naming the file's own entity (3 name a subsidiary or project entity of it: K.
  Hovnanian American Mortgage, LLC; Toll Northeast V Corp.; UBS Limited); alternate names 2 (both
  former names: Cencora, HII); identifiers 11 (8 tickers, an NMLS number, "the official website
  of", a labelled primary domain), no false positive; glossary 11 (4 with a glossary heading, 7 through a linked
  glossary page only), no false positive. Near misses excluded by the rule: "also known as RAG" (a term, not the
  entity), Vercel's `### aliases`, DeepL's "Create a glossary" endpoint, Synthesia's
  `translation-glossary` feature, "Key terms: Agile, Scrum…" keyword lists, link descriptions
  naming a company ("Rockwell Automation, Inc."). Known misses, by design: identity stated only in
  a link title or description, and glossary pages whose URL isn't `/glossary` (`insurance-glossary`).
- **Corpus impact of A8** (`node tools/audit-stats.mjs`, 156 domains). A8 still applies to all
  156. Before: 16 × 0.6 (stamp), 140 × 0.2, mean 0.241. After: 35 sites (22.4%) score above 0 — 7 ×
  0.7 (identity plus identifiers or former names: albemarle.com, cencora.com, expedia.com,
  hii.com, t-mobile.com, tollbrothers.com, khov-dev), 1 × 0.6 (cognizant.com: ticker and glossary),
  13 × 0.4 (identity only), 14 × 0.3 (a glossary or identifiers only); 121 (77.6%) score 0; none
  earns full marks (no file pairs an identity statement with a glossary); mean 0.096. Totals: mean
  57.94 → 57.53 (57.9 → 57.5 as published), median 59.1 → 58.4; 29 up, 118 down, 9 unchanged (all
  capped); grades 6 down (developers.deepl.com C → D; docs.unstructured.io, docs.leonardo.ai,
  dxc.com, wegmans.com and paypal.com D → F): B 1, C 15 → 14, D 53 → 49, F 87 → 92. The largest
  moves are hii.com +1.8, expedia.com, albemarle.com and t-mobile.com +1.7, and the falls of sites
  whose stamp earned 0.6 while Agent Readiness had few applicable audits (harley-davidson.com −4.1,
  americanexpress.com −3.8).
- **API error handling (A9) is measured from the file (A7.3).** A9 was a constant 0.2 (min(0.6,
  judged A5) with an LLM audit) wherever an API was documented. The parser now sets `api_errors`,
  `api_limits`, `api_auth` and `api_recovery`, and A9 scores the parts found ÷ 4 (rule and reasons
  in §4.4). The gate is unchanged (`has_api`) and the judged overlay is gone (owner decision 11.3).
  The report line names each part: "API errors: documented; rate limits: documented; auth:
  documented; recovery: not found".
- **Action endpoints (A6) are measured, and A6 has its own gate (A7.4).** A6 was a constant 0.7
  gated on the API signal, so a dentist's booking link never counted and an API-only docs site
  always did. The gate is now an action surface (`has_actions`: `api_write_operation`,
  `mcp_endpoint`, `mentions_webmcp`, `action_link`, `action_prose`), and the score is 0.5 for the
  action plus 0.5 for guidance on when to offer it (`offer_guidance`). The report line lists the
  surfaces found and whether the file says when to offer them.
- **Journey flows (A4) are measured, and A4's gate asks for multi-step evidence (A7.5).** A4 was
  0.8 wherever intent-routing wording appeared (0.8 × judged A3 with an LLM audit). The parser
  now counts flows (`journey_flows`) and whether one links its steps (`journey_linked_steps`) or
  states a prerequisite or handoff (`journey_prereq_handoff`); A4 scores 0.4/0.6 + 0.2 + 0.2. The
  gate is an agent-callable action or a flow (owner decision 11.2). The judged overlay is gone
  (11.3). Deterministic, no model, for all three.
- **Still constant** (the §5 ratchet in `tools/test/score.test.mjs`, mirrored by the app's
  `CONSTANT_SCORED`): R1, R2, R3, P6. A4, A6 and A9 left the list, and the ratchet now also fails
  if any of the three stops varying across the corpus files.
- **Corpus parse.** `corpus/derived/parsed-2026-07-19.ndjson` gains the 12 new flags, computed by the
  current parser from the same archived files, plus `mentions_webmcp`, which the parser has set
  since v2.0 but this file predated: A6's and A4's gates read it, and zapier.com's only
  agent-callable surface is its WebMCP manifest. Every other field is unchanged (each record,
  with these flags removed, is byte-identical to the previous line). No other audit reads
  `mentions_webmcp` differently: zapier.com, the one file that mentions WebMCP, already had MCP.
- **Corpus impact of A9, A6 and A4** (`node tools/audit-stats.mjs`, 156 domains, with the A7.10
  fixes below; at A7.5 every per-site total equalled the frozen estimate in
  `docs/research/a4-a6-a9-patterns-2026-09-29.md`). **A9** still applies to 74: 36 × 0, 16 ×
  0.25, 13 × 0.5, 9 × 0.75, none at 1.0; mean 0.2 → 0.233. Parts: auth 26, errors 22, limits 12,
  recovery 9. **A6** applies to 83 (was 74; 88 before the A7.10 fixes): 45 stay, 38 newly apply,
  29 leave (API docs with no action surface, such as developers.cloudflare.com and github.com, and
  the 5 whose only surface is a docs-search or read-only MCP server). Surfaces: 31 agent-callable
  (23 API write operations, 8 declared MCP endpoints that take actions, 1 WebMCP; 6 more files
  declare only a docs-search or read-only MCP server) and 57 user-facing (49 action links, 11
  prose actions; 34 files have contact links only). 74 × 0.5 and 9 × 1.0 (aflac.com, attio.com,
  docs.stripe.com, expedia.com, khov-dev, mccormick.com, ollama.com, trivago.com,
  wyndhamhotels.com); mean 0.7 → 0.554. **A4** applies to 35 (was 1; 40 before the A7.10 fixes):
  before the Quickstart partial credit (owner decision 2026-10-01, below), 28 × 0, 5 × 0.4
  (attio.com, carvana.com, firecrawl.dev, lmstudio.ai, trychroma.com), 1 × 0.8 (mccormick.com),
  1 × 1.0 (khov-dev); mean 0.8 → 0.109. With it, 17 of the 28 score 0.2 and 11 stay at 0; A4's
  mean is 0.206, the mean total 57.51 (57.5 as published) and the median 58.8, grades unchanged.
  The figures that follow are before it. On its own (frozen estimate, before A7.10), A9 moves
  the mean total 57.53 → 57.55, A6 → 57.60 and A4 → 57.34. Together: mean 57.53 → 57.45 (57.5 as
  published; 57.44 at A7.5), median 58.4 → 58.55 (58.6 as published then); 40 up, 65
  down, 51 unchanged; grades 8 up and 8 down (B 1, C 14 → 11, D 49 → 55, F 92 → 89). Up to D:
  dxc.com, westerndigital.com, wegmans.com, mccormick.com, paypal.com, albemarle.com, lumen.com,
  paychex.com. Down: apify.com, clerk.com and docs.poolside.ai (C → D); elevenlabs.io,
  posthog.com, pinecone.io, browserbase.com and ziprecruiter.com (D → F). The largest falls are
  API sites with no flows and no offer guidance (developers.cloudflare.com and
  developers.llamaindex.ai −2.7, prisma.io −2.4, augmentcode.com −2.3); the largest gains are sites
  with action links, offer guidance or a linked flow (mccormick.com +2.8; sentry.io, mistral.ai,
  wyndhamhotels.com and aflac.com +2.6; expedia.com +2.4).
- **A7.10 fixes (gaming and fairness review, AIplan.md item 11).** Five rule changes, each with
  the review's crafted gaming file as a test: (1) a docs-search or read-only MCP server (its line
  mentions `/_mcp/server`, an `mcp-docs.` host, "docs MCP" or documentation, or its H2 section
  says "read-only") still counts as declared and for A5, but no longer opens A6 or A4
  (`mcp_docs_only`, a new corpus parse flag; every other field of each record is unchanged); (2) an
  A4 flow needs more than half its steps with ≥ 2 words or a link or operation, and an arrow chain
  needs its intro word and a colon before the first arrow; (3) an A6 guidance heading needs a body
  and the same verb test as prose; (4) A9 recovery prose needs an idempotency key, not the bare
  word; (5) an A9 heading's body needs a link, URL, code or a valued prose line. Fixes 2–5 change
  no corpus figure. Fix 1: A4 applies 40 → 35 (mean 0.095 → 0.109), A6 88 → 83 (0.551 → 0.554),
  mean total 57.44 → 57.45, median unchanged; dev.hume.ai 68.9 → 70.2 (D → C), you.com 74.4 →
  75.5, assemblyai.com and docs.docker.com −0.1, ziprecruiter.com unchanged (its A6 0.5 and A4 0
  cancel); grades C 10 → 11, D 56 → 55. Fern's generated "connect to the MCP server at
  …/_mcp/server" line, which dev.hume.ai, developers.deepgram.com and you.com share, had cost
  dev.hume.ai its C. llmsrank.dev's own file scores the same under all five (A4 0, A6 1.0, A9 0.5).
- **Owner decision 2026-10-01: Quickstart partial credit.** The A7.10 review measured, and didn't
  recommend, 0.2 pointer credit for a linked Quickstart page when no flow is written (AIplan.md
  item 11, decision 11.8); the owner chose it. When A4 applies and `journey_flows` is 0, a link
  line whose whole normalised title is "quickstart", "quick start", "getting started" or "get
  started" (optionally + "guide" or "tutorial"; ≤ 120 characters; a page, not an in-file anchor)
  scores 0.2 (`journey_quickstart_link`, a new corpus parse flag; every other field of each record
  is unchanged). The rule is tighter than the review's (any list-item title containing the words,
  23 files): whole titles only, so "Getting started with feature flags", "W&B quickstart" and
  "API quickstart" don't count. It never adds to a flow's score and never opens the gate. Report
  line: "no numbered or arrow flow with a journey intro; a quickstart or getting-started page is
  linked (partial credit)". Corpus: A4 applies to 35, unchanged; 17 move 0 → 0.2 (11 × 0, 17 ×
  0.2, 5 × 0.4, 1 × 0.8, 1 × 1.0); A4 mean 0.109 → 0.206 (zero rate 0.80 → 0.31). Mean total
  57.45 → 57.51, median 58.55 → 58.8; no grade changes (B 1, C 11, D 55, F 89). 14 totals move,
  +0.5 to +0.8: mintlify.com 72.4 → 73.0, developers.deepl.com 68.1 → 68.8, developers.deepgram.com
  67.9 → 68.5, dev.writer.com 66.9 → 67.4, docs.perplexity.ai 66.4 → 67.0, docs.cartesia.ai 61.4 →
  62.0, inference-docs.cerebras.ai 61.3 → 62.0, docs.fireworks.ai 61.3 → 61.9, weaviate.io 60.2 →
  60.9, docs.unstructured.io 58.2 → 59.0, docs.leonardo.ai 58.2 → 58.8, docs.langchain.com 57.3 →
  58.1, vercel.com 54.5 → 55.1, platform.claude.com 49.1 → 49.7. algolia.com, twilio.com and
  together.ai gain A4 0.2 but stay at their caps (30, 30, 25). llmsrank.dev's own file has two
  flows and scores the same (A4 1.0).

### v1.1 — 2026-09-28

Measurement fixes from `updatesuggestions.md`; no weights changed.

- **C6 is measured.** It was a constant 0.6. It now uses the archived sitemap per §4.3 and has
  its own gate (`sitemap_locs`), so a sitemap we can't read completely makes C6 UNVERIFIED
  without switching off E2. Sitemaps are captured by `tools/capture-sitemaps.mjs`.
- **C6 doesn't apply below 50 sitemap pages.** On a small site a complete file and a sitemap
  mirror look the same (lumalabs.ai: 23 pages, 18 linked), so C6 is NOT_APPLICABLE there.
- **E2 is measured.** It was a constant 0.9 for "present". Now: 0.4 for a readable sitemap
  + 0.3 × share of pages with a `lastmod` + 0.3 × share of those that are plausible (a valid
  W3C date, not after the capture day, and not one timestamp on ≥ 90% of a 10+ page sitemap,
  which is a build time rather than an edit date). Index children that were archived count
  even when the index is incomplete. No readable entries = UNVERIFIED.
- **E7 needs a real file.** `AGENTS.md` or `CLAUDE.md` under 200 bytes no longer earns credit.
- **Corpus impact** (156 domains, sitemaps captured 2026-09-28): C6 applies to 98 sites, is
  NOT_APPLICABLE on 2 and UNVERIFIED on 56; the sites it scores down are mostly docs sites whose
  llms.txt lists ~80–98% of their sitemap. E2 applies to 130 sites, mean 0.84: 61 score 1.0 (60 as first published; the same capture
  and engine reproduce 61, corrected 2026-09-29) and
  24 with no `lastmod` score 0.4. Against v1.0 the mean moves +2.26 (115 up, 6 down; 24 grades
  up, 1 down), and rank agreement with the Edition 1 judgments goes 0.309 → 0.374.
- **Grades follow the stored total.** The engine stored totals to one decimal but graded the unrounded value, so an unrounded 59.96 was stored as 60.0 with an F. It now grades the stored total. Corpus impact: 2 domains (dell.com, developers.llamaindex.ai) move from F to D; no score changes.
- **AI crawler list.** R5/E1 recognise 23 AI crawler and control tokens (`NAMED_TOKENS` in `tools/ai-crawlers.mjs`), up from 13: 21 from the vendors' own documentation, each with its source, plus Bytespider and cohere-ai, kept because they are common in robots.txt files although no vendor page documents them. R5/E1 also ignore trailing `#` comments on User-agent lines (RFC 9309). The self-blocking set is unchanged. Corpus impact: none (every site naming a newer token already named an older one).
- **Still constant** (tracked in `tools/test/score.test.mjs`, which fails if the list grows):
  R1, R2, R3, A4, A6, A9, P6.
- **`robots_archived` gate fixed (D0.5).** A missing or unreadable robots.txt made R5 and E1
  NOT_APPLICABLE; §3.1 says a measurement we couldn't make is UNVERIFIED. Gate is now
  `robots.state === OK ? true : UV`. Corpus impact (156 domains, same capture): 8 domains
  (algolia.com, developer.civitai.com, docs.synthesia.io, gm.com, kaggle.com, ollama.com,
  qdrant.tech, t-mobile.com — the ones with no archived robots.txt) move R5 and E1 from
  NOT_APPLICABLE to UNVERIFIED; `total` is unchanged for all of them (mean Δ = 0), since both
  states drop the audit and redistribute its weight the same way — only the NA/UV counts shown
  in the report change.
