GEO/AEO house specification
This document is the contract. Every requirement below is testable, is tied
to the probe that enforces it, and names the authoritative source that governs
it. Untestable strategy belongs in geo-ai-search-strategy-2026.md, not here.
Why versioned. Before this existed, the probe set was frozen at whatever
mattered on 2026-07-11 and nothing noticed when the outside world moved. The
watcher (/api/cron/geo-spec-watch + scripts/geo/spec-triage.ts) tracks the
sources listed in src/lib/geo/spec-sources.ts and raises a checklist item when
one of them genuinely changes. spec_version in the frontmatter is bumped when
a requirement here changes; PROBE_SPEC_VERSION in src/lib/geo/probes.ts is
bumped alongside it and stamped on every probe run, so a score change can be
attributed to a spec change rather than a site regression.
How a change gets in. Only through evidence. Layer 1 of the watcher proves that bytes at a watched URL changed and produces a diff. Layer 2 lets a model interpret that diff, and discards any verdict whose quoted evidence is not literally a substring of it. A model is never asked whether the spec changed — it would answer.
R-01 — AI crawlers must be able to reach us, and be welcomed by name
Requirement. robots.txt must not block any answer-engine user-agent from
the site root, and should name them explicitly with their own Allow group
rather than relying on the * fallback. A Content-Signal line should state
search / ai-train / ai-retrieval intent.
Why the distinction matters. Merely not blocking a crawler is a weak, ambiguous signal. An explicit named group is an affirmative statement of intent. As of 2026-07-28, comparelocal.ca and docs.churchwiseai.com are the only portfolio properties doing this; the rest merely fail to block.
Enforced by ai_crawlers (src/lib/geo/probes.ts), agent list in
src/lib/geo/ai-crawlers.ts.
Governed by openai-bots, anthropic-crawlers, perplexity-crawlers,
google-extended, commoncrawl-ccbot.
Reference implementation: C:\dev\comparelocal\src\app\robots.txt\route.ts.
R-02 — Every property serves an llms.txt
Requirement. /llms.txt returns 200 with a non-empty body on every live
property, including host-aliased ones and Pro Website hosts.
Enforced by llms_txt.
Governed by llmstxt-spec.
R-03 — llms.txt must conform to the llmstxt.org shape
Requirement. An H1 title on line 1, a > one-sentence summary blockquote,
at least one ## section, and markdown link list items.
Why. A 200 response full of unstructured text passes R-02 while being useless to the thing it exists for. Shape is the difference between a file and a map.
Enforced by llms_txt_shape.
Governed by llmstxt-spec.
R-04 — Content must be answer-shaped and technically reachable
Requirement. Pages open with a definitional answer, carry a quotable stat
block or comparison, and include an FAQ block where the query shape warrants it.
Garbage URLs must return real HTTP 404s (Next.js 16 streaming soft-404s return
200 and poison crawl budget). Homepage <title> must be present and sane.
Enforced by hard_404, homepage_title; authoring standard in
.claude/skills/geo-article-writer/SKILL.md.
Governed by google-ai-features, google-search-essentials.
R-05 — Content must be people-first and honestly sourced
Requirement. Every factual or statistical claim carries a named, real, verifiable source and a stated limitation. No "studies show". No citing a real source for a figure it does not contain.
Why the limitation clause. It is the founder's own strongest voice signal ("treat 57 percent as an upper bound"), and independently it is the most effective structural defence against fabricated statistics in generated drafts.
Enforced by the article gate (planned, slice 6) and human review.
Governed by google-helpful-content.
R-06 — Structured data is a contract, not a smoke test
Requirement. Each property declares the JSON-LD @type values it is
expected to have, in geo_properties.expectations.jsonld_types_required, and
meets a minimum block count. Presence of some JSON-LD is not sufficient.
Why. A count > 0 check passes while a refactor silently drops the
Organization block.
Enforced by schema_coverage, jsonld.
Governed by schemaorg-releases.
R-07 — New and changed pages are actively submitted
Requirement. Live hosts are enrolled in the IndexNow cron and serve the key
file at the host root. Sitemaps carry lastmod. Sitemap index files are
followed one level down when counting URLs.
Enforced by indexnow, sitemap_fresh.
Governed by indexnow-docs.
R-08 — Per-property content assertions
Requirement. Any content bug that is machine-checkable and could silently
recur becomes an assertion in geo_properties.expectations, not just a fixed
line of code.
Origin. On 2026-07-28 every {slug}.john316.church Pro Website was found
serving another tenant's realtor llms.txt, including four paying church
customers. The code fix corrected it once; the expectation is what makes a
regression fail loudly.
Enforced by expected_content. See
.claude/skills/geo-wise/references/property-expectations.md.
Changing this document
- A watcher delta produces an agent-lane checklist item with verbatim evidence.
- A human reads the source and decides whether a rule actually changes.
- Rule edits here,
spec_versionbumped,PROBE_SPEC_VERSIONbumped, probe changes shipped by branch + PR.
Nothing in that chain is automatic. The watcher's job is to make sure the question gets asked, not to answer it.