fix: harden reproducible checkout races

This commit is contained in:
2026-07-31 18:25:20 -05:00
parent ece7653ca0
commit 0f7061195b
8 changed files with 413 additions and 72 deletions
+112
View File
@@ -0,0 +1,112 @@
import assert from 'node:assert/strict';
import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import { buildWeb } from './build-web.mjs';
const fixtureRoot = path.join(process.cwd(), '.mosaic-test-work', `build-web-${process.pid}`);
async function fixture(name) {
const root = path.join(fixtureRoot, name);
await mkdir(path.join(root, 'apps', 'web', '.next'), { 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('a successful web build atomically publishes its source fingerprint', async () => {
const root = await fixture('success');
const marker = path.join(root, 'apps', 'web', '.next', '.mosaic-source-hash');
await buildWeb({ root, fingerprint: async () => 'certified', runBuild: async () => {} });
assert.equal(await readFile(marker, 'utf8'), 'certified\n');
});
test('a failed web build leaves no certification marker', async () => {
const root = await fixture('failure');
const marker = path.join(root, 'apps', 'web', '.next', '.mosaic-source-hash');
await writeFile(marker, 'stale\n');
await assert.rejects(
buildWeb({
root,
fingerprint: async () => 'before',
runBuild: async () => {
throw new Error('build failed');
},
}),
/build failed/,
);
assert.equal(await exists(marker), false);
});
test('overlapping web builds are serialized while the marker remains absent', async () => {
const root = await fixture('overlap');
const marker = path.join(root, 'apps', 'web', '.next', '.mosaic-source-hash');
await writeFile(marker, 'stale\n');
let releaseFirst;
let secondEntered = false;
const firstEntered = new Promise((resolve) => {
releaseFirst = resolve;
});
let markFirstEntered;
const firstStarted = new Promise((resolve) => {
markFirstEntered = resolve;
});
const first = buildWeb({
root,
fingerprint: async () => 'certified',
runBuild: async () => {
markFirstEntered();
await firstEntered;
},
});
await firstStarted;
const second = buildWeb({
root,
fingerprint: async () => 'certified',
runBuild: async () => {
secondEntered = true;
},
});
await new Promise((resolve) => setTimeout(resolve, 75));
assert.equal(secondEntered, false);
assert.equal(await exists(marker), false);
releaseFirst();
await Promise.all([first, second]);
assert.equal(secondEntered, true);
assert.equal(await readFile(marker, 'utf8'), 'certified\n');
});
test('inputs changed during a web build are not certified', async () => {
const root = await fixture('changed-inputs');
const marker = path.join(root, 'apps', 'web', '.next', '.mosaic-source-hash');
const fingerprints = ['before', 'after'];
await assert.rejects(
buildWeb({
root,
fingerprint: async () => fingerprints.shift(),
runBuild: async () => {},
}),
/inputs changed during next build/,
);
assert.equal(await exists(marker), false);
});