Skip to content

Cut a standards release channel (standards/v1-stable) — consumers pin to it today but it has never existed #1091

Closed

Description

@don-petry

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:

  1. seed-repo-template.sh fetches standards at the latest published stable version, not the live default branch.
  2. 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

  1. A standards release channel exists and is documented. standards/v1-stable resolves, cut at a known-good commit on main (a commit where CI is green and the current standards content is intended to be the published baseline).
  2. How it advances is written down, following the existing channel-tag model (ADR-0002 in .github-private, and ci-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.
  3. 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.
  4. 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.
  5. The existing consumer is proven fixed. After the tag exists, scripts/seed-repo-template.sh resolves via its channel branch rather than head-fallback — demonstrate the resolved ref in the run output. That consumer is unpinned today and this is what re-pins it.
  6. No behavior change for anything that does not opt in. Repos and scripts that pass an explicit STANDARDS_REF / STANDARDS_DIR keep working exactly as they do.

Out of scope

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.sh already 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-private handled 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

  1. don-petry commented on Sep 8, 2026

    @don-petry
    ContributorAuthor

    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 owns standards/): a documented standards/vX.Y.Z immutable-release + standards/v1-stable moving-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).

    ⚠️ Premise reconciliation (surfaced before coding — AC #5)

    The Dev Notes reference scripts/seed-repo-template.sh:143 _resolve_standards_ref() with a STANDARDS_DIR → STANDARDS_REF → STANDARDS_CHANNEL → head-fallback ladder. That ladder does not exist in this repo. This repo's scripts/seed-repo-template.sh reads standards from a local STANDARDS_DIR checkout and performs no ref resolution — there is no STANDARDS_REF, no STANDARDS_CHANNEL, and no head-fallback branch to flip. Likewise scripts/cut-release.sh is not in this repo (it lives in .github-private, per standards/ci-standards.md). The consumer that pins STANDARDS_CHANNEL (and validate-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.sh re-pin. I will not modify seed-repo-template.sh at 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 (per standards/ci-standards.md and scripts/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 via gh api (mirrors canary-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 (the release-channel-tags ruleset already protects refs/tags/**), the N-1 policy, and the single-version starting state. Promotion automation is noted as deliberately out of scope (future work).
    • Link the new doc from AGENTS.md / standards/ci-standards.md.
    • .github/workflows/standards-release-tests.yml — shellcheck + bats gate for the new script/lib/suite (mirrors canary-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-run plan output via stubbed gh/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 immutable standards/v1.0.0 and moves standards/v1-stable onto it.

    Test Command

    bats tests/standards_release.bats and bash .dev-lead/scripts/dev-lead-lint.sh

  2. don-petry commented on Sep 8, 2026

    @don-petry
    ContributorAuthor

    Dev-Lead: Progress (in progress — not yet durable)

    Provisional. Nothing is pushed yet; the automation posts the authoritative
    completion record (with PR number + head SHA) after the push. Treat the notes
    below as intent, not delivered results.

    Plan execution (intended)

    • Added a pure decision core, an idempotent cut orchestrator, a standards doc, a CI gate, and a bats suite for the standards release channel. Local shellcheck, .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>-stable tag naming, semver compare, current+N-1 selection, refuse-to-clobber decision.
    • scripts/cut-standards-release.sh (new): thin orchestrator — cut/versions/resolve/channel; writes via gh api mirroring canary-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.

    ⚠️ Premise caveat for reviewers (AC #5)

    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.sh here reads a local STANDARDS_DIR and performs no ref resolution, and there is no cut-release.sh here (it lives in .github-private). The channel-pinning consumer is .github-private (#1707, explicitly out of scope). So AC #5 (demonstrate seed-repo-template.sh flips from head-fallback to its channel) cannot be demonstrated from this repo without fabricating a consumer the issue itself places elsewhere. I did not modify seed-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-tags ruleset bypass) runs, at a known-good green main commit:

    bash scripts/cut-standards-release.sh cut v1.0.0 --commit <known-good-main-sha>
    

    This cuts immutable standards/v1.0.0 and moves standards/v1-stable onto it. Verify with bash scripts/cut-standards-release.sh resolve.

    Notes

  3. don-petry commented on Sep 8, 2026

    @don-petry
    ContributorAuthor

    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.

    Review 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.

  4. added a commit that references this issue on Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    dev-leadFor dev-lead agent pickupenhancementFeature requests

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions