diff --git a/AGENTS.md b/AGENTS.md index 8c4416947..a1ca62b32 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -187,3 +187,12 @@ intentionally **mutable** and are an accepted, documented exception because they Compliance audits must therefore **not** flag `@pr-review/stable` / `@dev-lead/stable` (or other channel tags) on first-party callers as "unpinned actions" — they are the sanctioned version-selection mechanism (see the initiative analysis §5.1: `docs/initiatives/agentic-release-strategy.md`). + +**feature-ideation (cross-repo).** The `feature-ideation` agent is released through the same model with +the channel set `{next, ring0, ring1, stable}`, so its `feature-ideation/` pins are the **same +sanctioned mutable-ref exception** and must not be flagged as unpinned. Unlike `pr-review`/`dev-lead` +whose reusables live in this repo, `feature-ideation`'s reusable lives in **`petry-projects/.github`** +(this repo holds only the thin caller stub), so its release/channel tags are cut against that public +repo — and the protective ruleset bounding `feature-ideation/**` channel tags is therefore created +**there**, not on this repo (an untracked prerequisite in the public repo). See +[`docs/release/versioning.md`](./docs/release/versioning.md) "Cross-repo: feature-ideation". diff --git a/docs/release/runbook.md b/docs/release/runbook.md index c4cfb51de..1d4fc31b3 100644 --- a/docs/release/runbook.md +++ b/docs/release/runbook.md @@ -5,9 +5,16 @@ Operational procedures for the per-agent channel-tag release model (initiative [`versioning.md`](./versioning.md) and the [initiative analysis](../initiatives/agentic-release-strategy.md) §5.1, §7. -**Agents covered:** `pr-review`, `dev-lead`. Both live in this repo; callers -(consumers + this repo's own self-host caller) pin the moving channel tag -`@/stable` and thread `agent_ref: /stable`. +**Agents covered:** `pr-review`, `dev-lead`, `feature-ideation`. `pr-review` and +`dev-lead` live in this repo; callers (consumers + this repo's own self-host +caller) pin the moving channel tag `@/stable` and thread +`agent_ref: /stable`. `feature-ideation`'s reusable lives in +`petry-projects/.github`, so its release/channel tags are cut against **that** +repo, not this repo's `origin` (see "Cross-repo: feature-ideation" in +[`versioning.md`](./versioning.md)). `cut-release.sh` recognizes it and previews +its tags via `--dry-run`, but **live cross-repo pushes are not wired yet** — the +push target is an open question, so non-dry-run cuts for `feature-ideation` are +refused for now. **Two tag kinds per agent:** @@ -48,6 +55,11 @@ scripts/cut-release.sh pr-review 1.6.0 --ref origin/main --dry-run # Cut the immutable tag only (no promotion): scripts/cut-release.sh pr-review 1.6.0 --ref origin/main --push + +# feature-ideation is cross-repo (reusable in petry-projects/.github): dry-run +# previews the tag names, but live --push is refused until the cross-repo target +# is decided (see "Cross-repo: feature-ideation" in versioning.md): +scripts/cut-release.sh feature-ideation 1.4.0 --channel ring0 --dry-run ``` - Pick the version per semver (see `versioning.md#semantic-versioning`): bugfix → @@ -91,6 +103,13 @@ git push origin pr-review/stable --force > `--channel-only` flag exists (follow-up), use the direct `git tag -f` above. > Channel tags are lightweight (point straight at the commit); the immutable > `vX.Y.Z` tags are annotated. +> +> **feature-ideation is cross-repo.** Its `feature-ideation/` tags live +> on `petry-projects/.github`, so a promotion is a tag move on **that** repo +> (`git push feature-ideation/ --force`), +> not this repo's `origin`. The exact remote/target for the automated path is an +> open question — `cut-release.sh` refuses a live `feature-ideation` cut until it +> is resolved; preview with `--dry-run`. Then **verify** (§4). @@ -165,6 +184,9 @@ git push origin pr-review/stable --force - The immutable `vX.Y.Z` tags are the rollback targets — pick the last release known healthy (`git tag -l 'pr-review/v*' | sort -V`). +- For `feature-ideation`, the same reverse-tag-move applies but against + `petry-projects/.github` (where its `feature-ideation/v*` and channel tags + live), not this repo — see the cross-repo note in §2b. - Every caller picks up the rolled-back version on its **next** run with no change on their side. In-flight runs already started on the bad version finish; new runs use the restored one. diff --git a/docs/release/versioning.md b/docs/release/versioning.md index ce3c28043..266ee5ea3 100644 --- a/docs/release/versioning.md +++ b/docs/release/versioning.md @@ -15,9 +15,15 @@ together, so a version is a single repo commit that contains a known-good combin |---|---|---| | `pr-review` | `.github/workflows/pr-review.yml` | `scripts/review-one-pr.sh`, `scripts/review-batch.sh`, `scripts/post-pr-review.sh`, `scripts/engine.sh`, `scripts/lib/*` | | `dev-lead` | `.github/workflows/dev-lead-reusable.yml` | `scripts/dev-lead-*.sh`, `scripts/engine.sh`, `scripts/lib/*` | +| `feature-ideation` | `petry-projects/.github` → `.github/workflows/feature-ideation-reusable.yml` (**cross-repo**) | reusable-owned (lives in the public repo; this repo holds only the thin caller `.github/workflows/feature-ideation.yml`) | -Because both agents live in one repo, a release tag points at a whole-repo commit; the tag *name* -scopes it to one agent so the two can be released and promoted independently. +`pr-review` and `dev-lead` both live in this repo, so a release tag points at a whole-repo commit; the +tag *name* scopes it to one agent so the two can be released and promoted independently. + +`feature-ideation` is the exception: its reusable lives in **`petry-projects/.github`** (this repo holds +only the thin caller stub), so a `feature-ideation` "release" is a commit on that public repo, and its +release/channel tags must be cut **against `petry-projects/.github`, not this repo's `origin`**. See +[Cross-repo: feature-ideation](#cross-repo-feature-ideation) below. ## Tag scheme @@ -69,6 +75,12 @@ may sit at the same commit between releases; they diverge while a candidate is b rejects `@main` and frozen `@vX.Y.Z`/`@` — callers must pin a *moving* channel). pr-review still uses `stable` only; its ring channels are pending under #499. +`feature-ideation` uses the full per-ring channel set — `{next, ring0, ring1, stable}` — so it can be +promoted through the same canary → ring → stable model. Its tags follow the identical name scheme +(`feature-ideation/vX.Y.Z`, `feature-ideation/next`, `feature-ideation/ring0`, +`feature-ideation/ring1`, `feature-ideation/stable`) but are cut against `petry-projects/.github` +(see [Cross-repo: feature-ideation](#cross-repo-feature-ideation)). + ### Semantic versioning - **MAJOR** — breaking change to the caller contract (workflow inputs/secrets, required permissions, @@ -102,6 +114,28 @@ The first release was cut from the production `main` at the time of issue #496: version. The health-gated promotion added in Phase 2 (#501) is what makes *future* promotions to `stable` genuinely validated before they become production. +## Cross-repo: feature-ideation + +Everything above assumes the agent's reusable workflow lives **in this repo**, so a release tag is a +whole-repo commit here and tags are cut against this repo's `origin`. That holds for `pr-review` and +`dev-lead`. It does **not** hold for `feature-ideation`: + +- `feature-ideation`'s reusable lives in **`petry-projects/.github`** + (`.github/workflows/feature-ideation-reusable.yml`); this repo carries only the thin caller stub + `.github/workflows/feature-ideation.yml`. +- Therefore a `feature-ideation` release is a commit on **`petry-projects/.github`**, and its + `feature-ideation/vX.Y.Z` immutable + `feature-ideation/` tags must be cut **against that + repo**, not this repo's `origin`. +- `scripts/cut-release.sh` already recognizes `feature-ideation` and formats its tag names, and its + `--dry-run` prints the intended immutable + channel tags. **Live cross-repo pushes are not wired + yet** — the push target mechanism (a `--repo`/remote argument vs. dispatching against the public + repo) is an open question reserved for a human decision, so the script refuses a non-dry-run cut for + `feature-ideation` (see the `TODO(#872)` in `cut-release.sh`). Use `--dry-run` to preview tag names + until the target is decided. +- Because `feature-ideation`'s channel tags live on `petry-projects/.github`, the protective ruleset + that bounds them (the mutable-ref exception) is created **there**, not on this repo — see + [`AGENTS.md`](../../AGENTS.md) "Release channel tags & the mutable-ref exception". + ## Cutting / moving tags Use `scripts/cut-release.sh` (tested in `tests/test_cut_release.bats`) rather than ad-hoc `git tag`: @@ -115,6 +149,10 @@ scripts/cut-release.sh pr-review 1.1.0 --channel stable --push # Preview without touching anything: scripts/cut-release.sh pr-review 1.1.0 --channel stable --dry-run + +# feature-ideation: dry-run only for now (cross-repo target unresolved — see above). +# Prints feature-ideation/v1.4.0 and feature-ideation/ring0 without mutating anything: +scripts/cut-release.sh feature-ideation 1.4.0 --channel ring0 --dry-run ``` The promote/rollback **runbook** (when to move `stable`, how to roll back, verify, gotchas) lives in diff --git a/scripts/cut-release.sh b/scripts/cut-release.sh index db5053e1f..c23da9f90 100755 --- a/scripts/cut-release.sh +++ b/scripts/cut-release.sh @@ -9,7 +9,7 @@ # cut-release.sh [--ref ] [--channel ] # [--push] [--dry-run] # -# pr-review | dev-lead +# pr-review | dev-lead | feature-ideation # semantic version without the leading v, e.g. 1.2.0 # --ref commit/ref to tag (default: origin/main) # --channel also move / (e.g. stable) to the new release @@ -32,7 +32,7 @@ set -euo pipefail # valid_agent — return 0 iff agent is a known agent name. valid_agent() { case "$1" in - pr-review | dev-lead) return 0 ;; + pr-review | dev-lead | feature-ideation) return 0 ;; *) return 1 ;; esac } @@ -75,7 +75,7 @@ main() { done if ! valid_agent "$agent"; then - echo "::error::unknown agent '$agent' (expected: pr-review | dev-lead)" >&2 + echo "::error::unknown agent '$agent' (expected: pr-review | dev-lead | feature-ideation)" >&2 return 2 fi if ! validate_version "$version"; then @@ -95,10 +95,24 @@ main() { [ -n "$channel" ] && { chan="$(channel_ref "$agent" "$channel")"; echo "channel tag: $chan -> $rel"; } if [ "$dry" = true ]; then + if [ "$agent" = "feature-ideation" ]; then + echo "::warning::cross-repo placeholder: the SHA above ($sha) was resolved from petry-projects/.github-private. feature-ideation's canonical commits and tags live in petry-projects/.github — this SHA is a local placeholder only." + fi echo "(dry-run) no tags created or pushed." return 0 fi + # TODO(#872): feature-ideation's reusable lives in petry-projects/.github, so its + # release/channel tags must be cut against THAT repo — but main() pushes to this + # repo's `origin`. The cross-repo push target (a --repo/remote arg vs. dispatching + # against the public repo) is an open question reserved for a human decision. Until + # it is wired, only --dry-run is supported for feature-ideation; refuse live cuts so + # tags are never written to the wrong remote. + if [ "$agent" = "feature-ideation" ]; then + echo "::error::live cut/push for 'feature-ideation' is not wired yet — its tags belong on petry-projects/.github (cross-repo target is an open question; see TODO). Use --dry-run for now." >&2 + return 1 + fi + if git rev-parse -q --verify "refs/tags/$rel" >/dev/null; then echo "::error::release tag '$rel' already exists — immutable tags are never overwritten." >&2 return 1 diff --git a/tests/test_cut_release.bats b/tests/test_cut_release.bats index 22e32ad99..c2ea69703 100644 --- a/tests/test_cut_release.bats +++ b/tests/test_cut_release.bats @@ -24,6 +24,11 @@ setup() { [ "$status" -eq 0 ] } +@test "valid_agent: feature-ideation is accepted" { + run valid_agent "feature-ideation" + [ "$status" -eq 0 ] +} + @test "valid_agent: unknown agent is rejected" { run valid_agent "ci-failure-analyst" [ "$status" -ne 0 ] @@ -82,6 +87,11 @@ setup() { [ "$output" = "dev-lead/v2.3.1" ] } +@test "release_ref: feature-ideation variant" { + run release_ref "feature-ideation" "1.4.0" + [ "$output" = "feature-ideation/v1.4.0" ] +} + @test "channel_ref: agent-scoped channel name" { run channel_ref "pr-review" "stable" [ "$output" = "pr-review/stable" ] @@ -91,3 +101,8 @@ setup() { run channel_ref "dev-lead" "next" [ "$output" = "dev-lead/next" ] } + +@test "channel_ref: feature-ideation ring0 channel" { + run channel_ref "feature-ideation" "ring0" + [ "$output" = "feature-ideation/ring0" ] +}