Repository navigation
The sitemap said all 53 pages changed today, every day - #182
Conversation
c07776c to
4583d60
Compare
Independent verification of this PR's central claim — reproduced, with one correction to the bodyArrived here from #181 under a separate SEO sweep, found this open, and verified rather than duplicated. No competing branch was created and nothing here was pushed. The defect is still live and still on and the deployed file agrees — This branch fixes it, and the numbers reproduce exactly. Built 52 URLs carry a defensible Spot-checked four entries against git rather than against the emitted file, and checked committer and author dates separately, since a rebase resets the former and would have made a stale page look freshly changed:
Committer and author agree throughout, so these are real content changes, not rebase artifacts. The headline negative control, re-run from scratch
The read-back check is load-bearing: it caught the exact state where the hook's own count ( One correction to the PR body
Measured, its source last changed on 2026-09-06 ( State, for whoever picks this up
Prepared with AI assistance; reviewed before submission. |
mkdocs fills <lastmod> from get_build_date(), so every entry carried the day the docs were built. Measured live 2026-09-13: 53 of 53 read 2026-09-13, including a page whose source had not moved since 2026-08-16. Each page is now dated from the commit that last changed its own source, and a page with no such commit carries no element at all. A shallow checkout is refused outright rather than consulted: `git log -1 -- <path>` answers there for every path with the tip's date, which would have reinstated the build date under a new name. The build prints how many of how many pages it could date and names the rest; `sprout site-check` fails on a malformed, impossible or future date, and on a sitemap that dates nothing at all. The live sentinel no longer strips <lastmod> before comparing, so the deployed dates are now compared byte for byte.
4583d60 to
2870bcc
Compare
What was wrong
Measured on the live host, not in the generator. On 2026-09-13
https://sprout.chelseakr.com/sitemap.xmlcarried<lastmod>2026-09-13</lastmod>on all 53 of 53 entries — the day the docs were built. It would have said2026-09-14on all 53 the next morning, whether or not a single page changed.The pages do not agree.
/PUBLICATION-READINESS/'s source had not been touched since 2026-08-16;/ARCHITECTURE/'s since 2026-06-22.<lastmod>is the one element in a sitemap that is a factual claim about the content, and this one was a claim about the CI runner — this repository's own "absence rendered as a value" shape, with "we did not measure this per page" published as today's date.A note on the issue's table: the
2026-05-01it reports as the newest date visible on/ADAPT/is afetch_dateinside an example config snippet, not a date the page states about itself. The honest per-page date is the commit that last changed the page's source, and that is what this uses.What changed
docs_hooks/sitemap_lastmod.pydates each page fromgit log -1 --format=%cs -- <source>, andtheme_overrides/sitemap.xml(a one-filecustom_dir, mkdocs' own template with one element changed) emits<lastmod>only for a page it could date.<lastmod>is optional in the sitemaps protocol, so an omitted element is a valid sitemap that says nothing false, which a stamped-today one is not.The same build now publishes 52 dates across 16 distinct days, 2026-06-22 … 2026-09-07 — and none of them is the build's own date.
/is deliberately left undated.docs-pagescopiesweb-static/public/over the mkdocs output, so the document served at the root is the zero-server reference surface, notdocs/index.md. Dating that URL fromdocs/index.md's history would publish a change date for a document nobody receives — the same defect in different clothes.A shallow checkout is refused rather than consulted.
git log -1 -- <path>does not fail in a depth-1 clone: it answers for every path with the tip commit's date, because relative to no parent that single commit introduces the whole tree. Nothing about the reply is malformed, so asking-then-sanity-checking would have reinstated the build date under a new name. The hook asksgit rev-parse --is-shallow-repositoryfirst, once, and publishes nothing from a checkout that cannot answer. The three jobs that build the docs (ci.yml'sdocs,pages.yml,live-integrity.yml) now check out with full history and say in place why.Two voices, because an omission is honest and silent. The build prints
52 of 53 pages state when they last changedand names every page it could not date.sprout site-check— a merge gate viamake verify, and a step ofdocs-pages— now fails when a published<lastmod>is malformed, impossible, or in the future, and when none of the entries carries one, which is exactly what a lostfetch-depth: 0produces. Its success line carries both numbers.The count is checked against the file rather than trusted. The hook computes dates; the template publishes them; the first cannot see the second. Measured while writing this: with the override removed and the hook left in place, the build printed
52 of 53 pages state when they last changedwhile the file it had just written carried the build date on all 53 — a reassuring number beside the exact artifact it was wrong about.on_post_buildnow reads the sitemap back and fails the build unless its dates are precisely the derived ones, compared as multisets so a fallback that repeats one date cannot pass on a count.And the live sentinel can now see it.
scripts/verify_live_site.pyused to strip every<lastmod>before comparing the deployedsitemap.xml, because those elements were the build's own date. They are now a property of the commit, so the file is compared byte for byte, dates included — the elements the old exclusion removed are precisely the ones this change corrects, and a sentinel blind to them could not tell an honest deployed sitemap from a build-stamped one.sitemap.xml.gzkeeps its one documented exclusion (a gzip MTIME) and is still checked by decompressing it.How it was verified
make verifyran green through the pre-push hook (lint · mypy strict · tests ≥90% · security · eval · smoke · a11y · site-check · claims · calibrate · gate-inventory · slo · corpus-report · propose-check · docs · zizmor · ci-parity · web-static).sprout check-tuning-scope --base origin/mainreports no tunable-surface change — nothing here touchessrc/sprout/providers/or any other tuned path, so noTunes-Against:trailer is claimed.Four negative controls, each with a git-object baseline taken before the sabotage and re-asserted byte-identical after, and
__pycache__cleared between runs. The fixture's content dates (2026-05-01,2026-06-22) are deliberately months older than the day it is read on, so a gate that only checks the shape of a date cannot pass by accident.dated == 0→dated == -1test_a_sitemap_that_dates_nothing_at_all_is_reportedredstamped > today→stamped > date(2999, 1, 1)test_a_date_in_the_future_is_reportedred"false"→"true"theme_overrides/sitemap.xml2026-09-13on all 53; the build aborted under--strictnaming all 16 derived dates it did not publishThe fourth is the one worth reading: it is what exposed that the hook's count and the published file are two different things, and it is why the read-back check exists. The shallow-clone test is a real
git clone --depth 1and asserts both halves — that the naive per-path question does come back with the tip's date there (naive == REPO_TIP != PAGE_CHANGED), and that the hook refuses it.Closes #181.
Nothing about the
web-staticroot page's own date is settled here; it is listed above as deliberately undated.Prepared with AI assistance; reviewed before submission.