ARS defines repeatable checks for how AI retrieves and extracts information from a page. This document publishes the scoring rules, evidence format and conformance fixtures for version 0.3.
| Standard | The Agent Readability Spec (ARS), version 0.3.0 |
| Metric | The Agent Readability Score — an integer 0–100 and a band, or one of three non-grades |
| Reference implementation | @rebilder/agent-readability — scorer at ., probe at ./probe |
Ruleset hash (ARS 0.3.0) | 1d07a1223b3460c3b4d37669c0970d67e9f7e60f121d861201e73dafc9b544b0 |
| Corpus | packages/agent-readability/conformance/ — 44 fixture directories (§2.4.3), on GitHub |
| Supersedes | ARS 0.2.0 — what moved and why is §13 |
| Editors | Rebilder |
0. Measurement scope
ARS measures how readily a caller can retrieve and extract facts from a response. Explain that scope where users interpret results, with a link to this methodology. Interfaces can use concise labels and contextual help; repeating a fixed disclaimer on every score, badge or slide is not required.
Evidence wording, October 2026: evidence lines, remedies and flag messages are written in plain language for site owners, and the examples quoted below use that wording. They are not part of the conformance projection (§10.2).
1. Scope
1.1 Purpose
ARS scores a single fetched HTTP response set for one URL and answers one question:
Are the facts a caller needs for this kind of page present, findable, and cheap relative to what that kind of page costs?
The unit of measurement is one URL. Site-level aggregation exists in the reference implementation as an informative convenience and is not normative in 0.3; a normative sampling rule requires an explicit versioned specification.
1.2 What ARS does not measure
ARS does not measure, and no conformant implementation may claim that it measures:
- Truth. ARS never evaluates whether a stated price, hour, claim, or date is correct. A page that states four fabricated facts in perfect JSON-LD scores exactly as a page that states four true ones.
- Citation, ranking, or inclusion in any assistant's answer. ARS has no visibility into any model's retrieval or ranking. A high ARS is not a prediction that ChatGPT, Claude, Perplexity, Gemini, or any other system will read, cite, or recommend the page.
- Cloaking compliance. ARS identifies itself on every request (§3.2). It therefore cannot detect content that is varied by
User-Agent, including content varied specifically forrebilder-ars. ARS is not a cloaking-compliance certification. - Content quality, accuracy, legality, accessibility, or performance. ARS is not a WCAG audit, a Core Web Vitals measurement, or an editorial judgement.
- Commercial merit. A price is scored as present or absent, never as high or low. ARS 0.3 does not score cross-site price comparisons. Products can offer separate price analysis without representing it as this readability score.
- Whether a site should allow agents. A deliberate opt-out is a non-grade, not a failure (§7.3).
- JavaScript-rendered output. ARS never executes scripts. D1.3 estimates render independence from the served HTML and is labelled
heuristicfor exactly that reason (§5.3.3).
1.3 Notational conventions
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in BCP 14 (RFC 2119, RFC 8174) when, and only when, they appear in all capitals.
All arithmetic in this document is integer arithmetic unless stated otherwise. ⌊a ÷ b⌋ denotes exact integer division of non-negative integers (§6.1). Byte counts are decoded UTF-8 bytes (§3.4). KiB is 1024 bytes; MiB is 1048576 bytes.
1.4 Artifacts a conformant implementation reads
| Artifact | Role |
|---|---|
| This document | The prose definition |
conformance/ | The normative test of agreement. JSON in, JSON out, no TypeScript required (§10) |
DEFAULT_RULESET | Every constant, in one hashed object (Appendix A) |
Where this document and the conformance corpus disagree, the corpus is authoritative and the disagreement is a defect in this document; report it. Where the corpus and the reference implementation disagree, the corpus is authoritative and the disagreement is a defect in the implementation.
1.5 Terminology
Evidence bundle — the complete, self-contained input to scoring: the captures, the request headers used, the vantage, and the target. Type ArsEvidence.
Agent probe / agent representation — the response to the request whose Accept header prefers a machine representation, and the decoded body of that response. This is what a caller receives, and it is what D3 and D4 score.
Browser control probe / HTML representation — the response to the request whose Accept header prefers HTML.
Machine copy — a response whose media type is Markdown, plain text or JSON and whose body does not look like HTML (§5.4.1). The page negotiates when the agent probe receives a machine copy.
Linked Markdown copy — a Markdown alternate the page declares at a different URL on its own origin. The probe fetches it once, with request 3b (§3.1), when the page does not negotiate.
Structural representation — the representation that carries a tokenizable HTML document: the browser representation when it has one, otherwise the agent representation when it has one, otherwise none. D1.3 and D5 score this.
Core fact / extended fact — the facts a page kind is expected to state (§4.2). Core facts carry full weight; extended facts carry ¼ credit, and ¼ is the only extended-credit figure in this standard.
Corroborated — the same normalised value for the same fact kind was observed from two or more distinct sources, counting across both representations (§4.6.4).
Measured / heuristic — the basis of a check. A measured check reports a property of the bytes and headers that were received. A heuristic check reports an inference about them. Every check declares its basis in every result, and a heuristic value MUST NOT be rendered without its label.
Vantage — public (a third party, e.g. the public scanner), authenticated (a signed-in operator of the origin), or self (a run by or for the origin's own operator, including a local CLI run). Only public is gated on the scanner token (§7.5).
2. Versioning, determinism, and identity
2.1 Version scheme
Versions are ARS <MAJOR>.<MINOR>.<PATCH>.
| Component | Meaning | Score comparability |
|---|---|---|
| PATCH | No expected.json in the conformance corpus changes. Wording, documentation, performance, refactors. | Scores comparable |
| MINOR | Scores may change. Any ruleset constant, any extraction rule, any classification table, any tokenizer recovery rule. | Scores NOT comparable across a MINOR. Every published surface re-scores in a batch |
| MAJOR | The dimension set or the 100-point allocation changes. | Not comparable |
2.2 The PATCH gate
A PATCH release whose golden files differ MUST fail the build. This is a CI gate in the reference implementation, not a convention.
The consequence is deliberate and is the point of the rule: a bug fix in the tokenizer or the extractor that changes any fixture's expected.json cannot ship as a PATCH. It is a MINOR, with everything that follows — re-scoring, an incomparability notice, no silent drift. Implementations that publish scores MUST adopt an equivalent gate, or MUST NOT claim PATCH-level comparability.
2.3 The determinism guarantee
For a given
evidenceHash,rulesetHashandcorpusHash,score()returns a byte-identicalArsResultin any conformant implementation, in any language.
A number without all three hashes is not an ARS score. A surface that displays a grade MUST also make the three hashes retrievable for that grade. A badge, which has no room for them, MUST display the specVersion and a rulesetHash prefix, because a pinned-token update changes rulesetHash at an unchanged specVersion and two incomparable scores must not render identically.
Three properties are required to hold this guarantee, and each cost something to obtain:
- No floating point in the scoring path (§6.1). Two implementations that disagree in the third decimal disagree about grades at a band boundary, and the boundaries are what gets published.
- No third-party HTML parser. A parser version is an unversioned input to a versioned score: a dependency upgrade that changes how a malformed
<divis recovered changes scores with no ARS version bump, no conformance diff, and nothing in the release notes. A conformant implementation MUST implement the tokenizer rules itself, or MUST pin its parser and treat a parser upgrade as a MINOR. - No clock, no network, no randomness, and no language model inside
score(). All time-varying and network-varying inputs live in the evidence bundle, which is hashed.
2.4 The three hashes
2.4.1 rulesetHash
SHA-256, lowercase hex, of the RFC 8785 (JCS) canonical JSON serialisation of the frozen ruleset object. For ARS 0.3.0 this value is:
1d07a1223b3460c3b4d37669c0970d67e9f7e60f121d861201e73dafc9b544b0An implementation that produces a different value for the ARS 0.3.0 ruleset has a serialisation defect, a constant transcription error, or both, and MUST NOT publish rulesetHash as if it agreed.
2.4.2 evidenceHash
SHA-256 of the JCS canonical JSON of the evidence bundle with volatile fields removed:
capturedAt(bundle-level wall clock) is excluded.- The response headers
date,age,set-cookie,x-request-id,cf-ray,report-toare excluded, matched case-insensitively; all remaining header names are lowercased before hashing. - Response bodies are included in the hash where present.
bodySha256is already carried in each capture, but hashing the bytes as well means a bundle whose body was edited without updating its digest does not silently keep its identity. - The
markdownAlternateprobe record (§3.8) enters the hash only when the field is present, including asnull. A bundle without the field hashes exactly as it did under 0.2.
Without the exclusion list the hash changes on every scan of every real site — Date alone guarantees it — and replay, which is the only reason the hash is published, becomes impossible.
2.4.3 corpusHash
In ARS 0.3, corpusHash is a version marker: the SHA-256 of the ASCII string ars-0.3-corpus-unfrozen. It identifies the corpus version rather than verifying the contents of the fixture directory:
e88662472394316deb404ff02cde136b26a08a54439c52dcd299d80bc8fd9f9eAt corpus freeze, a digest of the fixture contents replaces this marker in a MINOR release.
The marker includes the spec version so results from 0.1, 0.2 and 0.3 carry distinct identity triples. Compare scores within a ruleset version; §12 describes comparisons across releases.
2.5 What rulesetHash does not cover in 0.3
Three scoring tables sit outside the hashed ruleset object and are pinned by conformance fixtures:
SUBPOINTS— the point split inside each check that scores a conjunction of named conditions (§5.3–§5.8). Published, exported, and reproduced in this document.- The classification tables — schema.org type → page kind,
og:type→ page kind, and the URL path grammar (§4.5). - The extraction tables — the label lexicon, availability vocabulary, no-published-price phrases, and schema.org property aliases.
All three are pinned data, not inference, and all three are pinned in practice by the conformance corpus: an edit to any of them moves an expected.json and is therefore at minimum a MINOR (§2.1). Appendix D, D-2 records the hash boundary.
0.2 added a second guard on SUBPOINTS specifically, because the 0.1 shape had a live trap in it. WEIGHTS and SUBPOINTS are two independent encodings of the same point values — the first says D1.1 is worth 11, the second says 5 + 3 + 3 — and nothing in 0.1 asserted that they agreed. Editing one without the other produced a scorer whose checks could not reach their own published weights, silently, across every page. The reference implementation now derives a total per check from SUBPOINTS and asserts it equals WEIGHTS, and asserts each dimension's DIMENSION_META weight equals the sum of its checks. A second implementation that keeps the two encodings SHOULD assert the same thing.
In 0.3, D2.1 carries two values in SUBPOINTS: 9 for negotiation and 6 for a working linked Markdown copy (§5.4.1). The total asserted against WEIGHTS is the full value, 9. The 6 sits outside the hashed ruleset like every other sub-point value, and fixture 064-linked-markdown-copy pins it.
2.6 Ordinality
ARS is ordinal, not cardinal. Display its integer score and band with the ruleset version and check evidence. A small point difference describes this scoring rubric, not a measured difference in sales or assistant preference. Comparisons should show the underlying checks and use the same ruleset.
3. The probe
The probe is the impure half. It is not part of score(), and everything it decides that could change a score is pinned in the ruleset for exactly that reason: two implementations with different caps produce different scores, so the caps are inputs to the score and therefore inputs to the hash.
3.1 The request set
Per URL, two sequential requests; per origin, three requests whose results are cached for at least one hour (24 hours in the public scanner). A cold single-URL scan is therefore 5 requests and a warm origin is 2. Request 3b fetches a linked Markdown copy only when the page declares one and does not negotiate (below), so a scan of such a page is 6 requests cold and 3 warm. A third request to the target URL is made only when a substance divergence is detected (§5.4.4).
| # | Request | Accept | Purpose |
|---|---|---|---|
| 1 | GET <origin>/robots.txt — fetched first | text/plain, */*;q=0.5 | Policy. Nothing else is fetched until this resolves |
| 2 | GET <url> — agent probe | text/markdown;q=1.0, text/html;q=0.8, text/plain;q=0.5, */*;q=0.1 | What a caller that prefers a machine representation receives |
| 3 | GET <url> — browser control | text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 | What a caller that prefers HTML receives |
| (3b) | GET <declared Markdown URL> — linked Markdown copy, only under the conditions below | same as #2 | D2.1, D2.2, D2.4 (§5.4) |
| 4 | GET <origin>/llms.txt | text/plain, */*;q=0.5 | D6.3 |
| 5 | GET <origin>/.well-known/ucp | text/plain, */*;q=0.5 | D6.5 |
| (6) | GET <url> — parity confirmation, ≥30 s after #2, only on detected divergence | same as #2 | §5.4.4 |
Request 3b, the linked Markdown copy. Made at most once, right after #3, and only when all three of these hold:
- the agent probe received a 2xx response;
- that response is not a machine copy (§5.4.1), so the page does not negotiate; and
- the page declares a Markdown alternate at a different URL on the target's origin.
A Markdown alternate is a Link header with rel="alternate" and a type of text/markdown or text/x-markdown, or a <link rel="alternate"> element with the same type and an href. Declarations are read in a fixed order: Link headers on the browser response, then on the agent response, then <link> elements in the browser HTML, then in the agent HTML. Each URL is resolved against the final URL of the response that declared it, with any fragment removed. The first one that is on the target's origin and is neither the agent probe's requested URL nor its final URL is the one fetched. Every other declaration is ignored, and a declaration on another origin is never fetched.
Request 3b carries exactly the headers of #2, follows the same body cap (§3.3), redirect limit (§3.5) and conduct rules (§3.9) as every other request, and obeys robots.txt for its own path. A path that robots.txt disallows is not fetched, and the record says policy-rejected. The scorer selects the URL with the same rule (§5.4.1), so the probe and the scorer cannot disagree about which declaration was meant.
Every request carries the same User-Agent:
rebilder-ars/0.3 (+https://rebilder.com/bots)The number tracks the spec version, so an operator reading their access log can tell which ruleset scored them. It is not the refusal token. A site that disallowed rebilder-ars under 0.1 or 0.2 is still refusing us under 0.3, because robots.txt is matched on the bare product token (§7.5) and that string does not move with the version. A versioned refusal token would let a spec bump quietly re-open sites that had already said no.
3.2 Comparing responses across Accept headers
Normative: requests #2 and #3 MUST be identical in every respect except the Accept header. Same User-Agent, same cookies (none), same protocol, same everything else. Request 3b MUST carry exactly the headers of #2.
This is what makes the parity check (D2.4) meaningful, and it is what prevents ARS from penalising a correct content-negotiation install, which negotiates on Accept and not on identity.
Both probes use the rebilder-ars identity. They measure response parity across Accept headers; they do not test responses served to other crawler identities or detect User-Agent-targeted cloaking.
D2.4 detects only the narrow case where the same identity receives different substance under different Accept headers. That is the case a content-negotiation product creates, so it is the case this standard is obliged to check.
3.3 Body cap
Normative: 2 MiB (2 097 152) decoded bytes.
- The body MUST be read as a stream and aborted at the cap. Reading the whole body and truncating afterwards is forbidden: the point of the cap is that the bytes are never all in memory.
- A capture that hit the cap MUST record
truncated: true. - A truncated capture produces the non-grade
unscored / truncated-evidence(§6.8). It is not scored, because every quantity would be wrong in the site's favour: the fact set would be whatever fell inside the first 2 MiB, the context cost would be measured against a byte count that is not the page's, and the token estimate would understate by however much was never read. Silently scoring a truncated body makes the worst sites score best on the one number the product is built on. - Where a truncated byte count is displayed at all it MUST be rendered as
≥ N bytes, never as a measurement.
3.4 Charset, bytes, and offsets
- Bodies MUST be decoded according to the
charsetparameter of the responseContent-Type, defaulting to UTF-8. ArsHttpCapture.bytesis the decoded UTF-8 byte length, not the transfer length and not the JavaScript/Java/Go string length. This is specified so that an implementation in another language reproduces our offsets exactly.- Every fact offset in a result is a UTF-8 byte offset into the decoded body. On a page with any multibyte character ahead of the price — a
€, an em dash, a CJK product name — a UTF-16 index and a UTF-8 offset differ, and a D4.1 band boundary sits between them. Conformance fixture048-shift-jis-charsetexists to make this checkable: the same JSON-LD block sits at UTF-8 offset 344 and at Shift_JIS offset 310. transferBytesis deliberately not a field. Runtimes auto-decompress and platforms manageAccept-Encoding, so a hashed field that will be absent or platform-dependent is worse than no field.
3.5 Redirects
Normative: at most 3 hops. A response chain longer than the limit produces unscored / too-many-redirects. Every hop MUST re-resolve DNS and re-validate the destination against the implementation's SSRF policy; re-parsing the URL without re-resolving is not sufficient.
The limit is inclusive: three hops is scored, four is not. Conformance fixture 042-redirect-chain-3 pins the boundary.
3.6 robots.txt
robots.txtMUST be fetched first, before the target URL.- Parsing follows RFC 9309 in the subset ARS reads:
User-agent,Allow,Disallow,Sitemap.Crawl-delayis honoured by the probe and never scored — waiting longer is not worse markup.HostandClean-paramare ignored without being counted as malformed. Any otherfield: valueline, and any line that is not blank, a comment, or afield: valuepair, incrementsmalformedLines. - Adjacent
User-agent:lines accumulate into one group; the first rule line closes the group to further agents (RFC 9309 §2.2.1). A rule that appears before anyUser-agent:line incrementsorphanRulesand belongs to no group. - Group selection is most-specific-match,
*as fallback, case-insensitive. Within the selected group, the longest matching path pattern wins and a tie goes toAllow(RFC 9309 §2.2.2). - Path patterns support
*and a trailing$. Implementations MUST NOT compile an attacker-supplied pattern into a regular expression;robots.txtis untrusted input andnew RegExp(patternFromTheInternet)is a denial-of-service primitive. - A persistent 5xx — one retry after 5 s, then still 5xx — produces
unscored / robots-unavailable. A single hiccup MUST NOT un-score a domain. A 4xx (including 404) ismissing, not an error, and is scored: an absentrobots.txtdisallows nothing.
3.7 What is not probed
Besides the target URL, ARS probes /robots.txt, /llms.txt, /.well-known/ucp and, when the page declares one, its linked Markdown copy, and nothing else. There is no guessing at /sitemap.xml, /mcp, /acp, /openapi.json, a .md suffix, or any other path.
Credit for a machine endpoint (D6.5) comes from declaration — a Link header, a <link rel="alternate">, an entry in llms.txt, or a Sitemap: line in robots.txt. The editors' stated principle:
An endpoint an agent cannot find is an endpoint that does not exist.
The three probed paths are the disclosed exception: they are published conventions with published locations, and an agent looking for them knows where to look. The linked Markdown copy is not an exception. The page named its address, and the probe fetches only that address (§3.1).
3.8 The evidence bundle
The evidence bundle is the only input to scoring besides the ruleset. It carries evidenceVersion, the target URL and origin, the six probe records, the vantage, and capturedAt.
ARS 0.3 adds one OPTIONAL probe record, markdownAlternate, for request 3b. It is omitted when the request was not taken, so a bundle for a page with no linked copy is the same bundle 0.2 produced, with the same evidenceHash. null also means "not taken", never "failed". The bundle's evidenceVersion is unchanged.
Each probe record carries the request headers actually sent and either a capture or a typed error (timeout, unreachable, non-2xx, blocked-redirect, too-many-redirects, challenge, policy-rejected).
Each capture carries requestedUrl, finalUrl, the redirect chain, status, headers as Record<string, string[]>, bytes, bodySha256, truncated, and OPTIONALLY body.
- Headers are multi-valued by type, not by convention.
Linkis routinely sent several times and is scored; an implementation that collapses repeated headers to the first value loses declared alternates and machine endpoints. Fixture051-multi-value-headerspins this, includingLinkvalues that contain commas inside<…>, which MUST NOT be split on the comma. bodyis optional because a published evidence bundle carries the hash and not the bytes. A bundle without bodies cannot be re-scored, and a conformant scorer MUST returnunscored / evidence-incompleterather than inventing a zero.
3.9 Probe conduct
These requirements bind any implementation that fetches third-party origins. They are not inputs to the score, but a standard that instructs people to fetch arbitrary URLs owes them:
- SSRF. Resolve the hostname; reject if any returned A/AAAA record is private, reserved, link-local, CGNAT (
100.64.0.0/10), benchmarking (198.18.0.0/15), multicast, or240.0.0.0/4; connect pinned to the validated IP withHostand SNI preserved; re-resolve and re-pin on every redirect hop. Reject.internal,.local, and single-label hosts. Handle IPv4-mapped IPv6. - Local mode is opt-in per invocation. An entry point that permits private hosts or plaintext HTTP MUST be a separate module requiring an explicit per-invocation flag, and MUST NOT be reachable from an HTTP transport. A boolean on a shared config object will eventually be passed as
trueby a hosted endpoint. - Politeness. At most one concurrent probe per host, a small global concurrency limit, at least one second between probes to the same host, and a soft per-run cap with an explicit override. The limiter MUST be injected rather than module-global: module state is correct in a CLI process and wrong in a warm serverless instance, where it would begin rejecting unrelated callers after a fixed lifetime count.
- Robots. Anything that produces a published or permanent artifact obeys
robots.txt,Crawl-delay,429/Retry-After, and any published opt-out endpoint.
4. Page kinds, facts, and extraction
4.1 Universality: fixed weights, varying fact schema and byte reference
Per-vertical weights are the wrong design. They make an 88 mean different things on different pages and they invite site-type shopping. ARS therefore uses one 100-point allocation for every page.
What varies by page kind is what counts as a fact and what a page of that kind reasonably costs in bytes. Neither is a weight.
ARS does not measure how much you say. It measures whether the facts a caller needs for this kind of page are present, findable, and cheap relative to what that kind of page costs.
The byte reference is specifically what stops long-form content — articles, documentation, datasets — from being structurally capped around C, which is what every design that used absolute byte bands did. §11.5 compares a 14 KB magazine feature with a 991-byte product page and explains the separate coverage and evidence-density effects.
4.2 The eight page kinds
The set is closed at eight. Every kind has a complete profile; there is no "no profile" path. The mapping MUST be total, and every fact name MUST be a member of the fact vocabulary.
| Kind | Core facts | Extended facts (¼ credit) | Byte reference |
|---|---|---|---|
product | title, price, currency, availability | brand, sku, shipping, returns, description | 8 KiB (8192) |
collection | title, item-count, item-link | price, availability, description | 16 KiB (16384) |
article | title, author, published | updated, section, description | 40 KiB (40960) |
place | org-name, address, hours, phone | email, service-area, primary-action-url, description | 8 KiB (8192) |
service | title, price, primary-action-url | duration, eligibility, service-area, description, updated | 8 KiB (8192) |
faq | title, question-answer | updated, description | 16 KiB (16384) |
document | title, updated, authority | description, primary-action-url, section | 24 KiB (24576) |
unknown | title, description, primary-action-url | updated, org-name | 16 KiB (16384) |
The complete fact vocabulary is: title, description, updated, published, price, currency, availability, brand, sku, shipping, returns, org-name, address, hours, phone, email, service-area, author, section, authority, primary-action-url, eligibility, duration, question-answer, item-count, item-link.
4.3 Definitions of the fact terms
These five terms are defined normatively because a score that depends on them is not reproducible otherwise.
primary-action-url — a same-origin-or-declared URL reachable from an <a href>, a <form action>, or a JSON-LD potentialAction.target, whose anchor text or name matches the ruleset's action lexicon. The lexicon is pinned (Appendix C) and MUST NOT be extended per page, per site, or by inference; primary-action-url is a core fact for service and unknown, so a lexicon that grew at runtime would take the whole score with it.
authority — an organisation or person named as the publisher of the content, extracted from JSON-LD publisher / author, <meta name="author">, or a rel="publisher" link.
org-name — JSON-LD Organization.name / LocalBusiness.name, og:site_name, or <meta itemprop="name"> on an org-typed scope.
item-count / item-link — for collection: the number of distinct item links inside the largest repeated-structure block, and whether those links resolve to same-origin URLs. Counting from the largest repeated block specifically is what stops a navigation menu being read as a product grid.
question-answer — a FAQPage/Question JSON-LD pair, or a <dt>/<dd> pair, a <details><summary>, or a heading followed by a paragraph. Counted once as present, never per item. A page with 40 questions is not four times better than a page with 10; fixture 017-faq pins this.
price is satisfied by an explicit "no published price" signal as well as by a number. ARS scores honesty, not disclosure: a service page that says "pricing on request" has told the agent what it needs to know. Fixture 013-service-pricing pins it — and also exposes an unresolved inconsistency with the D5.2 required-property table, recorded in Appendix D.
4.4 unknown is not an easy exit
unknown carries the smallest core set, but it still scores D5.2 against "any recognised schema.org type is present", which requires having a type at all. And because extraction runs against the resolved profile, a page that conceals its kind does not merely fail to be credited for its commerce facts — those facts are absent from the published fact set entirely.
Fixture 015-unknown-untyped uses a private @context: D5.1 earns 2/2, D5.2 earns 0/3, and the page’s stated price and availability do not enter the resolved fact profile.
pageKind and pageKindConfidence are reported on every result, including non-grades.
4.5 Classification: two passes
Fact extraction needs a profile; the profile comes from the page kind; the page kind looks like something one would infer from the facts. That circularity is cut with a hard wall:
Pass 1 extracts a fixed, kind-independent probe set: JSON-LD @type, microdata itemtype, RDFa typeof, og:type, URL path segments, presence of a parseable currency token, day-name/HH:MM adjacency, and presence of a telephone. Only pass-1 output may inform classification.
Pass 2 extracts against the resolved profile.
Precedence, applied in order:
| # | Signal | Confidence |
|---|---|---|
| 1 | JSON-LD @type, through the pinned type table | high |
| 2 | microdata itemtype, then RDFa typeof, through the same table | high |
| 3 | og:type | medium |
| 4 | URL path grammar | medium |
| 5 | Structural: opening-hours shape and a telephone → place | medium |
| — | anything less | low → unknown |
Ties within one signal are broken by document order, then lexicographically by type name. Below medium confidence the kind is unknown; an implementation MUST NOT guess into a vertical. Guessing product on an article scores the article against a fact set it had no reason to carry, and the publisher cannot argue with a number.
pageKindBasis is always heuristic in 0.3.
4.6 Facts, sources, and corroboration
4.6.1 Sources
A fact observation carries one of five sources, in this precedence order: json-ld, microdata, meta, html-text, negotiated. Precedence is used only to break an offset tie.
4.6.2 Which representation is extracted
- D3 (coverage) and D4 (position) score the agent representation. ARS measures what a caller actually receives. A merchant whose Markdown omits
availabilityloses coverage even though the HTML declares it, because the caller never saw it. - D1.3 (render independence) and D5 (structured data) score the structural representation, because those checks are about the document a caller that only reads HTML gets. JSON-LD does not live in Markdown, and "would this work without JavaScript" is a question about HTML.
- A linked Markdown copy is read only by D2.1, D2.2 and D2.4 (§5.4). It never becomes the agent representation. D3, D4 and D7 still score what the agent probe received at the page's own address, and the cost report does not count it as negotiated bytes (§6.9). A caller that does not follow the link never sees the copy, so it cannot lower the cost of reading the page.
4.6.3 Presence facts
Some facts are compared by value (price, title, phone). Others are only ever "declared or not": shipping, returns, hours, service-area, eligibility, question-answer, item-link. These normalise to the literal string declared from every source, deliberately — two sources that word the same shipping policy differently must still corroborate one another, and a free-text comparison of policy prose would not be deterministic.
4.6.4 Merging and corroboration
For each fact kind observed in the agent representation:
- The representative observation is the one with the lowest byte offset; ties are broken by source precedence, then lexicographically by normalised value. Lowest offset, because D4.1 scores the offset at which a fact first becomes available.
corroboratedis true when two or more distinct sources, counted across both representations, reported the representative's exact normalised value. A source that reported a different value corroborates nothing.- A fact observed only in the HTML does not enter the scored fact set. Corroboration strengthens a fact the agent received; it never conjures one the agent did not.
The published fact list is sorted by offset, then by fact kind.
4.7 Text extraction (normative)
Counted text is every text node except:
- the contents of
<script>,<style>and<template>; - any element carrying the
hiddenattribute, and its subtree; - any element carrying an inline
stylethat setsdisplay:none, and its subtree.
No CSS resolution. A cascade engine would not be deterministic across implementations, so a class that hides an element in an external stylesheet does not hide it here. Fixture 037-hidden-text-facts pins the rule in the direction that costs a page points.
JSON-LD script contents are not counted text. They are surfaced separately. The alternative reading — that <script type="application/ld+json"> is visible text because it is not "a script" — breaks two mechanisms this standard depends on: D5.3 checks that structured data agrees with visible text, and "corroborated" means observed in ≥2 sources. If JSON-LD were visible text, both would be satisfied by a single JSON-LD block, trivially and always.
The tokenizer MUST NOT throw for any input string — unterminated tags, unterminated comments, stray <, lone surrogates, deeply nested elements, binary noise. A scanner that can be crashed by the page it is scanning is a denial-of-service endpoint.
4.8 Money
Text is converted to a currency and an integer number of minor units (USD 14800 for $148.00), or to null. Ambiguous input MUST return null, and a null price is simply not a fact — it costs coverage points and nothing else. A wrong price would be published as truth and would make a parity check accuse a publisher of a divergence they did not commit. Every rule resolves toward null.
Supported formats, each of which has a test:
| Form | Examples |
|---|---|
| Leading symbol | $1,499.00 · €49 · ¥4,900 |
| Trailing symbol | 1.499,00 € · 49 zł |
| ISO code prefix / suffix | USD 1499 · 1499 USD |
| Comma and period as both group and decimal separator | disambiguated by position and by the minor-unit width of the resolved currency |
| Space / NBSP / narrow NBSP / apostrophe group separators | 1 499,00 · 1'499.00 |
| Zero-decimal currencies | ¥4,900 → 4900, not 490000 |
| Ranges | $148.00 – $198.00 → the lower bound, qualified: true |
| Qualifiers | From $9, $9+ → the stated bound, qualified: true |
qualified: true values are excluded from the D2.4 and D5.3 comparisons. "From $9" in Markdown against "$9 – $40" in HTML is one page saying one thing in two shapes, and flagging it would be a false accusation.
Implementations MUST NOT apply Unicode normalisation (NFC/NFKC) in the money path: normalisation is deterministic only for a fixed Unicode version, and the Unicode version is a property of the host's ICU build — the same class of unversioned input as a third-party parser. Every special-cased character is enumerated in a pinned table.
5. Dimensions and checks
5.1 The allocation
Fixed. 100 points. Identical for every page kind. No conditional weights, no redistribution, and no heuristic gate over measured points. Redistribution is a gaming vector — make the dimension you are bad at inapplicable and everything else inflates. A heuristic that can zero measured points makes those points heuristic.
| Dim | Weight | Check | id | Pts | Basis |
|---|---|---|---|---|---|
| D1 Retrievability | 28 | D1.1 Reachable on the agent path | retrievability.reachable | 11 | measured |
| D1.2 Robots policy for assistants | retrievability.robots-policy | 10 | measured | ||
| D1.3 Core facts present without JavaScript | retrievability.render-independence | 7 | heuristic | ||
| D2 Machine representation | 18 | D2.1 Machine representation on Accept negotiation | machine-representation.negotiated-response | 9 | measured |
| D2.2 Declared alternate representations | machine-representation.declared-alternates | 3 | measured | ||
D2.3 Vary: Accept declared correctly | machine-representation.vary-accept | 3 | measured | ||
| D2.4 Same substance across representations | machine-representation.substance-parity | 3 | heuristic | ||
| D3 Fact coverage | 20 | D3.1 Core facts for this page kind | fact-coverage.core-facts | 14 | heuristic |
| D3.2 Context cost relative to this page kind | fact-coverage.context-cost | 6 | measured | ||
| D4 Fact position | 10 | D4.1 Byte offset of the first core fact | fact-position.first-core-fact-offset | 5 | heuristic |
| D4.2 Core facts inside the front window | fact-position.front-window | 5 | heuristic | ||
| D5 Structured data | 6 | D5.1 Structured data present and parsing | structured-data.present | 2 | measured |
| D5.2 Recognised type with its required properties | structured-data.required-properties | 3 | measured | ||
| D5.3 Structured data agrees with visible text | structured-data.text-agreement | 1 | heuristic | ||
| D6 Contract & discovery | 10 | D6.1 Canonical URL present and self-consistent | contract-discovery.canonical | 4 | measured |
| D6.2 Cache validators | contract-discovery.cache-validators | 2 | measured | ||
| D6.3 llms.txt present and spec-shaped | contract-discovery.llms-txt | 1 | measured | ||
| D6.4 Sitemap declared in robots.txt | contract-discovery.sitemap | 1 | measured | ||
| D6.5 Declared machine endpoint | contract-discovery.machine-endpoint | 2 | measured | ||
| D7 Evidence density | 8 | D7.1 Quantities an answer can quote | evidence-density.quantities | 4 | measured |
| D7.2 Labelled term and value pairs | evidence-density.definitions | 2 | heuristic | ||
| D7.3 Comparable rows | evidence-density.comparisons | 2 | measured |
Check ids have the normative shape <dimension>.<check>. A result MUST carry the dimension explicitly as well; consumers MUST NOT parse an id string to group results.
earned is an integer in [0, weight] for every check, in every result, always.
The dimension list is ordered and MUST be complete. An implementation that assembles the result from a hand-maintained dimension order can drop a whole dimension by omission, and the visible symptom is a corpus that agrees with itself at a maximum below 100. A conformant implementation MUST derive the dimension set from the check table rather than from a second list, or MUST assert that the two agree.
5.2 The measured/heuristic split
Measured 63 · Heuristic 37 · spec ceiling on heuristic weight: 40.
The ceiling is a constraint on this standard, not a description of it: this ruleset keeps heuristic weight at or below 40, verified by its property tests. Future rulesets can revise the allocation explicitly and document the resulting comparability change.
The 63/37 split holds because D2.4 and D5.3 are scored checks, not gates. A parity failure loses its own 3 points and raises a warn; it does not touch D2's other 15 measured points. The same for D5.3 against D5.2. The property that must hold, and which is asserted over the whole corpus, is:
The sum of every check whose earned value can be zeroed or scaled by a heuristic equals the published heuristic weight.
The gates in D2 all point the safe way. D2.3 is gated on negotiation, and D2.4 on negotiation or a working linked Markdown copy. D2.2 scores 0 when the linked copy was fetched and is broken. Each gate is a measured observation of the kind D2.1 scores: a response's status, media type and first bytes. That direction is what makes the published ceilings in §8 provable. The forbidden direction, a heuristic gating a measured check, appears nowhere in this standard.
5.3 D1 — Retrievability (28)
5.3.1 D1.1 Reachable on the agent path — 11, measured
11 = 5 + 3 + 3:
| Condition | Pts |
|---|---|
| Agent-probe status is 2xx | 5 |
Redirect hops ≤ maxRedirects (3) | 3 |
| Both probes resolved to the same final URL | 3 |
The third condition is how "no Accept-conditional redirect" is measured. When there is no browser capture in the bundle it scores 0, with an evidence line saying not checked, because this scan has no browser request to compare. Awarding it by default would make a measured check heuristic; deducting it silently would be unexplained. The evidence line is the difference, and this rule applies to every sub-condition in this standard that compares the two probes.
5.3.2 D1.2 Robots policy for assistants — 10, measured
10 = 6 + 2 + 1 + 1:
| Condition | Pts |
|---|---|
| The assistant audience is not disallowed | 6 |
robots.txt parses with zero malformed lines | 2 |
Zero rules appear before any User-agent: line | 1 |
A Sitemap: line is declared | 1 |
If the assistant audience is disallowed by a group that does not name an assistant token, the whole check scores 0 and a robots-contradiction warn is raised (§7.3). An absent robots.txt scores 9/10: nothing is disallowed, nothing is malformed, nothing is orphaned, and no sitemap is declared.
The training audience is read, reported, and never scored (§7.4).
5.3.3 D1.3 Core facts present without JavaScript — 7, heuristic
Scored over the structural representation. Let r = ⌊100 × (distinct core fact kinds found in the structural representation) ÷ (core profile size)⌋.
r at least | Pts |
|---|---|
| 100 | 7 |
| 75 | 5 |
| 50 | 3 |
| 25 | 1 |
| — | 0 |
Plus a <noscript> floor of 1: a page whose facts are all client-rendered but which ships a non-empty <noscript> has told a JS-less caller something. earned = max(band, noscript ? 1 : 0). Fixtures 044-spa-empty-root (0/7) and 045-noscript-fallback (1/7) are the pair.
When there is no structural representation at all, D1.3 scores 0 with the evidence line not checked, because no HTML was returned.
Below r = 50 a render-dependent info flag is raised, carrying the ratio.
This check is labelled heuristic in the result and MUST be labelled heuristic in any UI, with its inputs shown. ARS never executes JavaScript, so this measures the served HTML rather than what a browser would render. An optional deep render profile MAY be offered as a diagnostic and MUST NOT change the score.
5.4 D2 — Machine representation (18)
5.4.1 D2.1 Machine representation on Accept negotiation — 9, measured
Earns 9, 6 or 0.
9 for negotiation. Earns 9 when the agent probe's response is a machine copy, which means both of these hold:
- The
Content-Typemedia type classifies asmarkdown(text/markdown,text/x-markdown),text(text/plain), orjson(application/json,application/ld+json, or any*/*+json); and - the body does not look like HTML — tested on the first 1 KiB, independently of what the header claims.
A response that declares text/markdown and returns an HTML document earns 0, and so does a response that returns real Markdown labelled text/html (fixture 036-negotiation-wrong-content-type). The body's opinion and the header's claim must agree.
A page whose bytes are HTML is still tokenized whatever the header said, so a mislabelled content type costs 9 points on D2.1 and does not additionally cost fact coverage. One mistake is charged once.
6 for a working linked Markdown copy. When the page does not negotiate, D2.1 earns 6 when the linked Markdown copy loaded. The scorer applies the selection rule of §3.1 to the agent and browser captures to find the one URL request 3b should have fetched, then reads the markdownAlternate record. The copy loaded when that record is a capture whose requestedUrl is exactly the selected URL, whose status is 2xx, and which is a machine copy under the two tests above.
- The page negotiates, or declares no qualifying Markdown alternate → there is no linked copy, and any record is ignored.
- No record, a
policy-rejectedrecord (the probe declined to fetch, for example becauserobots.txtdisallowsrebilder-arsfor that path), or a capture whoserequestedUrlis not the selected URL → the copy is unchecked. D2.1 earns 0 and D2.2 is scored as it would be without request 3b. A refusal is not an observation, and a measured check never deducts for what was not observed. - Any other failed fetch, a non-2xx status, or a response that is not a machine copy → the copy is broken. D2.1 earns 0, and D2.2 earns 0 (§5.4.2).
The partial credit is deliberate. A working copy at its own address reaches agents that look for the link, and publishing one is real work. It still reaches fewer agents than negotiation, because a caller has to find the declaration and make a second request, while negotiation answers every caller that asks for Markdown at the address it already has.
5.4.2 D2.2 Declared alternate representations — 3, measured
All or nothing. Earns 3 when at least one typed alternate is declared, from either probe:
- a
Linkheader withrel="alternate"and atypeparameter; or - a
<link rel="alternate">in the structural document with bothtypeandhref.
An alternate with no type does not count. Untyped alternates are language variants (hreflang), which every international site ships, and paying for them would make the check meaningless.
Link headers are parsed per RFC 8288. The separator is a comma and target IRIs contain commas, so <https://x/a,b>; rel="alternate" is one link; an implementation that splits on , silently drops alternates that were declared.
A broken linked Markdown copy earns 0. When request 3b's record shows the linked copy is broken (§5.4.1), D2.2 scores 0, even when the page declares other typed alternates. A declaration that leads an agent to a failed fetch or to HTML is worse than no declaration. When the copy is unchecked or was never selected, D2.2 is scored exactly as above, so every 0.2 bundle scores as it did. Fixture 065-linked-markdown-copy-broken pins the broken case.
5.4.3 D2.3 Vary: Accept declared correctly — 3, measured
All or nothing, and 0 unless the page negotiates (D2.1 earned 9). Earns 3 when either probe's response carries Accept in its Vary header token list.
The gate is the point: Vary: Accept on a response that does not vary by Accept is a claim about caching that is not true, and fixture 035-vary-without-negotiation pins it at 0. A working linked copy does not open this check. The page's own address still sends the same HTML whatever is asked for, and the copy's address is a separate resource. When the page negotiates and Vary: Accept is absent, a vary-missing warn is raised — a shared cache can serve the Markdown to a browser or the HTML to an agent.
0.2 worded this gate as "D2.1 earned points". In 0.2 that was the same condition, so every 0.2 bundle scores as it did.
5.4.4 D2.4 Same substance across representations — 3, heuristic
All or nothing, and 0 unless one of these holds:
- The page negotiates and a browser capture exists. The agent representation is compared with the HTML representation, exactly as in 0.2.
- The page does not negotiate and the linked Markdown copy loaded (D2.1 earned 6). The linked copy takes the agent representation's place in the comparison. Its facts are extracted against the page's resolved profile, by the same rules as the page's own facts, and compared with the facts of the browser capture, or of the agent capture when there is no browser capture. Both are HTML in this case.
Compared kinds are exactly four: price, currency, availability, title. Never description, never free text. A prose difference between a Markdown summary and an HTML page is a rendering difference, and calling it a substance divergence would be an accusation the evidence does not support.
The comparison rule is asymmetric, deliberately:
The value one side would publish — its lowest-offset unqualified observation, the same choice the published fact set makes — MUST appear somewhere among the values the other side stated.
Both symmetric rules are wrong, and each is wrong in a way that matters. Requiring the two representatives to be equal produces false accusations on ordinary pages: a real product page that names a backordered variant above the line saying the product is in stock states two availabilities, and both statements are true. Accepting any shared value lets a real divergence hide behind an incidental match, because "Free shipping over $50" appears in both representations. The asymmetric rule catches the second and not the first, which is the conservative direction: a missed divergence costs a publisher nothing, and a false one is published next to their name.
Scoring:
- No comparable kind present in both → 0, with the evidence line
Facts compared: none appear in both the Markdown copy and the page. This is a real cost for stating nothing in common, and it is not a flag. - Values agree → 3.
- Values disagree, and no confirming probe is present → 0, and no flag. One observation cannot distinguish a cloaker from an inventory change.
- Values disagree, and a confirming probe shows the difference did not reproduce → 3, recorded as a value that changed between requests.
- Values disagree and the difference reproduced on the confirming probe → 0 and a
substance-divergencewarn.
A linked copy never has a confirming probe, because request 6 re-fetches the page's own address. A difference between a linked copy and the page therefore always takes the "no confirming probe" branch: 0, and no flag.
The flag message is an observation, never an accusation, and MUST remain one: "The Markdown copy showed price USD 9900; the HTML page showed USD 14800. We saw the same difference on two requests taken apart." No assertion about which value is correct, and none about intent.
Known limitation: the confirming probe is required to be taken ≥30 s after the first, but the evidence bundle carries no per-probe timestamp, so the delay is a contract on the probe rather than something score() can verify. A bundle that puts a same-millisecond re-fetch in parityConfirm scores identically. Recorded in Appendix D.
5.5 D3 — Fact coverage (20)
Scored over the agent representation.
- D3.1 Core facts — 14, heuristic. Coverage against the page-kind profile, scaled by a density band. Arithmetic in §6.3.
- D3.2 Context cost — 6, measured. Decoded bytes against the page-kind byte reference. Arithmetic in §6.4.
5.6 D4 — Fact position (10)
Both checks are heuristic, and both are scored over the agent representation. Arithmetic in §6.5.
- D4.1 First core fact offset — 5.
- D4.2 Front window — 5. The denominator is the core facts that are present, not the profile size. Absence is D3.1's job, and charging the same absence twice would make the 100 points non-orthogonal. The evidence prints both numbers so a reader can see which question was asked.
5.7 D5 — Structured data (6)
Scored over the structural representation.
D5 assigns 6 points to structured-data presence, completeness and agreement with visible text. This is a readability allocation; it does not encode a measured effect on assistant inclusion.
5.7.1 D5.1 Structured data present and parsing — 2, measured
jsonLd = 2 when ≥1 JSON-LD block is present and every block parsed
= 1 when ≥1 block parsed and ≥1 did not
= 0 when blocks are present and none parsed
= — when no blocks are present
other = 2 when ≥1 microdata `itemtype` or RDFa `typeof` scope is present
= — otherwise
earned = max(jsonLd, other, 0)Microdata therefore rescues D5.1 on a page whose only JSON-LD block is broken — but it does not rescue D5.2, which walks JSON-LD only (§5.7.2).
A block that does not parse is not structured data. Fixture 033-invalid-jsonld pins the whole of D5 at 0 for a page whose only block is malformed — and pins that the scorer does not throw.
5.7.2 D5.2 Recognised type with its required properties — 3, measured
3 = 1 + 2:
- 1 for a recognised schema.org type being present at all.
- 2 × (satisfied ÷ required), half-up (§6.6), for required-property completeness.
Completeness resolution: at 2 points of completeness resolution, round½(2 × 3, 4) = 2, so a page satisfying 3 of 4 required properties earns the same completeness as one satisfying 4 of 4. 0.1 paid that difference a point because it had 5 to spend. Appendix D records this rounding behavior.
The scored node is the JSON-LD node whose @type decided the page kind; if none, the first node with any recognised type. Scoring the first node in the document would mark a page incomplete because its BreadcrumbList has no offers.price.
Required properties come from ruleset.requiredProperties (Appendix B), a hand-curated, versioned table shipped inside the package. D5.2 contributes 3 points in this ruleset:
- It is not "schema.org validity". schema.org defines no required properties at all.
- It is not a search engine's rich-results guidelines by reference. Those change without a version, and would make ARS scores move without an ARS release.
- It is the core fact set of each page kind expressed in schema.org vocabulary, plus the properties without which those facts are not actionable — a price with no
priceCurrencycannot be used by a buying agent, soproductandservicerequire both.
Notation: a dot is a property path from the scored node, and traversal enters array members, so offers.price matches offers[0].price. Alias handling (openingHours vs openingHoursSpecification, Article.headline vs name) belongs to the extractor and not to this table.
Known limitation: D5.2 walks JSON-LD only. Microdata and RDFa satisfy D5.1 but are not traversed for required properties, so a microdata-only page scores at most 2/6 on D5. Recorded in Appendix D.
5.7.3 D5.3 Structured data agrees with visible text — 1, heuristic
All or nothing. Applies the same asymmetric comparison as D2.4 (§5.4.4) over the same four kinds, but within one representation: the values the JSON-LD would publish against the values the visible page stated.
- Nothing comparable → 0 (no flag).
- Agreement → 1.
- Disagreement → 0 and a
structured-data-divergencewarn.
Fixture 034-jsonld-price-mismatch is the canonical case: JSON-LD says $99, the page says $148. The warn is the output that matters here, not the point. At 1 point the check is close to advisory arithmetically, and it is kept at full severity deliberately: a page whose structured data contradicts its own text is telling two callers two different things, and that is worth saying whether or not it is worth deducting.
5.8 D6 — Contract & discovery (10)
5.8.1 D6.1 Canonical — 4, measured
4 = 2 + 1 + 1: present (2), absolute http(s):// (1), identical across both probes (1). The HTML-side canonical is taken from the browser capture's Link header or <link rel="canonical">, falling back to the agent side; the agent-side canonical is taken from the agent capture only. A mismatch between the two raises canonical-mismatch (warn).
A representation with no canonical cannot be attributed to a page, which is why a Markdown response that declares none loses the third point even when the HTML is perfect (§11.1).
5.8.2 D6.2 Cache validators — 2, measured
2 = 1 + 1, read from the agent capture:
- 1 for a sane
Cache-Control: the header is present, does not containno-store, and either declaresmax-age/s-maxagegreater than zero, or declaresno-cache/must-revalidateand a validator exists. Revalidation only means something with a validator. - 1 for an
ETagor aLast-Modified.
5.8.3 D6.3 llms.txt — 1, measured
1 for a file that is spec-shaped — an H1 heading and at least one Markdown link. A file that exists but has no links earns 0: it tells an agent nothing.
0.2 removed the participation point. 0.1 paid 1 for existence and 2 for being usable. At a single point that split would make "exists" and "usable" score identically while the result still printed a remedy for the difference — advice worth nothing, which is worse than no advice.
This check contributes 1 point for a usable discovery file. The weight is part of the published rubric, not an estimate of citation lift.
5.8.4 D6.4 Sitemap — 1, measured
All or nothing: a Sitemap: line in robots.txt.
5.8.5 D6.5 Declared machine endpoint — 2, measured
All or nothing over any one declared endpoint. Declarations are read from:
- a
Linkheader, or a<link>in the structural document, whoserelis one ofservice-desc,service-doc,api,describedby,ucp,mcp; - a
Linkheader or<link>whosetypeis one ofapplication/rss+xml,application/atom+xml,application/feed+json,application/json,application/ld+json,application/vnd.api+json,application/schema+json,application/graphql,application/openapi+json,application/yaml,text/calendar; - a Markdown link in
/llms.txtwhose URL contains/api,/mcp,/graphql,/openapi,/.well-known/,.json,.ics,.rss, or.atom; - a non-empty
/.well-known/ucp.
Nothing is guessed (§3.7).
5.9 D7 — Evidence density (8)
Scored over the agent representation's counted text (§4.7), and over its tokenized document where one exists.
Why this dimension exists. D1 through D6 grade whether an agent can reach a page, parse it, and find the facts a page of its kind is expected to state. Nothing before 0.2 graded whether, having parsed it, there was anything on the page an assistant could quote. Those are different questions, and a page can pass the first cleanly and fail the second: a well-built services page with content negotiation, clean structured data, a canonical and a sitemap can score in the nineties while stating not one number a caller could lift into an answer. Fixture 061-evidence-thin-prose is that page.
What it is not. D7 does not measure quality, persuasiveness, or truth (§1.2 is unchanged). It counts three shapes that survive extraction into an answer — a quantity, a labelled fact, a comparable row — and pays for their presence, not their correctness. A page that states four fabricated measurements earns exactly what a page stating four true ones earns. That is disclosed here and again in §9, because it is the most obvious way to game this dimension.
D7 counts quantities, labelled facts and comparable rows. Question-shaped headings receive no separate D7 points. A faq page still scores its question-answer core fact under D3 (§4.3).
All three checks are AT-LEAST bands over an integer count. No model, no randomness, no clock (§2.3 is unchanged, and this dimension was written to keep it that way).
5.9.1 D7.1 Quantities an answer can quote — 4, measured
A count of distinct quantity tokens in the agent representation's counted text. A quantity is one of:
| Form | Examples |
|---|---|
| A currency amount | $148.00 · €49 · £12 |
| A percentage | 20% · 2.5 % |
| A number carrying a pinned unit | 1.7 l · 60 days · 2.4 kg · 48 hours · 1200 W |
| An ISO 8601 calendar date | 2026-08-15 |
The pinned unit list is in Appendix A and MUST NOT be extended per page or by inference.
Distinctness is normative and it is the whole mitigation. Tokens are compared case-insensitively with inner whitespace removed, and each distinct token counts once. A price stated in a header, a buy box and a footer is one fact said three times; paying for the repetition would reward exactly the padding this dimension exists to discourage. Fixture 062-repeated-quantity-padding is a page that states one price eleven times and earns the same 1 point as a page that states it once.
AT-LEAST over the distinct count:
| at least | Pts |
|---|---|
| 12 | 4 |
| 6 | 3 |
| 3 | 2 |
| 1 | 1 |
| — | 0 |
5.9.2 D7.2 Labelled term and value pairs — 2, heuristic
A count of lines in the counted text matching a Label: value shape — an optional list bullet, an optional bold marker, one to forty-one characters of label starting with a letter, a colon, and a non-empty value. <dt>/<dd> pairs and specification lists reach this shape once tokenized to text.
AT-LEAST over the count:
| at least | Pts |
|---|---|
| 6 | 2 |
| 2 | 1 |
| — | 0 |
Heuristic classification. Calling a Label: value line a definition is a judgement about what the author meant, not an observation of the artifact — a line reading Note: see below matches the shape and defines nothing. The basis field says heuristic for that reason and a UI MUST NOT render the value without it (§1.5).
5.9.3 D7.3 Comparable rows — 2, measured
A count of table rows that carry at least two cells, minus one for the header row. Counted structurally from the tokenized document by grouping td/th elements under their parent tr; when the agent representation has no tokenizable document — a Markdown-only response — pipe-delimited rows are counted instead, excluding the alignment rule, so a Markdown table is not silently scored zero.
AT-LEAST over the row count:
| at least | Pts |
|---|---|
| 3 | 2 |
| 2 | 1 |
| — | 0 |
One row is not a comparison. A single-row table states a fact that a sentence would have stated; two or more let a caller contrast options, which is the shape this check exists to reward. Fixture 063-single-row-table earns 0 here and 060-evidence-dense-pdp earns 2 on a three-row size table.
6. Scoring arithmetic
6.1 Integer arithmetic only
No floating point appears anywhere in the scoring path. Cross-language conformance is impossible with float rounding drift.
- All inputs are non-negative, so exact integer division is
⌊a ÷ b⌋ = (a − (a mod b)) ÷ b. Implementations SHOULD use this form rather thanfloor(a / b)on a double quotient, whose intermediate value can land on the wrong side of an integer boundary. - Half-up rounding is
(a + ⌊b ÷ 2⌋) ÷ bin integer division, writtenround½(a, b). - Division by zero yields 0.
round(),× 0.25and× 0.5are forbidden in the scoring path.
Two table encodings are used, and each is declared per table:
- AT-LEAST — descending
[threshold, value]pairs; the first pair withthreshold ≤ inputwins; no match scores 0. - AT-MOST — ascending
[threshold, value]pairs; the first pair withinput ≤ thresholdwins; no match scores 0.
A malformed or absent table scores 0. It MUST NOT invent points.
6.2 Fact weighting, in quarter-units
coreUnits = 4 × (corroborated core facts) + 2 × (uncorroborated core facts)
extendedUnits = 1 × (corroborated extended facts) + 0 × (uncorroborated extended facts)
denominator = 4 × coreSize
coveragePct = min(100, round½(100 × (coreUnits + extendedUnits), denominator))Full credit is every core fact corroborated. An uncorroborated extended fact contributes nothing — this is the single definition of "extended = ¼ credit", and it is the only one in this standard.
coveragePct is clamped at 100: a page with every core fact corroborated and several extended facts exceeds the denominator and gains nothing further. That clamp is a deliberate cap on stuffing (§9).
6.3 D3.1 — core-fact coverage (14 pts)
factsPerKiB100 = ⌊(coreFound × 100 + extendedFound × 25) × 1024 ÷ max(1, bytes)⌋bytes is the decoded byte length of the agent representation. The max(1, bytes) clamp is normative and replaces the max(1, bytes ÷ 1024) form, which let a 200-byte stub top the density band.
Density band, AT-LEAST over factsPerKiB100 (hundredths of a fact per KiB):
| at least | densityPct |
|---|---|
| 200 | 100 |
| 100 | 85 |
| 50 | 70 |
| 20 | 50 |
| 5 | 30 |
| 0 | 15 |
earned = ⌊(14 × coveragePct × densityPct + 5000) ÷ 10000⌋Density saturates at ≥ 2 facts per KiB. Above that threshold, adding bytes cannot buy a band the page could not already reach, and adding fabricated facts cannot buy anything at all because coverage is capped at the profile.
6.4 D3.2 — context cost (6 pts)
ratio = ⌊100 × bytes ÷ byteReference[pageKind]⌋AT-MOST over ratio:
| at most | Pts |
|---|---|
| 100 | 6 |
| 200 | 5 |
| 500 | 3 |
| 1500 | 1 |
| — | 0 |
6.5 D4 — position (5 + 5)
D4.1, AT-MOST over the byte offset of the first core fact:
| at most | Pts |
|---|---|
| 512 | 5 |
| 2048 | 4 |
| 8192 | 2 |
| 32768 | 1 |
| — | 0 |
No core facts found → 0.
D4.2. The front window is one expression, in integer division:
window = max(2048, ⌊bytes ÷ 5⌋)
inside = |{ core facts with offset < window }|
pct = ⌊100 × inside ÷ (core facts found)⌋The 0.2 × bytes form is deleted from this standard: two implementations rounding a float disagree at the boundary.
AT-LEAST over pct:
| at least | Pts |
|---|---|
| 90 | 5 |
| 70 | 4 |
| 50 | 3 |
| 25 | 1 |
| — | 0 |
No core facts found → 0.
6.6 D5.2 — completeness
completeness = round½(2 × satisfied, required) // 0 when the table row is empty → 2
earned = 1 + completeness // when a recognised type is present
earned = 0 // when none is6.6a D7 — evidence density (4 + 2 + 2)
All three are AT-LEAST bands over an integer count, and every count is over the agent representation.
quantities = |{ distinct quantity tokens }| // §5.9.1, case- and whitespace-folded
definitions = |{ lines matching the label shape }| // §5.9.2
comparisons = max(0, |{ rows with ≥2 cells }| − 1) // §5.9.3, the header row removed| Check | Encoding | Table |
|---|---|---|
| D7.1 | AT-LEAST | [12,4, 6,3, 3,2, 1,1] |
| D7.2 | AT-LEAST | [6,2, 2,1] |
| D7.3 | AT-LEAST | [3,2, 2,1] |
No fact set, no profile and no page kind enters any of the three. D7 asks what is quotable on the page, not what a page of this kind should have said, and mixing the two would charge the same absence twice — the same orthogonality argument that keeps D4 from re-charging D3's misses (§5.6).
6.7 Total and bands
The score is the sum of the seven dimension totals, each of which is the sum of its checks' earned. There is no normalisation step, no curve, and no rounding at the end, because nothing fractional ever entered.
| Grade | Range | Label | Meaning (descriptive only) |
|---|---|---|---|
| A | 90–100 | Agent-native | Facts are in a machine representation, complete for this page kind, front-loaded, and cheap to fetch. |
| B | 75–89 | Agent-friendly | All core facts are present and findable; the agent pays more bytes than it needs, or one signal is missing. |
| C | 60–74 | Readable with effort | Core facts are extractable from HTML, at high cost. |
| D | 40–59 | Partial | Some core facts for this page kind were not found in the fetched response. |
| F | 0–39 | Not extractable | Few or no core facts for this page kind were found in the fetched response. |
The band labels and meanings are descriptive only and their phrasing is load-bearing. Three formulations that appeared in earlier drafts are deliberately gone and MUST NOT be reintroduced:
- "expect wrong or missing details in AI answers about this page" — a predictive assertion about third-party model output that ARS does not measure;
- "effectively closed to agents" — falsifiable, and wrong for a site at 39 serving clean HTML;
- "…and can trust it" — asserts veracity, and ARS measures format, not truth.
6.8 Non-grades
Three outcomes carry score: null, grade: null, bandLabel: null, and an empty recommendation list. None of them is an F.
| Outcome | Reason |
|---|---|
opt-out | A deliberate, well-formed robots disallow for the assistant audience. A choice, not a failure. Never ranked, never listed |
unscored | blocked-at-edge — 403/401/429 or a challenge at the edge. The evidence is itself the finding |
unscored | unreachable, non-2xx, too-many-redirects, robots-disallow-scanner, robots-unavailable, truncated-evidence, evidence-incomplete |
Gate order is normative, because two gates can both be true:
robots-disallow-scanner— a statement about our conduct. If we were not permitted to fetch, nothing else in the bundle should be reported as a finding. Onlyvantage: 'public'is gated (§7.5).robots-unavailable— a policy we could not read is not a policy we may assume.opt-out— the site's own decision, reported before any failure of ours, because it is a choice and not a failure.- Probe failure → blocked status → non-2xx → redirect limit → truncated body → missing body.
A non-grade result still carries all seven dimensions with their weights and every check at earned: 0 with an evidence line naming the reason. It MUST NOT carry sub-scores: publishing D5 for a site that opted out would be scoring it by another name, and a ranking could be reconstructed from the parts.
6.9 Cost report
Every scored result carries: HTML bytes, negotiated bytes (or null), approximate tokens for each, a reduction figure, the first core-fact offset, and truncated. Negotiated bytes are null unless the page negotiates; a linked Markdown copy does not fill them (§4.6.2).
- Decoded UTF-8 bytes are the normative measurement. The token figures are
⌊bytes ÷ 4⌋, which is not a token count, and this standard says so: they are heuristic and MUST be rendered with≈and an "est." label wherever they appear. - The reduction figure is an integer percentage of HTML bytes eliminated by the machine representation (
94means "94% fewer bytes"). A multiple (N.N×) would be a float, and this is the same information with nothing for two implementations to disagree about. Interfaces may derive additional display formats from the recorded bytes, clearly labelled, without changing the canonical integer result.
6.10 Flags
Flags are observations attached to a result. Severity is info or warn. ARS 0.3 defines info and warn severity. Flag order in the output array is pinned so that two runs over the same evidence emit the same array.
substance-divergence, structured-data-divergence, robots-contradiction, paywalled, render-dependent, scanner-blocked, training-opt-out, assistant-opt-out, vary-missing, canonical-mismatch, body-truncated, vantage-variance.
7. Crawler policy
7.1 The three audiences
| Audience | Treatment |
|---|---|
assistant | The audience ARS scores. User-triggered fetches on behalf of a person asking a question |
training | Strictly neutral. Never lowers the score, under any circumstance |
search | Reported only. An input to robots-contradiction, never to a point |
7.2 Pinned tokens
Matching is case-insensitive (RFC 9309 §2.2.1); the casing below is the published casing.
- assistant —
ChatGPT-User,Claude-User,Claude-SearchBot,DuckAssistBot,Meta-ExternalFetcher,MistralAI-User,OAI-SearchBot,Perplexity-User - training —
AI2Bot,Applebot-Extended,Bytespider,CCBot,ClaudeBot,Diffbot,FacebookBot,Google-Extended,GPTBot,Meta-ExternalAgent,PanguBot,Timpibot,anthropic-ai,cohere-ai,omgili - search —
Applebot,Baiduspider,Bingbot,DuckDuckBot,Googlebot,Slurp,YandexBot
These lists are versioned data, not a heuristic. Adding a token changes the score of every site that named it, so additions land as a MINOR.
7.3 robots state → outcome
robots state for the assistant audience | Outcome |
|---|---|
| Allows, or is unspecified | Scored |
| Deliberate + well-formed disallow — a group that names an assistant token, zero malformed lines, zero orphan rules | opt-out, score: null, no letter grade, never listed |
Accidental or ambiguous — a blanket User-agent: * disallow, conflicting groups, malformed syntax | Scored, D1.2 = 0, robots-contradiction warn, remedy worth 6 |
Persistent 5xx on /robots.txt | unscored / robots-unavailable |
Disallows rebilder-ars (and vantage is public) | unscored / robots-disallow-scanner — we obey it and we say so |
Consent has to be specific. A blanket User-agent: * / Disallow: / on a site with full commerce markup is the accidental case, not an opt-out: it is scored with the contradiction flagged, and the flag message says what it observed rather than what it suspects. Conversely, a site cannot be measured after opting out on purpose. Fixtures 020 (deliberate) and 023 (blanket) are the pair.
7.4 Training-crawler neutrality — and its proof
Blocking model-training crawlers while allowing assistant fetches scores identically to a fully open site.
This is not a policy statement; it is a property of the corpus, and it is verified by the fixture pair below.
Fixtures 021-robots-training-optout-only and 022-robots-open are the same page, the same Markdown and HTML bodies, the same headers, the same vantage, the same llms.txt and /.well-known/ucp misses. 021's robots.txt adds one group naming GPTBot, CCBot, Google-Extended and anthropic-ai with Disallow: /; 022's does not. Both score 93, grade A.
Diffing the two expected.json files touches exactly three things, none of which is a point: policy.audiences.training, policy.trainingOptOut, and one info-severity training-opt-out flag whose stated effect on the score is "none". Every dimension, every check, every earned point, the cost figures and the recommendation list are identical.
Because two files that happen to agree today prove only that they agree today, the property is also asserted directly: the reference implementation re-reads both bundles, checks that they differ only in robots.txt — otherwise an identical score would prove nothing about robots.txt — and then compares the scored shape field by field, including the flag's severity. A warn there would be the score's disapproval wearing a different hat, which is precisely what this section forbids.
7.5 The scanner token and vantage
We publish a scanner token, rebilder-ars, so that a site can refuse us specifically without refusing assistants. Only a group that literally names rebilder-ars triggers the refusal; a blanket User-agent: * never does.
vantage: 'self' bypasses the scanner-token check. The owner can consent for their own origin: a publisher running a local CLI against their own store, or previewing in their own console, is not a third party who needs robots.txt's permission to look at their own site. Without this carve-out, the most careful publishers — the ones who block unknown crawler tokens — would be told their own site cannot be scored, which is a product regression dressed as rigour.
The carve-out is not a route around anyone else's wishes:
vantageis set by the impure half from proof of who is asking, it is covered byevidenceHash, and it is printed on every result.- It changes nothing about publication. Authenticated, merchant-sourced and locally run scans stay private by default. Sharing requires the operator’s authorization and a publication flow appropriate to the evidence; they do not enter the public index automatically.
Fixtures 025 and 026 are byte-identical bundles differing only in vantage: unscored / robots-disallow-scanner and A (93) respectively.
7.6 Publishing and sharing results
The score is independent of the product that presents it. Rebilder’s public Readable index requires verified domain-owner opt-in and honors its opt-out and removal controls. Those index requirements do not prohibit factual editorial comparisons, a business sharing its own results, or an authorized customer report.
Use the applicable sharing permissions and avoid exposing private evidence or personal data. Rebilder’s benchmark implementation applies a floor of 25 independent subjects and its contributor controls; that implementation requirement is not a universal ban on other properly authorized analyses.
Index badges read stored results, identify their subject and show when their verification is stale. A badge request does not trigger a probe. Removal from the index is free and independent of sales contact. Existing unsubscribe and removal controls remain in effect.
8. The designed ceilings
These results follow from the published weights.
| Missing | Points lost | Ceiling | Band |
|---|---|---|---|
| No content negotiation and no linked Markdown copy | D2.1 (9) + D2.3 (3, gated on negotiation) + D2.4 (3, nothing comparable) | 85 | capped at B |
| No content negotiation, but a working linked Markdown copy | 3 of D2.1 (earns 6 of 9) + D2.3 (3, gated on negotiation) | 94 | A reachable |
| No content negotiation, and the linked Markdown copy is broken | all of D2 (18): D2.1, D2.3 and D2.4 as above, and D2.2 (3) | 82 | capped at B |
| No valid structured data | all of D5 (6) | 94 | A still reachable |
| Nothing quotable on the page | all of D7 (8) | 92 | A still reachable, and only just |
Reaching an A requires a working machine representation, by negotiation or at a linked address. It does not require structured data.
The linked-copy ceiling counts D2 alone. D3, D4 and D7 still score the response at the page's own address (§4.6.2), so a page that relies on a linked copy reaches the nineties only when its HTML is itself compact and fact-dense. Fixture 064-linked-markdown-copy is the 91 KB page of 002 with a working linked copy: D2 rises from 0 to 12, and the page moves from D (59) to C (71).
These ceilings follow directly from the weights in §5.1 and the sub-points in §5.4. Any server that negotiates on Accept can satisfy the machine-representation checks, and any site that publishes a Markdown copy and links to it can earn 12 of their 18 points. Rebilder software is not required for either.
9. Adversarial mitigations
The following table describes the current checks and their coverage.
| Attack | Mitigation |
|---|---|
Serve clean Markdown to Accept: text/markdown and different substance to humans | D2.4 substance parity. Facts from both probes, normalised, compared on exactly four kinds — price, currency, availability, title — never description or free text. A reproduced mismatch scores 0 on D2.4 and raises a warn. Divergence MUST reproduce on a third confirming probe ≥30 s later before the flag is set: inventory and price genuinely change between two sequential requests |
| Serve different substance in a linked Markdown copy | D2.4 compares the linked copy with the page under the same asymmetric rule. A difference costs D2.4's 3 points. No flag is raised, because a linked copy has no confirming probe and one observation cannot tell a cloaker from a price change (§5.4.4) |
Forge the linked-copy record — a markdownAlternate record for a URL the page does not declare, or for a different URL than the one it declares | Ignored. The scorer selects the URL from the page's own declarations with the rule the probe uses (§3.1) and reads a capture only when its requestedUrl is exactly that URL. A record without a qualifying declaration earns nothing. A declaration on another origin, or one pointing back at the page itself, is never selected |
| Link to a Markdown copy that does not load | Earns nothing. A failed fetch, a non-2xx status, or HTML instead of a machine copy scores 0 on D2.1 and zeroes D2.2, including credit D2.2 would otherwise pay for other typed alternates. A fetch the probe declined (robots.txt) is unchecked, not broken. Fixture 065-linked-markdown-copy-broken |
Cloak against rebilder-ars specifically | Outside the measurement scope (§1.2, §3.2). Both probes use the same identity, so a site serving identical fabricated substance to both scores as if truthful. The methodology distinguishes response parity from crawler-identity testing |
| Keyword-stuff with fabricated facts | Coverage is capped at the profile — only facts the resolved profile names are extracted at all — and coveragePct is clamped at 100; density saturates at ≥ 2 facts/KiB. Fixtures 031 and 032 are the same kettle 691 bytes apart and produce identical scores across all twenty-two checks. Narrower in 0.2 than in 0.1, and the narrowing is stated in Appendix D: the stuffed page states 5 distinct quantities to the baseline's 3, both land in the same D7.1 band, and the tie is arithmetic rather than structural |
| Pad the page with repeated numbers | D7.1 counts distinct quantity tokens, folded on case and whitespace. Fixture 062-repeated-quantity-padding states one price eleven times and earns 1 of 4, the same as stating it once |
| State fabricated quantities | Not mitigated, and not mitigable. ARS never evaluates whether a number is true (§1.2), so a made-up measurement earns what a real one earns. D7 pays for evidence being present in a liftable shape, which is a property of the artifact; truth is not. A publisher who fabricates specifications to move a readability score has published false specifications, and that is a problem this standard does not detect and does not claim to |
| JSON-LD spam | D5.2 scores required-property completeness for the detected kind against a pinned table; D5.3 independently checks agreement with visible text. Fixture 034: JSON-LD $99 against a page saying $148 → D5.3 = 0 and a structured-data-divergence warn. Since 0.2 that costs 1 point rather than 3; the warn is unchanged, and the warn is the output that matters |
| Q&A phrasing as a checkbox | Earns nothing. question-answer is counted once as present under D3 and never per item, and D7 counts evidence rather than phrasing (§5.9) |
llms.txt as a checkbox | Worth 1 of 100, with the reasoning printed as an opinion (§5.8.3), and a file with no links earns 0 |
| False precision — "you gave me 81 and my competitor 83" | The score is an integer; every check reports its basis and its raw evidence; heuristic weight is capped at 40 and published. ARS is ordinal (§2.6). A property test pins every band boundary at 39/40, 59/60, 74/75, 89/90 |
| Penalising a deliberate opt-out | opt-out is a non-grade, not an F. Training-only opt-outs are strictly neutral (§7.4) |
| Paywalled content | A declared paywall (isAccessibleForFree: false, HTTP 402, WWW-Authenticate) raises an info flag and is not a penalty. Undeclared soft paywalls are undetectable without impersonation, and we will not impersonate. The declared-paywall exemption of the gated part from coverage and position is not implemented in 0.3 either — Appendix D |
| Bot management blocks the probe | unscored / blocked-at-edge, never a low grade. The evidence is itself the finding. A vantage: 'self' run produces an authoritative score from inside the perimeter |
| Vantage manipulation — serve well only to known scanner egress | Not mitigated in 0.3. A vantage-variance flag exists in the vocabulary; enforcement (re-probing from a second egress) is deferred again. Reports should identify the probe vantage (§7.6) |
| Threshold gaming | Exact thresholds are published and every sub-score is reported. There is nothing to reverse-engineer |
| Page-kind shopping | unknown still requires a recognised type for D5.2 and has no coverage advantage; concealing the kind removes the concealed facts from the scored set (§4.4) |
| "Render dependence is a guess" | It is, and it says so. D1.3 is labelled heuristic in the result and in the UI, with its inputs shown; <noscript> earns partial credit; an optional deep render profile is a diagnostic and does not change the score |
| "chars ÷ 4 is not a token count" | Correct. Decoded UTF-8 bytes are the normative measurement; approximate tokens carry ≈ and an "est." label everywhere (§6.9) |
Four that this standard does not mitigate, stated plainly: User-Agent-targeted cloaking, undeclared soft paywalls, egress-targeted vantage manipulation, and fabricated quantities. The first three are undetectable without either impersonating another party's crawler or fetching from infrastructure we do not disclose, and they are outside the current probe design. The fourth is undetectable by anything that does not evaluate truth, which §1.2 says ARS never does.
10. Conformance
10.1 Corpus layout
conformance/NNN-name/
evidence.json the INPUT — an ArsEvidence bundle, response bodies included
expected.json SPEC-DERIVABLE fields only. A second implementation MUST reproduce this
identity.json rulesetHash / corpusHash / evidenceHash — properties of the reference artifact.
A second implementation is explicitly NOT required to match them
README.md one paragraph on what the fixture provesEvery directory is a fixture. A runner MUST discover them by globbing and MUST treat a directory with a missing or unparseable evidence.json, a missing expected.json, or a missing README.md as an error, never as a skip. A corpus that quietly ignores half of itself reports green while proving nothing.
10.2 Why the expected.json / identity.json split exists
This split is what makes independent certification possible at all, and it is the most important structural decision in this section.
expected.json contains only what the spec determines. Every number and every identifier: earned per check and per dimension, the grade, the score, the band label, the page kind and its confidence, the fact set with offsets and corroboration, the flags raised with their severity and basis, the policy decisions, the cost report, and the recommendation arithmetic. A conformant implementation in any language MUST reproduce this file exactly.
It deliberately excludes:
- The three hashes. They live in
identity.json. specVersion, which is pinned by the ruleset rather than derived.- Every human-facing string — check labels, evidence lines, flag messages, recommendation titles and copy. Those are the editors' to reword in a PATCH, and a corpus that pinned them would turn a copy edit into a spec change.
identity.json contains only what is a property of our artifact: rulesetHash, corpusHash, evidenceHash. A second implementation is not required to match these. If certification required matching our hashes, it would require our exact serialisation of our exact object graph, and "conformant" would mean "a transliteration of the reference implementation". It does not. It means: given this evidence, produce these numbers.
Note that rulesetHash and evidenceHash are asserted by the reference implementation's own runner, because they are properties of inputs that do not move. corpusHash is recorded and not asserted per fixture: it is a digest over the whole corpus, so pinning it per fixture while the corpus is open would make every new fixture break every old one.
10.3 The fixture list
Golden pair — the same 91 226-byte HTML body, one variable changed:
| Fixture | Result |
|---|---|
001-pdp-gateway-md | A (93) — a product page with content negotiation installed |
002-pdp-raw-html | D (59) — the same page, raw |
Universality — every page kind, and the byte-reference proof:
010-place-hours-structured (C 71) · 011-place-hours-image-only (C 66) · 012-article-paywalled (C 73) · 013-service-pricing (C 71) · 014-document-openapi (C 73) · 015-unknown-untyped (C 67) · 016-collection (C 70) · 017-faq (C 61) · 018-article-longform (C 70)
Crawler-policy fixtures:
020-robots-deliberate-optout (opt-out, score: null) · 021-robots-training-optout-only and 022-robots-open → identical A (93) · 023-robots-accidental-blanket (B 83, D1.2 = 0) · 024-robots-persistent-500 (unscored) · 025-robots-blocks-our-scanner (unscored) · 026-self-vantage-bypasses-scanner-token (A 93)
Adversarial:
030-accept-conditional-divergence (B 88 + substance-divergence) · 031-keyword-stuffed and 032-density-baseline → identical C (73) · 033-invalid-jsonld (D 59) · 034-jsonld-price-mismatch (C 71) · 035-vary-without-negotiation (C 72) · 036-negotiation-wrong-content-type (C 73) · 037-hidden-text-facts (D 55) · 038-gateway-install-not-flagged-as-cloaking (A 97, zero flags)
Error and edge:
040-blocked-403 · 041-timeout · 042-redirect-chain-3 (C 71) · 043-body-over-cap-truncated · 044-spa-empty-root (F 34) · 045-noscript-fallback (F 35) · 046-paywall-declared (C 65) · 047-non-html-content-type (F 34) · 048-shift-jis-charset (C 64)
Evidence density — added in 0.2, one golden and three adversarial:
| Fixture | Result | What it proves |
|---|---|---|
060-evidence-dense-pdp | A (100) | The top of the scale is reachable. D7 = 8/8 on 12 distinct quantities, 8 labelled pairs and a three-row size table |
061-evidence-thin-prose | B (84) | A page that passes D1–D6 cleanly and states almost nothing quotable. D7 = 1/8 |
062-repeated-quantity-padding | A (90) | Distinctness defeats padding: one price stated eleven times earns 1 of 4 |
063-single-row-table | A (91) | One row is not a comparison. D7.3 = 0 |
Linked Markdown copy — added in 0.3, both built on the 002 page:
| Fixture | Result | What it proves |
|---|---|---|
064-linked-markdown-copy | C (71) | A working linked copy earns partial credit. D2 = 12/18: D2.1 6, D2.2 3, D2.3 0 because the page does not negotiate, D2.4 3 on the linked copy's facts |
065-linked-markdown-copy-broken | D (59) | A link to a copy that returns 404 earns nothing. D2 = 0/18, the same as 002 with no link at all |
Properties:
050-byte-identical-replay (C 72, with volatile headers present) · 051-multi-value-headers (A 94) — both are corpus directories. The remaining named properties — weights summing to 100 with earned ≤ weight; SUBPOINTS agreeing with WEIGHTS per check and WEIGHTS agreeing with the dimension table per dimension (§2.5); heuristic-controlled weight ≤ 40; band boundaries at 39/40, 59/60, 74/75, 89/90; no network; no clock; tokenizer fuzz; profile totality and fact-name typing; the money-parser format list — are properties of the ruleset, the profiles, the parsers or the build rather than of any evidence bundle, and are asserted as unit tests in the reference implementation rather than as fixture directories.
10.4 Running the corpus
In the reference implementation the corpus runs as part of the package test suite. Regeneration is available and uses an explicit regeneration mode: it rewrites expected.json and identity.json from the current implementation and then fails the run, so an update can never be mistaken for a pass. Read the diff before committing it — blessing output you have not checked is how a corpus stops encoding the spec and starts encoding the bug.
10.5 Claiming conformance
An implementation MAY describe itself as ARS 0.3 conformant when all of the following hold:
- For every fixture in the corpus, it produces
expected.jsonexactly, givenevidence.jsonand the ARS 0.3.0 ruleset. - It performs no floating-point arithmetic in the scoring path.
- Its scoring function performs no network access, reads no clock, consumes no randomness, and invokes no language model.
- Every result it emits carries
specVersion,rulesetHash,corpusHashandevidenceHash, and every check carries itsbasis. - Its presentation identifies the measurement scope and makes this methodology available (§0).
- Its sharing and publication flows follow the applicable permissions described in §7.6.
Scorer conformance is established by points 1–4. Presentation guidance does not change the computed score or require a fixed disclaimer.
11. Worked examples
Each of these derives a published fixture end to end from the rules in this document. They exist so an outside team can verify that the corpus encodes this standard rather than merely encoding our code. If any arithmetic below disagrees with the corresponding expected.json, the corpus wins and this document has a defect.
All seven derivations use only: the profile table (§4.2), the weights table (§5.1), the sub-point splits (§5.3–§5.9), the arithmetic in §6, and the fields published in the fixture's own expected.json and evidence.json.
11.1 001-pdp-gateway-md → A (93)
A product page served through a content-negotiation gateway. The agent probe receives 1 490 bytes of Markdown (text/markdown; charset=utf-8, Vary: Accept, Cache-Control: public, max-age=300, s-maxage=600, an ETag, no Link header); the browser probe receives the same page's 91 226 bytes of HTML. robots.txt carries a User-agent: * group disallowing eight administrative paths — none of which matches this URL — a Crawl-delay: 1 line, a second group for an ad crawler, and a Sitemap:. /llms.txt returns 200; /.well-known/ucp returns 404.
Page kind is product at high confidence, from the HTML's JSON-LD @type. Profile: core = title, price, currency, availability; extended = brand, sku, shipping, returns, description; byte reference 8192.
Published facts (all from the negotiated source, all corroborated by the HTML's JSON-LD): title @0, brand @101, currency @133, price @133, availability @161, shipping @186, returns @323, sku @1087. Description is absent from the Markdown.
D1 — 28/28. D1.1: 200 (5) + 0 hops ≤ 3 (3) + both probes ended at the same final URL (3) = 11. D1.2: assistant not disallowed (6) + 0 malformed lines (2) + 0 orphan rules (1) + Sitemap: declared (1) = 10. Crawl-delay is ignored by the parser and does not count as a malformed line (§3.6). D1.3: the structural representation is the HTML, which states all 4 core facts → ⌊100 × 4 ÷ 4⌋ = 100 → band 100 → 7.
D2 — 15/18. D2.1: text/markdown is a machine media type, the body is not HTML → 9. D2.2: no Link header on either response and no typed <link rel="alternate"> in the HTML → 0. This fixture omits the alternate declaration. D2.3: Vary: Accept present, and the page negotiates → 3. D2.4: comparable kinds present in both (price, currency, availability, title), values agree → 3.
D3 — 20/20. Core: 4 found, all corroborated → coreUnits = 4 × 4 = 16. Extended: 4 found (brand, sku, shipping, returns), all corroborated → extendedUnits = 4. denominator = 4 × 4 = 16. coveragePct = min(100, round½(100 × 20, 16)) = min(100, ⌊(2000 + 8) ÷ 16⌋) = min(100, 125) = **100**. factsPerKiB100 = ⌊(4 × 100 + 4 × 25) × 1024 ÷ 1490⌋ = ⌊512000 ÷ 1490⌋ = 343 → ≥ 200 → densityPct = 100. D3.1 = ⌊(14 × 100 × 100 + 5000) ÷ 10000⌋ = ⌊145000 ÷ 10000⌋ = **14**. D3.2: ratio = ⌊100 × 1490 ÷ 8192⌋ = 18 → ≤ 100 → 6.
D4 — 10/10. D4.1: first core-fact offset 0 → ≤ 512 → 5. D4.2: window = max(2048, ⌊1490 ÷ 5⌋ = 298) = 2048. Core offsets 0, 133, 133, 161 — all inside → pct = ⌊100 × 4 ÷ 4⌋ = 100 → ≥ 90 → 5.
D5 — 6/6. D5.1: every JSON-LD block in the HTML parsed → 2. D5.2: recognised type (1) + completeness. requiredProperties.product = name, offers.price, offers.priceCurrency, offers.availability; the page's offer is an AggregateOffer carrying lowPrice, so offers.price is absent → 3 of 4 satisfied → round½(2 × 3, 4) = ⌊(6 + 2) ÷ 4⌋ = 2 → 1 + 2 = **3**. This is the resolution loss of §5.7.2 on a real page: 0.1 charged this offer shape a point and 0.2 does not, because 3-of-4 and 4-of-4 both round to 2 out of 2. D5.3: JSON-LD and visible text agree on the compared kinds → 1.
D6 — 7/10. D6.1: canonical present in the HTML (2) + absolute (1) + the Markdown response declares none, so not consistent across probes (0) = 3. D6.2: public, max-age=300, s-maxage=600 is sane (1) + ETag (1) = 2. D6.3: /llms.txt is present with an H1 and Markdown links → 1. D6.4: Sitemap: declared → 1. D6.5: no endpoint declaration anywhere → 0.
D7 — 7/8. D7.1: 8 distinct quantities in the 1 490 bytes of Markdown → the ≥ 6 band → 3. D7.2: 14 labelled Label: value lines — the gateway renders the product's specification block as exactly that shape → ≥ 6 → 2. D7.3: a three-row variant table survives into the Markdown → 4 − 1 = 3 comparable rows → ≥ 3 → 2.
Total: 28 + 15 + 20 + 10 + 6 + 7 + 7 = 93 → A, "Agent-native". No flags.
Four missing points come from the fixture response headers: D2.2’s 3 and D6.1’s 1. This is a fixed example, not a claim about the current gateway release. Changing fixture evidence requires reviewing its expected result and comparability under §2.1.
11.2 002-pdp-raw-html → D (59) — the same page, one variable changed
The controlled experiment. Identical HTML body (91 226 bytes), identical origin, identical robots.txt. The only differences are that the agent probe now receives the HTML too, and the response envelope is the raw theme's: Cache-Control: max-age=0, private, must-revalidate, no ETag, Vary: Accept-Encoding, and /llms.txt returns 404.
Facts published (agent representation = the HTML): title @632 (corroborated), description @726 (uncorroborated), price @1683 (corr.), currency @1736 (corr.), availability @2102 (corr.), brand @2102, sku @2102, returns @31935, shipping @31935 (last four uncorroborated).
D1 — 28/28, unchanged: same status, same hops, same final URLs, same robots.txt, same HTML for D1.3.
D2 — 0/18. D2.1 = 0: the agent asked for Markdown and received HTML. D2.2 = 0: no typed alternate. D2.3 = 0: gated on negotiation — and note Vary: Accept-Encoding would not have earned it in any case. D2.4 = 0: no negotiation and no linked Markdown copy, and with one representation there is nothing to compare. This is the 85-point ceiling of §8 in one column.
D3 — 5/20. Core: 4 found, all corroborated → 16 units. Extended: 5 found, none corroborated → 0 units. denominator = 16. coveragePct = min(100, round½(1600, 16)) = 100. Coverage is perfect; the page states everything. factsPerKiB100 = ⌊(400 + 125) × 1024 ÷ 91226⌋ = ⌊537600 ÷ 91226⌋ = 5 → the ≥ 5 band → densityPct = 30. D3.1 = ⌊(14 × 100 × 30 + 5000) ÷ 10000⌋ = ⌊47000 ÷ 10000⌋ = **4**. D3.2: ratio = ⌊9122600 ÷ 8192⌋ = 1113 → ≤ 1500 → 1.
D4 — 9/10. D4.1: 632 → ≤ 2048 → 4. D4.2: window = max(2048, ⌊91226 ÷ 5⌋ = 18245) = 18245; all four core offsets are inside → 100% → 5.
That D4 scores 9 while D3 scores 5 is the orthogonality this standard requires: the facts that exist are near the front, and position is not asked to re-charge an absence that coverage already charged.
D5 — 6/6, unchanged from 11.1 — same HTML, same AggregateOffer.
D6 — 5/10. Canonical 4 (present, absolute, and identical across probes because both probes received the same document). Cache validators 0: max-age=0 is not greater than zero and there is no validator to revalidate against. llms.txt 0 (404). Sitemap 1. Endpoint 0.
D7 — 6/8. D7.1: 43 distinct quantities — the full theme, with every size, weight, shipping threshold and review date in it → ≥ 12 → 4. D7.2: the Specs accordion states six facts as <li><strong>Capacity:</strong> 28 L</li> and each <li> is one segment (§5.9.2) → 6 → ≥ 6 → 2. D7.3: the theme renders variants as a swatch list rather than a table, so there are 0 comparable rows → 0.
D7 scores 6 here against 7 in §11.1, which is the smallest gap between these two fixtures on any dimension, and that is the correct result. The raw HTML contains more distinct quantities than the Markdown — 43 against 8 — and takes the top D7.1 band for it. Evidence density is a property of what the page says, not of how it is served, so the two representations of one page should look similar here; the one point of difference is the variant table, which exists in the Markdown and not in the theme. Contrast D3, where the same two representations score 20 and 5. That is the separation this dimension was added to create: D3 asks what the caller had to pay to reach the facts, D7 asks whether the facts were worth quoting, and a page can be excellent on one and poor on the other.
Total: 28 + 0 + 5 + 9 + 6 + 5 + 6 = 59 → D, "Partial".
What the pair shows. The markup is not the problem: D1 is 28/28, D5 is 6/6 and D7 is within a point on both. The 34-point difference is entirely that everything a caller needs is buried in 91 KB with no machine representation. Moving the agent from 91 226 bytes to 1 490 turned D3.1 from 4 into 14 and D3.2 from 1 into 6 without changing a single fact on the page — the byte reduction lifted the density band, nothing else. This is also why the same publisher's page can be excellent HTML and a D: ARS scores what the caller received.
11.3 021 vs 022 → identical A (93) — the training-neutrality proof
The page: a product page with a gateway. The agent receives 334 bytes of Markdown with three Link values — a typed alternate, a canonical, and a service-desc typed application/json — plus Vary: Accept, Cache-Control: public, max-age=900, an ETag and a Last-Modified. The browser receives 899 bytes of HTML with a complete Product JSON-LD. /llms.txt is 404.
022's robots.txt is User-agent: * / Allow: / plus a Sitemap:. 021's is that file plus one group naming GPTBot, CCBot, Google-Extended and anthropic-ai with Disallow: /.
Derivation (identical for both fixtures):
- D1.1 5 + 3 + 3 = 11. D1.2: the assistant audience matches
*withAllow: /→ not disallowed (6) + clean parse (2) + no orphans (1) + sitemap (1) = 10. The training group is read and not scored. D1.3: 4 of 4 core facts in the HTML → 7. → D1 = 28. - D2.1 machine media type, non-HTML body → 9. D2.2 typed
alternate→ 3. D2.3Vary: Accepton a page that negotiates → 3. D2.4 compared and agreeing → 3. → D2 = 18. - D3: core 4/4 corroborated (16 units) + 3 corroborated extended (3 units) over
denominator = 16→round½(1900, 16) = ⌊1908 ÷ 16⌋ = 119→ clamped to 100.factsPerKiB100 = ⌊(400 + 75) × 1024 ÷ 334⌋ = ⌊486400 ÷ 334⌋ = 1456→ 100. D3.1 = 14.ratio = ⌊33400 ÷ 8192⌋ = 4→ D3.2 = 6. → D3 = 20. - D4: first offset 0 → 5.
window = max(2048, 66) = 2048; offsets 0, 123, 123, 151 all inside → 100% → 5. → D4 = 10. - D5: all blocks parse → 2.
productrequired properties all satisfied →1 + round½(2 × 4, 4) = 1 + 2 = 3. JSON-LD agrees with the page → 1. → D5 = 6. - D6: canonical present, absolute, and identical on both sides → 4.
max-age=900+ETag→ 2./llms.txt404 → 0. Sitemap → 1.Link rel="service-desc"; type="application/json"→ 2. → D6 = 9. - D7: 2 distinct quantities → the ≥ 1 band → 1. 5 labelled pairs → the ≥ 2 band → 1. No table → 0. → D7 = 2.
Total: 28 + 18 + 20 + 10 + 6 + 9 + 2 = 93 → A, in both fixtures.
The entire difference between the two results is: policy.audiences.training reads disallow via google-extended in 021 and allow via * in 022; policy.trainingOptOut is true and false; and 021 carries one additional info flag, training-opt-out, whose evidence line reads Effect on this score: none. No point moves.
This pair also shows D7 doing the job it was added for. Both fixtures are a 334-byte Markdown response that is otherwise close to perfect — 28/28, 18/18, 20/20, 10/10 — and they lose 6 of their 7 missing points on evidence density, because a 334-byte response states a price, an availability and very little else. Under 0.1 the same page scored 98 and the standard had no way to say "there is almost nothing here to quote". It says it now, and the page is still an A, which is also right: it is genuinely easy for an agent to consume.
11.4 031 vs 032 → identical C (73) — the stuffing proof
Two stores selling the same kettle from the same theme. The origins are deliberately the same length, so the two HTML bodies differ in exactly one thing: 031 appends a <section class="seo"> carrying keyword spam, a fabricated spec sheet (Material:, Capacity:, Warranty:, Colour:) and a comma-separated keyword tail. 031 is 1 686 bytes; 032 is 995 bytes — 69% larger.
Both serve HTML to both probes, carry a Link: rel="canonical", Cache-Control: public, max-age=600 and an ETag, have a robots.txt with a Sitemap:, and return 404 for /llms.txt and /.well-known/ucp.
Both publish the identical fact set: title @63 (corroborated), description @199 (corr.), availability @334 (corr.), brand @334, currency @334 (corr.), price @334 (corr.), sku @334, returns @788, shipping @788.
Where the two derivations differ, and where they do not:
- Coverage: core 4/4 corroborated → 16 units; extended 5 found, 1 corroborated → 1 unit;
denominator = 16→round½(1700, 16) = ⌊1708 ÷ 16⌋ = 106→ clamped to 100 in both.MaterialandWarrantyare not facts in the vocabulary at all, so they never enter the numerator; and the four core facts were already corroborated, so nothing could have moved this number upward even if the fabricated block had corroborated something real. - Density:
031= ⌊(400 + 125) × 1024 ÷ 1686⌋ = ⌊537600 ÷ 1686⌋ = 318.032= ⌊537600 ÷ 995⌋ = 540. Both are above the 200 threshold, so both takedensityPct = 100. This is saturation doing its job: the extra 691 bytes moved the density number by 222 hundredths and moved the band by nothing. - D3.1 =
⌊(14 × 100 × 100 + 5000) ÷ 10000⌋ = **14**in both. - D3.2:
031ratio = ⌊168600 ÷ 8192⌋ = 20;032ratio = ⌊99500 ÷ 8192⌋ = 12. Both ≤ 100 → 6 in both. - D4.1: first core offset 63 in both → 5. D4.2:
window = 2048in both (⌊1686 ÷ 5⌋ = 337,⌊995 ÷ 5⌋ = 199, both below the 2048 floor); all core offsets inside → 5.
- D7, the dimension 0.2 added and the only one where the two pages are not identical by construction:
031states 5 distinct quantities to032's 3, and031's fabricated spec sheet contributes 1 labelled pair to032's 0. Both counts land in the same band on both checks — D7.1's≥ 3band pays 2 and D7.2's≥ 2band is not reached by 1 — so D7 = 2 in both, and no comparable rows in either.
The rest is identical by construction: D1 = 28, D2 = 0 (no negotiation), D5 = 6, D6 = 7 (canonical 4 + cache 2 + llms 0 + sitemap 1 + endpoint 0).
Total: 28 + 0 + 20 + 10 + 6 + 7 + 2 = 73 → C, in both fixtures, across all twenty-two per-check values, with identical flags and identical recommendations.
Three observations; the third is new in 0.2.
First, what stuffing does change is the cost report: 031's HTML bytes and approximate token estimate are 69% higher, and ARS reports that without pretending the extra bytes were information. Second, this pair does not prove that stuffing is penalised, and it should not: a page that grows far enough falls through the density band on its own, which is what 002 shows at 91 KB.
Third — the tie is narrower than it was, and it is now arithmetic rather than structural. In 0.1 the equality held because every mechanism that could have paid for the fabricated block was capped or saturated: coverage is capped at the profile, density saturates, and Material and Warranty are not facts in the vocabulary at all. D7 has no such cap. It counts quantity tokens wherever they appear, and a fabricated spec sheet contains real quantity tokens. 031 and 032 still score identically because 5 and 3 fall in one band and 1 and 0 fall in another; two more distinct quantities in the fabricated block would have earned 031 a point. This is recorded as an open issue (Appendix D, D-13) rather than presented as a proof that survived the new dimension unchanged, because it did not.
11.5 018-article-longform → C (70) — the byte reference, and where it stops working
A ~2 400-word magazine feature: 14 284 bytes of HTML, no content negotiation, a single <link rel="alternate" type="application/rss+xml">, Cache-Control: public, max-age=600, an ETag and a Last-Modified, a robots.txt with a Sitemap:, and a /llms.txt that returns 200.
Page kind article at high confidence. Profile: core = title, author, published; extended = updated, section, description; byte reference 40 960.
Facts: title @66, description @131, author @307, published @349, updated @411, section @472 — all six corroborated.
- D1 = 28. 11 + 10 + 7 (all three core facts are in the HTML).
- D2 = 3. D2.1 = 0 (HTML to both probes), D2.2 = 3 (a typed alternate), D2.3 = 0 (gated), D2.4 = 0 (gated).
- D3 = 13. Core 3/3 corroborated → 12 units; extended 3/3 corroborated → 3 units;
denominator = 4 × 3 = 12→round½(1500, 12) = ⌊1506 ÷ 12⌋ = 125→ clamped to 100.factsPerKiB100 = ⌊(300 + 75) × 1024 ÷ 14284⌋ = ⌊384000 ÷ 14284⌋ = 26→ the 20–49 band →densityPct = 50. D3.1= ⌊(14 × 100 × 50 + 5000) ÷ 10000⌋ = ⌊75000 ÷ 10000⌋ = **7**. D3.2:ratio = ⌊1428400 ÷ 40960⌋ = 34→ ≤ 100 → 6. - D4 = 10. First offset 66 → 5.
window = max(2048, ⌊14284 ÷ 5⌋ = 2856) = 2856; offsets 66, 307, 349 all inside → 100% → 5. - D5 = 6. All blocks parse (2);
requiredProperties.article=headline,author,datePublished, all present →1 + 2 = 3; agreement → 1. - D6 = 10. Canonical 4 + cache 2 + llms.txt 1 + sitemap 1 + the RSS alternate as a declared endpoint 2.
- D7 = 0. Zero distinct quantities, zero labelled pairs, zero comparable rows. A 2 400-word magazine feature that names no number, no date in ISO form, no measurement.
Total: 28 + 3 + 13 + 10 + 6 + 10 + 0 = 70 → C, "Readable with effort".
The claim this fixture is for, and what 0.2 did to it. 14 284 bytes of prose scores 70, two points below 050-byte-identical-replay — a 991-byte product page with flawless JSON-LD and a perfect 20/20 on fact coverage, which scores 72. Under 0.1 this article scored three points above that fixture, and the sentence here read "long-form content is not structurally capped below short content". That ordering reversed in 0.2, and the reversal is D7, not the byte reference. The mechanism the fixture was written to demonstrate is intact and still demonstrable: D3.2 charges a page against what its kind reasonably costs, so this article earns 6/6 on context cost where the same bytes scored against product's 8 KiB reference would be ratio = ⌊1428400 ÷ 8192⌋ = 174 → 5/6, and the gap widens fast — an article stays at 6/6 up to 40 960 bytes, where a product page has already fallen to 3/6; at 002's 91 226 bytes a product scores 1/6 where an article scores 3/6. Absolute byte bands, the design defect the byte reference replaced, would have put a 40 KB article and a 40 KB product page in the same place.
What changed is that a second mechanism now also applies, and it is not kind-relative: an article that states no quantities loses all 8 points of D7, and a product page that states its price and sizes does not. The editors' position is that this is correct rather than an accident — a feature that cites no figure, date or measurement genuinely has less for an assistant to lift — but it is a real interaction between two dimensions, and the honest way to describe D7's effect on long-form content is that it is a second penalty on top of the D3.1 one described below, not that the byte reference stopped working. Both are in Appendix D.
Coverage interaction. The byte reference fixes D3.2. It does not reach D3.1, and D3.1 is where a residual long-form penalty lives. Density is (coreFound × 100 + extendedFound × 25) × 1024 ÷ bytes — an absolute facts-per-KiB measure with no page-kind term in it — so six facts across 14 284 bytes lands in the 20–49 band at 50%, and D3.1 pays 7 of 14 at 100% coverage. The band edge is arithmetic, not judgement: with six facts the 20-hundredths threshold sits at 19 200 bytes, so the same piece at 4 000 words drops a further three points, and stays there until 76 800 bytes. That is filed as an open issue (Appendix D), not papered over.
One further note in the same spirit: the single RSS <link rel="alternate"> earns 3 points on D2.2 and 2 on D6.5, even though a section feed is arguably not an alternate representation of this page. Every blog on a common CMS ships that tag.
11.6 020-robots-deliberate-optout → no number at all
Included because a standard that only derives numbers has not shown its most important behaviour.
The page is the same excellent product page that scores 93 (A) in 022. Its robots.txt names four assistant tokens in their own group and disallows them; the file parses with zero malformed lines and zero orphan rules.
The result is not 93, not 40, and specifically not an F:
outcome = { kind: 'opt-out', audience: 'assistant', wellFormed: true }
score = null
grade = null
bandLabel = null
recommendations = []
flags = [ { id: 'assistant-opt-out', severity: 'info', basis: 'measured' } ]All seven dimensions are present with their weights and every check reports earned: 0 with a "Not evaluated" reason — the shape is stable, and no sub-score leaks that a ranking could be reconstructed from.
The flag carries the matched group and the matched rule, because a publisher who disagrees with the verdict is entitled to see the line of their own file that produced it. The group reported is the longest matching token, which is RFC 9309 specificity and not a preference.
Two details make this opt-out deliberate rather than accidental, and both are load-bearing: the disallow comes from a group that names assistant tokens, and the file parses cleanly. A blanket User-agent: * / Disallow: / is fixture 023 instead — which is scored, at 83, with D1.2 at 0 and a robots-contradiction warn. Consent has to be specific in both directions.
11.7 064 vs 065 → C (71) and D (59) — the linked Markdown copy
Both fixtures are the 002 page with one line added to its <head>: <link rel="alternate" type="text/markdown" href="/products/alpine-trail-pack-28l.md">. The HTML is now 91 311 bytes. Both probes still receive that HTML with Vary: Accept-Encoding, so the page does not negotiate.
Request 3b. The agent probe got a 200 that is not a machine copy. The browser HTML declares a Markdown alternate, which resolves to https://basecamp-supply.example/products/alpine-trail-pack-28l.md: same origin, and not the page's own URL. The probe fetches it once with the agent probe's headers.
- In
064it returns 200,text/markdown; charset=utf-8, and a 1 490-byte body that is not HTML. The body is byte-identical to the Markdown001receives on negotiation. The copy loaded. - In
065it returns 404 with a 9-bytetext/plainbody. The copy is broken.
D2 in 064 — 12/18. D2.1: no negotiation, and the linked copy loaded → 6. D2.2: a typed <link rel="alternate"> in the HTML, and the copy is not broken → 3. D2.3: the page does not negotiate → 0. The copy's own response sends Vary: Accept, but D2.3 reads the page's responses, and those send Vary: Accept-Encoding. D2.4: the linked copy's facts against the HTML's. Price, currency, availability and title appear in both, and each value the copy would publish is among the values the HTML states → 3.
D2 in 065 — 0/18. D2.1 = 0: no negotiation, and the copy is broken. D2.2 = 0: the declared copy is broken (§5.4.2). D2.3 = 0 and D2.4 = 0: no negotiation and no working copy.
Everything else is 002's result, in both fixtures. The extra 85 bytes move no band. D1 = 28. D3 = 5: factsPerKiB100 = ⌊537600 ÷ 91311⌋ = 5 → densityPct = 30 → D3.1 = 4, and ratio = ⌊9131100 ÷ 8192⌋ = 1114 → D3.2 = 1. D4 = 9: first core fact at 632 → 4, and all four core offsets fall inside window = ⌊91311 ÷ 5⌋ = 18262 → 5. D5 = 6, D6 = 5, D7 = 6.
Totals. 064: 28 + 12 + 5 + 9 + 6 + 5 + 6 = 71 → C. 065: 28 + 0 + 5 + 9 + 6 + 5 + 6 = 59 → D, exactly 002's score.
What the pair shows. A working linked copy is worth 12 points here, and it does not reach D3, because a caller that does not follow the link still reads 91 KB (§4.6.2). Sending the same 1 490 bytes from the page's own address is what 001 does, and D3 there is 20 of 20. A link that leads nowhere earns nothing: 065 scores exactly what 002 scores without the link.
12. Score interpretation and version comparisons
The band edges (90 / 75 / 60 / 40) describe the published scoring allocation. They are not calibrated against a distribution of real pages. Conformance fixtures verify repeatable arithmetic; a difference in points identifies different rule results, not a measured difference in an AI system’s performance.
Consequently:
- Scores produced under ARS 0.3 are declared non-comparable to a future ARS 1.0 unless 1.0 is calibrated first, over a few hundred real pages across all eight kinds, before its bands are frozen.
- Any published time series that spans a MINOR boundary MUST either be re-scored from retained evidence or be labelled as version-segmented and non-comparable. Silently plotting across a MINOR is a misrepresentation of this standard.
- Implementations SHOULD present the band and withhold the integer where the audience is likely to read the integer as precise.
13. What changed in 0.3, and why
0.3 is a MINOR under §2.1. Two rules widened, so some pages score differently, and every result carries the new rulesetHash. Under §2.1 and §12.2, scores are not comparable across the boundary: every published surface re-scores in a batch, and a time series that crosses it MUST be re-scored from retained evidence or labelled as version-segmented. The weights and checks did not change, every fixture carried over from 0.2 keeps its expected.json, and two fixtures are new.
13.1 More action words count as the main action
Pages using common labels such as "Shop now", "Install", "Try it free" or "Get directions" were told their main action link was missing. primary-action-url is a core fact for service and unknown and an extended fact for place and document, so each missed label cost coverage. 0.3 adds the labels those pages use:
call · check availability · check out · create account · create an account · enquire · enrol · free trial · get a demo · get directions · get in touch · get the app · get tickets · hire · inquire · install · message us · order online · pre-order · preorder · rent · shop · shop now · talk to sales · talk to us · text us · try · upgrade
The matching rule is unchanged: a label matches a phrase when it equals the phrase or begins with the phrase followed by a space (Appendix C). So shop matches "Shop now" and never "Workshop", and try matches "Try it free". The lexicon is still pinned data, and it is part of the hashed ruleset, which is why rulesetHash changed.
13.2 A linked Markdown copy is checked
Some sites publish a Markdown copy at its own address, such as /products/kettle.md, and link to it from the page instead of negotiating. Under 0.2 that link earned D2.2's 3 points as a declaration, and nothing checked whether the copy existed. 0.3 fetches it once (request 3b, §3.1) and scores what comes back:
- A working copy earns 6 of D2.1's 9. It reaches agents that look for the link, and publishing one is real work. It reaches fewer agents than negotiation, and each of them needs a second request, so it earns less than a copy served from the page's own address.
- A working copy is checked for substance parity. D2.4 compares its facts with the page's under the same asymmetric rule (§5.4.4). A linked copy has no confirming probe, so a difference costs the points and raises no flag.
- A broken link stops earning. A copy that fails to load, returns a non-2xx status or returns HTML scores 0 on D2.1 and zeroes D2.2 (§5.4.2). An agent that follows that link finds nothing usable. A copy
robots.txtkeeps us from fetching is unchecked rather than broken: we did not see it, so we neither credit nor deduct. - The Vary gate is stated precisely. D2.3 is gated on negotiation itself. 0.2 wrote "D2.1 earned points", which meant the same thing while D2.1 was all or nothing. A linked copy does not make the page's own address vary by
Accept, so it does not open D2.3.
The recommendation to serve a machine representation follows the same split. When D2.1 is 0 it unlocks D2.3 and D2.4, as before. When D2.1 has the partial 6, D2.4 is already scoreable, so it unlocks D2.3 only.
The 6 lives in SUBPOINTS (§2.5), and fixtures 064 and 065 pin both outcomes (§10.3, §11.7).
13.3 Probe and evidence
The probe identifies itself as rebilder-ars/0.3. The refusal token is still the bare rebilder-ars (§3.1, §7.5). The evidence bundle gains one optional record, markdownAlternate, omitted when request 3b is not taken, so a bundle for a page with no linked copy keeps its 0.2 evidenceHash (§2.4.2, §3.8). The corpus marker is ars-0.3-corpus-unfrozen, and the corpus has 44 fixture directories.
13.4 What did not change
The weights and dimension weights, the 22 checks and their ids, the measured/heuristic split (63/37), the band edges (90/75/60/40) and labels, the page kinds and their profiles, the byte references, classification, text extraction, the money parser, the fact vocabulary, the crawler policy and its pinned tokens, the non-grade vocabulary and gate order, and the determinism guarantee. A page that declares no Markdown alternate and uses none of the new action words earns the same score under 0.2 and 0.3, and its result carries the 0.3 hashes.
The ARS 0.2 document keeps the earlier history, including what changed from 0.1 to 0.2 and why.
Appendix A — Ruleset constants
Everything a second implementation needs, in one place. Editing any value here is a spec version change, not a code change.
Weights — 22 checks summing to 100. See the table in §5.1.
Dimension weights — retrievability 28 · machine-representation 18 · fact-coverage 20 · fact-position 10 · structured-data 6 · contract-discovery 10 · evidence-density 8. Each equals the sum of its checks' weights; both are published, and a property test asserts they agree and total 100.
Totals — measuredWeight 63 · heuristicWeight 37 · heuristic ceiling 40.
Threshold tables (flat [threshold, value] pairs):
| Key | Encoding | Value |
|---|---|---|
fact-coverage.fact-units | [corrCore, uncorrCore, corrExt, uncorrExt] | [4, 2, 1, 0] |
fact-coverage.core-denominator-multiplier | scalar | [4] |
fact-coverage.density | AT-LEAST | [200,100, 100,85, 50,70, 20,50, 5,30, 0,15] |
fact-coverage.context-cost | AT-MOST | [100,6, 200,5, 500,3, 1500,1] |
fact-position.first-core-fact-offset | AT-MOST | [512,5, 2048,4, 8192,2, 32768,1] |
fact-position.front-window | AT-LEAST | [90,5, 70,4, 50,3, 25,1] |
fact-position.front-window-size | [floor, divisor] | [2048, 5] |
evidence-density.quantities | AT-LEAST | [12,4, 6,3, 3,2, 1,1] |
evidence-density.definitions | AT-LEAST | [6,2, 2,1] |
evidence-density.comparisons | AT-LEAST | [3,2, 2,1] |
cost.approx-bytes-per-token | scalar, heuristic | [4] |
Sub-point splits (not members of the hashed ruleset object — see §2.5):
| Check | Split |
|---|---|
| D1.1 | status 2xx 5 · within redirect limit 3 · same final URL 3 |
| D1.2 | assistant allowed 6 · parses clean 2 · no orphan rules 1 · sitemap declared 1 |
| D1.3 | AT-LEAST band [100,7, 75,5, 50,3, 25,1] · <noscript> floor 1 |
| D2.1 | negotiation 9 · linked Markdown copy 6 · otherwise 0 |
| D2.2 / D2.3 / D2.4 | all-or-nothing: 3 · 3 · 3 |
| D5.1 | clean 2 · partial 1 |
| D5.2 | recognised type 1 · completeness 2 |
| D5.3 | all-or-nothing 1 |
| D6.1 | present 2 · absolute 1 · consistent across probes 1 |
| D6.2 | sane Cache-Control 1 · validator 1 |
| D6.3 | spec-shaped 1 · present-but-not-spec-shaped 0 |
| D6.4 / D6.5 | all-or-nothing: 1 · 2 |
| D7.1 / D7.2 / D7.3 | bands only; see the threshold table above |
Pinned unit list for D7.1 (§5.9.1). Matched case-insensitively, immediately after a number with at most one intervening space, and terminated by a word boundary:
kg · g · lb · lbs · oz · mm · cm · m · km · mi · in · ft · ml · l · kb · mb · gb · tb · hz · w · kw · v · °c · °f · hour/hours · hr/hrs · minute/minutes · min/mins · second/seconds · sec/secs · day/days · week/weeks · month/months · year/years
Pinned block tags for D7.2 (§5.9.2), the elements a labelled fact is attributed to, innermost first: li · dt · dd · p · td · th · div. Inline elements are deliberately absent: a <span> in the set would split <strong>Capacity:</strong> 28 L into a label with no value and a value with no label.
Probe limits, hashed with everything else — maxBodyBytes 2 097 152 · maxRedirects 3 · timeoutMs 5 000. They are inputs to the score, so they are inputs to the hash: a 3 MiB cap turns a truncated-evidence non-grade into an F, and a 10 s timeout scores pages that a 5 s timeout reports as unreachable.
Bands — see §6.7. Audience tokens — see §7.2. Profiles — see §4.2. Action lexicon — see Appendix C.
Appendix B — requiredProperties, per page kind (D5.2)
Provenance in §5.7.2. A dot is a property path from the scored node; traversal enters array members.
| Page kind | Required properties |
|---|---|
product | name, offers.price, offers.priceCurrency, offers.availability |
collection | name, numberOfItems, itemListElement |
article | headline, author, datePublished |
place | name, address, openingHours, telephone |
service | name, offers.price, offers.priceCurrency, potentialAction.target |
faq | name, mainEntity, mainEntity.acceptedAnswer |
document | name, dateModified, publisher |
unknown | name |
unknown requires only name, deliberately: §4.4 scores unknown against "any recognised schema.org type is present", which requires having a type but cannot require properties of a kind we did not identify.
Appendix C — The action lexicon
Matched case-insensitively against normalised anchor text, <form> context, or potentialAction.name, with whitespace collapsed. A label matches a phrase when it equals the phrase or begins with the phrase followed by a space, so shop matches "Shop now" and never "Workshop". Pinned; sorted for readability; order is not significant. ARS 0.3 added 28 phrases and removed none (§13.1).
add to bag · add to basket · add to cart · apply · apply now · book · book now · buy · buy now · call · call now · check availability · check out · checkout · contact · contact us · create account · create an account · donate · download · email us · enquire · enrol · enroll · free trial · get a demo · get a quote · get directions · get in touch · get quote · get started · get the app · get tickets · hire · inquire · install · join · make an appointment · message us · order · order now · order online · pre-order · preorder · purchase · register · rent · request · request a demo · request a quote · reserve · schedule · shop · shop now · sign up · signup · start · start free trial · subscribe · talk to sales · talk to us · text us · try · upgrade
Appendix D — Reference implementation notes for 0.3
These implementation notes identify areas for future ruleset revisions. Each links to a fixture or reference implementation behavior.
| # | Issue | Where it is visible |
|---|---|---|
| D-1 | corpusHash identifies the corpus version. It is the SHA-256 of ars-0.3-corpus-unfrozen, not a digest of the fixture contents | §2.4.3 |
| D-2 | SUBPOINTS, the classification tables and the extraction tables are not covered by rulesetHash. They are pinned by the corpus instead | §2.5 |
| D-3 | D5.2 walks JSON-LD only. Microdata and RDFa satisfy D5.1 but are not traversed for required properties, so a microdata-only page cannot exceed 2/6 on D5 | §5.7.2 |
| D-4 | A declared paywall is flagged, not exempted. The rule that the gated part is exempt from coverage and position needs hasPart + cssSelector support and is not implemented | 046-paywall-declared |
| D-5 | The ≥30 s delay on the parity-confirmation probe is not verifiable by the scorer. The evidence bundle has no per-probe timestamp, so the delay is a contract on the probe | 030-accept-conditional-divergence |
| D-6 | "Pricing on request" satisfies D3.1 and cannot satisfy D5.2. The service row of requiredProperties asks for offers.price, so the same page is rewarded for candour by one check and charged two points for it by another. Closing this means editing the table, which is a MINOR | 013-service-pricing |
| D-7 | A nested Brand node's name can win the title tie-break and produce a false structured-data-divergence. | 038, and the same tie-break in 042, 046, 048 |
| D-8 | The money parser does not know the fullwidth yen sign (U+FFE5), which is the only yen sign a Shift_JIS page can contain | 048-shift-jis-charset |
| D-9 | D3.1's density term has no page-kind component, so long-form content still pays a coverage penalty that the byte reference does not reach | §11.5 |
| D-10 | Vantage variance is not enforced. A site that serves well only to known scanner egress is not detected in 0.3 | §9 |
| D-11 | Site-level scoring is informative only and appears on no published surface | §1.1 |
| D-12 | A truncated capture returns the non-grade truncated-evidence. | §3.3, 043-body-over-cap-truncated |
| D-13 | The stuffing tie in 031/032 is now arithmetic rather than structural. D7.1 counts quantity tokens with no cap and no profile, so a fabricated spec sheet contributes real ones; the two fixtures agree because 5 and 3 fall in one band, not because the dimension is immune. Two more distinct fabricated quantities would separate them | §11.4 |
| D-14 | D5.2's completeness has 2 points of resolution, so 3-of-4 and 4-of-4 required properties score identically. 0.1 distinguished them because it had 5 points to spend | §5.7.2, 001-pdp-gateway-md |
| D-15 | D7 has no page-kind term at all, by design (§6.6a), which means a page kind that genuinely states few quantities — an article, most document pages — pays the whole dimension. | §11.5, 018-article-longform |
| D-16 | readLabelledLines in the extractor still reads countedText as one line, so the labelled-fact path that D7.2 fixed for scoring is still line-blind for fact extraction on HTML. Fixing it moves fact sets across the corpus and is a MINOR of its own | §4.7 |
| D-17 | A linked Markdown copy that robots.txt keeps rebilder-ars from fetching is unchecked. It earns no D2.1 credit, because the probe never saw it, and D2.2 keeps its declaration credit, because a refusal is not evidence the copy is missing. A site can therefore keep D2.2 for a link it blocks us from verifying | §3.1, §5.4.2 |
Appendix E — Colophon
ARS 0.3 is published by Rebilder. Every threshold in this document is a number rather than a description, and the ruleset, the weights and the whole conformance corpus are published, so a score can be checked rather than believed.
Corrections, disagreements with a weight, and independent implementations are all welcome, and the corpus is the place to argue: a fixture that this standard scores wrongly is a better argument than a paragraph, and it is one we have to answer.
