diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ae457525..ddff1460 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,6 @@ # Replace @dotta if a different maintainer or team should own release infrastructure. -.github/workflows/release-*.yml @dotta +.github/workflows/release.yml @dotta scripts/release*.sh @dotta scripts/release-*.mjs @dotta scripts/create-github-release.sh @dotta diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml deleted file mode 100644 index 093f5427..00000000 --- a/.github/workflows/release-canary.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: Release Canary - -on: - push: - branches: - - master - -concurrency: - group: release-canary-master - cancel-in-progress: false - -jobs: - verify: - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: read - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.15.4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 24 - cache: pnpm - - - name: Install dependencies - run: pnpm install --no-frozen-lockfile - - - name: Typecheck - run: pnpm -r typecheck - - - name: Run tests - run: pnpm test:run - - - name: Build - run: pnpm build - - publish: - needs: verify - runs-on: ubuntu-latest - timeout-minutes: 45 - environment: npm-canary - permissions: - contents: write - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.15.4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 24 - cache: pnpm - - - name: Install dependencies - run: pnpm install --no-frozen-lockfile - - - name: Configure git author - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Publish canary - env: - GITHUB_ACTIONS: "true" - run: ./scripts/release.sh canary --skip-verify - - - name: Push canary tag - run: | - tag="$(git tag --points-at HEAD | grep '^canary/v' | head -1)" - if [ -z "$tag" ]; then - echo "Error: no canary tag points at HEAD after release." >&2 - exit 1 - fi - git push origin "refs/tags/${tag}" diff --git a/.github/workflows/release-stable.yml b/.github/workflows/release.yml similarity index 61% rename from .github/workflows/release-stable.yml rename to .github/workflows/release.yml index 316a624c..5342b20f 100644 --- a/.github/workflows/release-stable.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,9 @@ -name: Release Stable +name: Release on: + push: + branches: + - master workflow_dispatch: inputs: source_ref: @@ -19,11 +22,97 @@ on: default: false concurrency: - group: release-stable + group: release-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: false jobs: - verify: + verify_canary: + if: github.event_name == 'push' + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + run: pnpm install --no-frozen-lockfile + + - name: Typecheck + run: pnpm -r typecheck + + - name: Run tests + run: pnpm test:run + + - name: Build + run: pnpm build + + publish_canary: + if: github.event_name == 'push' + needs: verify_canary + runs-on: ubuntu-latest + timeout-minutes: 45 + environment: npm-canary + permissions: + contents: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + run: pnpm install --no-frozen-lockfile + + - name: Configure git author + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Publish canary + env: + GITHUB_ACTIONS: "true" + run: ./scripts/release.sh canary --skip-verify + + - name: Push canary tag + run: | + tag="$(git tag --points-at HEAD | grep '^canary/v' | head -1)" + if [ -z "$tag" ]; then + echo "Error: no canary tag points at HEAD after release." >&2 + exit 1 + fi + git push origin "refs/tags/${tag}" + + verify_stable: + if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest timeout-minutes: 30 permissions: @@ -59,9 +148,9 @@ jobs: - name: Build run: pnpm build - preview: - if: inputs.dry_run - needs: verify + preview_stable: + if: github.event_name == 'workflow_dispatch' && inputs.dry_run + needs: verify_stable runs-on: ubuntu-latest timeout-minutes: 45 permissions: @@ -98,9 +187,9 @@ jobs: fi ./scripts/release.sh "${args[@]}" - publish: - if: ${{ !inputs.dry_run }} - needs: verify + publish_stable: + if: github.event_name == 'workflow_dispatch' && !inputs.dry_run + needs: verify_stable runs-on: ubuntu-latest timeout-minutes: 45 environment: npm-stable diff --git a/doc/RELEASE-AUTOMATION-SETUP.md b/doc/RELEASE-AUTOMATION-SETUP.md index 5ef0bbf8..7e629c14 100644 --- a/doc/RELEASE-AUTOMATION-SETUP.md +++ b/doc/RELEASE-AUTOMATION-SETUP.md @@ -9,8 +9,7 @@ This document covers the GitHub and npm setup required for the current Paperclip Repo-side files that depend on this setup: -- `.github/workflows/release-canary.yml` -- `.github/workflows/release-stable.yml` +- `.github/workflows/release.yml` - `.github/CODEOWNERS` Note: @@ -24,8 +23,7 @@ Before touching GitHub or npm settings, merge the release automation code so the Required files: -- `.github/workflows/release-canary.yml` -- `.github/workflows/release-stable.yml` +- `.github/workflows/release.yml` - `.github/CODEOWNERS` ## 2. Configure npm Trusted Publishing @@ -46,21 +44,26 @@ For each package: 2. go to the package settings / publishing access area 3. add a trusted publisher for the GitHub repository `paperclipai/paperclip` -### 2.2. Add two trusted publisher entries per package +### 2.2. Add one trusted publisher entry per package -Because npm trusted publishing is tied to the workflow filename, configure both: +npm currently allows one trusted publisher configuration per package. -- workflow: `.github/workflows/release-canary.yml` -- workflow: `.github/workflows/release-stable.yml` +Configure: + +- workflow: `.github/workflows/release.yml` Repository: - `paperclipai/paperclip` -Branch expectations: +Environment name: -- canary workflow should only ever run from `master` -- stable workflow is manual but should also be restricted to `master` by GitHub environment policy +- leave the npm trusted-publisher environment field blank + +Why: + +- the single `release.yml` workflow handles both canary and stable publishing +- GitHub environments `npm-canary` and `npm-stable` still enforce different approval rules on the GitHub side ### 2.3. Verify trusted publishing before removing old auth @@ -167,8 +170,7 @@ If `@dotta` is not the right reviewer identity in the public repo, change it bef These files should always trigger code owner review: -- `.github/workflows/release-canary.yml` -- `.github/workflows/release-stable.yml` +- `.github/workflows/release.yml` - `scripts/release.sh` - `scripts/release-lib.sh` - `scripts/release-package-map.mjs` @@ -198,7 +200,7 @@ This keeps LLM spending intentional and avoids a high-value token sitting in Act After setup: 1. merge a harmless commit to `master` -2. open the `Release Canary` workflow run +2. open the `Release` workflow run triggered by that push 3. confirm it passes verification 4. confirm publish succeeds under the `npm-canary` environment 5. confirm npm now shows a new `canary` release @@ -215,7 +217,7 @@ npx paperclipai@canary onboard After at least one good canary exists: 1. prepare `releases/vYYYY.M.D.md` on the source commit you want to promote -2. open `Actions` -> `Release Stable` +2. open `Actions` -> `Release` 3. run it with: - `source_ref`: the tested commit SHA or canary tag source commit - `stable_date`: leave blank or set the intended UTC date diff --git a/doc/RELEASING.md b/doc/RELEASING.md index 0b94e9ec..cb7a14fe 100644 --- a/doc/RELEASING.md +++ b/doc/RELEASING.md @@ -53,7 +53,7 @@ Canaries only cover the first two surfaces plus an internal traceability tag. ### Canary -Every push to `master` runs [`.github/workflows/release-canary.yml`](../.github/workflows/release-canary.yml). +Every push to `master` runs the canary path inside [`.github/workflows/release.yml`](../.github/workflows/release.yml). It: @@ -70,7 +70,7 @@ npx paperclipai@canary onboard ### Stable -Use [`.github/workflows/release-stable.yml`](../.github/workflows/release-stable.yml) from the Actions tab. +Use [`.github/workflows/release.yml`](../.github/workflows/release.yml) from the Actions tab with the manual `workflow_dispatch` inputs. Inputs: diff --git a/doc/plans/2026-03-17-release-automation-and-versioning.md b/doc/plans/2026-03-17-release-automation-and-versioning.md index 45d7cbe2..35416956 100644 --- a/doc/plans/2026-03-17-release-automation-and-versioning.md +++ b/doc/plans/2026-03-17-release-automation-and-versioning.md @@ -158,20 +158,17 @@ This is the cleanest answer to the open-repo security concern. ### Concrete controls -#### 1. Split canary and stable into separate workflow files +#### 1. Use one release workflow file -Do not use one workflow file for both. +Use one workflow filename for both canary and stable publishing: -Recommended: - -- `.github/workflows/release-canary.yml` -- `.github/workflows/release-stable.yml` +- `.github/workflows/release.yml` Why: - npm trusted publishing is configured per workflow filename -- canary and stable need different blast radii -- stable should have stronger GitHub environment rules than canary +- npm currently allows one trusted publisher configuration per package +- GitHub environments can still provide separate canary/stable approval rules inside the same workflow #### 2. Use separate GitHub environments @@ -438,7 +435,7 @@ That is acceptable if canaries stay clearly separate: ### Phase 1: Security foundation -1. Create `release-canary.yml` and `release-stable.yml` +1. Create `release.yml` 2. Configure npm trusted publishers for all public packages 3. Create `npm-canary` and `npm-stable` environments 4. Add `CODEOWNERS` protection for release files