From c54dbe19a6d38c437172db27fd00d5dfe120e732 Mon Sep 17 00:00:00 2001 From: Dimitris Marlagkoutsos Date: Tue, 8 Sep 2026 11:54:47 +0200 Subject: [PATCH 1/3] ci: pin every third-party action to a full-length commit SHA The org now refuses any action referenced by tag: The action actions/checkout@v7 is not allowed in Consensys-Incorporated/ocap-kernel because all actions must be pinned to a full-length commit SHA. That is a setup-job failure, so it takes down every job that references one before a step runs. Nothing has run green in this repository since 2026-09-03; the policy was turned on after the last commit that touched CI, which is why merging main does not help. Each tag is replaced by the commit it currently resolves to, with the release it names in a trailing comment. Dependabot reads that comment, so `github-actions` updates keep working and arrive as a SHA bump. Local `./.github/...` references are exempt and left alone. Co-Authored-By: Claude Opus 5 (1M context) --- .github/actions/playwright-install/action.yml | 2 +- .github/workflows/changelog-check.yml | 2 +- .github/workflows/claude.yml | 2 +- .github/workflows/coverage-report.yml | 4 ++-- .github/workflows/dependabot-dedupe.yml | 6 +++--- .github/workflows/lint-build-test.yml | 18 +++++++++--------- .github/workflows/main.yml | 12 ++++++------ .github/workflows/publish-gh-pages.yml | 6 +++--- .github/workflows/publish-release.yml | 18 +++++++++--------- 9 files changed, 35 insertions(+), 35 deletions(-) diff --git a/.github/actions/playwright-install/action.yml b/.github/actions/playwright-install/action.yml index bf935813aa..09faaf5ef5 100644 --- a/.github/actions/playwright-install/action.yml +++ b/.github/actions/playwright-install/action.yml @@ -6,7 +6,7 @@ runs: steps: - name: Cache Playwright browsers id: playwright-cache - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('yarn.lock') }} diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml index 9a2727c463..930dd3b607 100644 --- a/.github/workflows/changelog-check.yml +++ b/.github/workflows/changelog-check.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check changelog - uses: MetaMask/github-tools/.github/actions/check-changelog@v1 + uses: MetaMask/github-tools/.github/actions/check-changelog@d0f155aad5d7b5403d29a094c2f752238bf43237 # v1.18.2 with: base-branch: ${{ github.event.pull_request.base.ref }} head-ref: ${{ github.head_ref }} diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index c6220f8d87..1616adef2a 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -26,7 +26,7 @@ jobs: actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 1 diff --git a/.github/workflows/coverage-report.yml b/.github/workflows/coverage-report.yml index 8df9dce719..a29f6a48a9 100644 --- a/.github/workflows/coverage-report.yml +++ b/.github/workflows/coverage-report.yml @@ -11,10 +11,10 @@ jobs: pull-requests: write steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Download coverage artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: coverage path: coverage/ diff --git a/.github/workflows/dependabot-dedupe.yml b/.github/workflows/dependabot-dedupe.yml index 656bc80295..2f98e6f6b9 100644 --- a/.github/workflows/dependabot-dedupe.yml +++ b/.github/workflows/dependabot-dedupe.yml @@ -14,7 +14,7 @@ jobs: if: ${{ github.actor == 'dependabot[bot]' }} steps: - name: Check out repo - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.head_ref }} fetch-depth: 0 @@ -24,13 +24,13 @@ jobs: git config user.email "actions@github.com" - name: Set up Node - uses: actions/setup-node@v7 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 - name: Install Yarn run: corepack enable - name: Restore Yarn cache - uses: actions/setup-node@v7 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 cache: yarn diff --git a/.github/workflows/lint-build-test.yml b/.github/workflows/lint-build-test.yml index 37e88a8a41..4d2a83f939 100644 --- a/.github/workflows/lint-build-test.yml +++ b/.github/workflows/lint-build-test.yml @@ -12,7 +12,7 @@ jobs: node-version: [22.x, 24.x] steps: - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@v3 + uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 with: cache-node-modules: true is-high-risk-environment: false @@ -27,7 +27,7 @@ jobs: node-version: [24.x] steps: - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@v3 + uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 with: is-high-risk-environment: false node-version: ${{ matrix.node-version }} @@ -49,7 +49,7 @@ jobs: node-version: [24.x] steps: - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@v3 + uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 with: is-high-risk-environment: false node-version: ${{ matrix.node-version }} @@ -71,7 +71,7 @@ jobs: node-version: [24.x] steps: - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@v3 + uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 with: is-high-risk-environment: false node-version: ${{ matrix.node-version }} @@ -93,7 +93,7 @@ jobs: node-version: [22.x, 24.x] steps: - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@v3 + uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 with: is-high-risk-environment: false node-version: ${{ matrix.node-version }} @@ -102,7 +102,7 @@ jobs: - run: "yarn test --coverage=${{ matrix.node-version == '24.x' && 'true' || 'false' }}" - name: Upload coverage artifact if: ${{ matrix.node-version == '24.x' }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: coverage path: coverage/ @@ -124,7 +124,7 @@ jobs: node-version: [22.x, 24.x] steps: - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@v3 + uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 with: is-high-risk-environment: false node-version: ${{ matrix.node-version }} @@ -166,7 +166,7 @@ jobs: directory: evm-wallet-experiment steps: - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@v3 + uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 with: is-high-risk-environment: false node-version: ${{ matrix.node-version }} @@ -177,7 +177,7 @@ jobs: run: yarn workspace ${{ matrix.package }} test:e2e:ci - name: Upload test artifacts if: ${{ failure() && steps.e2e.conclusion == 'failure' }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: playwright-traces-${{ matrix.directory }} path: packages/${{ matrix.directory }}/test-results diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e66ecd75d2..6d311621f0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,13 +18,13 @@ jobs: skip-merge-queue: ${{ steps.check-skip-merge-queue.outputs.up-to-date }} steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: github.event_name == 'merge_group' - name: Check pull request merge queue status id: check-skip-merge-queue if: github.event_name == 'merge_group' - uses: MetaMask/github-tools/.github/actions/check-skip-merge-queue@v1 + uses: MetaMask/github-tools/.github/actions/check-skip-merge-queue@d0f155aad5d7b5403d29a094c2f752238bf43237 # v1.18.2 detect-changes: name: Detect changes @@ -34,7 +34,7 @@ jobs: has-ci: ${{ steps.changes.outputs.has-ci }} has-lint-targets: ${{ steps.changes.outputs.has-lint-targets }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Detect change categories @@ -121,7 +121,7 @@ jobs: (github.event_name != 'merge_group' || needs.check-skip-merge-queue.outputs.skip-merge-queue != 'true') && (needs.detect-changes.outputs.has-ci == 'true' || needs.detect-changes.outputs.has-code == 'true') steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Download actionlint id: download-actionlint run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/7fdc9630cc360ea1a469eed64ac6d78caeda1234/scripts/download-actionlint.bash) 1.6.25 @@ -150,7 +150,7 @@ jobs: node-version: [24.x] steps: - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@v3 + uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 with: is-high-risk-environment: false node-version: ${{ matrix.node-version }} @@ -194,7 +194,7 @@ jobs: IS_RELEASE: ${{ steps.is-release.outputs.IS_RELEASE }} steps: - id: is-release - uses: MetaMask/action-is-release@v2 + uses: MetaMask/action-is-release@3cd51b98fa98d1347d06f5961299b0172ee31ae8 # v2.3.0 with: commit-starts-with: 'Release [version],Release v[version],Release/[version],Release/v[version],Release `[version]`' diff --git a/.github/workflows/publish-gh-pages.yml b/.github/workflows/publish-gh-pages.yml index f1aa65ffaf..27a17fdf8a 100644 --- a/.github/workflows/publish-gh-pages.yml +++ b/.github/workflows/publish-gh-pages.yml @@ -26,16 +26,16 @@ jobs: id-token: write steps: - name: Download artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: ${{ inputs.artifact_name }} path: _site/${{ inputs.destination_dir }} - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v5 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: path: _site - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v5 + uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index b63689de47..44ec99da2c 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -13,15 +13,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@v3 + uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 with: is-high-risk-environment: true - - uses: MetaMask/action-publish-release@v3 + - uses: MetaMask/action-publish-release@f01f1be110d60fb07d86c880ce3d6bdb353524d3 # v3.3.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: yarn build - name: Upload build artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: publish-release-artifacts-${{ github.sha }} include-hidden-files: true @@ -41,16 +41,16 @@ jobs: needs: publish-release steps: - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@v3 + uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 with: is-high-risk-environment: true ref: ${{ github.sha }} - name: Restore build artifacts - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: publish-release-artifacts-${{ github.sha }} - name: Dry run publish to NPM - uses: MetaMask/action-npm-publish@v6 + uses: MetaMask/action-npm-publish@18df42148c35aabb98e00f9fda127d421af141df # v6.5.0 publish-npm: name: Publish to NPM @@ -64,15 +64,15 @@ jobs: needs: publish-npm-dry-run steps: - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@v3 + uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 with: is-high-risk-environment: true ref: ${{ github.sha }} - name: Restore build artifacts - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: publish-release-artifacts-${{ github.sha }} - name: Publish to NPM - uses: MetaMask/action-npm-publish@v6 + uses: MetaMask/action-npm-publish@18df42148c35aabb98e00f9fda127d421af141df # v6.5.0 with: npm-token: ${{ secrets.NPM_TOKEN }} From 5c2bd571c4eb5c7943f5e021a5ba079c86dadf15 Mon Sep 17 00:00:00 2001 From: Dimitris Marlagkoutsos Date: Tue, 8 Sep 2026 16:13:18 +0200 Subject: [PATCH 2/3] ci: own the setup, changelog, and merge-queue steps instead of calling out for them Pinning our own references was necessary but not sufficient. The policy resolves actions transitively, and the MetaMask actions we call reach mutable tags one layer down: action-checkout-and-setup -> cache/restore@v6, checkout@v7, setup-node@v6, action-retry-command@v1, cache/save@v6 github-tools/check-changelog -> checkout@v6 x2, setup-node@v6 github-tools/check-skip-merge-queue -> github-script@v9 x2 All three are on their latest releases, so no upgrade reaches them, and they live in an organization we no longer belong to. A pin one layer deep is no pin at all, so the only way to make the chain true is to hold it. `action-checkout-and-setup` becomes an explicit `actions/checkout` plus a local `setup-environment` action -- Node, Corepack, Yarn's download cache, install. Checkout cannot live inside it: `uses: ./...` resolves against the workspace, so the repository has to be on disk before the runner can find the file. The local action is narrower than the one it replaces. It drops the node_modules cache and the lookup-only fast path, keeping only Yarn's download cache, because those turn on a cache key that has to account for the native rebuilds `postinstall` runs. `yarn install` runs every job now, which costs time and buys the guarantee that what is on disk matches the lockfile. `check-changelog` cloned github-tools to run a script over the diff. The local job asks the same question directly: every package with a CHANGELOG that this pull request touches must have that changelog in the diff too. It compares against the merge base rather than the base tip, so commits landing on main afterwards are not read as this branch's. `no-changelog` still opts out. `check-skip-merge-queue` is ported as-is, its two scripts unchanged, onto a pinned `actions/github-script`. The release path still calls `action-npm-publish` and `action-is-release`, which have the same defect. Both are gated on `push`, so they block releases rather than pull requests, and neither is part of the `all-jobs-complete` gate. Publishing is where a mistake is expensive and OIDC permissions were only just fixed in #1041, so they are better handled upstream than rewritten here. Co-Authored-By: Claude Opus 5 (1M context) --- .../actions/check-skip-merge-queue/action.yml | 119 ++++++++++++++++++ .github/actions/setup-environment/action.yml | 65 ++++++++++ .github/workflows/changelog-check.yml | 60 +++++++-- .github/workflows/lint-build-test.yml | 50 ++++---- .github/workflows/main.yml | 9 +- .github/workflows/publish-release.yml | 26 ++-- 6 files changed, 287 insertions(+), 42 deletions(-) create mode 100644 .github/actions/check-skip-merge-queue/action.yml create mode 100644 .github/actions/setup-environment/action.yml diff --git a/.github/actions/check-skip-merge-queue/action.yml b/.github/actions/check-skip-merge-queue/action.yml new file mode 100644 index 0000000000..7b505b50e3 --- /dev/null +++ b/.github/actions/check-skip-merge-queue/action.yml @@ -0,0 +1,119 @@ +name: Check skip merge queue +description: >- + Report whether the pull request in a merge group is first in the queue and + already up to date with the base branch, in which case the checks about to run + would test a commit nothing has changed under. + + Only the report is produced here. Nothing can make the merge queue skip an + entry from inside a workflow, so callers gate their own jobs on the output. + +# Ported from MetaMask/github-tools/.github/actions/check-skip-merge-queue, +# which calls actions/github-script by mutable tag. See the note in +# ../checkout-and-setup/action.yml for why that cannot be pinned from outside. + +inputs: + head-ref: + description: The merge group's head ref, which carries the pull request number. + required: true + default: ${{ github.event.merge_group.head_ref }} + base-ref: + description: The branch the pull request is merging into. + required: true + default: main + github-token: + description: The token used to read the pull request and compare branches. + required: true + default: ${{ github.token }} + +outputs: + up-to-date: + description: >- + Whether the pull request is first in the queue and up to date with the + base branch. `false` whenever that cannot be established, so a failure + here costs a redundant run rather than an untested merge. + value: ${{ steps.up-to-date.outputs.up-to-date || 'false' }} + +runs: + using: composite + steps: + # `continue-on-error` on both steps: this is an optimization, and the safe + # answer is always available. A throw here leaves `up-to-date` empty, which + # the output above reads as `false`. + - name: Find the pull request behind the merge group + continue-on-error: true + id: pr-details + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + HEAD_REF: ${{ inputs.head-ref }} + with: + github-token: ${{ inputs.github-token }} + script: | + const { HEAD_REF } = process.env; + const match = HEAD_REF.match(/\/pr-([0-9]+)-/u); + if (!match) { + return core.setFailed(`Could not extract pull request number from head ref: "${HEAD_REF}".`); + } + + const number = parseInt(match[1], 10); + const result = await github.graphql(` + query($owner: String!, $name: String!, $number: Int!) { + repository(owner: $owner, name: $name) { + pullRequest(number: $number) { + headRefName + mergeQueueEntry { position } + } + } + } + `, { + owner: context.repo.owner, + name: context.repo.repo, + number, + }); + + if (!result.repository.pullRequest) { + return core.setFailed(`Pull request #${number} not found in repository "${context.repo.owner}/${context.repo.repo}".`); + } + + const position = result.repository.pullRequest.mergeQueueEntry?.position; + if (!position) { + return core.setFailed(`Pull request #${number} is not in the merge queue.`); + } + + core.setOutput('pr-number', number); + core.setOutput('pr-branch', result.repository.pullRequest.headRefName); + core.setOutput('merge-queue-position', position); + + - name: Check whether it is up to date with the base branch + continue-on-error: true + id: up-to-date + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + BASE_REF: ${{ inputs.base-ref }} + PR_BRANCH: ${{ steps.pr-details.outputs.pr-branch }} + MERGE_QUEUE_POSITION: ${{ steps.pr-details.outputs.merge-queue-position }} + with: + github-token: ${{ inputs.github-token }} + script: | + const { BASE_REF, PR_BRANCH, MERGE_QUEUE_POSITION } = process.env; + + // Only the first entry can be up to date with the base branch by + // definition; anything behind it is waiting on merges above it. + if (parseInt(MERGE_QUEUE_POSITION, 10) !== 1) { + core.info(`Pull request is not first in the merge queue (position: ${MERGE_QUEUE_POSITION}).`); + core.setOutput('up-to-date', 'false'); + return; + } + + const comparison = await github.rest.repos.compareCommitsWithBasehead({ + owner: context.repo.owner, + repo: context.repo.repo, + basehead: `${BASE_REF}...${PR_BRANCH}`, + }); + + if (comparison.data.status === 'identical' || comparison.data.status === 'ahead') { + core.info(`Pull request branch "${PR_BRANCH}" is up-to-date with base branch "${BASE_REF}".`); + core.setOutput('up-to-date', 'true'); + } else { + core.info(`Pull request branch "${PR_BRANCH}" is not up-to-date with base branch "${BASE_REF}".`); + core.setOutput('up-to-date', 'false'); + } diff --git a/.github/actions/setup-environment/action.yml b/.github/actions/setup-environment/action.yml new file mode 100644 index 0000000000..9fe1d67d59 --- /dev/null +++ b/.github/actions/setup-environment/action.yml @@ -0,0 +1,65 @@ +name: Set up environment +description: Set up Node.js and Yarn and install dependencies, in a repository already checked out. + +# Replaces the setup half of MetaMask/action-checkout-and-setup. That action is +# pinned by SHA where we call it, but calls actions/checkout, actions/setup-node +# and actions/cache by mutable tag internally, which this organization refuses -- +# rightly, since a pin one layer deep is no pin at all. Keeping the steps here is +# the only way to pin the whole chain, and the chain is short. +# +# Checkout is not part of this action and cannot be: `uses: ./...` resolves +# against the workspace, so the repository has to be on disk before the runner +# can find this file. Callers check out first, with their own pinned step. +# +# Deliberately narrower than the action it replaces: no node_modules cache and +# no lookup-only fast path, only Yarn's download cache. Those are worth real +# minutes on a monorepo this size, but they turn on a cache key that has to +# account for the native rebuilds `postinstall` runs, and getting that subtly +# wrong yields a job that passes against stale binaries. `yarn install` runs +# every time here, so what is on disk always matches the lockfile. + +inputs: + node-version: + description: The Node.js version to use. Defaults to the one in `.nvmrc`. + required: false + default: '' + cache: + description: >- + Whether to restore and save Yarn's download cache. Pass `false` from jobs + holding publish credentials, where a poisoned entry buys an attacker more + than the cache saves us. + required: false + default: 'true' + +runs: + using: composite + steps: + - name: Set up Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: ${{ inputs.node-version }} + node-version-file: ${{ inputs.node-version == '' && '.nvmrc' || '' }} + + - name: Enable Corepack + shell: bash + run: corepack enable + + # `enableGlobalCache: false` puts Yarn's downloads in `.yarn/cache`, which is + # gitignored, so without this every job refetches the whole dependency tree. + # Restoring a partial cache is still worth it, hence `restore-keys`: a + # lockfile change invalidates the key but leaves most entries reusable. + - name: Restore Yarn's download cache + if: inputs.cache == 'true' + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: .yarn/cache + key: yarn-${{ runner.os }}-${{ inputs.node-version }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + yarn-${{ runner.os }}-${{ inputs.node-version }}- + yarn-${{ runner.os }}- + + # The LavaMoat plugin in `.yarnrc.yml` runs the scripts `lavamoat.allowScripts` + # permits, so there is no separate `yarn allow-scripts` step to run. + - name: Install dependencies + shell: bash + run: yarn install --immutable diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml index 930dd3b607..88cc67c4c5 100644 --- a/.github/workflows/changelog-check.yml +++ b/.github/workflows/changelog-check.yml @@ -8,12 +8,58 @@ jobs: check-changelog: name: Check changelog runs-on: ubuntu-latest + # `no-changelog` is the escape hatch for a pull request that genuinely + # changes no published behaviour. + if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-changelog') }} steps: - - name: Check changelog - uses: MetaMask/github-tools/.github/actions/check-changelog@d0f155aad5d7b5403d29a094c2f752238bf43237 # v1.18.2 + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - base-branch: ${{ github.event.pull_request.base.ref }} - head-ref: ${{ github.head_ref }} - labels: ${{ toJSON(github.event.pull_request.labels) }} - pr-number: ${{ github.event.pull_request.number }} - repo: ${{ github.repository }} + # The whole comparison is against the merge base, so the history back + # to it has to be here. + fetch-depth: 0 + + - name: Require a changelog entry for every package this changes + shell: bash + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -euo pipefail + + # Against the merge base rather than the base tip, so that commits + # landing on the base branch after this pull request opened are not + # read as its own changes. + merge_base="$(git merge-base "$BASE_SHA" "$HEAD_SHA")" + changed="$(git diff --name-only "$merge_base" "$HEAD_SHA")" + + missing=() + for package_dir in packages/*/; do + package="${package_dir%/}" + changelog="${package}/CHANGELOG.md" + + # A package with no changelog is private and releases nothing, so + # there is no entry for it to be missing. This is the same set + # `yarn changelog:validate` covers, which runs `--no-private`. + [[ -f "$changelog" ]] || continue + + # Changes to the changelog itself do not oblige a changelog entry. + if ! grep -qE "^${package}/" <<< "$changed" \ + || [[ "$(grep -E "^${package}/" <<< "$changed")" == "$changelog" ]]; then + continue + fi + + grep -qxF "$changelog" <<< "$changed" || missing+=("$package") + done + + if [[ ${#missing[@]} -gt 0 ]]; then + echo "::error::These packages changed without a changelog entry:" + printf ' %s\n' "${missing[@]}" + echo + echo "Add an entry under '## [Unreleased]' in each package's CHANGELOG.md," + echo "or apply the 'no-changelog' label if this genuinely changes no" + echo "published behaviour." + exit 1 + fi + + echo "Every changed package has a changelog entry." diff --git a/.github/workflows/lint-build-test.yml b/.github/workflows/lint-build-test.yml index 4d2a83f939..a75af652a6 100644 --- a/.github/workflows/lint-build-test.yml +++ b/.github/workflows/lint-build-test.yml @@ -11,11 +11,11 @@ jobs: matrix: node-version: [22.x, 24.x] steps: - - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Set up environment + uses: ./.github/actions/setup-environment with: - cache-node-modules: true - is-high-risk-environment: false node-version: ${{ matrix.node-version }} lint: @@ -26,10 +26,11 @@ jobs: matrix: node-version: [24.x] steps: - - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Set up environment + uses: ./.github/actions/setup-environment with: - is-high-risk-environment: false node-version: ${{ matrix.node-version }} - run: yarn lint - name: Require clean working directory @@ -48,10 +49,11 @@ jobs: matrix: node-version: [24.x] steps: - - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Set up environment + uses: ./.github/actions/setup-environment with: - is-high-risk-environment: false node-version: ${{ matrix.node-version }} - run: yarn changelog:validate - name: Require clean working directory @@ -70,10 +72,11 @@ jobs: matrix: node-version: [24.x] steps: - - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Set up environment + uses: ./.github/actions/setup-environment with: - is-high-risk-environment: false node-version: ${{ matrix.node-version }} - run: yarn build - name: Require clean working directory @@ -92,10 +95,11 @@ jobs: matrix: node-version: [22.x, 24.x] steps: - - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Set up environment + uses: ./.github/actions/setup-environment with: - is-high-risk-environment: false node-version: ${{ matrix.node-version }} - uses: ./.github/actions/playwright-install - run: yarn build @@ -123,10 +127,11 @@ jobs: matrix: node-version: [22.x, 24.x] steps: - - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Set up environment + uses: ./.github/actions/setup-environment with: - is-high-risk-environment: false node-version: ${{ matrix.node-version }} - run: yarn build - run: yarn test:integration @@ -165,10 +170,11 @@ jobs: - package: '@ocap/evm-wallet-experiment' directory: evm-wallet-experiment steps: - - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Set up environment + uses: ./.github/actions/setup-environment with: - is-high-risk-environment: false node-version: ${{ matrix.node-version }} - uses: ./.github/actions/playwright-install - run: VITE_DB_FOLDER=e2e yarn build diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6d311621f0..6ae0250ee3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: - name: Check pull request merge queue status id: check-skip-merge-queue if: github.event_name == 'merge_group' - uses: MetaMask/github-tools/.github/actions/check-skip-merge-queue@d0f155aad5d7b5403d29a094c2f752238bf43237 # v1.18.2 + uses: ./.github/actions/check-skip-merge-queue detect-changes: name: Detect changes @@ -149,10 +149,11 @@ jobs: matrix: node-version: [24.x] steps: - - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Set up environment + uses: ./.github/actions/setup-environment with: - is-high-risk-environment: false node-version: ${{ matrix.node-version }} - run: yarn lint - name: Require clean working directory diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 44ec99da2c..55635ae122 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -12,10 +12,12 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Set up environment + uses: ./.github/actions/setup-environment with: - is-high-risk-environment: true + cache: false - uses: MetaMask/action-publish-release@f01f1be110d60fb07d86c880ce3d6bdb353524d3 # v3.3.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -40,11 +42,14 @@ jobs: runs-on: ubuntu-latest needs: publish-release steps: - - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - is-high-risk-environment: true ref: ${{ github.sha }} + - name: Set up environment + uses: ./.github/actions/setup-environment + with: + cache: false - name: Restore build artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -63,11 +68,14 @@ jobs: runs-on: ubuntu-latest needs: publish-npm-dry-run steps: - - name: Checkout and setup environment - uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0 + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - is-high-risk-environment: true ref: ${{ github.sha }} + - name: Set up environment + uses: ./.github/actions/setup-environment + with: + cache: false - name: Restore build artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: From 905a499f3d2ee706e90198b31100d11f34d81067 Mon Sep 17 00:00:00 2001 From: Dimitris Marlagkoutsos Date: Tue, 8 Sep 2026 17:45:05 +0200 Subject: [PATCH 3/3] fix(kernel-store): move to better-sqlite3 13, which survives teardown on Node 24.20 On Node 24.20.0, `better-sqlite3` 12.x aborts the process while it is shutting down: node::RemoveEnvironmentCleanupHook(Isolate*, CleanupHook, void*) at ../src/api/hooks.cc:142 Assertion failed: (env) != nullptr Statement::~Statement() [better_sqlite3.node] The destructor runs after the environment is gone and asserts on the way out. Everything it kills has already passed, so it reads as a worker dying rather than as a test failure, and vitest reports only that the fork exited. Not a missing `close`. The suites that hit it close their databases; a plain script, a vitest worker holding an open database, and one holding an open database plus a live worker thread are all clean on 24.20.0. It takes the whole kernel to provoke, and the same code on 24.18.0 is clean, which places it between the two Node releases rather than in this repository. The tests never ran into it because CI floats on `24.x` and had not run since 2026-09-03, when the runner still resolved that to 24.19.0. 13.x is a major, but it moves none of the surface this driver uses -- `prepare`, `run`, `get`, `all`, `iterate`, `pluck`, `transaction`, `exec`, `close` -- and its Node floor is the one this package already sets. Verified on 24.20.0: the unit suite, the integration suite, and the `kernel-node-runtime` e2e all run without the abort, and kernel-store passes on 22.23.2. Co-Authored-By: Claude Opus 5 (1M context) --- packages/kernel-store/CHANGELOG.md | 6 +++++ packages/kernel-store/package.json | 2 +- yarn.lock | 39 +++++++++--------------------- 3 files changed, 18 insertions(+), 29 deletions(-) diff --git a/packages/kernel-store/CHANGELOG.md b/packages/kernel-store/CHANGELOG.md index 51e5eb1116..502a921823 100644 --- a/packages/kernel-store/CHANGELOG.md +++ b/packages/kernel-store/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **BREAKING:** Bump `better-sqlite3` from `^12.4.1` to `^13.0.3`, which requires Node 22 or later — the same floor this package already declares + - On Node 24.20.0, 12.x aborts the process during teardown: `Statement::~Statement` reaches `RemoveEnvironmentCleanupHook` after the environment is gone, and Node asserts. The tests it kills have already passed, so it surfaces as a worker dying rather than as a failure anyone can read + - Nothing in this package's driver changes. The 13.x major touches none of `prepare`, `run`, `get`, `all`, `iterate`, `pluck`, `transaction`, `exec`, or `close` + ### Fixed - `rollbackSavepoint` discards the enclosing transaction when `ROLLBACK TO` itself fails, instead of leaving the savepoint on its stack and the transaction open ([#1005](https://github.com/MetaMask/ocap-kernel/pull/1005)) diff --git a/packages/kernel-store/package.json b/packages/kernel-store/package.json index 51245d50fd..8b0ed94b04 100644 --- a/packages/kernel-store/package.json +++ b/packages/kernel-store/package.json @@ -79,7 +79,7 @@ "dependencies": { "@metamask/logger": "workspace:^", "@sqlite.org/sqlite-wasm": "^3.49.1-build3", - "better-sqlite3": "^12.4.1", + "better-sqlite3": "^13.0.3", "ses": "^1.14.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 436bb1784a..4335442ee0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2628,7 +2628,7 @@ __metadata: "@typescript-eslint/parser": "npm:^8.29.0" "@typescript-eslint/utils": "npm:^8.29.0" "@vitest/eslint-plugin": "npm:^1.6.14" - better-sqlite3: "npm:^12.4.1" + better-sqlite3: "npm:^13.0.3" depcheck: "npm:^1.4.7" eslint: "npm:^9.23.0" eslint-config-prettier: "npm:^10.1.1" @@ -7282,14 +7282,13 @@ __metadata: languageName: node linkType: hard -"better-sqlite3@npm:^12.4.1": - version: 12.4.6 - resolution: "better-sqlite3@npm:12.4.6" +"better-sqlite3@npm:^13.0.3": + version: 13.0.3 + resolution: "better-sqlite3@npm:13.0.3" dependencies: - bindings: "npm:^1.5.0" + node-addon-api: "npm:^8.0.0" node-gyp: "npm:latest" - prebuild-install: "npm:^7.1.1" - checksum: 10/383b1acc7c9f03e0677ab2aad5d3a44b4d36565396b1d10dfa5ebcf7840799afeb180c2b7c9813d7ffd1d5c92e497d5c9c52118fe7c5839987df87370df69943 + checksum: 10/035f8fa70784762fd73e1dbeb6f157186abb97705ff759b312cfb0dc1c675fd1c6b5378d2c036758000ce9da1d49bf61689531aa2718a7e32dc272c114a4c71f languageName: node linkType: hard @@ -7328,15 +7327,6 @@ __metadata: languageName: node linkType: hard -"bindings@npm:^1.5.0": - version: 1.5.0 - resolution: "bindings@npm:1.5.0" - dependencies: - file-uri-to-path: "npm:1.0.0" - checksum: 10/593d5ae975ffba15fbbb4788fe5abd1e125afbab849ab967ab43691d27d6483751805d98cb92f7ac24a2439a8a8678cd0131c535d5d63de84e383b0ce2786133 - languageName: node - linkType: hard - "bl@npm:^4.0.3": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -9700,13 +9690,6 @@ __metadata: languageName: node linkType: hard -"file-uri-to-path@npm:1.0.0": - version: 1.0.0 - resolution: "file-uri-to-path@npm:1.0.0" - checksum: 10/b648580bdd893a008c92c7ecc96c3ee57a5e7b6c4c18a9a09b44fb5d36d79146f8e442578bc0e173dc027adf3987e254ba1dfd6e3ec998b7c282873010502144 - languageName: node - linkType: hard - "fill-range@npm:^7.1.1": version: 7.1.1 resolution: "fill-range@npm:7.1.1" @@ -12515,12 +12498,12 @@ __metadata: languageName: node linkType: hard -"node-addon-api@npm:^8.3.0, node-addon-api@npm:^8.3.1": - version: 8.5.0 - resolution: "node-addon-api@npm:8.5.0" +"node-addon-api@npm:^8.0.0, node-addon-api@npm:^8.3.0, node-addon-api@npm:^8.3.1": + version: 8.9.2 + resolution: "node-addon-api@npm:8.9.2" dependencies: node-gyp: "npm:latest" - checksum: 10/9a893f4f835fbc3908e0070f7bcacf36e37fd06be8008409b104c30df4092a0d9a29927b3a74cdbc1d34338274ba4116d597a41f573e06c29538a1a70d07413f + checksum: 10/5354f9b53631d99a952408112d795f36782fd41c0ac073bd91814805fe931b619c743689d0cedcc310c0ae8392fc9b104b2852d28cce294236407ca57d01743e languageName: node linkType: hard @@ -13527,7 +13510,7 @@ __metadata: languageName: node linkType: hard -"prebuild-install@npm:^7.1.1, prebuild-install@npm:^7.1.3": +"prebuild-install@npm:^7.1.3": version: 7.1.3 resolution: "prebuild-install@npm:7.1.3" dependencies: