From 79bcdb6996001e4bbc7fa47737ccd6d654985518 Mon Sep 17 00:00:00 2001 From: Gerard Date: Mon, 1 Jun 2026 15:02:45 +0200 Subject: [PATCH] ci: gate OIDC publish mint surface + durable mutation-score reporter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two CI/tooling hardenings (no src/, no version bumps): 1. OIDC publish-token mint-surface gate (Sapper M2 H1 / STALE-4) - Narrow publish.yml paths from '**/package.json' to 'packages/*/package.json' so only a package-version edit (the real release signal) can start the id-token:write mint job. Root/devDep manifest churn no longer mints OIDC tokens. - Add `environment: npm-publish` to the publish job. The npm-publish environment is created with a required-reviewer protection rule + protected-branch deployment policy — a human gate in front of the mint. 2. Durable mutation-score reporter (Scout M5 / QM M4 F-4) - Add json + html Stryker reporters across all 11 packages, writing to packages//reports/mutation/ (gitignored — CI artifact only). - Add an always()-conditioned upload-artifact step in the CI mutation gate so per-package scores are retrievable run artifacts, not just ephemeral stdout. break:90 thresholds unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 13 +++++++++++++ .github/workflows/publish.yml | 11 ++++++++++- packages/adapter-store/stryker.config.mjs | 4 +++- packages/cached-adapter-store/stryker.config.mjs | 4 +++- packages/dialog/stryker.config.mjs | 4 +++- packages/helpers/stryker.config.mjs | 4 +++- packages/http/stryker.config.mjs | 4 +++- packages/loading/stryker.config.mjs | 4 +++- packages/router/stryker.config.mjs | 4 +++- packages/storage/stryker.config.mjs | 4 +++- packages/theme/stryker.config.mjs | 4 +++- packages/toast/stryker.config.mjs | 4 +++- packages/translation/stryker.config.mjs | 4 +++- 13 files changed, 56 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9ec23f..831fc0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,3 +23,16 @@ jobs: - run: npm run lint:pkg - run: npm run test:coverage - run: npm run test:mutation + # Durable mutation-score artifact: each package's Stryker run writes + # JSON + HTML to packages//reports/mutation/. Retain them as a + # downloadable run artifact so the per-package score is retrievable + # after the run, not just ephemeral stdout. if: always() captures the + # report even when the break:90 gate fails (the score is the evidence). + - name: Upload mutation reports + if: always() + uses: actions/upload-artifact@v7 + with: + name: mutation-reports + path: packages/*/reports/mutation/ + retention-days: 30 + if-no-files-found: warn diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 092a8e9..9774e54 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,8 +3,12 @@ name: Publish on: push: branches: [main] + # Narrowed from '**/package.json' to package manifests only: a real + # release signal is a version edit under packages//package.json. + # Root/devDep manifest churn (dependabot, tooling) must NOT start the + # OIDC-minting publish job. (Sapper M2 H1 / STALE-4.) paths: - - '**/package.json' + - 'packages/*/package.json' jobs: build: @@ -28,6 +32,11 @@ jobs: publish: needs: build runs-on: ubuntu-latest + # Environment gate: the OIDC mint (id-token: write) is fronted by the + # 'npm-publish' deployment environment, whose protection rule (required + # reviewer / wait timer) gates the token mint behind a human/policy + # checkpoint. (Sapper M2 H1 / STALE-4.) + environment: npm-publish permissions: contents: write id-token: write diff --git a/packages/adapter-store/stryker.config.mjs b/packages/adapter-store/stryker.config.mjs index cea608c..8617354 100644 --- a/packages/adapter-store/stryker.config.mjs +++ b/packages/adapter-store/stryker.config.mjs @@ -4,7 +4,9 @@ export default { vitest: {configFile: 'vitest.config.ts'}, mutate: ['src/**/*.ts', '!src/**/types.ts'], thresholds: {high: 95, low: 90, break: 90}, - reporters: ['clear-text', 'progress'], + reporters: ['clear-text', 'progress', 'json', 'html'], + jsonReporter: {fileName: 'reports/mutation/mutation.json'}, + htmlReporter: {fileName: 'reports/mutation/mutation.html'}, incremental: true, incrementalFile: '.stryker-incremental.json', cleanTempDir: 'always', diff --git a/packages/cached-adapter-store/stryker.config.mjs b/packages/cached-adapter-store/stryker.config.mjs index cea608c..8617354 100644 --- a/packages/cached-adapter-store/stryker.config.mjs +++ b/packages/cached-adapter-store/stryker.config.mjs @@ -4,7 +4,9 @@ export default { vitest: {configFile: 'vitest.config.ts'}, mutate: ['src/**/*.ts', '!src/**/types.ts'], thresholds: {high: 95, low: 90, break: 90}, - reporters: ['clear-text', 'progress'], + reporters: ['clear-text', 'progress', 'json', 'html'], + jsonReporter: {fileName: 'reports/mutation/mutation.json'}, + htmlReporter: {fileName: 'reports/mutation/mutation.html'}, incremental: true, incrementalFile: '.stryker-incremental.json', cleanTempDir: 'always', diff --git a/packages/dialog/stryker.config.mjs b/packages/dialog/stryker.config.mjs index cea608c..8617354 100644 --- a/packages/dialog/stryker.config.mjs +++ b/packages/dialog/stryker.config.mjs @@ -4,7 +4,9 @@ export default { vitest: {configFile: 'vitest.config.ts'}, mutate: ['src/**/*.ts', '!src/**/types.ts'], thresholds: {high: 95, low: 90, break: 90}, - reporters: ['clear-text', 'progress'], + reporters: ['clear-text', 'progress', 'json', 'html'], + jsonReporter: {fileName: 'reports/mutation/mutation.json'}, + htmlReporter: {fileName: 'reports/mutation/mutation.html'}, incremental: true, incrementalFile: '.stryker-incremental.json', cleanTempDir: 'always', diff --git a/packages/helpers/stryker.config.mjs b/packages/helpers/stryker.config.mjs index cea608c..8617354 100644 --- a/packages/helpers/stryker.config.mjs +++ b/packages/helpers/stryker.config.mjs @@ -4,7 +4,9 @@ export default { vitest: {configFile: 'vitest.config.ts'}, mutate: ['src/**/*.ts', '!src/**/types.ts'], thresholds: {high: 95, low: 90, break: 90}, - reporters: ['clear-text', 'progress'], + reporters: ['clear-text', 'progress', 'json', 'html'], + jsonReporter: {fileName: 'reports/mutation/mutation.json'}, + htmlReporter: {fileName: 'reports/mutation/mutation.html'}, incremental: true, incrementalFile: '.stryker-incremental.json', cleanTempDir: 'always', diff --git a/packages/http/stryker.config.mjs b/packages/http/stryker.config.mjs index cea608c..8617354 100644 --- a/packages/http/stryker.config.mjs +++ b/packages/http/stryker.config.mjs @@ -4,7 +4,9 @@ export default { vitest: {configFile: 'vitest.config.ts'}, mutate: ['src/**/*.ts', '!src/**/types.ts'], thresholds: {high: 95, low: 90, break: 90}, - reporters: ['clear-text', 'progress'], + reporters: ['clear-text', 'progress', 'json', 'html'], + jsonReporter: {fileName: 'reports/mutation/mutation.json'}, + htmlReporter: {fileName: 'reports/mutation/mutation.html'}, incremental: true, incrementalFile: '.stryker-incremental.json', cleanTempDir: 'always', diff --git a/packages/loading/stryker.config.mjs b/packages/loading/stryker.config.mjs index cea608c..8617354 100644 --- a/packages/loading/stryker.config.mjs +++ b/packages/loading/stryker.config.mjs @@ -4,7 +4,9 @@ export default { vitest: {configFile: 'vitest.config.ts'}, mutate: ['src/**/*.ts', '!src/**/types.ts'], thresholds: {high: 95, low: 90, break: 90}, - reporters: ['clear-text', 'progress'], + reporters: ['clear-text', 'progress', 'json', 'html'], + jsonReporter: {fileName: 'reports/mutation/mutation.json'}, + htmlReporter: {fileName: 'reports/mutation/mutation.html'}, incremental: true, incrementalFile: '.stryker-incremental.json', cleanTempDir: 'always', diff --git a/packages/router/stryker.config.mjs b/packages/router/stryker.config.mjs index cea608c..8617354 100644 --- a/packages/router/stryker.config.mjs +++ b/packages/router/stryker.config.mjs @@ -4,7 +4,9 @@ export default { vitest: {configFile: 'vitest.config.ts'}, mutate: ['src/**/*.ts', '!src/**/types.ts'], thresholds: {high: 95, low: 90, break: 90}, - reporters: ['clear-text', 'progress'], + reporters: ['clear-text', 'progress', 'json', 'html'], + jsonReporter: {fileName: 'reports/mutation/mutation.json'}, + htmlReporter: {fileName: 'reports/mutation/mutation.html'}, incremental: true, incrementalFile: '.stryker-incremental.json', cleanTempDir: 'always', diff --git a/packages/storage/stryker.config.mjs b/packages/storage/stryker.config.mjs index cea608c..8617354 100644 --- a/packages/storage/stryker.config.mjs +++ b/packages/storage/stryker.config.mjs @@ -4,7 +4,9 @@ export default { vitest: {configFile: 'vitest.config.ts'}, mutate: ['src/**/*.ts', '!src/**/types.ts'], thresholds: {high: 95, low: 90, break: 90}, - reporters: ['clear-text', 'progress'], + reporters: ['clear-text', 'progress', 'json', 'html'], + jsonReporter: {fileName: 'reports/mutation/mutation.json'}, + htmlReporter: {fileName: 'reports/mutation/mutation.html'}, incremental: true, incrementalFile: '.stryker-incremental.json', cleanTempDir: 'always', diff --git a/packages/theme/stryker.config.mjs b/packages/theme/stryker.config.mjs index cea608c..8617354 100644 --- a/packages/theme/stryker.config.mjs +++ b/packages/theme/stryker.config.mjs @@ -4,7 +4,9 @@ export default { vitest: {configFile: 'vitest.config.ts'}, mutate: ['src/**/*.ts', '!src/**/types.ts'], thresholds: {high: 95, low: 90, break: 90}, - reporters: ['clear-text', 'progress'], + reporters: ['clear-text', 'progress', 'json', 'html'], + jsonReporter: {fileName: 'reports/mutation/mutation.json'}, + htmlReporter: {fileName: 'reports/mutation/mutation.html'}, incremental: true, incrementalFile: '.stryker-incremental.json', cleanTempDir: 'always', diff --git a/packages/toast/stryker.config.mjs b/packages/toast/stryker.config.mjs index cea608c..8617354 100644 --- a/packages/toast/stryker.config.mjs +++ b/packages/toast/stryker.config.mjs @@ -4,7 +4,9 @@ export default { vitest: {configFile: 'vitest.config.ts'}, mutate: ['src/**/*.ts', '!src/**/types.ts'], thresholds: {high: 95, low: 90, break: 90}, - reporters: ['clear-text', 'progress'], + reporters: ['clear-text', 'progress', 'json', 'html'], + jsonReporter: {fileName: 'reports/mutation/mutation.json'}, + htmlReporter: {fileName: 'reports/mutation/mutation.html'}, incremental: true, incrementalFile: '.stryker-incremental.json', cleanTempDir: 'always', diff --git a/packages/translation/stryker.config.mjs b/packages/translation/stryker.config.mjs index cea608c..8617354 100644 --- a/packages/translation/stryker.config.mjs +++ b/packages/translation/stryker.config.mjs @@ -4,7 +4,9 @@ export default { vitest: {configFile: 'vitest.config.ts'}, mutate: ['src/**/*.ts', '!src/**/types.ts'], thresholds: {high: 95, low: 90, break: 90}, - reporters: ['clear-text', 'progress'], + reporters: ['clear-text', 'progress', 'json', 'html'], + jsonReporter: {fileName: 'reports/mutation/mutation.json'}, + htmlReporter: {fileName: 'reports/mutation/mutation.html'}, incremental: true, incrementalFile: '.stryker-incremental.json', cleanTempDir: 'always',