From 6b602a9673d099a88ea75dea3efc24377db847e8 Mon Sep 17 00:00:00 2001 From: garrytan Date: Thu, 29 Oct 2026 05:39:15 +0000 Subject: [PATCH] test(train-stamp): S_i keeps the base's four-part package.json (#3557) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 +- test/ship-queue-train-stamp.test.ts | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67cc56664..9d6e6d262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ - `test/ship-queue-land.test.ts` (bare origin and a gh shim): happy path; two PRs from one base without a collision; a legacy stamp plus a dependency edit; a title-derived entry; a product conflict; the lock and `--break-lock`; the base moving during CI; a foreign push never overwritten; a missing receipt; drift, `--repair` and the lock release; the census; a receipt that fails #3567's depth check refuses; a base with a four-part package.json in `stamp_files` lands four parts (#3557), gstack's own shape stays three. - `test/version-gate-fragments.test.ts`: fragment heads, premature version changes, a bot author, an advanced base, an invalid fragment, the stamp commit's effective bump, and the workflow's missing path filter. - `test/restamp-command-boundary.test.ts`: the hook reads its counter from `GSTACK_RESTAMP_BASE_SHA`, not the branch. -- `test/ship-queue-train-stamp.test.ts`: S2 keeps S1's release and runs the versions in order with each slot's bump; a stacked child folds only its own fragment; a tampered earlier version or a product change is refused. +- `test/ship-queue-train-stamp.test.ts`: S2 keeps S1's release and runs the versions in order with each slot's bump; a stacked child folds only its own fragment; a four-part package.json base keeps four parts in every S_i, read from S_(i-1); a tampered earlier version or a product change is refused. - `test/ship-queue-train-land.test.ts`: build records S1 and S2 and gates S2; land publishes each slot exactly with the label; a foreign push is never overwritten; a merge outside the train freezes the queue; a plain train refuses `train land`. - `test/train-backstop.test.ts`: main equal to S1 is covered; the same tree with another VERSION is gated; a chain mixing stamped and plain prefixes is refused. diff --git a/test/ship-queue-train-stamp.test.ts b/test/ship-queue-train-stamp.test.ts index eb58f7a6d..9431bfc21 100644 --- a/test/ship-queue-train-stamp.test.ts +++ b/test/ship-queue-train-stamp.test.ts @@ -67,6 +67,21 @@ describe('stamped train prefixes', () => { expect(top).not.toContain('- parent change'); }, 60_000); + test('a base with a four-part package.json listed in stamp_files keeps four parts in every S_i, read from S_(i-1)', () => { + const policy = { ...POLICY, stamp_files: ['VERSION', 'package.json', 'CHANGELOG.md', 'changelog.d/*.md'], stamp_patterns: ['"version": "{version}"'] }; + const r = makeFixtureRepo({ policy, prefix: 'train-stamp-' }); repos.push(r); + const pkg = JSON.parse(fs.readFileSync(path.join(r.work, 'package.json'), 'utf8')); + fs.writeFileSync(path.join(r.work, 'package.json'), JSON.stringify({ ...pkg, version: '1.2.3.0' }, null, 2) + '\n'); + fs.rmSync(path.join(r.work, 'package-lock.json')); + r.git('add', '-A'); r.git('commit', '-q', '-m', 'four-part manifest'); r.git('push', '-q', 'origin', 'main'); + const a = pin(r, 'a', 'origin/main', '- a change\n'); + const b = pin(r, 'b', 'origin/main', '- b change\n'); + const o = { ...chain(r, [['1', a], ['2', b]]), policy: mergePolicy(policy, 'test').policy }; + const out = stampChain(o); + if ('code' in out) throw new Error(out.message); + expect(out.stamped.map(s => JSON.parse(show(r, s.commit + ':package.json')).version)).toEqual(['1.2.4.0', '1.2.5.0']); + }, 60_000); + test('a tampered earlier version or a product change in S_i is refused', () => { const r = makeFixtureRepo({ policy: POLICY, prefix: 'train-stamp-' }); repos.push(r); const a = pin(r, 'a', 'origin/main', '- a change\n');