Repository navigation
fix(web): make the SEO lastmod manifest resolvable on conflict and explain itself - #712
Merged
Merged
Conversation
…plain itself apps/web/app/seo/lastmod.generated.json is generated and committed, and nearly every PR restages part of it — including package-only changes, because the API reference pages move with a package's surface. Two PRs touching the same page therefore conflict, and that conflict was expensive twice over (#710). - .gitattributes marks it `-merge`. A conflict here is never informative: the file is wholly derived, so the answer is always to regenerate rather than to pick a side. Without this git wrote conflict markers INTO the JSON, which left it unparseable — so the generator could not run and the one command that resolves the conflict was blocked behind hand-editing generated content. Git still reports the conflict; it now leaves valid JSON. Preferred over a merge driver, which every clone must register itself. - linguist-generated collapses it in review. - the --check assertions now carry the child's own output. The generator already prints the exact fix command, and asserting on the exit code alone discarded it: a stale manifest reds the required validate check with "expected 1 to be +0" and nothing else. - a guard test pins the attribute through `git check-attr`, verified to fail when the entry is removed. - AGENTS.md records both the regenerate-in-the-same-commit rule and the conflict resolution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove
enabled auto-merge (squash)
September 17, 2026 20:22
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
blove
temporarily deployed
to
vercel-preview
September 17, 2026 20:23 — with
GitHub Actions
Inactive
blove
added a commit
that referenced
this pull request
Sep 17, 2026
None of the three touches this page: verify registry error codes, body-less middleware reject status, and a k8s smoke watchdog. Only the generated SEO manifest conflicted, resolved by regenerating per the procedure #712 added to AGENTS.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove
added a commit
that referenced
this pull request
Sep 18, 2026
…main rule #712 landed a bullet telling contributors to regenerate the SEO manifest in the same commit or CI goes red. This branch's own bullet, a few lines above, says the opposite — do NOT regenerate for an ordinary content edit — because the workflow now does it on main. Leaving both made the standards doc contradict itself on the one point it was added to settle. Removes the older bullet and keeps the half of it that is still true and not covered by the new one: what to do when the manifest does conflict, which a route add or removal can still cause. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove
added a commit
that referenced
this pull request
Sep 18, 2026
…713) * chore(web): regenerate the SEO lastmod manifest on main, not in PRs Every docs branch had to run the generator to satisfy a freshness assertion, so two branches touching any page conflicted inside a generated artifact. That cost five rebases on one PR in a single day. - New SEO lastmod workflow regenerates and commits the manifest after a content change reaches main, serialized against itself and scoped to the paths the manifest derives from so it cannot retrigger itself. - The freshness tests now assert the generator's own round trip instead of asserting the committed file is current, so a stale timestamp no longer fails a PR. - A new --check-routes mode keeps the half a PR must still own: a route the manifest has never seen has no timestamp at all, and requireValidLastModified throws during the build. Adding or removing a page still fails until the author regenerates. The file stays committed deliberately. It is the only record of when each route last changed, and the sitemap imports it statically, so generating it from scratch during a build would stamp every route with the build clock and destroy the signal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(release): audit the SEO lastmod workflow entrypoints The workflow contracts pin every workflow's executable entrypoints in two allowlists, so a new workflow file is rejected until both describe it. The audit reports a mismatch without a diff, deliberately, so these entries are the descriptor it computes: the three pinned actions, the three run steps, and the job's contents: write permission. Pinning the push step's exact shell text is the point — changing it later has to be a deliberate, re-audited edit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(agents): fold the manifest conflict note into the regenerate-on-main rule #712 landed a bullet telling contributors to regenerate the SEO manifest in the same commit or CI goes red. This branch's own bullet, a few lines above, says the opposite — do NOT regenerate for an ordinary content edit — because the workflow now does it on main. Leaving both made the standards doc contradict itself on the one point it was added to settle. Removes the older bullet and keeps the half of it that is still true and not covered by the new one: what to do when the manifest does conflict, which a route add or removal can still cause. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(release): format the workflow entrypoints fixture Biome reports the fixture this branch adds as unformatted, which is an error rather than a warning, so `pnpm lint` exits non-zero and reds source-validate and the required validate check. Formatting only. `pnpm test:release-integrity` and the workflow-contracts digest snapshot both still pass, so no pinned digest moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This branch was successfully deployed
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.
Fixes #710.
Problem
apps/web/app/seo/lastmod.generated.jsonis generated and committed, and nearly every PR restages part of it — including package-only changes, since the API reference pages move whenever a package's surface does. Two PRs touching the same page always overlap, because the generator stamps one timestamp onto every route whose digest changed.Three recent overlaps on main, within about a day:
/docs/deployment/kubernetes/docs/middleware/docs/api/permissionsThe conflict was expensive twice over. Git wrote conflict markers into the JSON, which left it unparseable, so the generator could not run and the one command that resolves the conflict was blocked behind hand-editing generated content. And when the manifest was merely stale, the test asserted on the exit code alone, so CI showed
expected 1 to be +0while the generator's own message — which names the exact fix command — was discarded.Change
.gitattributesmarks the manifest-merge. A conflict here is never informative: the file is wholly derived from content plus recorded timestamps, so the correct resolution is always to regenerate rather than to pick a side. Git still reports the conflict, but leaves valid JSON in the worktree, sopnpm --dir apps/web seo:lastmod && git addresolves it. Chosen over a custom merge driver, which every clone would have to register in its own git config before it took effect.linguist-generatedcollapses it in review.--checkassertions carry the child's own output, so a stale manifest explains itself and names its fix.git check-attr.Verification
Both new behaviours were checked against their failure modes rather than only their success path.
The
-mergeattribute, in a scratch repo. Without it, the merged file contains markers and is not parseable JSON; with it, one clean side survives:The guard test fails when the entry is removed:
And a deliberately stale manifest now reports the cause and the fix instead of a bare exit code:
pnpm lint,node scripts/check-docs.mjsand theapps/webSEO suite (59 tests) pass.Scope
This makes the conflict trivial to resolve; it does not stop git from reporting one. Eliminating it entirely would need either a merge driver requiring per-clone setup or moving regeneration to a post-merge job on main, both more invasive than the problem warrants. No changeset: nothing under
packages/*/srcchanges.🤖 Generated with Claude Code