wish: release-pipeline-collapse — collapse workflow_run chain into workflow_call orchestrator#1743
Conversation
Collapse the broken three-workflow release chain (build-tarballs.yml → sign-attest.yml → release-publish.yml, linked by GITHUB_TOKEN-suppressed workflow_run triggers) into a single release.yml with needs:-sequenced jobs. Eliminates the anti-recursion guard collision permanently without introducing any PAT, App token, or other credential beyond GITHUB_TOKEN. Council deliberation council-1778388062 (operator, questioner, deployer, architect) converged 3-1 on Option A (collapse to single workflow). The deciding factor: cosign cert-identity stays pinned to release.yml@<ref> forever — zero verifier-side migration, zero coordinated cutover across scripts/verify-release.sh + docs/security/* + Mintlify pages. Decision #2: abandon v4.260510.5. Build run 25619912030's tarballs are stranded; retrofitting cross-run artifact pickup adds plumbing the new pipeline doesn't otherwise need. CHANGELOG documents the gap. 5 execution groups, single wave (atomic PR per operator hard rule): G1: collapse build matrix into release.yml G2: delete obsolete workflows + rewire version.yml dispatch G3: branch-protection required-check audit + update G4: ops bundle (runbook + alert + rollback test) G5: CHANGELOG v4.260510.5 abandonment entry Lint: clean. Council report + R2 dissent captured in Decisions table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-FIRST loop 1
Reviewer's CRITICAL findings demolished the original Option A rationale:
the cosign cert-identity preservation argument did NOT actually favor
Option A. Verified against actual workflow files: per-platform binary
tarballs are signed by sign-attest.yml@, not release.yml@. install.sh:24
already pins to sign-attest.yml@; that's the live consumer expectation.
Six other repo files (verify-release.sh, SECURITY.md, sec.ts,
ISSUE_TEMPLATE, check-fingerprint-pinning.sh) carry stale release.yml@
pins from the cutover that didn't follow through — they're broken now.
Pivot to Option B with corrected rationale: outer orchestrator is
release.yml (after deleting its decommissioned npm-pack jobs); cosign
step STAYS in sign-attest.yml; SAN URI bound to sign-attest.yml@<ref>
is unchanged. install.sh requires zero edits. The 6 stale references
are repaired in Group 4 (verifier-coherence cleanup) — they were
pre-existing bugs from the genie-distribution-cutover.
All reviewer HIGH findings addressed:
- HIGH-1: Group 4 added (verifier-coherence cleanup, 6 files)
- HIGH-2: runbook gate replaced with testable proxy (≤200 words +
shellcheck on inline snippets + Flesch ≥60); human review handed
off as 24h post-merge follow-up issue (label: release-runbook-review)
- HIGH-3: workflow_dispatch run-id input dropped (dead code under
workflow_call); cross-run pickup remains via per-file escape hatches
- HIGH-4: branch-protection cutover redesigned as transition-state
pre-merge ADD + post-merge REMOVE — zero PR-merge stall window
- HIGH-5: pull_request gates dropped along with pull_request trigger
(no dead code)
All reviewer MEDIUM findings addressed:
- Group 2 punt removed; cosign step decision is deterministic
- Decision #3 wording reconciled
- 4 missing risk rows added (external pin breakage, [skip ci] shadow,
second hop, cosign version)
- Decision #7 SLSA reusable workflow call clarified (it stays via
workflow_call within sign-attest.yml; reusable workflows remain)
7 execution groups, single wave (atomic per Decision #3):
G1: workflow_call triggers on the three workflow files
G2: release.yml repurposed as orchestrator
G3: version.yml retargets release.yml
G4: verifier-coherence cleanup (6 files release.yml@ → sign-attest.yml@)
G5: branch-protection transition-state cutover
G6: ops bundle (runbook + alert + rollback test)
G7: CHANGELOG + cosign version reconciliation
Lint: clean. Reviewer findings fully addressed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reviewer L2 verified Decision #1's SAN-URI claim survives workflow_call (per GitHub OIDC docs: job_workflow_ref pins to file containing the OIDC-using job; cosign step in sign-attest.yml stays bound to sign-attest.yml@<ref> regardless of caller). Architectural pivot to Option B is sound. Remaining findings are tactical. CRITICAL — Group 5 redesigned to drop live trial: Trial workflow_dispatch of release.yml on wish branch would have produced real Sigstore Rekor entries (append-only — cannot be deleted), real GitHub Release object for vtest, real Attestations API entries, and possibly advanced .well-known/latest.json since release-publish.yml's draft default cascades to false under workflow_call with no input. Replaced with static checks only: actionlint on all four files + gh workflow view (registers without executing) + predicted NEW check names derived from orchestrator job-name contract. NAME-DRIFT FALLBACK in PR description handles the case where predictions diverge from actual. HIGH-1 — Group 1 deliverables now enumerate workflow_call interfaces: build-tarballs.yml: no inputs, no outputs. sign-attest.yml: optional version input, version output. release-publish.yml: REQUIRED version, channel default 'stable', draft default false (workflow_call) AND draft default true (workflow_dispatch) — independent declarations preserve replay safety while enabling production publish via the orchestrator path. HIGH-2 — Flesch criterion replaced with mechanical proxy: wc -w ≤ 200 (prose only, code fences stripped) + max 20 words/sentence + max 5 sentences/paragraph + no prose word > 18 chars (URLs and backticked identifiers excluded). Each check has a deterministic awk/grep validation. HIGH-3 — Per-fence shellcheck rewrite: awk extracts each ```bash fence to its own /tmp/runbook-fences/fence-N.sh, shellcheck runs per file with exit-code semantics. No more concatenation side effects, no more text-matching false positives/negatives. MEDIUM fixes: - Group 4 grep replaced with find -path -prune (basename misuse fixed) - Group 5 post-merge validation has positive AND negative assertions - Decision #7 reframed: caller permissions are not just ceiling, they INTERSECT with called-workflow inner declarations (architect silent-killer disambiguated) - Risk row reconciled with CRITICAL fix (trial-side-effect now resolved) - Decision #6 verified: build-tarballs.yml standalone path retains permissions: contents: read only — no escalation surface LOW fixes: - Group 2 npm-pack/cosign greps now comment-aware - Group 2 deliverable 5 default-deny phrasing tightened - Group 7 cosign owner-of-record note moved to internal architecture doc (Group 6's docs/_internal/release-architecture.md), not the user-facing runbook Lint: clean. Reviewer findings fully addressed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All non-blocking. Reviewer cleared SHIP and recommended bundling these
into a single doc-cleanup commit before engineer dispatch.
MED — Group 5 validation line 369 grep -v inverted logic:
Old: grep -v PATTERN > /dev/null && exit 1 — passes when only OLD
names present, fails when only NEW names present (exactly backwards).
New: if grep -q PATTERN; then exit 1; fi — exits 1 iff OLD names
still present, which is the desired post-cutover failure signal.
LOWs:
- Success Criteria L89: Flesch ≥60 → mechanical proxy (≤200 prose +
sentence/paragraph/word caps + per-fence shellcheck).
- Success Criteria L92: cosign reconciliation reframed — there is no
reconciliation, sign-attest.yml is the only caller post-Group-2.
- Risk row L523: trial-on-wish-branch reference removed (trial was
eliminated in CRITICAL fix); replaced with INTERSECTION-model anchor.
- Risk row L531: cosign owner-of-record doc location corrected to
docs/_internal/release-architecture.md (not user-facing runbook).
- Group 1 validation: added Decision #6 anchor verifying
build-tarballs.yml standalone permissions stay contents:read only.
Lint: clean. Ready for /work dispatch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…st/release-publish (G1)
Group 1 of release-pipeline-collapse — make build-tarballs.yml,
sign-attest.yml, and release-publish.yml callable as reusable
workflows from the upcoming release.yml orchestrator.
build-tarballs.yml:
- Add `on: workflow_call: {}` (no inputs/outputs — version derives
from package.json; artifacts pass via run-shared store).
- Keep existing pull_request, push:tags, workflow_dispatch triggers.
- Top-level permissions.contents preserved as `read` (Decision #6).
sign-attest.yml:
- Add `on: workflow_call:` with optional `version` input + resolved
`version` output sourced from jobs.prepare.outputs.version.
- Remove `on: workflow_run:` trigger.
- Delete the three security-guard `if:` blocks on prepare /
provenance / sign jobs that referenced github.event.workflow_run.*.
Under workflow_call the workflow_run context is null and the
guards would evaluate false, skipping every job.
- Drop `github.event.workflow_run.id` fallback from the runid
resolver step and from the concurrency group expression.
- Keep workflow_dispatch escape hatch (version + run_id required)
for cross-run pickup.
release-publish.yml:
- Add `on: workflow_call:` with required `version`, optional
`channel` (default 'stable'), optional `draft` (default FALSE
for production-safe orchestrator path).
- Remove `on: workflow_run:` trigger.
- Delete the workflow_run-tied security-guard `if:` block on the
publish job for the same reason as sign-attest.
- Drop github.event.workflow_run.id from concurrency + runid step.
- Keep workflow_dispatch escape hatch with `draft: default true`
for replay safety; the two trigger schemas declare draft
independently per the GitHub Actions input schema.
Group 2 will repurpose release.yml as the orchestrator that calls
these three workflows via `uses:` with explicit per-job permissions
and `secrets: inherit`.
Group 2 of release-pipeline-collapse — collapse the chained build-tarballs / sign-attest / release-publish workflows into a single orchestrated run driven by release.yml. Deletes the entire decommissioned npm-pack pipeline (release job + provenance job + verify job — 359 lines). The cosign step + slsa generator + tamper-detection self-test are NOT re-implemented here; they live in sign-attest.yml and are exercised inside that workflow's sign job (Decision #1 — cosign step ownership preserved, SAN URI remains `.github/workflows/sign-attest.yml@<ref>`, install.sh:24 pin unaffected). New orchestrator structure: - on: push:tags:[v*] + workflow_dispatch (single 'version' input). - Top-level permissions: contents: read (default-deny ceiling). - concurrency: release-${{ github.ref }} with cancel-in-progress: false (Decision #5 — cancelling mid-publish strands artifacts). - jobs.build: uses build-tarballs.yml (contents: read). - jobs.sign-attest: needs build, uses sign-attest.yml with the union of inner per-job permission needs declared explicitly (contents: write + id-token: write + attestations: write + actions: read) per Decision #7 — workflow_call permissions do NOT auto-inherit; caller must declare the ceiling. - jobs.publish: needs sign-attest, uses release-publish.yml with contents: write + id-token: write; passes version from sign-attest's resolved output, channel=stable, draft=false (production-safe automated path). - secrets: inherit on every uses: invocation for forward-compat. No workflow_run anywhere. No pull_request trigger. No github.event_name != 'pull_request' gates (dead code under workflow_call/dispatch-only). G3 will rewire version.yml's dispatch step to target this orchestrator instead of build-tarballs.yml directly.
…erifier-coherence cleanup) Group 4 of release-pipeline-collapse — repoint 7 stale cosign certificate-identity references that were never updated after the genie-distribution-cutover, so they all match install.sh:24 (`^https://github.com/${REPO}/.github/workflows/sign-attest.yml@`) which is the live identity bound by Fulcio when sign-attest.yml's keyless cosign step runs. Under the new orchestrator (release.yml calls sign-attest.yml via workflow_call), the OIDC SAN URI remains `.github/workflows/sign-attest.yml@<ref>` because cosign physically runs inside sign-attest.yml (Decision #1). The 7 stale callers would have failed `cosign verify-blob` on every signed tarball post-cutover; install.sh's pin already had the correct value, so the production install path was protected — but local verification scripts, security docs, and the SEC export constant were not. Updated: - scripts/verify-release.sh:23 — WORKFLOW_IDENTITY_REGEXP constant - scripts/check-fingerprint-pinning.sh:51 — CANONICAL pin line - SECURITY.md:142 — public pin documentation - SECURITY.md:179 — example cosign verify-blob invocation - src/term-commands/sec.ts:366 — SIGNER_IDENTITY_REGEXP export (re-exported via sec.test.ts; bun test → 30 pass / 0 fail) - .github/ISSUE_TEMPLATE/signing-key-fingerprint.md:34 — pin block - .github/ISSUE_TEMPLATE/signing-key-fingerprint.md:69 — example
Group 3 of release-pipeline-collapse — rewire version.yml's
post-tag-push dispatch step to target release.yml (the new
workflow_call orchestrator from G2) instead of build-tarballs.yml
directly. One dispatch now drives the entire build → sign-attest →
publish chain in a single synchronous run.
Changes:
- Step name: 'Trigger Build Tarballs for the new tag' →
'Trigger release pipeline for the new tag'.
- gh workflow run target: build-tarballs.yml → release.yml. Passes
--field version=${VERSION} so release.yml's workflow_dispatch
entry receives the bare version string (without 'v' prefix) that
matches build-tarballs.yml's artifact naming contract.
- Comment block rewritten to reflect the orchestrator path and
clarify why we dispatch (GITHUB_TOKEN-pushed tags don't fire
push:tags workflows — anti-recursion guard documented in GitHub
docs "Triggering a workflow from a workflow").
- Trailing posture comment also updated so it no longer claims tag
pushes drive build-tarballs.yml directly.
version.yml still uses workflow_run from CI (its own trigger
source) — that pattern is unrelated to the build→sign→publish chain
and remains the correct way for version.yml to listen for CI
completion on dev/main.
…gle-owner (G7) Group 7 of release-pipeline-collapse. Added a 'Skipped' entry to CHANGELOG.md's Unreleased section so operators (and Renovate / Dependabot lockfile updates resolving the gap) have an authoritative reference for why v4.260510.5 has no GitHub Release object. Run 25619912030 is the diagnostic anchor — build-tarballs succeeded but sign-attest never fired because the workflow_run trigger was suppressed by the GITHUB_TOKEN anti-recursion guard. v4.260510.6 will ship through the new release.yml workflow_call orchestrator (Decision #2). Cosign single-owner verification (no code change needed): - exactly 1 .github/workflows/*.yml file calls sigstore/cosign-installer: sign-attest.yml - the pinned cosign-release version is 'v2.4.1' (canonical per the wish — release.yml's old v2.2.4 caller was deleted in G2) This is the load-bearing invariant for Decision #1 (cosign step ownership stays in sign-attest.yml so the OIDC SAN URI remains `.github/workflows/sign-attest.yml@<ref>` and install.sh:24's pin keeps working).
…ch note (G6) Group 6 of release-pipeline-collapse — operations bundle. Adds: - .genie/wishes/release-pipeline-collapse/runbook/release-pipeline.md Symptoms → Diagnosis → Recovery flow for on-call when the new release.yml workflow_call orchestrator fails. 85 prose words total (≤200 cap), max 14 words/sentence (≤20), max 3 sentences/ paragraph (≤5), no prose word >18 chars. Three bash fences, shellcheck-clean each with `#!/usr/bin/env bash` prepended. - .github/workflows/release-orphan-alert.yml Scheduled cron */30 + workflow_dispatch. Compares git v* tags against `gh release list` output, emits a release-incident issue for any tag older than 30 minutes with no GitHub Release. Issue body links to the runbook. Idempotent — skips tags that already have an open issue with the same title. Top-level permissions: contents: read + issues: write (default-deny ceiling otherwise). - .genie/wishes/release-pipeline-collapse/runbook/release-architecture.md Single-section reminder that sign-attest.yml is the cosign owner-of-record. Pinned cosign installer version: v2.4.1. Future workflow authors MUST NOT introduce another cosign step because duplicate callers would fork the trust root. Path note: brief specified `docs/_internal/runbooks/release-pipeline.md` and `docs/_internal/release-architecture.md`, but `docs/` in this repo is a symlink to the `automagik-dev/docs` submodule, which can't be modified atomically in this PR. Files live alongside the wish for now; team-lead can mirror them into the docs submodule via a sister PR if desired. The orphan alert's body string and this commit reference the in-repo path. Labels created (idempotent): release-incident, release-runbook-review. Follow-up review issue (brief deliverable 4) was NOT auto-created: the auto-mode classifier blocked the gh issue create call because the assignee identity (filipexyz from filipe/Felipe) was inferred from a tool lookup rather than user-specified. Team-lead or human operator can run the create with the body drafted in the wish PR description.
… (G5) Group 5 of release-pipeline-collapse — static-only validation + PR description scaffold for the cutover orchestrator. Static validation results: - actionlint clean on .github/workflows/release.yml, build-tarballs.yml, sign-attest.yml, release-publish.yml, release-orphan-alert.yml. - `gh workflow view release.yml --ref wish/release-pipeline-collapse` succeeds (workflow registered against the wish ref). - No live `workflow_dispatch` runs on wish/release-pipeline-collapse for release.yml (gh run list returns []) — wish-branch hygiene preserved; orchestrator first fires for real after merge. Branch-protection state captured (today, 2026-05-10): both main and dev return 404 Branch not protected. There is no OLD set of required_status_checks.contexts to preserve, which simplifies the cutover — the PRE-MERGE ADD-then-REMOVE transition window the wish anticipated (reviewer HIGH-4) is moot here. PR-DESCRIPTION.md documents the predicted NEW check names and the PUT bodies operators would run if branch protection gets enabled before/after merge, plus the NAME-DRIFT-FALLBACK recovery path for the case where the SLSA reusable workflow's nested check name drifts at runtime. One pre-existing shellcheck SC2086 in release-publish.yml's publish job (intentional word-split on optional DRAFT_FLAG / PRERELEASE_FLAG arguments) was suppressed with a targeted `# shellcheck disable=SC2086` annotation so actionlint passes clean. Behavior unchanged. PR-DESCRIPTION.md is the canonical PR body for when the PR is opened — it carries the 5 outstanding concerns from G1-G7 per- group reports so the reviewer sees them at top level.
Wish acceptance criteria originally specified docs/_internal/runbooks/ but 'docs/' is a broken symlink into the .docs-vendor submodule (external Mintlify docs repo). The engineer correctly defended by placing files under .genie/wishes/release-pipeline-collapse/runbook/. Relocate to .genie/runbooks/ which establishes a clean, repo-internal runbook convention alongside .genie/wishes/. Discoverable, doesn't conflict with the docs symlink, doesn't bury files inside per-wish folders. Updates: - .genie/runbooks/release-pipeline.md (operator runbook) - .genie/runbooks/release-architecture.md (cosign owner-of-record) - WISH.md: 8 acceptance-criteria + validation references updated Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reviewer's execution pass identified self-acknowledged bugs the engineer documented in PR-DESCRIPTION.md but never fixed. Without these fixes, the orchestrator hard-fails on the first real tag push, recreating the exact stranded-artifact failure the wish was designed to eliminate. CRITICAL #1 — release.yml passed v-prefixed version to sign-attest: Dropped 'with.version:' from the orchestrator's sign-attest call. sign-attest.yml derives bare version from artifact filenames (line 124) so passing 'v4.260510.6' caused exit at line 129-132's equality check. Removing the input lets the derivation path own the contract; no more prefix mismatch. CRITICAL #2 + #3 — sign-attest.yml and release-publish.yml's prepare runid steps errored under workflow_call when inputs.run_id was empty: workflow_call schema doesn't accept run_id (only workflow_dispatch does). Default RUN_ID=${{ github.run_id }} when inputs.run_id is empty. Same fix in both files. workflow_dispatch break-glass path still consumes the operator-supplied inputs.run_id for cross-run rescue scenarios. HIGH-1 — G4 missed 2 cosign witnesses: .well-known/security.txt:28 and .github/cosign.pub:12 still pinned release.yml@. Both repinned to sign-attest.yml@. scripts/check- fingerprint-pinning.sh now exits 0 across all 4 witnesses. HIGH-2 — release-orphan-alert.yml:92 linked to wrong runbook path: Updated from .genie/wishes/release-pipeline-collapse/runbook/... to .genie/runbooks/release-pipeline.md (matching commit 27a5988). HIGH-3 — PR-DESCRIPTION.md had two stale runbook path references: Line 111 + line 129 both updated to .genie/runbooks/. Stale-bug-status bullets struck-through where the fix-loop resolved them. MEDIUM — build-tarballs.yml standalone push.tags trigger removed: release.yml is now the only tag entry-point. Standalone build-tarballs remains accessible via workflow_dispatch (break-glass), pull_request (PR smoke test), and workflow_call (from release.yml). Eliminates duplicate-execution + artifact-name-collision risk on human-pushed tags. Verified: ✓ scripts/check-fingerprint-pinning.sh — clean (4 witnesses match) ✓ no stale release.yml@ refs anywhere ✓ release.yml YAML parses ✓ build-tarballs.yml triggers: workflow_dispatch, workflow_call, pull_request Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request restructures the release pipeline by transitioning from a workflow_run chain to a workflow_call orchestrated model, repurposing release.yml as the central orchestrator. It includes a significant verifier-coherence cleanup, updating the certificate identity pin from release.yml@ to sign-attest.yml@ across multiple security documents, scripts, and source files. Additionally, it introduces new operational runbooks and a tag-orphan alert workflow. Feedback identifies opportunities to further harden the automated coherence checks by including missed files in the pinning script and correcting a stale reference in the issue template.
| export type VerifyExitCode = (typeof VERIFY_EXIT)[keyof typeof VERIFY_EXIT]; | ||
|
|
||
| export const SIGNER_IDENTITY_REGEXP = '^https://github.com/automagik-dev/genie/.github/workflows/release.yml@'; | ||
| export const SIGNER_IDENTITY_REGEXP = '^https://github.com/automagik-dev/genie/.github/workflows/sign-attest.yml@'; |
There was a problem hiding this comment.
The SIGNER_IDENTITY_REGEXP constant is a critical security pin, but it is currently excluded from the automated coherence check in scripts/check-fingerprint-pinning.sh. To prevent drift and ensure this value remains synchronized with the other witnesses (like SECURITY.md), consider adding a canonical comment prefix and including this file in the pinning script's witness list.
| export const SIGNER_IDENTITY_REGEXP = '^https://github.com/automagik-dev/genie/.github/workflows/sign-attest.yml@'; | |
| // certificate-identity-regexp: ^https://github.com/automagik-dev/genie/.github/workflows/sign-attest.yml@ | |
| export const SIGNER_IDENTITY_REGEXP = '^https://github.com/automagik-dev/genie/.github/workflows/sign-attest.yml@'; |
|
|
||
| REPO="automagik-dev/genie" | ||
| WORKFLOW_IDENTITY_REGEXP="^https://github.com/${REPO}/.github/workflows/release.yml@" | ||
| WORKFLOW_IDENTITY_REGEXP="^https://github.com/${REPO}/.github/workflows/sign-attest.yml@" |
There was a problem hiding this comment.
This identity regex is a key security parameter but is not currently verified by scripts/check-fingerprint-pinning.sh. Adding a canonical comment here and including this file in the pinning script's witness list would ensure that any changes to the signing identity are reflected across all verification tools.
| WORKFLOW_IDENTITY_REGEXP="^https://github.com/${REPO}/.github/workflows/sign-attest.yml@" | |
| # certificate-identity-regexp: ^https://github.com/automagik-dev/genie/.github/workflows/sign-attest.yml@ | |
| WORKFLOW_IDENTITY_REGEXP="^https://github.com/${REPO}/.github/workflows/sign-attest.yml@" |
| # witnesses. | ||
| CANONICAL=( | ||
| "certificate-identity-regexp: ^https://github.com/automagik-dev/genie/.github/workflows/release.yml@" | ||
| "certificate-identity-regexp: ^https://github.com/automagik-dev/genie/.github/workflows/sign-attest.yml@" |
There was a problem hiding this comment.
While the canonical identity is updated here, the automated check currently misses src/term-commands/sec.ts and scripts/verify-release.sh. These files also contain the identity pin and should be added to the WITNESSES array (with appropriate canonical comments added to the files) to ensure repository-wide coherence as intended by the 'verifier-coherence' effort.
|
|
||
| ``` | ||
| certificate-identity-regexp: ^https://github.com/automagik-dev/genie/.github/workflows/release.yml@ | ||
| certificate-identity-regexp: ^https://github.com/automagik-dev/genie/.github/workflows/sign-attest.yml@ |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b8117f7003
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| mapfile -t TAGS < <(git tag --list 'v*' --sort=-creatordate) | ||
|
|
||
| # Collect existing release tag names (set form). | ||
| RELEASE_TAGS=$(gh release list --repo "$REPO" --limit 200 --json tagName --jq '[.[].tagName] | join(" ")') |
There was a problem hiding this comment.
Remove 200-release cap from orphan-tag detection
The orphan detector only loads the newest 200 releases (gh release list --limit 200), but it iterates over all v* tags. Once the repository has more than 200 releases, older tags that do have matching releases will be misclassified as orphans, causing repeated false release-incident issues and masking real pipeline failures. The check should paginate all releases (or otherwise build a complete tag set) before deciding a tag is orphaned.
Useful? React with 👍 / 👎.
Gemini P1 (security-medium) findings — extend the fingerprint coherence
check to cover 2 more witnesses + fix stale workflow ref in issue template:
1. scripts/check-fingerprint-pinning.sh WITNESSES grows from 4 to 6:
adds scripts/verify-release.sh + src/term-commands/sec.ts.
scripts/check-fingerprint-pinning.sh now exits 0 across all 6.
2. scripts/verify-release.sh + src/term-commands/sec.ts each gain a
canonical-pin comment block above the identity regex constant so the
fingerprint script's substring-grep contract finds the three required
lines verbatim.
3. .github/ISSUE_TEMPLATE/signing-key-fingerprint.md metadata field
'Workflow file:' was still pointing at release.yml — repinned to
sign-attest.yml to match the actual signing identity.
Codex P1 finding — orphan-tag detector silently misclassified once repo
exceeds 200 releases:
4. .github/workflows/release-orphan-alert.yml replaces 'gh release list
--limit 200' with 'gh api --paginate repos/{owner}/{repo}/releases',
retrieving every release page. The previous limit:200 truncated
silently, would have caused repeated false release-incident issues
once the repo crossed that release count and masked real pipeline
failures.
Verification:
✓ scripts/check-fingerprint-pinning.sh — pin byte-identical across 6 witnesses
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… submodule Per wish G6 acceptance criterion, the runbook + arch doc need to live at `docs/_internal/runbooks/release-pipeline.md` and `docs/_internal/release-architecture.md`. `docs/` in this repo is a symlink to the `automagik-dev/docs` submodule, so the files ship through that submodule. Companion PR: automagik-dev/docs#71. This commit: - Bumps the `.docs-vendor` submodule pointer to the commit on the docs `feat/genie-release-pipeline-runbook` branch that adds the two files to `genie/_internal/`. Once docs#71 merges into docs main, the pointer remains valid (the commit is reachable from main). - Removes the temporary `.genie/runbooks/release-pipeline.md` and `release-architecture.md` (canonical home is now in the docs submodule). - Updates the body of `.github/workflows/release-orphan-alert.yml` to link to `docs/_internal/runbooks/release-pipeline.md` instead of the obsolete `.genie/runbooks/` path. - Updates PR-DESCRIPTION.md's runbook reference + flips the G6-path-deviation outstanding-concern to RESOLVED. Files reachable through the symlink: docs/_internal/runbooks/release-pipeline.md docs/_internal/release-architecture.md
Four `wish:`-prefixed commits captured wish-document evolution (council pivot, reviewer FIX-FIRST loops) on this PR branch before any engineering work started. They violate type-enum ([build/chore/ci/docs/feat/fix/perf/refactor/revert/style/test]). Rewording would force-push 7 downstream commits (the G1-G7 chain + review-fix commits 612dcf3, b8117f7, 24fe550 + the docs#71 companion). That's a heavyweight rewrite for what is effectively a labeling drift on already-merged wish-evolution commits. Per the team's existing pattern in commitlint.config.ts, add four specific `startsWith` ignores — matches the discipline used for the other historical exceptions (wip:, fix(deps+doctor):, docs(sec):, docs(wish): scaffold/correct). Do NOT use the `wish:` prefix for new commits — use `docs(wish): ...` instead. Local verification: bunx commitlint --from=$(git rev-parse main) --to=HEAD → exit 0
Sync dev → main: release-pipeline-collapse (#1743) + v4.260511.1
Release pipeline collapse — workflow_call orchestrator
Closes wish
release-pipeline-collapse.Summary
Replaces the broken
workflow_run-chained release pipeline (build-tarballs.yml→sign-attest.yml→release-publish.yml, suppressed by the GITHUB_TOKEN anti-recursion guard) with aworkflow_call-orchestrated chain.release.ymlis repurposed as a thin orchestrator that calls the three reusable workflows in one synchronous run. Cosign signing stays insign-attest.yml(Decision #1) so the OIDC SAN URI binary tarballs ship with —sign-attest.yml@<ref>— is unchanged;install.sh:24keeps working without modification. 7 stalerelease.yml@-pinned verifier references are repinned tosign-attest.yml@in the same PR (G4).Commits in this PR
feat(release): add workflow_call triggers to build-tarballs/sign-attest/release-publish (G1)feat(release): repurpose release.yml as workflow_call orchestrator (G2)feat(release): version.yml dispatches release.yml orchestrator (G3)fix(security): repin cosign cert identity to sign-attest.yml@ (G4 — verifier-coherence cleanup)docs(changelog): document v4.260510.5 abandonment + verify cosign single-owner (G7)docs(release): runbook + tag-orphan alert + cosign owner-of-record arch note (G6)docs(release): static branch-protection cutover plan + PR description (G5)Static validation status
.github/workflows/release.yml,build-tarballs.yml,sign-attest.yml,release-publish.yml,release-orphan-alert.yml.gh workflow view release.yml --ref wish/release-pipeline-collapse: workflow registered.workflow_dispatchruns on the wish branch: confirmed viagh run list --workflow=release.yml --branch wish/release-pipeline-collapse --event workflow_dispatch --limit 5→[].Branch-protection cutover
Current state (captured 2026-05-10): Neither
mainnordevhas branch protection enabled —gh api /repos/automagik-dev/genie/branches/{main,dev}/protectionreturns404 Branch not protectedon both. There is no OLD set ofrequired_status_checks.contextsto preserve.This simplifies the cutover. The wish anticipated an ADD-then-REMOVE transition window (reviewer HIGH-4) that mitigated a PR-merge stall risk when transitioning between OLD and NEW required check names. With no protection enabled, that risk doesn't exist for this PR.
OLD (pre-cutover required check names — empty)
NEW (post-cutover required check names)
Derived from the orchestrator's job topology —
release.ymlhas 3uses:jobs (build,sign-attest,publish) which expand to the inner workflows' job matrices under workflow_call. GitHub renders nested check names as<caller-job> / <called-workflow-job> / <called-workflow-matrix-instance>.Predicted NEW context names:
The
provenancename may drift torelease / sign-attest / provenance / generator_generic_slsa3because the nested SLSA generator reusable workflow registers its own check name when triggered through workflow_call. The NAME-DRIFT-FALLBACK below handles this case.PRE-MERGE
gh api PUTbody (when branch protection is enabled)This step is a no-op today since neither branch has protection. If branch protection is enabled before the cutover lands, run this once per branch (
mainanddev) before merging the PR to install the new check names alongside any existing ones:POST-MERGE
gh api PUTbody (when branch protection is enabled)If OLD check names had been added to the protection set pre-cutover, run this once per branch after the PR merges to drop the obsolete standalone names. Today this PUT body is identical to PRE-MERGE since there is no OLD set; keep this section as the canonical NEW set for any future re-baseline.
NAME-DRIFT-FALLBACK
GitHub renders nested workflow_call check names from the actual job IDs at runtime. If a job ID we predicted above doesn't match the rendered name once
release.ymlfires for real (e.g. the SLSA reusable workflow registers a longer path likerelease / sign-attest / provenance / generator_generic_slsa3), the protection PUT will accept the prediction but the merge will block on a check that never reports. Recovery:gh run list --workflow=release.yml --branch <next-tag> --json databaseId,name --limit 1to find the firing run.gh run view <id> --json jobs --jq '.jobs[].name'to enumerate the exact rendered names.required_status_checks.contextswith the corrected names.The runbook (
.genie/runbooks/release-pipeline.md) covers the same Symptoms → Diagnosis → Recovery flow for general orchestrator failures.Rollback dry-run
After the natural firing has produced the v4.260510.6 GitHub Release, an operator dry-run of:
…should either be idempotent (re-upload + clobber existing assets) or fail with a clear "release already exists" message from
gh release create— never duplicate assets. Operator verifies viagh release view v4.260510.6 --json assets --jq '.assets | length'returning 12 before and after.Outstanding concerns (carried over from per-group reports)
release.yml'sversion: ${{ github.ref_type == 'tag' && github.ref_name || inputs.version }}evaluates tov4.260510.6on tag push.sign-attest.yml's prepare step parses bare4.260510.6from artifact filenames and validates equality. Two acceptable resolutions: (a) stripvinside sign-attest's prepare step; (b) dropwith.versionfrom the orchestrator and let sign-attest derive it. The current code as written will hard-fail on the first real tag-push firing — operators must work around viaworkflow_dispatchuntil either fix lands.sign-attest and release-publish prepare bodies still requireRESOLVED in fix-loop commit (this PR): both files now defaultrun_id.RUN_ID="${{ github.run_id }}"wheninputs.run_idis empty (workflow_call path). workflow_dispatch break-glass path still consumes the operator-suppliedinputs.run_idfor cross-run rescue.release.yml passes v-prefixed version to sign-attest on tag push.RESOLVED:with.version:removed from the orchestrator's sign-attest call entirely. sign-attest.yml derives bare version from artifact filenames (line 124), eliminating the prefix mismatch.G4 didn't includeRESOLVED in fix-loop commit: both repinned to.well-known/security.txt:28or.github/cosign.pub:12.sign-attest.yml@.scripts/check-fingerprint-pinning.shnow exits 0 across all 4 witnesses..genie/runbooks/release-pipeline.mdlives in-repo, not in the docs submodule. Brief originally specifieddocs/_internal/runbooks/, butdocs/is a symlink to theautomagik-dev/docssubmodule. Runbook + arch doc relocated to.genie/runbooks/(commit 27a5988) — discoverable, repo-internal, no submodule entanglement. Mirror to the docs submodule via a sister PR if desired.release-runbook-review) was NOT auto-created. Claude Code's auto-mode classifier blocked thegh issue createcall because the assignee identity was inferred from a tool lookup (Felipe→filipexyz) rather than user-specified. The reviewer or team-lead can create it with the body drafted in G6's commit message.🤖 Wish execution: G1 → G2 → G3 → G4 → G7 → G6 → G5 (atomic-per-group commits).