mirror of
https://github.com/stablyai/orca.git
synced 2026-10-09 00:02:39 +00:00
Use the measured pnpm lookup policy automatically in hosted root CI (#24951)
* Select lookup automatically for the measured hosted root-install profile * Record hosted automatic-mode cold cache publication proof
This commit is contained in:
@@ -7,9 +7,9 @@ inputs:
|
||||
required: false
|
||||
default: 'true'
|
||||
cache-pnpm-store-lookup-only:
|
||||
description: On non-PR producers, refresh existing stores without downloading and save stores on a miss.
|
||||
description: Auto uses measured hosted Node 24 root installs; true forces lookup, false retains archive restoration.
|
||||
required: false
|
||||
default: 'false'
|
||||
default: auto
|
||||
cache-pnpm-verification:
|
||||
description: Restore pnpm's policy-checked lockfile verification record.
|
||||
required: false
|
||||
@@ -67,6 +67,30 @@ outputs:
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Resolve pnpm store mode
|
||||
id: pnpm-store-mode
|
||||
if: >-
|
||||
github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' &&
|
||||
(inputs.cache-pnpm-store-lookup-only == 'true' ||
|
||||
(inputs.cache-pnpm-store-lookup-only == 'auto' &&
|
||||
inputs.cache-dependency-path == 'pnpm-lock.yaml' &&
|
||||
runner.environment == 'github-hosted' && job.container.id == '' &&
|
||||
(runner.os == 'Linux' || runner.os == 'macOS' || runner.os == 'Windows') &&
|
||||
(runner.arch == 'X64' || runner.arch == 'ARM64') &&
|
||||
(inputs.node-version == '' || inputs.node-version == '24')))
|
||||
shell: bash
|
||||
env:
|
||||
LOOKUP_REQUEST: ${{ inputs.cache-pnpm-store-lookup-only }}
|
||||
run: |
|
||||
lookup_only=true
|
||||
case "$LOOKUP_REQUEST" in
|
||||
[aA][uU][tT][oO])
|
||||
# Hosted runners have Node for this manifest-only check before toolchain setup.
|
||||
lookup_only="$(node -p 'const p = require("./package.json"); p.engines?.node === "24" && typeof p.packageManager === "string" && p.packageManager.split("+")[0] === "pnpm@12.8.1"')"
|
||||
;;
|
||||
esac
|
||||
printf 'lookup-only=%s\n' "$lookup_only" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# setup-node needs pnpm on PATH to locate and restore its store.
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/setup@v2
|
||||
@@ -80,7 +104,7 @@ runs:
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: package.json
|
||||
cache: ${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && inputs.cache-pnpm-store-lookup-only != 'true' && 'pnpm' || '' }}
|
||||
cache: ${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && steps.pnpm-store-mode.outputs.lookup-only != 'true' && 'pnpm' || '' }}
|
||||
cache-dependency-path: ${{ inputs.cache-dependency-path }}
|
||||
package-manager-cache: false
|
||||
|
||||
@@ -90,7 +114,7 @@ runs:
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
cache: ${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && inputs.cache-pnpm-store-lookup-only != 'true' && 'pnpm' || '' }}
|
||||
cache: ${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && steps.pnpm-store-mode.outputs.lookup-only != 'true' && 'pnpm' || '' }}
|
||||
cache-dependency-path: ${{ inputs.cache-dependency-path }}
|
||||
package-manager-cache: false
|
||||
|
||||
@@ -104,11 +128,11 @@ runs:
|
||||
!(runner.arch == 'X64' && contains(inputs.cache-dependency-path, 'mobile/pnpm-lock.yaml')) &&
|
||||
!((runner.arch == 'X64' || runner.arch == 'ARM64') && inputs.cache-dependency-path == 'pnpm-lock.yaml')) ||
|
||||
(github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' &&
|
||||
inputs.cache-pnpm-store-lookup-only == 'true')
|
||||
steps.pnpm-store-mode.outputs.lookup-only == 'true')
|
||||
shell: bash
|
||||
env:
|
||||
LOCKFILE_HASH: ${{ hashFiles(inputs.cache-dependency-path) }}
|
||||
STORE_LOOKUP_ONLY: ${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store-lookup-only == 'true' }}
|
||||
STORE_LOOKUP_ONLY: ${{ steps.pnpm-store-mode.outputs.lookup-only == 'true' }}
|
||||
run: |
|
||||
test -n "$LOCKFILE_HASH"
|
||||
cache_path="$(pnpm store path --silent)"
|
||||
@@ -137,12 +161,10 @@ runs:
|
||||
# Producers can refresh access and publish misses without downloading existing archives.
|
||||
- name: Keep pnpm download store without restoring
|
||||
id: pnpm-store-lookup
|
||||
if: >-
|
||||
github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' &&
|
||||
inputs.cache-pnpm-store-lookup-only == 'true'
|
||||
if: steps.pnpm-store-mode.outputs.lookup-only == 'true'
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
# Post-job saves cannot resolve the composite's internal step outputs.
|
||||
# Twice-nested composite cleanup loses internal step outputs.
|
||||
path: ${{ env.ORCA_PNPM_STORE_CACHE_PATH }}
|
||||
key: node-cache-${{ runner.os }}-${{ steps.pnpm-store.outputs.arch }}-pnpm-${{ hashFiles(inputs.cache-dependency-path) }}
|
||||
lookup-only: true
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('CI dependency download caches', () => {
|
||||
expect(action.inputs['cache-dependency-path'].default).toBe('pnpm-lock.yaml')
|
||||
for (const step of action.runs.steps.filter((step) => step.uses === 'actions/setup-node@v6')) {
|
||||
expect(step.with.cache).toBe(
|
||||
"${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && inputs.cache-pnpm-store-lookup-only != 'true' && 'pnpm' || '' }}"
|
||||
"${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && steps.pnpm-store-mode.outputs.lookup-only != 'true' && 'pnpm' || '' }}"
|
||||
)
|
||||
expect(step.with['cache-dependency-path']).toBe('${{ inputs.cache-dependency-path }}')
|
||||
expect(step.with['package-manager-cache']).toBe(false)
|
||||
@@ -41,7 +41,7 @@ describe('CI dependency download caches', () => {
|
||||
"github.event_name == 'pull_request' && inputs.cache-pnpm-store != 'false' && !((runner.os == 'Linux' || runner.os == 'macOS') && (runner.arch == 'X64' || runner.arch == 'ARM64') && inputs.cache-dependency-path == 'pnpm-lock.yaml') && (runner.os != 'Windows' || !(runner.arch == 'X64' && contains(inputs.cache-dependency-path, 'mobile/pnpm-lock.yaml')) && !((runner.arch == 'X64' || runner.arch == 'ARM64') && inputs.cache-dependency-path == 'pnpm-lock.yaml'))"
|
||||
)
|
||||
expect(resolve.if).toBe(
|
||||
`${restore.if} || (github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && inputs.cache-pnpm-store-lookup-only == 'true')`
|
||||
`${restore.if} || (github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && steps.pnpm-store-mode.outputs.lookup-only == 'true')`
|
||||
)
|
||||
expect(restore.uses).toBe('actions/cache/restore@v5')
|
||||
expect(restore.with.path).toBe('${{ steps.pnpm-store.outputs.path }}')
|
||||
@@ -72,11 +72,9 @@ describe('CI dependency download caches', () => {
|
||||
it('keeps producer lookup optional and compatible with the existing store archive', () => {
|
||||
const lookup = action.runs.steps.find((step) => step.id === 'pnpm-store-lookup')
|
||||
const restore = action.runs.steps.find((step) => step.id === 'pnpm-store-restore')
|
||||
expect(action.inputs['cache-pnpm-store-lookup-only'].default).toBe('false')
|
||||
expect(action.inputs['cache-pnpm-store-lookup-only'].default).toBe('auto')
|
||||
expect(lookup.uses).toBe('actions/cache@v5')
|
||||
expect(lookup.if).toBe(
|
||||
"github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && inputs.cache-pnpm-store-lookup-only == 'true'"
|
||||
)
|
||||
expect(lookup.if).toBe("steps.pnpm-store-mode.outputs.lookup-only == 'true'")
|
||||
expect(lookup.with).toEqual({
|
||||
path: '${{ env.ORCA_PNPM_STORE_CACHE_PATH }}',
|
||||
key: restore.with.key,
|
||||
@@ -203,6 +201,16 @@ describe('CI dependency download caches', () => {
|
||||
const context = {
|
||||
github: { event_name: event },
|
||||
runner: { os, arch },
|
||||
steps: {
|
||||
'pnpm-store-mode': {
|
||||
outputs: {
|
||||
'lookup-only':
|
||||
event !== 'pull_request' && storeCache !== 'false' && lookupOnly === 'true'
|
||||
? 'true'
|
||||
: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
inputs: {
|
||||
'cache-pnpm-store': storeCache,
|
||||
'cache-pnpm-store-lookup-only': lookupOnly,
|
||||
@@ -218,6 +226,10 @@ describe('CI dependency download caches', () => {
|
||||
const evaluate = (expression) =>
|
||||
runInNewContext(
|
||||
expression
|
||||
.replaceAll(
|
||||
'steps.pnpm-store-mode.outputs.lookup-only',
|
||||
'steps["pnpm-store-mode"].outputs["lookup-only"]'
|
||||
)
|
||||
.replaceAll(
|
||||
'inputs.cache-pnpm-store-lookup-only',
|
||||
'inputs["cache-pnpm-store-lookup-only"]'
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { runInNewContext } from 'node:vm'
|
||||
import { parse } from 'yaml'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { runProcessSync } from './script-child-process.mjs'
|
||||
|
||||
const action = parse(readFileSync('.github/actions/install-node-dependencies/action.yml', 'utf8'))
|
||||
const mode = action.runs.steps.find((step) => step.id === 'pnpm-store-mode')
|
||||
const defaultContext = {
|
||||
github: { event_name: 'push' },
|
||||
runner: { os: 'Linux', arch: 'X64', environment: 'github-hosted' },
|
||||
job: { container: { id: '' } },
|
||||
inputs: {
|
||||
'cache-pnpm-store': 'true',
|
||||
'cache-pnpm-store-lookup-only': 'auto',
|
||||
'cache-dependency-path': 'pnpm-lock.yaml',
|
||||
'node-version': ''
|
||||
}
|
||||
}
|
||||
const expression = mode.if.replaceAll(/inputs\.([\w-]+)/g, 'inputs["$1"]')
|
||||
|
||||
function eligible(changes) {
|
||||
const context = structuredClone(defaultContext)
|
||||
for (const [name, fields] of Object.entries(changes)) {
|
||||
Object.assign(context[name], fields)
|
||||
}
|
||||
return runInNewContext(expression, context)
|
||||
}
|
||||
|
||||
function resolveMode(request, node, manager) {
|
||||
const directory = mkdtempSync(join(tmpdir(), 'orca-store-mode-'))
|
||||
const output = join(directory, 'output')
|
||||
try {
|
||||
writeFileSync(
|
||||
join(directory, 'package.json'),
|
||||
JSON.stringify({ engines: { node }, packageManager: manager })
|
||||
)
|
||||
const result = runProcessSync({
|
||||
program: 'bash',
|
||||
args: ['-e', '-o', 'pipefail', '-c', mode.run],
|
||||
cwd: directory,
|
||||
env: { ...process.env, LOOKUP_REQUEST: request, GITHUB_OUTPUT: output }
|
||||
})
|
||||
expect(result.code, result.stderr || result.stdout).toBe(0)
|
||||
return readFileSync(output, 'utf8')
|
||||
} finally {
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
describe('automatic pnpm store mode', () => {
|
||||
it.each([
|
||||
['auto', '24', 'pnpm@12.8.1', '', true],
|
||||
['auto', '25', 'pnpm@12.8.1', 'pnpm', false],
|
||||
['auto', '24', 'pnpm@13.0.0', 'pnpm', false],
|
||||
['true', '25', 'pnpm@13.0.0', '', true]
|
||||
])(
|
||||
'routes resolved %s mode for Node %s / %s into both cache steps',
|
||||
(request, node, manager, cache, lookup) => {
|
||||
const context = structuredClone(defaultContext)
|
||||
context.inputs['cache-pnpm-store-lookup-only'] = request
|
||||
const resolved = resolveMode(request, node, manager).split('=')[1].trim()
|
||||
const evaluate = (value) =>
|
||||
runInNewContext(
|
||||
value
|
||||
.replaceAll(/inputs\.([\w-]+)/g, 'inputs["$1"]')
|
||||
.replaceAll(
|
||||
'steps.pnpm-store-mode.outputs.lookup-only',
|
||||
'steps["pnpm-store-mode"].outputs["lookup-only"]'
|
||||
),
|
||||
{ ...context, steps: { 'pnpm-store-mode': { outputs: { 'lookup-only': resolved } } } }
|
||||
)
|
||||
const nodeSetup = action.runs.steps.find((step) => step.id === 'default-node')
|
||||
expect(evaluate(nodeSetup.with.cache.slice(3, -2))).toBe(cache)
|
||||
expect(evaluate(action.runs.steps.find((step) => step.id === 'pnpm-store-lookup').if)).toBe(
|
||||
lookup
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it.each(
|
||||
['Linux', 'Windows', 'macOS'].flatMap((os) => ['X64', 'ARM64'].map((arch) => [os, arch]))
|
||||
)('qualifies the measured %s/%s hosted root context', (os, arch) => {
|
||||
expect(eligible({ runner: { os, arch } })).toBe(true)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['PR', { github: { event_name: 'pull_request' } }],
|
||||
['opted-out store', { inputs: { 'cache-pnpm-store': 'false' } }],
|
||||
['opted-out lookup', { inputs: { 'cache-pnpm-store-lookup-only': 'false' } }],
|
||||
['unknown request', { inputs: { 'cache-pnpm-store-lookup-only': 'other' } }],
|
||||
[
|
||||
'mixed lockfiles',
|
||||
{ inputs: { 'cache-dependency-path': 'pnpm-lock.yaml\nmobile/pnpm-lock.yaml' } }
|
||||
],
|
||||
['custom lockfile', { inputs: { 'cache-dependency-path': 'cloud/pnpm-lock.yaml' } }],
|
||||
['Node 25', { inputs: { 'node-version': '25' } }],
|
||||
['job container', { job: { container: { id: 'container-id' } } }],
|
||||
['self-hosted runner', { runner: { environment: 'self-hosted' } }],
|
||||
['unknown host kind', { runner: { environment: '' } }],
|
||||
['unmeasured architecture', { runner: { arch: 'X86' } }],
|
||||
['unmeasured OS', { runner: { os: 'other' } }]
|
||||
])('retains the legacy policy for %s', (_name, changes) => {
|
||||
expect(eligible(changes)).toBe(false)
|
||||
})
|
||||
|
||||
it('allows an explicit request to preserve the existing force-lookup contract', () => {
|
||||
expect(
|
||||
eligible({
|
||||
inputs: {
|
||||
'cache-pnpm-store-lookup-only': 'true',
|
||||
'node-version': '25',
|
||||
'cache-dependency-path': 'custom-lock.yaml'
|
||||
},
|
||||
runner: { environment: 'self-hosted' },
|
||||
job: { container: { id: 'container-id' } }
|
||||
})
|
||||
).toBe(true)
|
||||
expect(
|
||||
eligible({
|
||||
github: { event_name: 'pull_request' },
|
||||
inputs: { 'cache-pnpm-store-lookup-only': 'true' }
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['24', 'pnpm@12.8.1', 'true'],
|
||||
['24', 'pnpm@12.8.1+sha512.fixture', 'true'],
|
||||
['25', 'pnpm@12.8.1', 'false'],
|
||||
['24.x', 'pnpm@12.8.1', 'false'],
|
||||
['24', 'pnpm@12.8.2', 'false'],
|
||||
['24', 'pnpm@12.8.10', 'false'],
|
||||
['24', undefined, 'false'],
|
||||
[undefined, 'pnpm@12.8.1', 'false'],
|
||||
['24', 12, 'false']
|
||||
])('checks manifest Node %s and manager %s before choosing lookup', (node, manager, expected) => {
|
||||
expect(resolveMode('auto', node, manager)).toBe(`lookup-only=${expected}\n`)
|
||||
})
|
||||
|
||||
it('checks uppercase auto requests consistently with GitHub expression comparisons', () => {
|
||||
expect(resolveMode('AUTO', '25', 'pnpm@12.8.1')).toBe('lookup-only=false\n')
|
||||
})
|
||||
|
||||
it('does not constrain an explicit request to the automatic manifest profile', () => {
|
||||
expect(resolveMode('true', '25', 'pnpm@13.0.0')).toBe('lookup-only=true\n')
|
||||
})
|
||||
})
|
||||
@@ -123,7 +123,7 @@ const REVIEWED_COMPUTED_PATHS = [
|
||||
'${{ env.ORCA_PNPM_STORE_CACHE_PATH }}',
|
||||
// Only pnpm's lockfile-verified.jsonl record, never Metro transforms.
|
||||
'${{ steps.verification-cache.outputs.path }}',
|
||||
"${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && inputs.cache-pnpm-store-lookup-only != 'true' && 'pnpm' || '' }} store"
|
||||
"${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && steps.pnpm-store-mode.outputs.lookup-only != 'true' && 'pnpm' || '' }} store"
|
||||
]
|
||||
|
||||
/** Every step a workflow runs, descending into the repository's own composite actions. */
|
||||
|
||||
@@ -290,7 +290,7 @@ describe('PR workflow parallelism', () => {
|
||||
expect(steps[pnpmIndex].with.version).toBeUndefined()
|
||||
expect(steps[pnpmIndex].with.install).toBe(false)
|
||||
const saveOutsidePrs =
|
||||
"${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && inputs.cache-pnpm-store-lookup-only != 'true' && 'pnpm' || '' }}"
|
||||
"${{ github.event_name != 'pull_request' && inputs.cache-pnpm-store != 'false' && steps.pnpm-store-mode.outputs.lookup-only != 'true' && 'pnpm' || '' }}"
|
||||
expect(steps[nodeIndex].with.cache).toBe(saveOutsidePrs)
|
||||
expect(steps[nodeIndex].if).toBe("inputs.node-version == ''")
|
||||
expect(steps[requestedNodeIndex].if).toBe("inputs.node-version != ''")
|
||||
|
||||
@@ -228,8 +228,8 @@ proved that lookup left the payload absent, refreshed the existing cache's acces
|
||||
time, and published a miss that a fresh job restored. A
|
||||
[nested composite control](https://github.com/stablyai/orca/actions/runs/37084946789)
|
||||
then saved and restored a fresh payload using the actual environment-path pattern.
|
||||
The installer exports its resolved store path through `GITHUB_ENV`: post-job saves
|
||||
cannot resolve the composite's internal step outputs. The primary key is captured
|
||||
The installer exports its resolved store path through `GITHUB_ENV`: twice-nested composite post-job saves
|
||||
cannot resolve their internal step outputs. The primary key is captured
|
||||
by the cache action before cleanup. Paths, architecture and lockfile keys match
|
||||
`setup-node`, so existing default-branch archives remain reusable.
|
||||
|
||||
@@ -1814,3 +1814,40 @@ collector stopped its observer before signal routing, and the corrected trial
|
||||
received the signal after both builders finished. The qualifying trial requested
|
||||
normal cancellation earlier in the same preparation sequence to account for
|
||||
observed delivery delay; no workload, wait or proof predicate was shortened.
|
||||
|
||||
## October 3 producer follow-up: automatic selection for the measured profile
|
||||
|
||||
The first producer rollout in [#24927](https://github.com/stablyai/orca/pull/24927)
|
||||
passed all 46 PR checks, all five manual warmers and all 11 manual Headless
|
||||
qualifications on `a2c489c0cca5e46d24333a4d40ba910af0de0208`. The same root installer
|
||||
also serves recurring unit, browser and performance workflows that had not opted
|
||||
in. The follow-up defaults the existing input to `auto`, reusing lookup mode for
|
||||
non-PR root-only installs on GitHub-hosted Linux/macOS/Windows x64/ARM64 runners,
|
||||
with no job container, the manifest's Node 24/pnpm 12.8.1 profile and no conflicting
|
||||
Node override. Explicit `true` and `false` retain their previous meanings. Mixed
|
||||
lockfiles, other toolchains, containers and self-hosted runners retain full cache
|
||||
restoration; PR policies are unchanged. The manifest check runs only when the
|
||||
context is potentially eligible, before setup-node chooses its cache behavior.
|
||||
|
||||
A second cleanup audit distinguished nesting depth. The
|
||||
[twice-nested control](https://github.com/stablyai/orca/actions/runs/37087090689)
|
||||
published the environment-path payload and lost the output-path payload with an
|
||||
`Input required and not supplied: path` warning. The
|
||||
[direct control](https://github.com/stablyai/orca/actions/runs/37087211236) published
|
||||
and restored both payloads. Current Electron archive callers are direct, so they
|
||||
need no cache-path change. Keeping the producer's exported path also makes its
|
||||
new lookup mode safe for callers that nest the shared installer. These tiny
|
||||
controls establish publication behavior, not installer time savings.
|
||||
|
||||
The [actual automatic-mode cold publisher control](https://github.com/stablyai/orca/actions/runs/37097980789)
|
||||
passed both jobs on `7b8858bdc8f`. A twice-nested wrapper called the installer
|
||||
without overriding its default input. The writer selected lookup, missed its
|
||||
unique root-lockfile key, completed the frozen policy-checked install and saved
|
||||
that key during cleanup. A fresh reader restored the exact key and installed the
|
||||
same dependency successfully. The fixture retained the manifest toolchain and
|
||||
applicable workspace policies; its one dependency keeps the publication check
|
||||
small. Two earlier trials failed fixture assertions (the pnpm multi-document
|
||||
header placement, then its empty cache-miss output), and are excluded. This proves
|
||||
automatic selection and cold publication, not a new timing result. Local
|
||||
verification passed eight suites / 184 tests, the changed-code quality gate and
|
||||
compiled-composite actionlint.
|
||||
|
||||
Reference in New Issue
Block a user