fix(pins): CI exact pins for this platform are alternatives; the installer prefers a non-CI exact pin, else the install default among CI alternatives, else the highest verified (integration item 1)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-15 01:14:59 +00:00
1 parent ba887cc7ce
commit 5630ea3c34
5 files changed
+155 -18

No files matched your search

+23 -8
View File
@@ -125,23 +125,38 @@ if [ -f "$VERDICT" ]; then
REC_RULE=$(gstack_install_verdict_field "$VERDICT" bun_rule) REC_BUN_INSTALL=$(gstack_install_verdict_field "$VERDICT" bun_install)
fi
LAUNCH_DIR="" RES_BIN="" RES_VER="" RES_RULE=""
PINS_OUT="" PINS_MODE=none RUNNER="" PIN="" PIN_SRC="" PIN_EXACT=""
PINS_OUT="" PINS_MODE=none RUNNER="" PIN="" PIN_SRC="" PIN_EXACT="" PIN_ALTS=""
# read_pins RUNNER — the project's pins through the doctor's parser
# (lib/runtime-pins.ts: engines, packageManager, .tool-versions, .bun-version,
# CI lanes) when RUNNER can run it, else the package.json / .bun-version subset
# read with jq. Sets PINS_MODE (parser|jq|none) and PIN_EXACT (the one exact Bun pin).
# read with jq. Sets PINS_MODE (parser|jq|none) and PIN_EXACT, the exact Bun
# to prefer: the one non-CI exact pin (two different ones conflict), else
# among this platform's CI exact pins (alternatives; any one satisfies CI)
# the install version when listed, else the highest verified one, else the
# highest one.
read_pins() {
RUNNER="$1" PINS_OUT="" PINS_MODE=none PIN="" PIN_SRC="" PIN_EXACT=""
RUNNER="$1" PINS_OUT="" PINS_MODE=none PIN="" PIN_SRC="" PIN_EXACT="" PIN_ALTS=""
[ -n "$PROJECT" ] || return 0
[ -n "$RUNNER" ] && [ -f "$ROOT/bin/gstack-runtime-pins.ts" ] && PINS_OUT=$("$RUNNER" "$ROOT/bin/gstack-runtime-pins.ts" --project "$PROJECT" 2>/dev/null)
if printf '%s\n' "$PINS_OUT" | grep -q '^PINS_VERDICT: '; then
PINS_MODE=parser
_exact=$(printf '%s\n' "$PINS_OUT" | sed -n 's/^PIN: bun \([^ ]*\) exact \([^ ]*\) lane=[a-z]*$/\1 \2/p')
[ -n "$_exact" ] || return 0
if [ "$(printf '%s\n' "$_exact" | cut -d' ' -f1 | sort -u | wc -l | tr -d ' ')" -gt 1 ]; then
_exact=$(printf '%s\n' "$PINS_OUT" | sed -n 's/^PIN_EXACT_REQUIRED: bun \([^ ]*\) \([^ ]*\)$/\1 \2/p')
if [ "$(printf '%s\n' "$_exact" | grep -c .)" -gt 1 ]; then
stop RUNTIME_PIN_CONFLICT "Bun conflict: $PROJECT pins more than one exact Bun ($(printf '%s\n' "$_exact" | sed 's/^\([^ ]*\) \(.*\)$/\2 pins \1/' | tr '\n' ';' | sed 's/;$//; s/;/; /g'))" "make the project's exact Bun pins agree"
fi
PIN_EXACT=$(printf '%s\n' "$_exact" | head -1 | cut -d' ' -f1) PIN_SRC="$PROJECT/$(printf '%s\n' "$_exact" | head -1 | cut -d' ' -f2 | sed 's/:[0-9]*$//')"
if [ -n "$_exact" ]; then
PIN_EXACT=${_exact%% *} PIN_SRC="$PROJECT/$(printf '%s' "${_exact#* }" | sed 's/:[0-9]*$//')"
return 0
fi
_alts=$(printf '%s\n' "$PINS_OUT" | sed -n 's/^PIN_CI_ALTERNATIVES: bun //p')
[ -n "$_alts" ] || return 0
case "$_alts" in *' '*) PIN_ALTS="$_alts" ;; esac
PIN_SRC="$PROJECT/.github/workflows (CI exact pins: $_alts)"
case " $_alts " in *" $INSTALL_VERSION "*) PIN_EXACT="$INSTALL_VERSION"; return 0 ;; esac
for _v in $(printf '%s\n' $_alts | sort -t. -k1,1nr -k2,2nr -k3,3nr); do
gstack_bun_archive_sha256 linux-x64 "$_v" >/dev/null && [ "$(gstack_bun_status "$_v")" = ok ] && { PIN_EXACT="$_v"; return 0; }
done
PIN_EXACT=$(printf '%s\n' $_alts | sort -t. -k1,1nr -k2,2nr -k3,3nr | head -1)
return 0
fi
command -v jq >/dev/null 2>&1 || return 0
@@ -244,7 +259,7 @@ else
resolve || conflict "bun $WANT in $STORE does not satisfy $PROJECT's pins $(pins_text) (judged: ${_gb_judged:-none})"
fi
[ -n "${GSTACK_BUN_INSTALL_VERSION:-}" ] && say "install version $INSTALL_VERSION from GSTACK_BUN_INSTALL_VERSION (default $GSTACK_BUN_DEFAULT)"
say "bun $RES_VER at $RES_BIN ($(gstack_bun_rule_text "$RES_RULE"); satisfies gstack's supported minimum $GSTACK_BUN_TESTED${PIN_EXACT:+ and the project pin $PIN_EXACT}$([ -z "$PIN_EXACT" ] && [ -n "$PIN" ] && printf ' and the project pin %s' "$PIN")$([ -z "$PIN_EXACT" ] && [ "$PINS_MODE" = parser ] && printf ' and the project pins'))"
say "bun $RES_VER at $RES_BIN ($(gstack_bun_rule_text "$RES_RULE"); satisfies gstack's supported minimum $GSTACK_BUN_TESTED$([ -n "$PIN_ALTS" ] && printf " and the project's CI pins (alternatives: %s)" "$PIN_ALTS" || printf '%s' "${PIN_EXACT:+ and the project pin $PIN_EXACT}")$([ -z "$PIN_EXACT" ] && [ -n "$PIN" ] && printf ' and the project pin %s' "$PIN")$([ -z "$PIN_EXACT" ] && [ "$PINS_MODE" = parser ] && printf ' and the project pins'))"
fi
if [ -n "$RES_BIN" ] && [ "$(gstack_realpath "$(command -v bun 2>/dev/null || echo /nonexistent)" 2>/dev/null)" != "$RES_BIN" ]; then
LAUNCH_DIR=$(dirname "$RES_BIN")
+16 -8
View File
@@ -21,7 +21,7 @@ import * as fs from 'node:fs';
import * as path from 'node:path';
import { findInstallVerdict, readInstallVerdict, sourceRoot } from '../install-verdict';
import { resultCodeSuffix } from '../result-codes';
import { collectPins, evaluatePins, parseVersion, satisfies } from '../runtime-pins';
import { collectPins, evaluatePins, exactPlan, parseVersion, satisfies } from '../runtime-pins';
import { resolveStateRoot } from '../state-root';
type Env = NodeJS.ProcessEnv;
@@ -106,16 +106,24 @@ function enginesBun(file: string): string | null {
try { const v = JSON.parse(fs.readFileSync(file, 'utf8'))?.engines?.bun; return typeof v === 'string' && v.trim() ? v.trim() : null; } catch { return null; }
}
const PLATFORM_LANE: Record<string, string> = { linux: 'linux', darwin: 'macos', win32: 'windows' };
const byVersionDesc = (a: string, b: string) => { const x = parseVersion(a)!, y = parseVersion(b)!; return y[0] - x[0] || y[1] - x[1] || y[2] - x[2]; };
/** The project's pins as the resolver's eligibility hook, and its one applicable exact Bun pin. */
export function projectBunPins(repoRoot: string | null): { pinExact: string | null; accept: (version: string) => boolean } {
/**
* The project's pins as the resolver's eligibility hook, and the exact Bun to
* prefer (read_pins in bin/gstack-capy-install): the one non-CI exact pin,
* else among the CI alternatives the install version, the highest verified,
* or the highest.
*/
export function projectBunPins(repoRoot: string | null, env: Env = process.env): { pinExact: string | null; accept: (version: string) => boolean } {
const gstackPin = enginesBun(path.join(GSTACK_ROOT, 'package.json'));
const pins = repoRoot ? collectPins(repoRoot).filter(p => p.tool === 'bun') : [];
const here = PLATFORM_LANE[process.platform] ?? 'any';
const exact = [...new Set(pins.filter(p => p.kind === 'exact' && (p.lane === 'any' || p.lane === here)).map(p => p.constraint.replace(/^v/, '')))];
const plan = exactPlan(pins, 'bun');
const alts = plan.ciAlternatives.filter(v => parseVersion(v)).sort(byVersionDesc);
const installVersion = bunInstallVersion(env);
const pinExact = plan.required.length ? (plan.required.length === 1 && parseVersion(plan.required[0].version) ? plan.required[0].version : null)
: alts.find(v => v === installVersion) ?? alts.find(v => BUN_VERIFIED.includes(v) && bunStatusOk(v)) ?? alts[0] ?? null;
return {
pinExact: exact.length === 1 && parseVersion(exact[0]) ? exact[0] : null,
pinExact,
accept: (version: string) => (!gstackPin || satisfies(version, gstackPin) === true) && evaluatePins(pins, { bun: version }).failures.length === 0,
};
}
@@ -157,7 +165,7 @@ export function launchRuntime(repoRoot: string, env: NodeJS.ProcessEnv): LaunchR
} else {
verdict = 'ok';
}
const { pinExact, accept } = projectBunPins(project);
const { pinExact, accept } = projectBunPins(project, env);
const { resolution } = resolveBun({ store: bunStore(resolveStateRoot(env)), env, pinExact, accept });
if (resolution) return launch(resolution.bin, resolution.version, `resolved: ${resolution.rule}`, verdict, verdictLine);
return { dir: null, version: null, env, label: `PATH (no eligible Bun in the runtime store, $BUN_INSTALL/bin, ~/.bun/bin or PATH)${verdictLine ? `; ${verdictLine}` : ''}`, verdict, verdictLine };
+33 -2
View File
@@ -11,6 +11,11 @@
* the platform lane of the job's `runs-on`, so a Windows job's pin never fails
* a Linux run. A value this module cannot evaluate (`lts/*`, a matrix
* expression) is reported as skipped, never silently dropped.
*
* CI exact pins that apply to this platform are alternatives: a repository's
* lanes may test several Bun versions (gstack pins its minimum everywhere and
* runs one job on its install default), so the running Bun satisfies CI when
* it equals any one of them. CI ranges and every non-CI pin stay required.
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
@@ -210,20 +215,43 @@ export function evaluatePins(pins: Pin[], running: Running, platform: NodeJS.Pla
const version = running[tool];
const applicable = mine.filter(p => p.lane === 'any' || p.lane === here);
const unmet: Pin[] = [];
const ciExact: Pin[] = [];
for (const pin of applicable) {
if (pin.kind === 'unevaluable') { skipped.push(`${tool} ${pin.constraint} (${pin.source}) not evaluated`); continue; }
if (!version) { skipped.push(`${tool} ${pin.constraint} (${pin.source}): ${tool} not found on PATH`); continue; }
evaluated++;
if (satisfies(version, pin.constraint) === false) unmet.push(pin);
if (pin.field === 'CI' && pin.kind === 'exact') ciExact.push(pin);
else if (satisfies(version, pin.constraint) === false) unmet.push(pin);
}
const ciMet = ciExact.some(pin => satisfies(version!, pin.constraint) !== false);
if (!ciMet) unmet.push(...ciExact);
if (unmet.length === 0) continue;
const [first, ...rest] = groupPins(unmet);
const context = rest.map(g => `${describe(g)} pins ${g.pin.constraint}`);
failures.push(`${tool} ${version} outside ${first.pin.field} ${first.pin.constraint} (${first.pin.source}${context.length ? '; ' + context.join('; ') : ''})`);
const alternatives = ciMet ? [] : [...new Set(ciExact.map(p => p.constraint))];
const anyOf = alternatives.length > 1 ? `; CI exact pins are alternatives, any one of ${alternatives.join(', ')} satisfies CI` : '';
failures.push(`${tool} ${version} outside ${first.pin.field} ${first.pin.constraint} (${first.pin.source}${context.length ? '; ' + context.join('; ') : ''}${anyOf})`);
}
return { pins, summary, failures, skipped, verdict: failures.length ? 'fail' : evaluated ? 'pass' : 'none' };
}
export interface ExactPlan {
/** Distinct non-CI exact pins (engines, packageManager, .tool-versions, .bun-version): all required. */
required: Array<{ version: string; source: string }>;
/** Distinct CI exact pins for this platform's lanes: any one satisfies CI. */
ciAlternatives: string[];
}
/** The exact pins of one tool that apply on this platform, split into required and CI alternatives. */
export function exactPlan(pins: Pin[], tool: Tool, platform: NodeJS.Platform = process.platform): ExactPlan {
const here = PLATFORM_LANE[platform] ?? 'any';
const exact = pins.filter(p => p.tool === tool && p.kind === 'exact' && (p.lane === 'any' || p.lane === here));
const strip = (v: string) => v.replace(/^v/, '');
const required: ExactPlan['required'] = [];
for (const p of exact.filter(p => p.field !== 'CI')) if (!required.some(r => r.version === strip(p.constraint))) required.push({ version: strip(p.constraint), source: p.source });
return { required, ciAlternatives: [...new Set(exact.filter(p => p.field === 'CI').map(p => strip(p.constraint)))] };
}
/** The wire lines bin/gstack-doctor-check.sh reads (PINS_VERDICT last). */
export function renderPinLines(report: PinReport, platform: NodeJS.Platform = process.platform): string[] {
const here = PLATFORM_LANE[platform] ?? 'any';
@@ -231,6 +259,9 @@ export function renderPinLines(report: PinReport, platform: NodeJS.Platform = pr
for (const s of report.summary) lines.push(`PIN_SUMMARY: ${s}`);
for (const s of report.skipped) lines.push(`PIN_SKIP: ${s}`);
for (const f of report.failures) lines.push(`PIN_FAIL: ${f}`);
const plan = exactPlan(report.pins, 'bun', platform);
for (const r of plan.required) lines.push(`PIN_EXACT_REQUIRED: bun ${r.version} ${r.source}`);
if (plan.ciAlternatives.length) lines.push(`PIN_CI_ALTERNATIVES: bun ${plan.ciAlternatives.join(' ')}`);
lines.push(`PINS_VERDICT: ${report.verdict}`);
return lines;
}
+35
View File
@@ -437,6 +437,41 @@ describe.skipIf(process.platform !== 'linux')('gstack-capy-install: one resoluti
expect(outside.stdout).toContain(`GSTACK_LAUNCH: PATH=${bi}/bin:$PATH `);
});
// Integration item 1: gstack's own CI pins 1.4.2 on every lane and 1.4.3 on
// the install-default job. They are alternatives, so the real checkout as
// --project certifies with either Bun; a repo whose CI pins only 1.4.3 still
// refuses 1.4.2.
test('the real gstack checkout as --project with a separate global install certifies with ~/.bun 1.4.2 and with 1.4.3; nothing is downloaded', () => {
for (const version of ['1.4.2', '1.4.3']) {
const f = fixture({ bun: '1.3.14' });
withPins(f);
realBun(path.join(f.home, '.bun/bin/bun'), version);
const r = run(f, ['--for', 'review', '--project', REPO], {}, f.home);
expect(r.status, r.stdout + r.stderr).toBe(0);
expect(r.stderr).not.toContain('RUNTIME_PIN_CONFLICT');
expect(r.stdout).toContain(`bun ${version} at ${f.home}/.bun/bin/bun ($BUN_INSTALL/bin; satisfies gstack's supported minimum 1.4.2 and the project's CI pins (alternatives: 1.4.2 1.4.3))`);
expect(f.calls().some(l => l.startsWith('curl '))).toBe(false);
expect(verdictFor(f, r)).toMatchObject({ project: REPO, bun_version: version, bun_rule: 'BUN_INSTALL' });
expect(run(f, ['--check', '--for', 'review', '--project', REPO], {}, f.home).status).toBe(0);
}
});
test('CI alternatives with nothing installed: the install default is published when it is one of them; a gbrain-like repo (CI 1.4.3 only) refuses ~/.bun 1.4.2 and installs 1.4.3', () => {
const f = fixture({ bun: '1.3.14' });
withPins(f);
const r = run(f, ['--for', 'review', '--project', REPO], { STUB_CURL_ZIP: zipFor(f, '1.4.3') }, f.home);
expect(r.status, r.stdout + r.stderr).toBe(0);
expect(r.stdout).toContain(`bun 1.4.3 at ${store(f, '1.4.3')}/bun (`);
const g = fixture({ bun: '1.3.14' });
withPins(g);
realBun(path.join(g.home, '.bun/bin/bun'), '1.4.2');
const project = ciProject(g, 'gbrain', '1.4.3');
const gr = run(g, ['--for', 'review'], { STUB_CURL_ZIP: zipFor(g, '1.4.3') }, project);
expect(gr.status, gr.stdout + gr.stderr).toBe(0);
expect(gr.stdout).toContain(`judged: bun 1.4.2 at ${g.home}/.bun/bin/bun (BUN_INSTALL)`);
expect(gr.stdout).toContain(`bun 1.4.3 at ${store(g, '1.4.3')}/bun (the project's exact pin`);
});
test('no installed Bun satisfies the pins: the line names the Bun actually judged, the exact pin is published in the store, and the next --check agrees', () => {
const f = fixture({ bun: '1.3.14' });
withPins(f);
+48
View File
@@ -136,6 +136,54 @@ describe('collectPins and evaluatePins', () => {
});
});
// Integration item 1: CI exact pins for this platform are alternatives (any
// one satisfies CI); non-CI pins stay required.
describe('CI exact pins are alternatives', () => {
const lane = (name: string, version: string, os = 'ubuntu-latest') => [` ${name}:`, ` runs-on: ${os}`, ' steps:', ' - uses: oven-sh/setup-bun@v2', ' with:', ` bun-version: ${version}`];
const workflow = (...jobs: string[][]) => ['jobs:', ...jobs.flat(), ''].join('\n');
test('the real gstack checkout (CI 1.4.2 lanes plus the 1.4.3 install-default job) passes on 1.4.2 and on 1.4.3, and names the alternatives on 1.4.1', () => {
const pins = collectPins(ROOT);
const ci = [...new Set(pins.filter(p => p.tool === 'bun' && p.field === 'CI' && p.kind === 'exact' && p.lane !== 'windows' && p.lane !== 'macos').map(p => p.constraint))].sort();
expect(ci).toEqual(['1.4.2', '1.4.3']);
for (const bun of ['1.4.2', '1.4.3']) expect(evaluatePins(pins, { bun }, 'linux').failures.filter(f => f.startsWith('bun'))).toEqual([]);
const low = evaluatePins(pins, { bun: '1.4.1' }, 'linux').failures.find(f => f.startsWith('bun '))!;
expect(low).toStartWith('bun 1.4.1 outside engines.bun >=1.4.2 (package.json:');
expect(low).toContain('CI exact pins are alternatives, any one of 1.4.2, 1.4.3 satisfies CI');
expect(renderPinLines(evaluatePins(pins, { bun: '1.4.2' }, 'linux'), 'linux')).toContain('PIN_CI_ALTERNATIVES: bun 1.4.2 1.4.3');
});
test('gbrain-like (every CI lane 1.4.3) still fails a local 1.4.2 exactly as before', () => {
const dir = project({ '.github/workflows/ci.yml': workflow(lane('a', '1.4.3'), lane('b', '1.4.3')) });
const fail = evaluatePins(collectPins(dir), { bun: '1.4.2' }, 'linux');
expect(fail.verdict).toBe('fail');
expect(fail.failures).toEqual(['bun 1.4.2 outside CI 1.4.3 (.github/workflows/ci.yml:7)']);
expect(evaluatePins(collectPins(dir), { bun: '1.4.3' }, 'linux').verdict).toBe('pass');
});
test('GBRA-39, gbrain-evals-like: one workflow pins 1.4.2, another 1.4.3; a local 1.4.3 used to fail "outside CI 1.4.2" and now passes', () => {
const dir = project({
'.github/workflows/test.yml': workflow(lane('unit', '1.4.3')),
'.github/workflows/evals.yml': workflow(lane('evals', '1.4.2')),
});
for (const bun of ['1.4.2', '1.4.3']) expect(evaluatePins(collectPins(dir), { bun }, 'linux').verdict).toBe('pass');
expect(evaluatePins(collectPins(dir), { bun: '1.4.4' }, 'linux').failures).toEqual([expect.stringContaining('any one of ')]);
});
test('non-CI exact pins stay required beside CI alternatives; another platform\'s CI pin is not an alternative here', () => {
const dir = project({
'.bun-version': '1.4.2\n',
'.github/workflows/ci.yml': workflow(lane('linux', '1.4.3'), lane('mac', '1.4.4', 'macos-latest')),
});
const r = evaluatePins(collectPins(dir), { bun: '1.4.3' }, 'linux');
expect(r.failures).toEqual(['bun 1.4.3 outside .bun-version 1.4.2 (.bun-version:1)']);
const lines = renderPinLines(r, 'linux');
expect(lines).toContain('PIN_EXACT_REQUIRED: bun 1.4.2 .bun-version:1');
expect(lines).toContain('PIN_CI_ALTERNATIVES: bun 1.4.3');
expect(evaluatePins(collectPins(dir), { bun: '1.4.4' }, 'linux').verdict).toBe('fail');
});
});
describe('bin/gstack-runtime-pins.ts', () => {
const run = (args: string[]) => spawnSync(process.execPath, [path.join(ROOT, 'bin/gstack-runtime-pins.ts'), ...args], { encoding: 'utf8', timeout: 30_000 });