@bongos/core 1.19.865 → 1.19.867

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,172 @@
1
+ // tests/ship_merge_gate.mjs — the fitness gate on the merge result (task 1004032).
2
+ //
3
+ // WHY. The local-merge land path pushes straight to origin/main, where no
4
+ // required status check can reach it. On 2026-09-17 a branch that ALREADY
5
+ // failed fitness was merged and pushed by ship.js, main went red, and three
6
+ // confirmed tasks could not land until the baseline was raised by hand.
7
+ //
8
+ // The load-bearing assertions are the two that were wrong before this gate
9
+ // existed: a merge result that regresses a clean base must be REFUSED, and a
10
+ // failure inherited from an already-red base must NOT be — a gate that blocked
11
+ // on an inherited failure would have deadlocked that same incident instead of
12
+ // merely spoiling an afternoon.
13
+ //
14
+ // The decision is exercised as the pure function it is; the wiring is pinned
15
+ // separately by reading ship-merge.js, because a correct verdict that is never
16
+ // consulted, or consulted after the push, is worth nothing.
17
+ import assert from 'node:assert/strict';
18
+ import { test } from 'node:test';
19
+ import { createRequire } from 'node:module';
20
+ import fs from 'node:fs';
21
+ import os from 'node:os';
22
+ import path from 'node:path';
23
+ import { fileURLToPath } from 'node:url';
24
+
25
+ const require = createRequire(import.meta.url);
26
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
27
+ const gate = require(path.join(ROOT, 'scripts', 'gds', 'ship-merge-gate.js'));
28
+
29
+ test('a clean merge result is waved through, and the base is never consulted', () => {
30
+ assert.deepEqual(gate.mergeGateVerdict({ mergeClean: true, baseClean: null }), { block: false, reason: 'clean' });
31
+ assert.deepEqual(gate.mergeGateVerdict({ mergeClean: true, baseClean: false }), { block: false, reason: 'clean' });
32
+ });
33
+
34
+ test('THE INCIDENT: a failing merge result over a clean base is REFUSED', () => {
35
+ const verdict = gate.mergeGateVerdict({ mergeClean: false, baseClean: true });
36
+ assert.equal(verdict.block, true, 'a branch that turns main red must not be pushed');
37
+ assert.equal(verdict.reason, 'regression');
38
+ });
39
+
40
+ test('an INHERITED failure is not the shipper’s fault — it must not block', () => {
41
+ const verdict = gate.mergeGateVerdict({ mergeClean: false, baseClean: false });
42
+ assert.equal(verdict.block, false, 'blocking here deadlocks every ship while main is red');
43
+ assert.equal(verdict.reason, 'pre-existing');
44
+ });
45
+
46
+ test('an unmeasurable base blocks — the gate may not guess innocence', () => {
47
+ for (const baseClean of [null, undefined]) {
48
+ const verdict = gate.mergeGateVerdict({ mergeClean: false, baseClean });
49
+ assert.equal(verdict.block, true);
50
+ assert.equal(verdict.reason, 'unmeasured-base');
51
+ }
52
+ });
53
+
54
+ test('every verdict reason carries a message that names its own case', () => {
55
+ const seen = new Set();
56
+ for (const reason of ['clean', 'pre-existing', 'regression', 'unmeasured-base']) {
57
+ const msg = gate.gateMessage(reason);
58
+ assert.match(msg, /fitness/, `${reason} must say what was measured`);
59
+ assert.equal(seen.has(msg), false, `${reason} reuses another verdict’s wording`);
60
+ seen.add(msg);
61
+ }
62
+ // A blocked ship has to say the task is parked, or the builder reads a bare
63
+ // failure and re-runs the ship instead of fixing the tree.
64
+ assert.match(gate.gateMessage('regression'), /confirmed/);
65
+ assert.match(gate.gateMessage('unmeasured-base'), /confirmed/);
66
+ });
67
+
68
+ test('probeBaseClean cannot answer without a base commit', () => {
69
+ // The null-sha case must not reach `git worktree add` at all: an empty ref
70
+ // there resolves to HEAD, which after the merge is the merge itself — the
71
+ // gate would then compare the merge result against ITSELF and always pass.
72
+ assert.equal(gate.probeBaseClean(ROOT, null), null);
73
+ assert.equal(gate.probeBaseClean(ROOT, ''), null);
74
+ });
75
+
76
+ test('WIRING: ship-merge.js runs the gate BEFORE the push that makes it main', () => {
77
+ const src = fs.readFileSync(path.join(ROOT, 'scripts', 'gds', 'ship-merge.js'), 'utf8');
78
+ const gateAt = src.indexOf('gateMergeResult({');
79
+ const pushAt = src.indexOf('push-main.js');
80
+ assert.notEqual(gateAt, -1, 'ship-merge.js must call the gate');
81
+ assert.notEqual(pushAt, -1, 'ship-merge.js must still push through push-main.js');
82
+ assert.ok(gateAt < pushAt, 'the gate runs after the push — it would measure a main that is already red');
83
+ // …and it must actually act on the verdict. A call whose result is dropped
84
+ // reads as a gate in a diff and is a no-op at runtime.
85
+ assert.match(src, /fitnessVerdict\.block/, 'the verdict must be consulted');
86
+ assert.match(src.slice(gateAt, pushAt), /return landBailed\(/, 'a blocked merge must bail before the push');
87
+ });
88
+
89
+ test('the gate measures the PRE-MERGE base, captured before the branch lands', () => {
90
+ const src = fs.readFileSync(path.join(ROOT, 'scripts', 'gds', 'ship-merge.js'), 'utf8');
91
+ const baseAt = src.indexOf('const baseSha =');
92
+ const mergeAt = src.indexOf('buildMergeArgv(branch, valueSummary)');
93
+ assert.notEqual(baseAt, -1, 'the base sha must be captured');
94
+ assert.ok(baseAt < mergeAt, 'the base sha is read after the merge — it would name the merge commit itself');
95
+ });
96
+
97
+ // The bug that the first cut of this gate actually shipped with, and that only
98
+ // a replay of the real incident caught: fitness.js resolves the repo it
99
+ // measures from its OWN __dirname and ignores cwd, so running this worktree's
100
+ // copy against another tree measures this worktree and calls the other one
101
+ // clean. These fixtures are exit codes, not real gates — if runFitnessAt ever
102
+ // goes back to invoking a fixed sibling script, the failing fixture returns
103
+ // true and this reds.
104
+ function fakeTree(exitCode) {
105
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'merge-gate-fixture-'));
106
+ fs.mkdirSync(path.join(dir, 'scripts', 'gds'), { recursive: true });
107
+ const body = exitCode === 0 ? 'process.exit(0);\n' : `process.exit(${exitCode});\n`;
108
+ fs.writeFileSync(path.join(dir, 'scripts', 'gds', 'fitness.js'), body);
109
+ return dir;
110
+ }
111
+
112
+ test('runFitnessAt runs the MEASURED TREE\u2019s own fitness.js, not this one\u2019s', () => {
113
+ const green = fakeTree(0);
114
+ const red = fakeTree(1);
115
+ try {
116
+ assert.equal(gate.runFitnessAt(green), true, 'a passing tree must certify');
117
+ assert.equal(gate.runFitnessAt(red), false, 'a failing tree reported clean means the wrong tree was measured');
118
+ } finally {
119
+ fs.rmSync(green, { recursive: true, force: true });
120
+ fs.rmSync(red, { recursive: true, force: true });
121
+ }
122
+ });
123
+
124
+ test('a tree with no fitness.js cannot be certified', () => {
125
+ const bare = fs.mkdtempSync(path.join(os.tmpdir(), 'merge-gate-bare-'));
126
+ try {
127
+ assert.equal(gate.runFitnessAt(bare), false, 'no gate present must read as "cannot certify", never as clean');
128
+ } finally {
129
+ fs.rmSync(bare, { recursive: true, force: true });
130
+ }
131
+ });
132
+
133
+ // The hacker finding on the first cut: a boolean outcome made "fitness crashed"
134
+ // and "fitness found something" the same answer, and since a FAILING base waves
135
+ // the ship through, a merely-broken probe would have certified a real
136
+ // regression as inherited. A control that degrades to permissive under infra
137
+ // flakiness is not a control.
138
+ test('a crashed run is UNMEASURABLE, not a finding', () => {
139
+ const crashed = fs.mkdtempSync(path.join(os.tmpdir(), 'merge-gate-crash-'));
140
+ fs.mkdirSync(path.join(crashed, 'scripts', 'gds'), { recursive: true });
141
+ // A tree whose gate cannot even be parsed: node exits non-zero, so this one
142
+ // still reads as a finding — that is deliberate, it IS a broken committed
143
+ // gate. The infra case is the one below it: no gate in the tree at all.
144
+ try {
145
+ assert.equal(gate.fitnessOutcome(path.join(crashed, 'nope')), 'unmeasurable');
146
+ assert.equal(gate.fitnessOutcome(crashed), 'unmeasurable', 'a tree with no fitness.js cannot be judged');
147
+ } finally {
148
+ fs.rmSync(crashed, { recursive: true, force: true });
149
+ }
150
+ });
151
+
152
+ test('the tri-state keeps clean and failing apart', () => {
153
+ const green = fs.mkdtempSync(path.join(os.tmpdir(), 'merge-gate-green-'));
154
+ const red = fs.mkdtempSync(path.join(os.tmpdir(), 'merge-gate-red-'));
155
+ for (const [dir, code] of [[green, 0], [red, 1]]) {
156
+ fs.mkdirSync(path.join(dir, 'scripts', 'gds'), { recursive: true });
157
+ fs.writeFileSync(path.join(dir, 'scripts', 'gds', 'fitness.js'), `process.exit(${code});\n`);
158
+ }
159
+ try {
160
+ assert.equal(gate.fitnessOutcome(green), 'clean');
161
+ assert.equal(gate.fitnessOutcome(red), 'failing', 'a non-zero exit is a real verdict, not infra noise');
162
+ } finally {
163
+ fs.rmSync(green, { recursive: true, force: true });
164
+ fs.rmSync(red, { recursive: true, force: true });
165
+ }
166
+ });
167
+
168
+ test('an unmeasurable base blocks rather than waving the ship through', () => {
169
+ // The end-to-end shape of the finding: probeBaseClean returns null for a base
170
+ // it could not measure, and null blocks.
171
+ assert.equal(gate.mergeGateVerdict({ mergeClean: false, baseClean: null }).block, true);
172
+ });