mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 14:06:50 +01:00
The field wave's residual gaps are closed. v1.91.96.0 through v1.91.100.0 shipped the install-honesty and unattended-`/autoplan` wave (`docs/designs/GSTACK_FIELD_WAVE_2026_10_12.md`); running that release on a Capy machine left six gaps, each reproduced here with a test that failed first.
41 lines
2.3 KiB
TypeScript
41 lines
2.3 KiB
TypeScript
/**
|
|
* autoplan-admission — who may run `gstack-autoplan`'s mutating verbs. The
|
|
* decision is lib/install-verdict.ts `checkInstallAdmission` (the same one
|
|
* `gstack-skill-start` makes): on a Capy host (GSTACK_CAPY=1) the install
|
|
* verdict the Capy wrapper wrote must pass for this checkout, HEAD and
|
|
* workflow; any unattended session also needs the PATH Bun (the one every
|
|
* `#!/usr/bin/env bun` child runs) at gstack's supported minimum. GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1 admits anyway and the
|
|
* caller records the returned deviation. Interactive sessions, and other
|
|
* hosts' ambient spawned sessions (a warning line), are not refused.
|
|
*/
|
|
import { spawnSync } from 'node:child_process';
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import { RunError } from './autoplan-state';
|
|
import { checkInstallAdmission } from './install-verdict';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
|
|
export interface Admission { lines: string[]; deviation?: string }
|
|
|
|
/**
|
|
* A workflow lease on the shared installation (bin/gstack-lease): the upgrader
|
|
* refuses UPGRADE_BUSY while a live lease it does not own exists, so a
|
|
* session-start fast-forward cannot move the checkout under a running
|
|
* autoplan driver. Fail-open: a missing or failing bin never blocks the run.
|
|
*/
|
|
export function touchInstallLease(env: NodeJS.ProcessEnv = process.env): void {
|
|
const bin = path.join(ROOT, 'bin', 'gstack-lease');
|
|
if (process.platform === 'win32' || !fs.existsSync(bin)) return;
|
|
try { spawnSync(bin, ['touch', '--root', ROOT, '--from', String(process.ppid)], { encoding: 'utf8', timeout: 10_000, env, stdio: 'ignore' }); } catch { /* fail-open */ }
|
|
}
|
|
|
|
/** Throws RunError (exit 3) unless the session may write; an override or a warning returns the line to print. */
|
|
export function admit(env: NodeJS.ProcessEnv = process.env): Admission {
|
|
const bunVersion = spawnSync('bun', ['--version'], { encoding: 'utf8', timeout: 30_000, env }).stdout?.trim() || '0.0.0';
|
|
const a = checkInstallAdmission({ skill: 'autoplan', gstackRoot: ROOT, env, bunVersion });
|
|
if (a.overridden) return { lines: [`ADMISSION: override GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1 (${a.detail})`], deviation: `GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1: admitted despite ${a.detail}` };
|
|
if (a.refuse) throw new RunError(a.code ?? 'INSTALL_UNVERIFIED', a.detail, 3);
|
|
return { lines: a.warn ? [a.line] : [] };
|
|
}
|