Repository navigation
Cut a standards release channel (standards/v1-stable) — consumers pin to it today but it has never existed #1091
Description
The gap
standards/ is consumed by other repos as a versioned artifact, but no standards/* tag has ever been cut, so every consumer effectively tracks this repo's default branch.
Verified 2026-09-07:
$ git ls-remote --tags https://github.com/petry-projects/.github | grep standards/
(no output)
$ gh api /repos/petry-projects/.github/git/refs/tags/standards/v1-stable
404 Not Found
Why this is not theoretical
#1448 (in .github-private) already decided the policy:
seed-repo-template.shfetches standards at the latest published stable version, not the live default branch.- The compliance/drift check accepts N-1 — the current published version or the one before it — so a standards promotion does not instantly fail every consumer during propagation.
That shipped. scripts/seed-repo-template.sh implements it with STANDARDS_CHANNEL defaulting to standards/v1-stable and a _resolve_standards_ref() ladder. But because the tag does not exist, that resolution falls through to its head-fallback branch and reads HEAD anyway. The mechanism is built, merged, and inert.
It has already caused an outage. On 2026-09-07, commit c676b4f3 (PR #1082) tightened standards/personas/persona.schema.json. .github-private's validate-personas.py fetches that schema live from main, so every open PR in that repo went red 21 seconds after the merge — its last green Lint run was 16:07:31, the merge landed 16:07:52. Tracked as combsmith/.github-private#1707, which is blocked on this issue.
Acceptance criteria
- A standards release channel exists and is documented.
standards/v1-stableresolves, cut at a known-good commit onmain(a commit where CI is green and the current standards content is intended to be the published baseline). - How it advances is written down, following the existing channel-tag model (ADR-0002 in
.github-private, andci-standards.md→ Reusable workflow versioning): what the immutable release form is (standards/vX.Y.Z), what the moving channel is (standards/v1-stable), and who/what moves it. If promotion should be automated later, say so; do not build that here. - Cutting is scripted and idempotent, not a one-off hand action. Re-running must not clobber an existing immutable release tag — mirror
cut-release.sh's refusal to overwrite. A human still decides when; the mechanism decides how. - N-1 is resolvable. A consumer can determine the current published version and the one before it, since #1448's second clause requires accepting both. If only one version exists at first, that is the documented starting state, not a special case in consumer code.
- The existing consumer is proven fixed. After the tag exists,
scripts/seed-repo-template.shresolves via its channel branch rather thanhead-fallback— demonstrate the resolved ref in the run output. That consumer is unpinned today and this is what re-pins it. - No behavior change for anything that does not opt in. Repos and scripts that pass an explicit
STANDARDS_REF/STANDARDS_DIRkeep working exactly as they do.
Out of scope
- Changing
validate-personas.pyin.github-private(that is [bug] validate-personas fetches the org schema from main unpinned — an org-repo merge red-lined every PR here in 21 seconds .github-private#1707, which this unblocks). - Automating channel promotion on a schedule.
- Any change to the content of
standards/.
Dev Notes
- The consumer-side ladder to match is
scripts/seed-repo-template.sh:143 _resolve_standards_ref()—STANDARDS_DIR(local seam) →STANDARDS_REF(explicit) →STANDARDS_CHANNEL(channel) →head-fallback. The goal is that the third branch starts winning. scripts/canary-rollout.shalready implements immutable-release + moving-channel semantics for agents (cut, then move the channel tag, refusing to re-point a tag that resolves elsewhere). Reuse its vocabulary and its guardrails rather than inventing a second dialect; note that agents are per-reusable and standards are per-repo, so this is a simpler shape.- Creating the tag itself is a repo mutation rather than a file change. If that cannot be done from a PR flow, land the script + docs here and record the tag-cut as an explicit operator step in the PR description, the way
.github-privatehandled org-team creation in its epic #1627.
Blocks combsmith/.github-private#1707. Policy precedent: combsmith/.github-private#1448. Outage: PR #1082 → combsmith/.github-private#1701.
Activity
don-petry commentedon Sep 8, 2026
Dev-Lead Implementation Plan
Issue: #1091 — Cut a standards release channel (standards/v1-stable) — consumers pin to it today but it has never existed
Scope
Deliver the host-side standards release-channel mechanism in
petry-projects/.github(the repo that ownsstandards/): a documentedstandards/vX.Y.Zimmutable-release +standards/v1-stablemoving-channel model, and an idempotent, clobber-refusing cut script — plus record the actual tag-cut as an operator step (tag creation is a repo mutation, not a file change).The Dev Notes reference
scripts/seed-repo-template.sh:143 _resolve_standards_ref()with aSTANDARDS_DIR → STANDARDS_REF → STANDARDS_CHANNEL → head-fallbackladder. That ladder does not exist in this repo. This repo'sscripts/seed-repo-template.shreads standards from a localSTANDARDS_DIRcheckout and performs no ref resolution — there is noSTANDARDS_REF, noSTANDARDS_CHANNEL, and nohead-fallbackbranch to flip. Likewisescripts/cut-release.shis not in this repo (it lives in.github-private, perstandards/ci-standards.md). The consumer that pinsSTANDARDS_CHANNEL(andvalidate-personas.py) lives in.github-private— explicitly out of scope here and tracked as the blocked #1707.Therefore AC #5 cannot be demonstrated from this repo without fabricating a consumer subsystem the issue itself says already exists elsewhere. I will build the host-side mechanism (ACs #1–4, #6) that unblocks #1707, and will not invent a fake
seed-repo-template.shre-pin. I will not modifyseed-repo-template.shat all (so AC #6 holds trivially).Implementation Checklist
scripts/lib/standards-release.sh— pure, side-effect-free decision core: release/channel tag naming (standards/vX.Y.Z,standards/v1-stable), strict-semver validation/compare, current+N-1 selection, and the clobber-refusal decision (idempotent no-op at same commit, refuse at a different commit). Reuses the org's channel-tag vocabulary (perstandards/ci-standards.mdandscripts/lib/canary-rollout.sh).scripts/cut-standards-release.sh— thin I/O orchestrator over the pure core:cut <vX.Y.Z> [--commit <sha>] [--dry-run],versions,resolve(current + N-1),channel <vX.Y.Z>. Writes viagh api(mirrorscanary-rollout.sh_gh_create_annotated_tag/_gh_move_tag) so the protected-tag ruleset bypass applies.standards/standards-versioning.md— documents the immutable release form (standards/vX.Y.Z), the moving channel (standards/v1-stable), who/what moves it (therelease-channel-tagsruleset already protectsrefs/tags/**), the N-1 policy, and the single-version starting state. Promotion automation is noted as deliberately out of scope (future work).AGENTS.md/standards/ci-standards.md..github/workflows/standards-release-tests.yml— shellcheck + bats gate for the new script/lib/suite (mirrorscanary-rollout-tests.yml).Tests to Write
tests/standards_release.bats— release/channel tag naming; semver validation + compare; current+N-1 selection (incl. the single-version starting state → empty previous); clobber-refusal (same-commit no-op vs different-commit refuse);cut --dry-runplan output via stubbedgh/git.Operator step (recorded here; tag creation is a repo mutation)
After merge, an authorized mover runs:
bash scripts/cut-standards-release.sh cut v1.0.0 --commit <known-good main SHA>— cuts immutablestandards/v1.0.0and movesstandards/v1-stableonto it.Test Command
bats tests/standards_release.batsandbash .dev-lead/scripts/dev-lead-lint.shdon-petry commentedon Sep 8, 2026
Dev-Lead: Progress (in progress — not yet durable)
Plan execution (intended)
.dev-lead/scripts/dev-lead-lint.sh, and the new bats suite pass in this workspace (not the pushed run).Files touched (this workspace, not yet pushed)
scripts/lib/standards-release.sh(new): pure core —standards/vX.Y.Z/standards/v<major>-stabletag naming, semver compare, current+N-1 selection, refuse-to-clobber decision.scripts/cut-standards-release.sh(new): thin orchestrator —cut/versions/resolve/channel; writes viagh apimirroringcanary-rollout.sh.standards/standards-versioning.md(new): immutable release + moving channel, who/what moves it, N-1 policy, single-version starting state, opt-in.tests/standards_release.bats(new): unit + orchestrator dry-run/refuse coverage..github/workflows/standards-release-tests.yml(new): shellcheck + bats gate.AGENTS.md,standards/ci-standards.md: link the new doc.The Dev Notes'
scripts/seed-repo-template.sh:143 _resolve_standards_ref()ladder (STANDARDS_DIR → STANDARDS_REF → STANDARDS_CHANNEL → head-fallback) does not exist in this repo —seed-repo-template.shhere reads a localSTANDARDS_DIRand performs no ref resolution, and there is nocut-release.shhere (it lives in.github-private). The channel-pinning consumer is.github-private(#1707, explicitly out of scope). So AC #5 (demonstrateseed-repo-template.shflips fromhead-fallbackto its channel) cannot be demonstrated from this repo without fabricating a consumer the issue itself places elsewhere. I did not modifyseed-repo-template.sh(AC #6 holds trivially). This host-side work is what unblocks #1707.Operator step (tag creation is a repo mutation, not a PR diff)
After merge, an authorized mover (with the
release-channel-tagsruleset bypass) runs, at a known-good greenmaincommit:This cuts immutable
standards/v1.0.0and movesstandards/v1-stableonto it. Verify withbash scripts/cut-standards-release.sh resolve.Notes
don-petry commentedon Sep 8, 2026
Dev-Lead: Implementation Complete — PR #1094
The implementation for issue #1091 is durable: commits are pushed and a pull request is open. This record is backed by verifiable artifacts you can confirm in one click.
aca48dc4214cd06795e3fa98d563d9064609df27Review happens on the PR. Acceptance criteria and test results are evidenced by the PR's diff and CI checks — not asserted here. If a later stage fails, this claim is retracted in place.
feat: implement issue #1091 — Cut a standards release channel (standa…