Structured data on every route, and a dateModified derived from git - #76
Merged
Conversation
…hat cannot lie A GEO audit on 2026-09-04 scored the property at 51/100 on the root page and 43.25/100 across all eight routes, both in the tool's "foundation" band. The measured categories tell a clear story: meta was perfect at 14/14 on every page, robots allowed all 27 AI crawlers, and the raw HTML carried 777 words and 19 headings without JavaScript. What was missing was structured data (2.25/16), llms.txt (0/18) and AI discovery endpoints (0/6). Structured data, on every page rather than only the root. The previous convention emitted Organization on the home page alone, reasoning that it describes the property rather than the page and that repeating it five times states one fact five times. That is right about authorship and wrong about retrieval: an answer engine parses each URL independently and cannot carry the root page's entity across to /method. Six of eight routes emitted nothing. The answer is a per-page @graph with stable @ids, not copy-paste. The Organization is one entity referenced from every route, which satisfies the original objection properly -- one fact, stated once, addressed by identity -- while giving each page its own primary entity. Pages that support more say more: ContactPage on /scope, CollectionPage plus an ItemList roster on /experts, ProfilePage plus Person on a profile, FAQPage on /engagements built from the copy deck's existing questions. makesOffer rides only on the two pages that actually display the rate card. dateModified is derived from git rather than hand-kept. A date written by hand is a claim nobody verifies, and a page that says it was updated last week when it was not is the kind of small untruth this property avoids everywhere else. scripts/content-modified.mjs records the last commit touching each route's sources before the build; a route with no recorded date emits no dateModified at all rather than a fabricated one. The deploy workflow now checks out full history, because git log on a shallow clone reports the shallow boundary and every page would claim the date of the most recent push. llms.txt, /ai/summary.json and /ai/faq.json are generated from the copy deck for the same reason knowsAbout is: a hand-kept second copy of the site's structure goes stale the first time a route changes. /.well-known/ai.txt states the crawling posture explicitly and disallows /api/, which carries live matter detail written before a conflict check has run. Two things deliberately not done. No SearchAction in the WebSite node -- the property has no search endpoint and declaring one that 404s is a claim the site cannot honour. No areaServed, still: the copy nowhere states which jurisdictions this practice takes, and that remains a copy decision the markup follows rather than leads. sameAs is wired but empty, on both Organization and Person, and emitted only when populated. It is the Knowledge Graph disambiguation signal and the audit scored Social Trust 0/5 and Academic Trust 0/5 on its absence, but every URL has to be a profile that exists and that the practice controls -- a sameAs pointing at something that is not the same entity merges two entities in the index. The fields are waiting on real URLs. astro check goes from 4 errors to 2; the remaining two are pre-existing parser complaints in scope.astro. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EDibtmcGWcytbgVpb2su7F
This was referenced Sep 4, 2026
Both verified before publishing. The ORCID record resolves to Rob Jacques through the public API; the LinkedIn vanity URL is the one the profile copy was written from. A sameAs pointing at something that is not the same entity merges two entities in the index, so neither went in unchecked. ORCID is the resolvable https:// form rather than the bare identifier, because sameAs takes URLs. No Google Scholar entry. A Scholar profile is a researcher page claimed at scholar.google.com, not a blog or a product, and there is no reason to create one around a single conference paper from 2000. The Organization sameAs is still empty. It needs profiles the practice itself controls -- a company page, a society listing -- and a personal LinkedIn is not one of those. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EDibtmcGWcytbgVpb2su7F
This was referenced Sep 4, 2026
This was referenced Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A GEO audit on 2026-09-04 scored the property at 51/100 on the root page and 43.25/100 across all eight routes, both in the tool's
foundationband.The measured categories made the diagnosis easy. Meta was 14/14 on every page. Robots allowed all 27 AI crawlers. Raw HTML carried 777 words and 19 headings without JavaScript, so the prerender is doing its job. What was missing:
Six of eight routes emitted no structured data at all.
Structured data on every page
The previous convention emitted
Organizationon the home page only, reasoning that it describes the property rather than the page, so repeating it on five routes states one fact five times. That is right about authorship and wrong about retrieval — an answer engine parses each URL independently and has no mechanism to carry the root page's entity across to/method.The fix is a per-page
@graphwith stable@ids rather than copy-paste. The Organization is one entity referenced from every route, which answers the original objection properly: one fact, stated once, addressed by identity. Pages that support more say more./makesOffer/engagements/makesOffer, FAQPage from the deck's existing questions/experts//experts/[slug]/worksForas an@idreference/scope//method/,/matters/,/privacy/makesOfferrides only on the two pages that actually display the rate card.dateModified derived from git
A date written by hand is a claim nobody verifies, and a page that says it was updated last week when it was not is the kind of small untruth this property avoids everywhere else.
scripts/content-modified.mjsrecords the last commit touching each route's sources before the build. A route with no recorded date emits nodateModifiedrather than a fabricated one. The deploy workflow now checks out full history —git logon a shallow clone reports the shallow boundary, so every page would otherwise claim the date of the most recent push.The assumption worth reviewing: a route's inputs are its page file plus the copy deck. The deck is one file, so a copy edit anywhere moves the date on every route that reads it. Coarse, but not false — a change to a page's source inputs is a modification of that page.
AI discovery
llms.txt,/ai/summary.jsonand/ai/faq.jsonare generated from the copy deck for the same reasonknowsAboutis: a hand-kept second copy of the site's structure goes stale the first time a route changes./.well-known/ai.txtstates the crawling posture explicitly and disallows/api/, which carries live matter detail written before a conflict check has run.Deliberately not done
areaServed, still. The copy nowhere states which jurisdictions this practice takes. That remains a copy decision the markup follows rather than leads.geo fix --applywas not used. Its generated FAQPage is in Italian ("Cos'è forensics.rootsystem.com?"), its descriptions carry mojibake, and its SearchAction points at a route that does not exist. The plan was taken; the files were written by hand.Needs your input
sameAsis wired on both Organization and Person, defaults to empty, and is emitted only when populated. The audit scored Social Trust 0/5 and Academic Trust 0/5 on its absence and it is the cheapest E-E-A-T signal available — but every URL has to be a profile that exists and that the practice controls, since asameAspointing at something that is not the same entity merges two entities in the index. LinkedIn, ORCID, Google Scholar. The fields are insrc/copy/landing.ts.Verification
yarn workspace @rootsystem/forensics build— clean, 11 routes prerendered including the three new endpoints.astro check— 4 errors before, 2 after. Both remaining are pre-existing parser complaints inscope.astro.Re-audit after deploy to measure the real score against the tool's 79/100 projection. Note that
llms.txtalone is 18 of those points and the tool's own output calls it "an organizational signal, not a proven ranking factor" — the schema andsameAswork is the part expected to move citations.🤖 Generated with Claude Code
https://claude.ai/code/session_01EDibtmcGWcytbgVpb2su7F