From b9d1883778e6323e7150278f7df2115f23528532 Mon Sep 17 00:00:00 2001 From: namastex888 Date: Sat, 25 Jul 2026 18:32:30 -0300 Subject: [PATCH 1/4] test(release): pin the trigger_sha plumbing end to end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generic SLSA provenance embeds the workflow_run that TRIGGERED the release, whose head is the commit CI ran on — one before the [auto-version] bump the tarballs are built from. Nothing asserted that this value is actually threaded through version.yml -> release.yml -> sign-attest.yml + release-publish.yml, so dropping it from any single 'with:' block would silently re-break exact verification (or hard-fail on an empty TRIGGER_SHA) exactly as it did on 2026-07-25. Pins the full chain, the verifier comparing the trigger commit rather than source_sha, and the per-channel descriptor comparison. --- scripts/release-docs.test.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/scripts/release-docs.test.ts b/scripts/release-docs.test.ts index ad8c21d99..f19d21c1a 100644 --- a/scripts/release-docs.test.ts +++ b/scripts/release-docs.test.ts @@ -463,6 +463,39 @@ describe('Group E release and documentation contracts', () => { expect(read('src/genie.ts')).toContain(".command('__install-promote', { hidden: true })"); }); + test('the admitting CI head is plumbed to provenance verification end to end', () => { + // The generic SLSA provenance embeds the workflow_run that TRIGGERED the + // release, whose head is the commit CI ran on — one BEFORE the + // [auto-version] bump the tarballs are built from. Verification must bind + // that field to the trigger commit, never to source_sha (they can never be + // equal, which silently broke every dev release on 2026-07-25). If any link + // in this chain drops trigger_sha, exact verification either mis-binds or + // hard-fails on an empty value, so the whole wiring is pinned here. + const version = read('.github/workflows/version.yml'); + const release = read('.github/workflows/release.yml'); + const signAttest = read('.github/workflows/sign-attest.yml'); + const publish = read('.github/workflows/release-publish.yml'); + const verifier = read('scripts/release-generic-provenance.sh'); + + expect(version).toContain('trigger_sha: ${{ github.event.workflow_run.head_sha }}'); + expect(release).toContain('trigger_sha:'); + expect(release).toContain('trigger_sha: ${{ inputs.trigger_sha }}'); + expect(signAttest).toContain('trigger_sha:'); + expect(signAttest).toContain('TRIGGER_SHA: ${{ inputs.trigger_sha }}'); + expect(publish).toContain('TRIGGER_SHA: ${{ inputs.trigger_sha }}'); + + // The verifier compares the trigger commit, and refuses to run exact + // verification without a well-formed one. + expect(verifier).toContain('$run.head_sha == $trigger_sha'); + expect(verifier).not.toContain('$run.head_sha == $source_sha'); + + // Stable records source_sha directly; automated channels cannot, so the + // generic descriptor is compared per channel. + expect(publish).toContain('generic_expected_sha="$SOURCE_SHA"'); + expect(publish).toContain('generic_expected_sha="$TRIGGER_SHA"'); + expect(publish).toContain('.sourceSha == $genericSourceSha'); + }); + test('release create and promotion paths retain the one-time convergence caveat', () => { const workflow = read('.github/workflows/release-publish.yml'); const helper = read('scripts/reconcile-release-note.sh'); From 37c912bd6f6556c8fc1f6511af5adf07ef4a9af6 Mon Sep 17 00:00:00 2001 From: namastex888 Date: Sat, 25 Jul 2026 19:00:35 -0300 Subject: [PATCH 2/4] fix(release): deliver TRIGGER_SHA to the step that verifies provenance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix wired trigger_sha into sign-attest.yml's native-predicate step (line 340), whose script never reads it, while the step that actually runs 'release-generic-provenance.sh verify-exact' had no TRIGGER_SHA at all. verify_automated_event requires a well-formed one on dev/homolog and exits 2 without it — so the next dev release would have died at the same place with the same silent exit, a fourth consecutive broken release. The wiring test did not catch it because it used a whole-file toContain(), which the misplaced value satisfied. It now resolves the step that consumes each value and asserts the env of THAT step; verified to fail against the misplacement and pass once corrected. - CRITICAL: TRIGGER_SHA into the 'Verify SLSA provenance (self-check)' env; dead assignment removed from the native-predicate step - dead TRIGGER_SHA removed from release-publish prepare-delivery-evidence (no consumer); the live consumer in the security gate is untouched - dev version derivation uses max+1 instead of count+1, so a burned build number cannot collide, and a tag collision is now a hard error instead of being reported as a benign race that drops the release with a green run --- .github/workflows/release-publish.yml | 1 - .github/workflows/sign-attest.yml | 5 ++++- .github/workflows/version.yml | 19 +++++++++++++++--- scripts/release-docs.test.ts | 28 +++++++++++++++++++++++++-- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 4eb139a2a..6687eb53f 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -187,7 +187,6 @@ jobs: VERSION: ${{ inputs.version }} CHANNEL: ${{ inputs.channel }} SOURCE_SHA: ${{ inputs.source_sha }} - TRIGGER_SHA: ${{ inputs.trigger_sha }} SOURCE_BRANCH: ${{ inputs.source_branch }} SOURCE_CI_RUN_ID: ${{ inputs.source_ci_run_id }} CONTROL_SHA: ${{ github.sha }} diff --git a/.github/workflows/sign-attest.yml b/.github/workflows/sign-attest.yml index b7463efca..761d8288a 100644 --- a/.github/workflows/sign-attest.yml +++ b/.github/workflows/sign-attest.yml @@ -337,7 +337,6 @@ jobs: SOURCE_SHA: ${{ inputs.source_sha }} SOURCE_BRANCH: ${{ inputs.source_branch }} SOURCE_CI_RUN_ID: ${{ inputs.source_ci_run_id }} - TRIGGER_SHA: ${{ inputs.trigger_sha }} CONTROL_SHA: ${{ github.sha }} RUN_ID: ${{ github.run_id }} RUN_ATTEMPT: ${{ github.run_attempt }} @@ -380,6 +379,10 @@ jobs: SOURCE_SHA: ${{ inputs.source_sha }} SOURCE_BRANCH: ${{ inputs.source_branch }} SOURCE_CI_RUN_ID: ${{ inputs.source_ci_run_id }} + # Required by release-generic-provenance.sh verify-exact on the + # automated channels: it binds the provenance's triggering + # workflow_run head to this value and exits 2 when it is absent. + TRIGGER_SHA: ${{ inputs.trigger_sha }} CONTROL_SHA: ${{ github.sha }} run: | set -euo pipefail diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 628620d09..784e83b9d 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -180,8 +180,14 @@ jobs: run: | PREFIX="${{ steps.context.outputs.prefix }}" TODAY=$(date -u +%y%m%d) - EXISTING=$(git tag --list "v${PREFIX}.${TODAY}.*" | wc -l) - BUILD_NUMBER=$((EXISTING + 1)) + # Highest existing build number + 1, never the count: a gap (a tag + # deleted, or a build number burned by a failed release) would make + # count+1 collide with an existing tag, and the push failure below is + # reported as a benign race, silently dropping the release. + HIGHEST=$(git tag --list "v${PREFIX}.${TODAY}.*" \ + | sed -n "s|^v${PREFIX}\.${TODAY}\.\([0-9][0-9]*\)$|\1|p" \ + | sort -n | tail -1) + BUILD_NUMBER=$(( ${HIGHEST:-0} + 1 )) VERSION="${PREFIX}.${TODAY}.${BUILD_NUMBER}" echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "build_number=${BUILD_NUMBER}" >> "$GITHUB_OUTPUT" @@ -313,7 +319,14 @@ jobs: # in CI logs and detector-greppable. The next merge's run catches # up. SHORT_SHA="${TRIGGER_SHA:0:7}" - if ! git push --atomic origin "HEAD:refs/heads/dev" "refs/tags/v${VERSION}"; then + if ! push_output=$(git push --atomic origin "HEAD:refs/heads/dev" "refs/tags/v${VERSION}" 2>&1); then + printf '%s\n' "$push_output" >&2 + # A tag collision is a version-derivation defect, not a race: exiting + # 0 here would drop the release with a green run and no diagnosis. + if printf '%s' "$push_output" | grep -qiE 'already exists|cannot lock ref|tag .* exists'; then + echo "::error ::release-version.tag-collision v${VERSION} already exists; version derivation picked a used build number" + exit 1 + fi echo "::notice ::release-race.next-tag-pin-skipped.detected dev advanced past triggering SHA ${SHORT_SHA}; tag push skipped — next merge will republish" exit 0 fi diff --git a/scripts/release-docs.test.ts b/scripts/release-docs.test.ts index f19d21c1a..53bf83aed 100644 --- a/scripts/release-docs.test.ts +++ b/scripts/release-docs.test.ts @@ -481,8 +481,32 @@ describe('Group E release and documentation contracts', () => { expect(release).toContain('trigger_sha:'); expect(release).toContain('trigger_sha: ${{ inputs.trigger_sha }}'); expect(signAttest).toContain('trigger_sha:'); - expect(signAttest).toContain('TRIGGER_SHA: ${{ inputs.trigger_sha }}'); - expect(publish).toContain('TRIGGER_SHA: ${{ inputs.trigger_sha }}'); + + // Substring checks are NOT enough: the first attempt at this fix put + // TRIGGER_SHA in the native-predicate step, whose script never reads it, + // while the step that actually runs verify-exact had none — a whole-file + // toContain() passed anyway and the next release died with the same silent + // exit 2. Assert the value reaches the env of the step that CONSUMES it. + const stepEnvFor = (workflow: string, needle: string): string => { + const lines = workflow.split('\n'); + let start = -1; + for (let i = 0; i < lines.length; i += 1) { + if (/^\s+- name:/.test(lines[i])) start = i; + if (lines[i].includes(needle) && start >= 0) { + let end = start + 1; + while (end < lines.length && !/^\s+- name:/.test(lines[end])) end += 1; + return lines.slice(start, end).join('\n'); + } + } + throw new Error(`no step found containing ${needle}`); + }; + + const verifyStep = stepEnvFor(signAttest, 'release-generic-provenance.sh verify-exact'); + expect(verifyStep).toContain('TRIGGER_SHA:'); + expect(verifyStep).toContain('${{ inputs.trigger_sha }}'); + + const gateStep = stepEnvFor(publish, 'generic_expected_sha'); + expect(gateStep).toContain('TRIGGER_SHA:'); // The verifier compares the trigger commit, and refuses to run exact // verification without a well-formed one. From f8da9bc9213b2e633d9988c55fdd382ce4bf1c8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 22:04:40 +0000 Subject: [PATCH 3/4] chore(version): bump to 5.260725.7 [auto-version] --- .claude-plugin/marketplace.json | 2 +- package.json | 2 +- plugins/genie/.claude-plugin/plugin.json | 2 +- plugins/genie/.codex-plugin/plugin.json | 2 +- plugins/genie/package.json | 2 +- plugins/hermes-genie/plugin.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 083d81fd5..ed5f68519 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "genie", - "version": "5.260725.5", + "version": "5.260725.7", "source": "./plugins/genie", "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project." } diff --git a/package.json b/package.json index b28ad27f0..1db088693 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@automagik/genie", - "version": "5.260725.5", + "version": "5.260725.7", "description": "Collaborative terminal toolkit for human + AI workflows. NOTE: npm distribution discontinued 2026-05-09 — install via `curl -fsSL https://raw.githubusercontent.com/automagik-dev/genie/main/install.sh | bash` (cosign + SLSA verified). See https://automagik.dev/genie/release-process", "license": "MIT", diff --git a/plugins/genie/.claude-plugin/plugin.json b/plugins/genie/.claude-plugin/plugin.json index d777687b8..67a1ecc0d 100644 --- a/plugins/genie/.claude-plugin/plugin.json +++ b/plugins/genie/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "genie", - "version": "5.260725.5", + "version": "5.260725.7", "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.", "author": { "name": "Namastex Labs" diff --git a/plugins/genie/.codex-plugin/plugin.json b/plugins/genie/.codex-plugin/plugin.json index e11fd0610..8783dcff6 100644 --- a/plugins/genie/.codex-plugin/plugin.json +++ b/plugins/genie/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "genie", - "version": "5.260725.5", + "version": "5.260725.7", "description": "Plan, execute, review, and ship software with Genie workflows in Codex.", "author": { "name": "Namastex Labs", diff --git a/plugins/genie/package.json b/plugins/genie/package.json index ccfd9c2ab..046f5220b 100644 --- a/plugins/genie/package.json +++ b/plugins/genie/package.json @@ -1,6 +1,6 @@ { "name": "genie-plugin", - "version": "5.260725.5", + "version": "5.260725.7", "private": true, "description": "Runtime dependencies for genie bundled CLIs", "license": "MIT", diff --git a/plugins/hermes-genie/plugin.yaml b/plugins/hermes-genie/plugin.yaml index 1f6bc9d12..36b9ade81 100644 --- a/plugins/hermes-genie/plugin.yaml +++ b/plugins/hermes-genie/plugin.yaml @@ -1,5 +1,5 @@ name: genie -version: 5.260725.5 +version: 5.260725.7 description: "Native Hermes surface for Genie orchestration: read-only status plus work-plan and review-plan gap tools that the MCP board surface does not cover, hooks, commands, and a thin cockpit skill. Board/task truth comes from the genie MCP tools." provides_tools: # Default surface: exactly the three gap tools the MCP board surface does not cover. From f6910b8d02f6e30031e163e4a7cfef757ddf1bf6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 22:15:09 +0000 Subject: [PATCH 4/4] chore(version): bump to 5.260725.8 [auto-version] --- .claude-plugin/marketplace.json | 2 +- package.json | 2 +- plugins/genie/.claude-plugin/plugin.json | 2 +- plugins/genie/.codex-plugin/plugin.json | 2 +- plugins/genie/package.json | 2 +- plugins/hermes-genie/plugin.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index ed5f68519..292f40cd2 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "genie", - "version": "5.260725.7", + "version": "5.260725.8", "source": "./plugins/genie", "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project." } diff --git a/package.json b/package.json index 1db088693..9aa6ff05f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@automagik/genie", - "version": "5.260725.7", + "version": "5.260725.8", "description": "Collaborative terminal toolkit for human + AI workflows. NOTE: npm distribution discontinued 2026-05-09 — install via `curl -fsSL https://raw.githubusercontent.com/automagik-dev/genie/main/install.sh | bash` (cosign + SLSA verified). See https://automagik.dev/genie/release-process", "license": "MIT", diff --git a/plugins/genie/.claude-plugin/plugin.json b/plugins/genie/.claude-plugin/plugin.json index 67a1ecc0d..789ffd7f7 100644 --- a/plugins/genie/.claude-plugin/plugin.json +++ b/plugins/genie/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "genie", - "version": "5.260725.7", + "version": "5.260725.8", "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.", "author": { "name": "Namastex Labs" diff --git a/plugins/genie/.codex-plugin/plugin.json b/plugins/genie/.codex-plugin/plugin.json index 8783dcff6..507107a1e 100644 --- a/plugins/genie/.codex-plugin/plugin.json +++ b/plugins/genie/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "genie", - "version": "5.260725.7", + "version": "5.260725.8", "description": "Plan, execute, review, and ship software with Genie workflows in Codex.", "author": { "name": "Namastex Labs", diff --git a/plugins/genie/package.json b/plugins/genie/package.json index 046f5220b..3f132366d 100644 --- a/plugins/genie/package.json +++ b/plugins/genie/package.json @@ -1,6 +1,6 @@ { "name": "genie-plugin", - "version": "5.260725.7", + "version": "5.260725.8", "private": true, "description": "Runtime dependencies for genie bundled CLIs", "license": "MIT", diff --git a/plugins/hermes-genie/plugin.yaml b/plugins/hermes-genie/plugin.yaml index 36b9ade81..b38f694f4 100644 --- a/plugins/hermes-genie/plugin.yaml +++ b/plugins/hermes-genie/plugin.yaml @@ -1,5 +1,5 @@ name: genie -version: 5.260725.7 +version: 5.260725.8 description: "Native Hermes surface for Genie orchestration: read-only status plus work-plan and review-plan gap tools that the MCP board surface does not cover, hooks, commands, and a thin cockpit skill. Board/task truth comes from the genie MCP tools." provides_tools: # Default surface: exactly the three gap tools the MCP board surface does not cover.