Skip to content

feat(ci): make the template repo subject to the gates it ships - #252

Merged
gtauzin merged 6 commits into
mainfrom
feat/template-repo-parity
Jul 26, 2026
Merged

feat(ci): make the template repo subject to the gates it ships#252
gtauzin merged 6 commits into
mainfrom
feat/template-repo-parity

Conversation

@gtauzin

@gtauzin gtauzin commented Jul 26, 2026

Copy link
Copy Markdown
Member

Why

This repository generates seven packages and receives nothing back. Every improvement flows out of template/; nothing flows in. Two measured consequences:

  • fix(ci): pin exact uv version in **all** setup-uv steps (fix(ci): pin exact uv version in all setup-uv steps #220) touched eleven files, none of them .github/workflows/. "All" silently meant "all in the template". Six steps here stayed unpinned, and the count grew from five while nobody was looking.
  • The ci-passed roll-up shipped in v0.32.0 as the check name meant to be the required one. No ruleset required it here for four releases. Every generated project already required it; only the factory did not.

The gaps are absences, not drift, so a render-and-diff check cannot find them: you cannot diff a file that is not there.

What this adds

A parity manifest (tests/parity_manifest.yml): every gate the template ships resolves to matched, excluded with a written reason, or leading. The shipped-gate set is derived from a rendered project, so a new template gate arrives as an unanswered entry rather than not arriving at all.

Closed alongside it: uv pinned across all six setup-uv steps and asserted over this repo's own workflows; a nightly canary with issue filing; the docs entry points and content gate moved onto the publishing engine; the skills mirror enforced byte-identically over tracked files; .git-cliff.toml migrated to commit.remote.* with byte-identical output.

Generated projects can now commit a CLAUDE.md: the template seeds one, registers it in _skip_if_exists, and the six-variant ignore block is gone. This repo's own CLAUDE.md is tracked and reconciled with its Copilot copy.

Every assertion was verified to fail first

Two of those checks found real defects in this PR's own work:

  1. The manifest initially verified that gates were answered, never that a matched claim was true. Deleting this repo's nightly workflow outright failed nothing. Now a matched file must exist, a matched hook must run here, and a matched job must name an equivalent: this repo actually runs.
  2. Deriving gates from jobs and hooks alone was blind to controls that exist only as files, which is how CODEOWNERS and SECURITY.md reached seven projects and not this one. Derivation now covers the project root too: 37 gates became 56.

Recorded as shortfalls, not justified

SECURITY.md, codeql.yml and scorecard.yml are absent here only because harden-fleet-security shipped them to the template and never to this repo. This repo is public and all three are free. The manifest says so out loud rather than leaving it silently true.

Known caveat

_skip_if_exists guarding CLAUDE.md is verified to preserve a rewritten file through a real copier update. The negative control did not reproduce a loss: removing the guard preserved the content anyway, for both a wholly-rewritten file and the v0.22.0 partial-divergence shape. The guard's necessity rests on that documented incident, not on anything reproduced here.

Separately: template/.copier-answers.yml.jinja hardcodes _src_path: gh:stateful-y/python-package-copier and an update rewrites the answers file, so a local override is wiped by the first update. That makes local template testing impossible after one update.

gtauzin added 2 commits July 26, 2026 14:27
This repository generates seven packages and receives nothing back: every
improvement flows out of template/, and nothing flows in. That is how
"pin exact uv version in all setup-uv steps" pinned every step in the
template and none of the six here, and how the ci-passed roll-up shipped
in v0.32.0 while no ruleset required it on this repo for four releases.

The gaps are absences, not drift, so a render-and-diff check cannot find
them. This adds a parity manifest instead: every gate the template ships
resolves to matched, excluded with a written reason, or leading, and the
shipped-gate set is derived from a rendered project rather than listed by
hand so a new gate arrives unanswered instead of not arriving.

Closed here: uv pinned across all six setup-uv steps and asserted over
this repo's own workflows; a nightly canary; the docs entry points and
content gate moved onto the publishing engine; the skills mirror enforced
byte-identically over tracked files; git-cliff migrated to commit.remote.*.

Generated projects can now commit a CLAUDE.md: the template seeds one and
registers it in _skip_if_exists, and the six-variant ignore block is gone.
This repo's own CLAUDE.md is tracked and reconciled with its Copilot copy,
correcting three claims that were not true of main.

Every assertion was verified to fail before being accepted. Two found real
defects: the manifest initially checked that gates were *answered* but never
that a "matched" claim was *true*, so deleting this repo's nightly workflow
failed nothing; and deriving gates from jobs and hooks alone was blind to
controls that are only files, which is how CODEOWNERS and SECURITY.md
reached seven projects and not this one.
The instruction-file check asserted a cited path exists. On a maintainer's
machine `openspec/` exists, so a citation under it resolved; it is
gitignored, so a fresh clone has no such path and CI failed on a citation
that pointed at nothing for every reader but the author.

The test was weaker locally than in CI, which is the same defect class it
was written to catch. Asking git instead makes both environments agree and
matches what the guidance actually promises a reader.

Also drops the `openspec/specs/` citation itself, since that tree is
local-only.
@codecov-commenter

codecov-commenter commented Jul 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.72031% with 19 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
tests/test_repo_docs.py 58.33% 10 Missing ⚠️
tests/test_parity_manifest.py 92.91% 9 Missing ⚠️

📢 Thoughts on this report? Let us know!

gtauzin added 2 commits July 26, 2026 14:54
…te repo

These three were shipped to generated projects by harden-fleet-security and
never to this repository. It is public, so all three are free, and the parity
manifest recorded them as shortfalls rather than judgments. Adopting them
turns three "excluded, unresolved" entries into verified matches.

SECURITY.md is reworded for what this repo is: a fix reaches a generated
project only when that project runs `copier update`, so there is no patched
release to consume.

Also pins the seed-once guarantee behaviourally. test_claude_md_is_registered_
as_seed_once only asserted the entry exists; the new test re-runs the template
over a project that rewrote its CLAUDE.md and asserts the content survives.
Removing CLAUDE.md from _skip_if_exists makes it fail with the file clobbered,
so the guard is load-bearing rather than decorative. The test carries a control
(an unguarded README.md that must be overwritten) so a run that clobbered
nothing cannot pass as proof.
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

gtauzin added 2 commits July 26, 2026 15:48
Codecov flagged 32 unrun lines in the new tests. Most were the bodies of
slow/integration tests, which the coverage job excludes by selection and
cannot reach. The rest were the branches that only execute when a check
fails, which is precisely when they need to be right and precisely when
nobody has watched them run.

Extracts two pieces of logic so those branches can be driven directly:
unsupported_matched_claims (the four distinct ways a manifest entry can
claim a match that reality does not support) and instruction_body_difference
(which must report both directions, since the section telling contributors
to keep both instruction files in step was missing from one of them, and a
one-sided comparison would have called that clean).

Genuinely environment-dependent branches -- gh absent, network unreachable,
uv missing -- are marked no-cover with the reason rather than chased with
contrived tests. test_repo_workflows.py reaches 100%.
Codecov commented ":x: Patch coverage is 87% ... Please review" on a pull
request whose eighteen checks were green and which nothing could stop
merging. Two facts make that verdict meaningless, and neither is fixed by
tightening a threshold.

Coverage measures the wrong thing here, unavoidably: source is ["tests"]
because this repo has no src/, so it answers "did this test file execute",
which a passing test answers by definition. The product is template/ --
Jinja sources coverage cannot instrument, run only inside generated projects
in temp dirs. A generated project measures its own package and the number
means something there.

And nothing enforces it, deliberately: the coverage job sits outside the
ci-passed roll-up so a Codecov outage, the thing the OIDC upload is a canary
for, cannot block merges. That decision stays.

A red signal that can never mean anything trains readers to ignore the
source, including on the day it matters. Both statuses are turned off rather
than set informational: informational only stops the status blocking, while
Codecov still computes the target and the comment still reads "Please
review". The grade is what is meaningless, so the grade is what goes. The
comment stays for the per-line annotations, which is how the unrun failure
branches in the parity tests were found.

Codecov reads repository config from the default branch, so this takes
effect on merge, not on this pull request.
@gtauzin
gtauzin force-pushed the feat/template-repo-parity branch from 4acd263 to 9c79314 Compare July 26, 2026 14:13
@gtauzin
gtauzin merged commit 80ca9f8 into main Jul 26, 2026
19 checks passed
@gtauzin
gtauzin deleted the feat/template-repo-parity branch July 26, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants