Files
paperclip-docs/.github/workflows/docs-checks.yml
Aron PrinsandClaude Opus 5 4644dda3a3 test: guard sitemap lastmod against shallow clones and metadata sweeps (#119)
Picks up the regression test from #104 (@cryppadotta) — closed as
superseded, but its fixture design was the best part of it and worth
landing on its own.

## Why this gap mattered

The sitemap `lastmod` bug shipped twice, and **neither failure was
detectable by any check in `docs:test`**:

| | Symptom | Why nothing caught it |
|---|---|---|
| #117 | 0 dates in production for weeks | Shallow checkout doesn't make
`git log` *fail* — it succeeds, returning one date per file |
| #118 | 192 concurrent `git fetch --unshallow` fighting over
`.git/shallow.lock`, dateless sitemap | Reproduces only on a shallow
clone — never locally, never in CI |

Both are Cloudflare-only failure modes. The only detector we had was
reading the live sitemap, which is how this whole thread started.

## What landed

`scripts/verify-sitemap-lastmod-history.mjs`, wired into `docs:test`.
Three cases against **real depth-1 clones** — the fixture design and
`--no-local` insight are @cryppadotta's:

1. Full history publishes real per-document dates
2. Shallow clone that can reach origin recovers history
3. Shallow clone with unreachable origin omits dates rather than lying

Two changes on top of the original:

**Overlay the working-tree builder into the clone.** The clone carries
committed HEAD, so without this the shallow cases silently test the last
commit instead of the change being made. No-op in CI; the difference is
real locally, and it is what makes the check usable while iterating.

**Assert no single date covers >75% of pages.** The existing "more than
one distinct date" assertion passes at *2* distinct dates — precisely
the state #118 fixed, where 190 of 193 pages carried the date of a
frontmatter-only sweep. Bounding the largest cluster catches that class
without pinning a date that legitimate edits will move.

## Verified by breaking it

A check that has never failed proves nothing, so I reverted each fix in
turn:

**Revert body-aware dating (#118):**
```
  ok  lastmod values vary across documents (2 distinct) rather than one build date
FAIL  full history: no single date dominates (2026-08-25 covers 190/193, 98% <= 75%)
```
Note the pre-existing assertion passing right above the failure — that
is the gap this closes.

**Revert the single-flight memo (#118):**
```
FAIL  unshallowed build dates every route (2/193)
FAIL  unshallowed build produces varied dates (1 distinct)
```
The `.git/shallow.lock` race, caught by Case 2 and nothing else.

Restored, clean run is green: 193/193 dated, 31 distinct, largest
cluster 28%.

## Cost

**~55s** added to `docs:test` for three builds (`docs-test` was 32s).
Steep for a test suite, and worth flagging rather than burying — but the
alternative detector for this failure class is noticing the live sitemap
is wrong.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-08-29 11:10:35 +02:00

57 lines
1.6 KiB
YAML

name: Docs checks
on:
pull_request:
push:
branches: [main, nightly]
jobs:
docs-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Sitemap <lastmod> is derived from per-file git history. A shallow
# checkout collapses every file to the same commit date, which the
# build detects and responds to by omitting the dates entirely.
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
# Separate steps with `if: always()` so one failure cannot mask the rest.
# An `&&` chain short-circuits, which silently skipped later gates.
- name: Static routes
run: npm run docs:test:static-routes
# Builds from real depth-1 clones: the shallow-checkout and
# concurrent-unshallow failures both reproduce only there, never on the
# full-history checkout the other gates run against.
- name: Sitemap lastmod history
if: always()
run: npm run docs:test:sitemap-lastmod-history
- name: Asset fingerprints
if: always()
run: npm run docs:test:asset-fingerprints
- name: Hierarchical skills nav
if: always()
run: npm run docs:test:hierarchical-skills-nav
- name: Crawlable links
if: always()
run: npm run docs:test:crawlable-links
- name: Authored SEO metadata
if: always()
run: npm run docs:test:seo-metadata
- name: Skill source blocks
if: always()
run: npm run docs:test:skill-source-blocks