fix: make checkout gates environment-aware

This commit is contained in:
mosaic-coder
2026-07-31 17:50:58 -05:00
parent 01e966f36d
commit a0209ee102
11 changed files with 634 additions and 8 deletions
+1
View File
@@ -8,6 +8,7 @@ coverage
.env.local
*.tsbuildinfo
.pnpm-store
__pycache__/
docs/reports/
# Step-CA dev password — real file is gitignored; commit only the .example
+1 -1
View File
@@ -1 +1 @@
pnpm typecheck && pnpm lint && pnpm format:check
pnpm preflight && pnpm typecheck && pnpm lint && pnpm format:check
+4 -4
View File
@@ -1,5 +1,5 @@
@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/
# Pin the pnpm store to the same path the ci-base image warms (Dockerfile.ci),
# so the pipeline `pnpm install --prefer-offline` consumes the baked store
# instead of repopulating a fresh one.
store-dir=/root/.local/share/pnpm/store
# HOME resolves to /root in the ci-base image, preserving its warmed-store path.
# Non-root checkouts use their own HOME. Override without editing this file via
# NPM_CONFIG_STORE_DIR (pnpm's environment form of the store-dir setting).
store-dir=${HOME}/.local/share/pnpm/store
+7
View File
@@ -201,8 +201,14 @@ git clone [email protected]:mosaicstack/stack.git
cd stack
# Install dependencies. The local tier uses in-process PGlite; leave DATABASE_URL unset.
# The pnpm store defaults to $HOME/.local/share/pnpm/store. Override it without
# editing the checkout with NPM_CONFIG_STORE_DIR=$HOME/another-store if needed.
pnpm install
# Verify dependencies and generated state before running source-quality gates.
# Missing dependencies exit 42; stale/foreign apps/web/.next state exits 43.
pnpm preflight
# Optional local queue service only. This does not start PostgreSQL.
docker compose up -d valkey
@@ -230,6 +236,7 @@ Gateway start command until KBN-101-02 makes that state fail closed.
### Quality Gates
```bash
pnpm preflight # Checkout/dependency/generated-state validation
pnpm typecheck # TypeScript type checking (all packages)
pnpm lint # ESLint (all packages)
pnpm test # Vitest (all packages)
@@ -0,0 +1,20 @@
# RM-01 — Reproducible checkout
- Task/ref: RM-01 (`docs/remediation/TASKS.md`, internal mission tracking)
- Objective: make checkout/install/typecheck hooks fail on code rather than environmental residue, for root CI and non-root seats.
- Scope: pnpm store configuration, transactional Husky installation, dependency/generated-state preflight, checkout regression tests, developer documentation.
- Constraints: isolated worktree; no skip-switch fixes; no writes under `/root` or `/tmp`; workers do not edit `docs/remediation/TASKS.md`; author does not review or merge.
- Acceptance: AC1AC8 from the orchestrator dispatch/addendum.
- Plan:
1. Add RED-first tests for missing dependencies, stale/foreign `.next`, and interrupted hook installation.
2. Implement environment-overridable HOME-based pnpm store defaults, deterministic preflight, and transactional hook installation.
3. Run focused tests, install/build/baseline gates, and explicit AC negative controls.
4. Obtain independent review, push after queue guard, open PR, and send evidence to `mos-remediation`.
- Budget: orchestrator estimate 6K/60K; no explicit hard token cap. Keep scope to RM-01 and avoid unrelated cleanup.
- Risks: 97%-full shared `/tmp`; native dependency install size; root-owned fixtures may require Docker for realistic verification.
## Progress / evidence
- Worktree created at `/home/hermes/agent-work/rm-01` from `origin/main` `06e0d403`.
- `/tmp` baseline: 28G used, 889M available (97%); worktree and planned store are on `/home`.
- Root causes confirmed from source: committed `.npmrc` pins `/root`; `prepare` invokes Husky directly; web typecheck includes generated `.next` types without validating ownership/freshness.
+6 -3
View File
@@ -6,11 +6,14 @@
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"typecheck": "turbo run typecheck",
"test": "turbo run test",
"preflight": "node scripts/preflight.mjs",
"clean:generated": "node scripts/clean-generated.mjs",
"typecheck": "pnpm preflight && turbo run typecheck",
"test:checkout": "node --test scripts/*.test.mjs",
"test": "pnpm test:checkout && turbo run test",
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\"",
"prepare": "husky"
"prepare": "node scripts/install-hooks.mjs"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.0.0",
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env node
import { access, mkdir, rename, rm } from 'node:fs/promises';
import path from 'node:path';
const root = process.cwd();
const generated = path.join(root, 'apps', 'web', '.next');
const quarantineRoot = path.join(root, '.mosaic-test-work', 'generated-quarantine');
try {
await access(generated);
} catch (error) {
if (error.code === 'ENOENT') process.exit(0);
throw error;
}
await mkdir(quarantineRoot, { recursive: true });
const quarantine = path.join(quarantineRoot, `web-next-${Date.now()}-${process.pid}`);
try {
await rename(generated, quarantine);
} catch (error) {
console.error(
`MOSAIC_GENERATED_CLEAN_FAILED: could not quarantine apps/web/.next. Fix: sudo rm -rf '${generated}', then rerun pnpm preflight`,
);
throw error;
}
try {
await rm(quarantine, { recursive: true, force: true });
} catch {
console.warn(
`Generated state was deactivated but could not be deleted; quarantined at ${quarantine}`,
);
}
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env node
import { access, lstat, mkdir, readFile, readdir, rename, rm } from 'node:fs/promises';
import { execFile, spawn } from 'node:child_process';
import { promisify } from 'node:util';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const execFileAsync = promisify(execFile);
function run(command, args, options) {
return new Promise((resolve, reject) => {
const child = spawn(command, args, options);
child.once('error', reject);
child.once('exit', (code, signal) => {
if (code === 0) resolve();
else reject(new Error(signal ? `husky terminated by ${signal}` : `husky exited ${code}`));
});
});
}
async function pathExists(target) {
try {
await access(target);
return true;
} catch (error) {
if (error.code === 'ENOENT') return false;
throw error;
}
}
async function directorySnapshot(root) {
const snapshot = [];
async function walk(current) {
const children = await readdir(current, { withFileTypes: true });
for (const child of children.sort((left, right) => left.name.localeCompare(right.name))) {
const target = path.join(current, child.name);
const relative = path.relative(root, target);
const stats = await lstat(target);
if (child.isDirectory()) {
snapshot.push([relative, 'directory', stats.mode & 0o777]);
await walk(target);
} else {
snapshot.push([
relative,
'file',
stats.mode & 0o777,
(await readFile(target)).toString('base64'),
]);
}
}
}
await walk(root);
return JSON.stringify(snapshot);
}
async function directoriesMatch(left, right) {
return (await directorySnapshot(left)) === (await directorySnapshot(right));
}
export async function installHooks({
root = process.cwd(),
disabled = process.env.HUSKY === '0',
quarantineRoot = path.join(root, '.mosaic-test-work', 'husky-quarantine'),
runHusky = async (_stagingHooks, stagingRepo) => {
await execFileAsync('git', ['init', '--quiet', stagingRepo]);
await run(path.join(root, 'node_modules', '.bin', 'husky'), ['.husky'], {
cwd: stagingRepo,
stdio: 'inherit',
});
},
activateHooks = async () => {
await run('git', ['config', 'core.hooksPath', '.husky/_'], { cwd: root, stdio: 'inherit' });
},
readHooksPath = async () => {
try {
const { stdout } = await execFileAsync('git', ['config', '--get', 'core.hooksPath'], {
cwd: root,
});
return stdout.trim() || null;
} catch (error) {
if (error.code === 1) return null;
throw error;
}
},
restoreHooksPath = async (previous) => {
if (previous === null) {
try {
await execFileAsync('git', ['config', '--unset', 'core.hooksPath'], { cwd: root });
} catch (error) {
if (error.code !== 5) throw error;
}
} else {
await execFileAsync('git', ['config', 'core.hooksPath', previous], { cwd: root });
}
},
} = {}) {
if (disabled) return;
const huskyDir = path.join(root, '.husky');
const active = path.join(huskyDir, '_');
const nonce = `${Date.now()}-${process.pid}`;
const stagingRepo = path.join(root, '.mosaic-test-work', `husky-stage-${nonce}`);
const stagingHooks = path.join(stagingRepo, '.husky');
const quarantined = path.join(quarantineRoot, `${path.basename(root)}-${nonce}`);
const previousHooksPath = await readHooksPath();
await mkdir(huskyDir, { recursive: true });
await mkdir(quarantineRoot, { recursive: true });
const previousComplete = (await pathExists(active)) && (await pathExists(path.join(active, 'h')));
let previousQuarantined = false;
try {
if ((await pathExists(active)) && !previousComplete) {
await rename(active, quarantined);
previousQuarantined = true;
}
await mkdir(stagingRepo, { recursive: true });
await runHusky(stagingHooks, stagingRepo);
const staged = path.join(stagingHooks, '_');
if (!(await pathExists(path.join(staged, 'h')))) {
throw new Error('husky did not produce its required h shim');
}
if (previousComplete) {
if (!(await directoriesMatch(active, staged))) {
throw new Error('existing complete hook set differs from the installed Husky version');
}
await rm(stagingRepo, { recursive: true, force: true });
} else {
await rename(staged, active);
await rm(stagingRepo, { recursive: true, force: true });
}
await activateHooks();
if (previousQuarantined) {
try {
await rm(quarantined, { recursive: true, force: true });
} catch {
console.warn(
`Previous hook state was deactivated but remains quarantined at ${quarantined}`,
);
}
}
} catch (error) {
const cleanupFailures = [];
try {
if ((await pathExists(active)) && !previousComplete) {
await rename(active, `${quarantined}-failed`);
}
} catch (cleanupError) {
cleanupFailures.push(`active hooks: ${cleanupError.message}`);
}
try {
if (await pathExists(stagingRepo)) {
await rename(stagingRepo, `${quarantined}-staging`);
}
} catch (cleanupError) {
cleanupFailures.push(`staging hooks: ${cleanupError.message}`);
}
try {
await restoreHooksPath(previousHooksPath);
} catch (cleanupError) {
cleanupFailures.push(`core.hooksPath: ${cleanupError.message}`);
}
const cleanup =
cleanupFailures.length === 0
? 'No partial hook set was activated.'
: `Automatic cleanup was incomplete (${cleanupFailures.join('; ')}).`;
throw new Error(
`Hook installation failed: ${error.message}. ${cleanup} Fix: rm -rf .husky/_ && git config core.hooksPath .husky/_ && pnpm install --frozen-lockfile`,
{ cause: error },
);
}
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
try {
await installHooks();
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
}
+148
View File
@@ -0,0 +1,148 @@
import assert from 'node:assert/strict';
import { access, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import { installHooks } from './install-hooks.mjs';
const fixtureRoot = path.join(process.cwd(), '.mosaic-test-work', `hooks-${process.pid}`);
const quarantineRoot = path.join(fixtureRoot, 'quarantine');
async function fixture(name) {
const root = path.join(fixtureRoot, name);
await mkdir(path.join(root, '.husky'), { recursive: true });
return root;
}
async function exists(target) {
try {
await access(target);
return true;
} catch {
return false;
}
}
test.after(async () => {
await rm(fixtureRoot, { recursive: true, force: true });
});
test('an interrupted install quarantines the partial active hook set and fails loudly', async () => {
const root = await fixture('interrupted');
let restoredHooksPath = 'not-called';
await assert.rejects(
installHooks({
root,
quarantineRoot,
runHusky: async (stagingHooks) => {
await mkdir(path.join(stagingHooks, '_'), { recursive: true });
await writeFile(path.join(stagingHooks, '_', 'h'), 'partial');
throw new Error('simulated interruption');
},
activateHooks: async () => {},
readHooksPath: async () => null,
restoreHooksPath: async (value) => {
restoredHooksPath = value;
},
}),
(error) => {
assert.match(error.message, /Hook installation failed/);
assert.match(error.message, /pnpm install --frozen-lockfile/);
return true;
},
);
assert.equal(await exists(path.join(root, '.husky', '_')), false);
assert.equal(restoredHooksPath, null);
const quarantined = await readdir(quarantineRoot);
assert.equal(quarantined.length, 1);
});
test('a failed replacement restores a previously complete active hook set', async () => {
const root = await fixture('rollback');
const activeShim = path.join(root, '.husky', '_', 'h');
await mkdir(path.dirname(activeShim), { recursive: true });
await writeFile(activeShim, 'previous-complete');
let previousRemainedActiveDuringStaging = false;
await assert.rejects(
installHooks({
root,
quarantineRoot: path.join(fixtureRoot, 'rollback-quarantine'),
runHusky: async () => {
previousRemainedActiveDuringStaging =
(await readFile(activeShim, 'utf8')) === 'previous-complete';
throw new Error('simulated replacement failure');
},
activateHooks: async () => {},
readHooksPath: async () => '.husky/_',
restoreHooksPath: async () => {},
}),
/Hook installation failed/,
);
assert.equal(previousRemainedActiveDuringStaging, true);
assert.equal(await readFile(activeShim, 'utf8'), 'previous-complete');
});
test('a mismatched complete hook set fails loudly instead of reporting a stale install as current', async () => {
const root = await fixture('mismatch');
const activeShim = path.join(root, '.husky', '_', 'h');
await mkdir(path.dirname(activeShim), { recursive: true });
await writeFile(activeShim, 'old-complete');
await assert.rejects(
installHooks({
root,
quarantineRoot: path.join(fixtureRoot, 'mismatch-quarantine'),
runHusky: async (stagingHooks) => {
await mkdir(path.join(stagingHooks, '_'), { recursive: true });
await writeFile(path.join(stagingHooks, '_', 'h'), 'new-complete');
},
activateHooks: async () => {},
readHooksPath: async () => '.husky/_',
restoreHooksPath: async () => {},
}),
/Hook installation failed.*pnpm install --frozen-lockfile/,
);
assert.equal(await readFile(activeShim, 'utf8'), 'old-complete');
});
test('an explicit interactive HUSKY=0 opt-out preserves existing hooks without running installer', async () => {
const root = await fixture('disabled');
const activeShim = path.join(root, '.husky', '_', 'h');
await mkdir(path.dirname(activeShim), { recursive: true });
await writeFile(activeShim, 'preserved');
let ran = false;
await installHooks({
root,
disabled: true,
runHusky: async () => {
ran = true;
},
});
assert.equal(ran, false);
assert.equal(await readFile(activeShim, 'utf8'), 'preserved');
});
test('a successful install leaves a complete active hook set', async () => {
const root = await fixture('success');
await installHooks({
root,
quarantineRoot,
runHusky: async (stagingHooks) => {
await mkdir(path.join(stagingHooks, '_'), { recursive: true });
await writeFile(path.join(stagingHooks, '_', 'h'), 'complete');
},
activateHooks: async () => {},
readHooksPath: async () => null,
restoreHooksPath: async () => {},
});
assert.equal(await exists(path.join(root, '.husky', '_', 'h')), true);
});
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env node
import { constants } from 'node:fs';
import { access, lstat, readdir } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
export const MISSING_DEPS_EXIT = 42;
export const GENERATED_STATE_EXIT = 43;
async function entries(root) {
const result = [];
async function walk(current) {
let children;
try {
children = await readdir(current, { withFileTypes: true });
} catch (error) {
if (error.code === 'ENOENT') return;
throw error;
}
for (const child of children) {
const target = path.join(current, child.name);
result.push(target);
if (child.isDirectory() && !child.isSymbolicLink()) await walk(target);
}
}
await walk(root);
return result;
}
async function fileMtimeRange(paths) {
let newest = 0;
let oldest = Number.POSITIVE_INFINITY;
for (const target of paths) {
const stats = await lstat(target);
if (stats.isFile()) {
newest = Math.max(newest, stats.mtimeMs);
oldest = Math.min(oldest, stats.mtimeMs);
}
}
return { newest, oldest: Number.isFinite(oldest) ? oldest : 0 };
}
export async function runPreflight({ root = process.cwd(), uid = process.getuid?.() } = {}) {
const binDir = path.join(root, 'node_modules', '.bin');
const requiredBinaries = ['eslint', 'husky', 'prettier', 'tsc', 'turbo', 'vitest'];
const missingBinaries = [];
for (const binary of requiredBinaries) {
try {
await access(path.join(binDir, binary), constants.X_OK);
} catch {
missingBinaries.push(binary);
}
}
if (missingBinaries.length > 0) {
return {
code: MISSING_DEPS_EXIT,
message: `MOSAIC_PREFLIGHT_MISSING_DEPS: dependency installation is missing ${missingBinaries.join(', ')}; run pnpm install --frozen-lockfile`,
};
}
const nextDir = path.join(root, 'apps', 'web', '.next');
let generated = [];
try {
await lstat(nextDir);
generated = [nextDir, ...(await entries(nextDir))];
} catch (error) {
if (error.code !== 'ENOENT') throw error;
}
if (generated.length > 0) {
const foreign = [];
if (uid !== undefined) {
for (const target of generated) {
if ((await lstat(target)).uid !== uid) foreign.push(path.relative(root, target));
}
}
const sourceRoots = [
path.join(root, 'apps', 'web', 'src'),
path.join(root, 'apps', 'web', 'next.config.ts'),
path.join(root, 'apps', 'web', 'package.json'),
path.join(root, 'apps', 'web', 'tsconfig.json'),
];
const source = [];
for (const sourceRoot of sourceRoots) {
try {
const stats = await lstat(sourceRoot);
source.push(sourceRoot);
if (stats.isDirectory()) source.push(...(await entries(sourceRoot)));
} catch (error) {
if (error.code !== 'ENOENT') throw error;
}
}
const generatedTypes = generated.filter((target) =>
target.startsWith(path.join(nextDir, 'types') + path.sep),
);
const sourceMtime = await fileMtimeRange(source);
const generatedMtime = await fileMtimeRange(generatedTypes);
const stale =
source.length > 0 && generatedTypes.length > 0 && sourceMtime.newest > generatedMtime.oldest;
if (foreign.length > 0 || stale) {
const reasons = [
foreign.length > 0 ? `foreign-owned paths: ${foreign.slice(0, 3).join(', ')}` : '',
stale ? 'generated output is older than web source/configuration' : '',
].filter(Boolean);
return {
code: GENERATED_STATE_EXIT,
message: `MOSAIC_PREFLIGHT_GENERATED_STATE: apps/web/.next is not trustworthy (${reasons.join('; ')}); run pnpm clean:generated, then rerun the gate`,
};
}
}
return { code: 0, message: 'checkout preflight passed' };
}
async function main() {
const result = await runPreflight();
const stream = result.code === 0 ? process.stdout : process.stderr;
stream.write(`${result.message}\n`);
process.exitCode = result.code;
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
await main();
}
+104
View File
@@ -0,0 +1,104 @@
import assert from 'node:assert/strict';
import { chmod, mkdir, rm, symlink, utimes, writeFile } from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import { runPreflight } from './preflight.mjs';
const fixtureRoot = path.join(process.cwd(), '.mosaic-test-work', `preflight-${process.pid}`);
const requiredBins = ['eslint', 'husky', 'prettier', 'tsc', 'turbo', 'vitest'];
async function fixture(name) {
const root = path.join(fixtureRoot, name);
await mkdir(path.join(root, 'apps', 'web', 'src', 'app'), { recursive: true });
await writeFile(path.join(root, 'apps', 'web', 'src', 'app', 'page.tsx'), 'export default 1;\n');
return root;
}
async function installRequiredBins(root) {
const binDir = path.join(root, 'node_modules', '.bin');
await mkdir(binDir, { recursive: true });
await Promise.all(
requiredBins.map(async (name) => {
const target = path.join(binDir, name);
await writeFile(target, '');
await chmod(target, 0o755);
}),
);
}
test.after(async () => {
await rm(fixtureRoot, { recursive: true, force: true });
});
test('missing dependencies have a dedicated exit code and install remediation', async () => {
const root = await fixture('missing-deps');
const result = await runPreflight({ root });
assert.equal(result.code, 42);
assert.match(result.message, /MOSAIC_PREFLIGHT_MISSING_DEPS/);
assert.match(result.message, /run pnpm install/i);
});
test('a partial dependency install keeps the dedicated missing-deps result', async () => {
const root = await fixture('partial-deps');
await mkdir(path.join(root, 'node_modules', '.bin'), { recursive: true });
await writeFile(path.join(root, 'node_modules', '.bin', 'tsc'), '', { mode: 0o755 });
const result = await runPreflight({ root });
assert.equal(result.code, 42);
assert.match(result.message, /turbo/);
});
test('a dangling required dependency shim keeps the dedicated missing-deps result', async () => {
const root = await fixture('dangling-deps');
await installRequiredBins(root);
const turbo = path.join(root, 'node_modules', '.bin', 'turbo');
await rm(turbo);
await symlink(path.join(root, 'node_modules', 'missing-turbo'), turbo);
const result = await runPreflight({ root });
assert.equal(result.code, 42);
assert.match(result.message, /turbo/);
});
test('installed dependencies pass when generated state is absent', async () => {
const root = await fixture('clean');
await installRequiredBins(root);
assert.deepEqual(await runPreflight({ root }), { code: 0, message: 'checkout preflight passed' });
});
test('foreign-owned generated Next state is identified separately from source errors', async () => {
const root = await fixture('foreign-next');
await installRequiredBins(root);
const generated = path.join(root, 'apps', 'web', '.next', 'types', 'validator.ts');
await mkdir(path.dirname(generated), { recursive: true });
await writeFile(generated, 'generated output');
const result = await runPreflight({ root, uid: (process.getuid?.() ?? 0) + 1 });
assert.equal(result.code, 43);
assert.match(result.message, /MOSAIC_PREFLIGHT_GENERATED_STATE/);
assert.match(result.message, /foreign-owned/);
});
test('stale generated Next state is identified separately from source errors', async () => {
const root = await fixture('stale-next');
await installRequiredBins(root);
const generated = path.join(root, 'apps', 'web', '.next', 'types', 'validator.ts');
await mkdir(path.dirname(generated), { recursive: true });
await writeFile(generated, 'stale generated output');
const old = new Date('2020-01-01T00:00:00Z');
await utimes(generated, old, old);
const fresh = path.join(root, 'apps', 'web', '.next', 'types', 'routes.ts');
await writeFile(fresh, 'fresh generated output');
const future = new Date('2030-01-01T00:00:00Z');
await utimes(fresh, future, future);
const result = await runPreflight({ root });
assert.equal(result.code, 43);
assert.match(result.message, /MOSAIC_PREFLIGHT_GENERATED_STATE/);
assert.match(result.message, /apps\/web\/\.next/);
assert.match(result.message, /pnpm clean:generated/);
});