fix(harness-version): a pin ahead of the manifest version counts as drift (GST-1084)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-11-03 09:59:11 +00:00
1 parent 37b736ec65
commit ecd3caa2d6
2 files changed
+27 -2

No files matched your search

+2 -2
View File
@@ -66,11 +66,11 @@ export function workingHarnessFiles(root: string, patterns: readonly string[]):
return tracked.map((entry, index) => ({ file: entry.file, blob: blobs[index]! }));
}
/** Harness files whose blob differs from, is missing from, or no longer matches an entry in the manifest. */
/** Harness files whose blob differs from, is missing from, or no longer matches an entry in the manifest; a pin ahead of the manifest version (a bump that failed partway) counts as changed. */
export function harnessDrift(manifest: HarnessManifest, files: TreeEntry[]): Drift {
const current = new Map(files.map(entry => [entry.file, entry.blob]));
return {
changed: files.filter(entry => manifest.files[entry.file] && manifest.files[entry.file]!.blob !== entry.blob).map(entry => entry.file).sort(),
changed: files.filter(entry => manifest.files[entry.file] && (manifest.files[entry.file]!.blob !== entry.blob || manifest.files[entry.file]!.since > manifest.version)).map(entry => entry.file).sort(),
added: files.filter(entry => !manifest.files[entry.file]).map(entry => entry.file).sort(),
removed: Object.keys(manifest.files).filter(file => !current.has(file)).sort(),
};
+25
View File
@@ -105,6 +105,31 @@ describe('HARNESS_VERSION guard', () => {
}
});
// Value: protects=a --bump that fails partway (pins written, manifest version not) cannot pass the guard with behavior pooled into the
// old series; fails_when=a pin ahead of the manifest version reads as no drift; seam=writeManifest failing on a real write, then harnessDrift
test('a bump that fails partway leaves a pin ahead of the manifest version, and the guard reports it (GST-1084)', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-partial-'));
try {
copyManifest(dir);
const live = readManifest(dir);
const current = workingHarnessFiles(ROOT, live.patterns);
const [first, second] = current.map(entry => entry.file);
const blocked = path.join(dir, PINS_DIR, `${second}.json`);
const original = fs.readFileSync(blocked, 'utf8');
fs.rmSync(blocked);
fs.mkdirSync(blocked);
expect(() => writeManifest(dir, decide(live, current, 'bump', 'behavior changed in the runner'))).toThrow();
fs.rmdirSync(blocked);
fs.writeFileSync(blocked, original);
const after = readManifest(dir);
expect(after.version).toBe(live.version);
expect(after.files[first!]!.since).toBe(live.version + 1);
expect(harnessDrift(after, current).changed).toEqual([first]);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
// Value: protects=PRs that record decisions for different harness files merge without a conflict (the one-line-per-file manifest
// conflicted on adjacent lines and on the trailing comma); fails_when=two non-behavioral decisions on adjacent pins conflict in a
// real merge, or two decisions on one pin merge silently; seam=writeManifest and git merge-tree --write-tree on two branches