fix(ship-release): GST-1031 re-pin route reads per-file harness pins (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:14:28 +00:00
1 parent f0613db882
commit 876b064a86
2 files changed
+28 -15

No files matched your search

+12 -8
View File
@@ -171,16 +171,20 @@ function commitRelease(cwd: string, release: string[], trailer: string | undefin
/**
* A harness pin edit that only records changed harness files as non-behavioral at the same version and patterns (GST-1031): release
* metadata, like a version stamp. A bump, a pattern change or an entry without its reason is behavior.
* metadata, like a version stamp. The pins live one file per harness file under scripts/harness-pins/ (GST-1084), beside a manifest
* holding only version and patterns. A bump, a pattern change or a pin without its reason is behavior.
*/
const HARNESS_PIN = 'scripts/harness-version.json';
const HARNESS_MANIFEST = 'scripts/harness-version.json';
const HARNESS_PINS = 'scripts/harness-pins/';
function harnessRepin(cwd: string, from: string, to: string, p: string): boolean {
if (p !== HARNESS_PIN) return false;
const read = (tree: string): any => { const s = git(cwd, ['show', `${tree}:${p}`]); try { return s.status === 0 ? JSON.parse(s.stdout) : null; } catch { return null; } };
const a = read(from), b = read(to);
if (!a?.files || !b?.files || typeof b.files !== 'object' || a.version !== b.version || JSON.stringify(a.patterns) !== JSON.stringify(b.patterns)) return false;
return Object.entries(b.files as Record<string, any>).every(([f, e]) => JSON.stringify(a.files[f]) === JSON.stringify(e)
|| (e?.since === b.version && typeof e?.nonBehavioral === 'string' && e.nonBehavioral.trim().length >= 10));
if (p !== HARNESS_MANIFEST && !(p.startsWith(HARNESS_PINS) && p.endsWith('.json'))) return false;
const read = (tree: string, file: string): any => { const s = git(cwd, ['show', `${tree}:${file}`]); if (s.status !== 0) return undefined; try { return JSON.parse(s.stdout); } catch { return null; } };
const a = read(from, HARNESS_MANIFEST), b = read(to, HARNESS_MANIFEST);
if (!a || !b || Object.keys(b).sort().join() !== 'patterns,version' || a.version !== b.version || JSON.stringify(a.patterns) !== JSON.stringify(b.patterns)) return false;
if (p === HARNESS_MANIFEST) return true;
const e = read(to, p);
return e === undefined || JSON.stringify(read(from, p)) === JSON.stringify(e)
|| (e?.since === b.version && typeof e?.nonBehavioral === 'string' && e.nonBehavioral.trim().length >= 10);
}
/** Step 16 stage 2's route. With --reviewed-base, a path whose branch patch is the reviewed one counts as base (lib/ship-patch.ts). */
+16 -7
View File
@@ -309,25 +309,34 @@ describe('gstack-ship-release push: branch names', () => {
});
describe('GST-1031: a non-behavioral harness re-pin after review is release metadata', () => {
// Value: protects=a scripts/harness-version.json edit after review that only records changed harness files as non-behavioral at the same
// Value: protects=a scripts/harness-pins/<path>.json edit after review that only records changed harness files as non-behavioral at the same
// version routes as metadata, so a clean round 2 is not sent back through 5-11.5, while a version bump, a pattern change or an entry
// without its reason stays behavior; fails_when=the re-pin stops finish, or a bump or bare re-pin routes as metadata;
// why_new=GSTA-150 GST-1027 (icm_01M6ERWWBZ3VWXZY07F2W5ZSFB); seam=route; no_claim=that the recorded reason is true
const pin = (version: number, entry: Record<string, unknown>, patterns = ['test/helpers/x.ts']) => JSON.stringify({ version, patterns, files: { 'test/helpers/x.ts': entry } }) + '\n';
const finish = (after: string) => {
const PIN = 'scripts/harness-pins/test/helpers/x.ts.json';
const pin = (version: number, entry: Record<string, unknown>, patterns = ['test/helpers/x.ts']) => ({
'scripts/harness-version.json': JSON.stringify({ version, patterns }) + '\n',
[PIN]: JSON.stringify(entry) + '\n',
});
const finish = (after: Record<string, string>) => {
const f = fx();
f.write({ 'scripts/harness-version.json': pin(6, { blob: 'a'.repeat(40), since: 6 }) }); f.commit('pin');
f.write(pin(6, { blob: 'a'.repeat(40), since: 6 })); f.commit('pin');
const reviewed = wtree(f);
const post = snapshot(f);
f.write({ 'scripts/harness-version.json': after }); f.commit('re-pin');
f.write(after); f.commit('re-pin');
return f.run(RELEASE, ['finish', '--base', 'main', '--reviewed-tree', reviewed, '--docs-post', post, '--lane', LANE]).stdout;
};
test('a non-behavioral re-pin is metadata; a bump, a pattern change or an entry without its reason is behavior', () => {
const repin = finish(pin(6, { blob: 'b'.repeat(40), since: 6, nonBehavioral: 'comment-only edit: no verdict can change' }));
expect(lineOf(repin, 'ROUTE')).toMatch(/behavior=0 docs=0 metadata=1/);
expect(repin).not.toContain('STOP: behavior');
for (const after of [pin(7, { blob: 'b'.repeat(40), since: 7 }), pin(6, { blob: 'b'.repeat(40), since: 6 }), pin(6, { blob: 'b'.repeat(40), since: 6, nonBehavioral: 'comment-only edit: no verdict can change' }, ['test/helpers/**'])])
expect(finish(after)).toContain('STOP: behavior, tests or build inputs changed since review (scripts/harness-version.json)');
expect(finish(pin(7, { blob: 'b'.repeat(40), since: 7 }))).toContain(`STOP: behavior, tests or build inputs changed since review (${PIN},scripts/harness-version.json)`);
expect(finish(pin(6, { blob: 'b'.repeat(40), since: 6 }))).toContain(`STOP: behavior, tests or build inputs changed since review (${PIN})`);
expect(finish(pin(6, { blob: 'b'.repeat(40), since: 6, nonBehavioral: 'comment-only edit: no verdict can change' }, ['test/helpers/**']))).toContain(`STOP: behavior, tests or build inputs changed since review (${PIN},scripts/harness-version.json)`);
});
test('the manifest regaining a files table is behavior', () => {
const f = { 'scripts/harness-version.json': JSON.stringify({ version: 6, patterns: ['test/helpers/x.ts'], files: {} }) + '\n' };
expect(finish(f)).toContain('STOP: behavior, tests or build inputs changed since review (scripts/harness-version.json)');
});
});