Skip to main content

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

  1. A watcher delta produces an agent-lane checklist item with verbatim evidence.
  2. A human reads the source and decides whether a rule actually changes.
  3. Rule edits here, spec_version bumped, PROBE_SPEC_VERSION bumped, 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.