@bongos/core 1.19.889 → 1.19.891
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.
- package/.bongos-core.json +39 -24
- package/docs/module-api-changelog.md +4 -0
- package/modules/grading/grader-rubric.json +17 -7
- package/modules/grading/grader-score.js +7 -1
- package/modules/grading/grader-workers/extract-json.js +7 -1
- package/modules/grading/grader-workers/index.js +43 -0
- package/modules/grading/grader-workers/reader.js +218 -0
- package/modules/grading/grader.js +47 -11
- package/modules/lifecycle/cascade.js +23 -13
- package/modules/lifecycle/tests/cascade.mjs +18 -9
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/build-cli-package.js +4 -0
- package/scripts/gds/ship-grade.js +28 -2
- package/scripts/gds/status.js +89 -13
- package/scripts/gds/version-literal-guard.js +22 -4
- package/src/module-api.js +1 -1
- package/tests/extract_json.mjs +8 -0
- package/tests/grader_reader_lens.mjs +257 -0
- package/tests/status_default_version.mjs +136 -0
- package/tests/version_literals.mjs +92 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// tests/grader_reader_lens.mjs — the advisory Reader lens (task 1003977, ADR 0298).
|
|
2
|
+
//
|
|
3
|
+
// The owner settled three terms on 2026-09-17 and each is pinned here:
|
|
4
|
+
// SCOPE copy-bearing diffs only, keyed off the ONE existing predicate.
|
|
5
|
+
// AUTHORITY advisory, full stop — it cannot fail, cannot reach 'concerns',
|
|
6
|
+
// and cannot block via an error or a timeout.
|
|
7
|
+
// STANDARD docs/voice.md, cited by rule id.
|
|
8
|
+
//
|
|
9
|
+
// The task names the error exemption as "the single most important test in this
|
|
10
|
+
// task", because a worker error normally HARD-FAILS the panel
|
|
11
|
+
// (panel_gate: 'ANY worker.error != null → passed=false'). It is §2 below.
|
|
12
|
+
//
|
|
13
|
+
// The subtlest case is §3: a HEALTHY reader must not mask a total grader
|
|
14
|
+
// outage. Before the non-gating partition, a passing fifth worker would make
|
|
15
|
+
// "every gating worker is missing signal" false, and a total outage would
|
|
16
|
+
// present as FAIL · 0/10 with every axis null — the exact regression class
|
|
17
|
+
// task 1003574 documents, where a builder sat dark on an unclearable re-grade.
|
|
18
|
+
|
|
19
|
+
import { test } from 'node:test';
|
|
20
|
+
import assert from 'node:assert/strict';
|
|
21
|
+
import fs from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { createRequire } from 'node:module';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
28
|
+
const grader = require('../modules/grading/grader.js');
|
|
29
|
+
const WORKERS = require('../modules/grading/grader-workers/index.js');
|
|
30
|
+
const reader = require('../modules/grading/grader-workers/reader.js');
|
|
31
|
+
const { hasPanelEvidence } = require('../modules/grading/grader-score.js');
|
|
32
|
+
const { hasCopyOrVisualWork } = require('../modules/lifecycle/cascade.js');
|
|
33
|
+
|
|
34
|
+
const W = (kind, verdict, extra = {}) => ({
|
|
35
|
+
kind, name: kind, verdict, scores: {}, issues: [], confidence: 0.9, error: null, ...extra,
|
|
36
|
+
});
|
|
37
|
+
const healthyFour = () => [
|
|
38
|
+
W('narc', 'pass', { scores: { functional_fidelity: 9 } }),
|
|
39
|
+
W('quality', 'pass', { scores: { code_quality: 9, economy: 9 } }),
|
|
40
|
+
W('hacker', 'pass', { scores: { security_score: 9 } }),
|
|
41
|
+
W('efficiency', 'pass', { scores: { efficiency_score: 9 } }),
|
|
42
|
+
];
|
|
43
|
+
const agg = (ws) => grader.aggregatePanel(ws, {});
|
|
44
|
+
|
|
45
|
+
// ---- 1. AUTHORITY: the verdict itself cannot be a blocking one -------------
|
|
46
|
+
|
|
47
|
+
test('computeVerdict returns "advisory" on EVERY path, including its own failures', () => {
|
|
48
|
+
// Structural, not a filter: there is no branch that returns pass/concerns/fail,
|
|
49
|
+
// so "it cannot block" is a property of the function rather than a promise
|
|
50
|
+
// kept by its callers.
|
|
51
|
+
const paths = [
|
|
52
|
+
['clean', [{ copy_score: 10, issues: [] }, {}]],
|
|
53
|
+
['bad copy', [{ copy_score: 0, issues: [{ severity: 'major', kind: 'break', rule: 'V1' }] }, {}]],
|
|
54
|
+
['subagent error', [null, { subprocess_error: 'SUBAGENT_TIMEOUT' }]],
|
|
55
|
+
['spawn failure', [null, { subprocess_error: 'SUBAGENT_SPAWN_FAILED' }]],
|
|
56
|
+
['unparseable', ['not json at all', {}]],
|
|
57
|
+
['schema error', [{ copy_score: 'banana', issues: [] }, {}]],
|
|
58
|
+
['null parse', [null, {}]],
|
|
59
|
+
];
|
|
60
|
+
for (const [name, [parsed, meta]] of paths) {
|
|
61
|
+
const v = reader.computeVerdict(parsed, meta);
|
|
62
|
+
assert.equal(v.verdict, 'advisory', `${name} → ${v.verdict}`);
|
|
63
|
+
assert.equal(v.verdict, reader.ADVISORY_VERDICT);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('an errored Reader reports NO score rather than a zero', () => {
|
|
68
|
+
// 0/10 is a real reading ("would embarrass the project in front of a user").
|
|
69
|
+
// An outage is missing signal, not a bad signal — the distinction #943 draws
|
|
70
|
+
// for the whole panel, applied to one worker.
|
|
71
|
+
const v = reader.computeVerdict(null, { subprocess_error: 'SUBAGENT_TIMEOUT' });
|
|
72
|
+
assert.deepEqual(v.scores, {});
|
|
73
|
+
assert.equal(v.error, 'SUBAGENT_TIMEOUT');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('a blocker severity is FLOORED to major, not dropped', () => {
|
|
77
|
+
// Dropping would hide a real observation; honouring would hand this lens the
|
|
78
|
+
// veto the owner's term denies it.
|
|
79
|
+
const v = reader.computeVerdict({ copy_score: 3, issues: [{ severity: 'blocker', kind: 'break', rule: 'V2' }] }, {});
|
|
80
|
+
assert.equal(v.issues.length, 1, 'the finding survives');
|
|
81
|
+
assert.equal(v.issues[0].severity, 'major');
|
|
82
|
+
assert.equal(v.issues[0].severity_floored_from, 'blocker');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ---- 2. THE EXEMPTION THE TASK CALLS THE MOST IMPORTANT --------------------
|
|
86
|
+
|
|
87
|
+
test('NO Reader outcome can stop a ship — including an error, which normally hard-fails', () => {
|
|
88
|
+
const baseline = agg(healthyFour());
|
|
89
|
+
assert.equal(baseline.passed, true);
|
|
90
|
+
|
|
91
|
+
const cases = {
|
|
92
|
+
'clean reader': W('reader', 'advisory', { scores: { copy_score: 9 } }),
|
|
93
|
+
'copy_score 0': W('reader', 'advisory', { scores: { copy_score: 0 } }),
|
|
94
|
+
'errored (timeout)': W('reader', 'advisory', { error: 'SUBAGENT_TIMEOUT' }),
|
|
95
|
+
'errored (prompt too long)': W('reader', 'advisory', { error: 'PROMPT_TOO_LONG' }),
|
|
96
|
+
'two major findings': W('reader', 'advisory', { scores: { copy_score: 2 }, issues: [{ severity: 'major' }, { severity: 'major' }] }),
|
|
97
|
+
// Belt AND braces: even if a future edit made the worker emit a gating
|
|
98
|
+
// verdict, the panel partition still refuses to read it.
|
|
99
|
+
'ILLEGALLY emits fail': W('reader', 'fail', { issues: [{ severity: 'blocker' }] }),
|
|
100
|
+
};
|
|
101
|
+
for (const [name, row] of Object.entries(cases)) {
|
|
102
|
+
const r = agg([...healthyFour(), row]);
|
|
103
|
+
assert.equal(r.passed, true, `${name} must not block the ship`);
|
|
104
|
+
assert.equal(r.signals.panel_outcome, 'pass', `${name} must not move the outcome`);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('the Reader is absent from every gate count', () => {
|
|
109
|
+
const r = agg([...healthyFour(), W('reader', 'advisory', { error: 'SUBAGENT_TIMEOUT' })]);
|
|
110
|
+
assert.equal(r.signals.worker_count, 4, 'the gate denominator counts gating workers only');
|
|
111
|
+
assert.equal(r.signals.error_count, 0, "an errored Reader is not a gate error");
|
|
112
|
+
assert.equal(r.signals.fail_count, 0);
|
|
113
|
+
assert.equal(r.signals.pass_count, 4);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ---- 3. THE SUBTLE ONE -----------------------------------------------------
|
|
117
|
+
|
|
118
|
+
test('a HEALTHY Reader does not mask a total grader outage', () => {
|
|
119
|
+
const allErrored = ['narc', 'quality', 'hacker', 'efficiency'].map((k) => W(k, 'fail', { error: 'SUBAGENT_SPAWN_FAILED' }));
|
|
120
|
+
const without = agg(allErrored);
|
|
121
|
+
assert.equal(without.signals.panel_outcome, 'unavailable', 'baseline: a total outage is an outage');
|
|
122
|
+
|
|
123
|
+
const with_ = agg([...allErrored, W('reader', 'advisory', { scores: { copy_score: 9 } })]);
|
|
124
|
+
assert.equal(with_.signals.panel_outcome, 'unavailable',
|
|
125
|
+
'a passing fifth worker must not turn an outage into a FAIL 0/10 (the task-1003574 class)');
|
|
126
|
+
assert.equal(with_.passed, false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("the Reader's confidence cannot move the server-gate median", () => {
|
|
130
|
+
const four = healthyFour();
|
|
131
|
+
const noReader = agg(four).signals.confidence;
|
|
132
|
+
const withReader = agg([...four, W('reader', 'advisory', { confidence: 0.01, scores: { copy_score: 1 } })]).signals.confidence;
|
|
133
|
+
assert.equal(withReader, noReader, 'a low-confidence Reader must not drag the forwarded confidence');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ---- 4. THE SIGNAL STILL LANDS ---------------------------------------------
|
|
137
|
+
|
|
138
|
+
test('copy_score and the findings reach the RECORD even though the gate ignores them', () => {
|
|
139
|
+
const r = agg([...healthyFour(), W('reader', 'advisory', { scores: { copy_score: 4 }, issues: [{ severity: 'major', rule: 'V1' }] })]);
|
|
140
|
+
assert.equal(r.rubric_scores.copy_score, 4, 'the score is recorded');
|
|
141
|
+
assert.ok(r.signals.workers.some((w) => w.kind === 'reader'), 'the Reader row is in the record');
|
|
142
|
+
assert.equal(agg(healthyFour()).rubric_scores.copy_score, null, 'and is null when no Reader ran');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('a Reader row still counts as evidence that the panel ran', () => {
|
|
146
|
+
// hasPanelEvidence is an `every` over the worker verdicts. Leave 'advisory'
|
|
147
|
+
// out of WORKER_VERDICTS and a fully-healthy five-worker panel reads as NO
|
|
148
|
+
// evidence, firing the implausible-pass guard on exactly the ships carrying
|
|
149
|
+
// the most lenses.
|
|
150
|
+
const rows = (extra = []) => ({ workers: [
|
|
151
|
+
{ kind: 'narc', verdict: 'pass' }, { kind: 'quality', verdict: 'pass' },
|
|
152
|
+
{ kind: 'hacker', verdict: 'pass' }, { kind: 'efficiency', verdict: 'pass' }, ...extra,
|
|
153
|
+
] });
|
|
154
|
+
assert.equal(hasPanelEvidence(rows()), true);
|
|
155
|
+
assert.equal(hasPanelEvidence(rows([{ kind: 'reader', verdict: 'advisory' }])), true);
|
|
156
|
+
assert.equal(hasPanelEvidence(rows([{ kind: 'reader', verdict: 'nonsense' }])), false, 'the vocabulary is still closed');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// ---- 5. SCOPE --------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
test('the panel is byte-identical to today when the diff is not copy-bearing', () => {
|
|
162
|
+
assert.deepEqual(WORKERS.panelFor({ copyBearing: false }), WORKERS.DEFAULT_PANEL);
|
|
163
|
+
assert.deepEqual(WORKERS.panelFor(), WORKERS.DEFAULT_PANEL, 'and when the caller says nothing');
|
|
164
|
+
assert.deepEqual(WORKERS.panelFor({ copyBearing: true }), [...WORKERS.DEFAULT_PANEL, 'reader']);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('scope keys off the ONE existing predicate, not a second copy', () => {
|
|
168
|
+
// The at-ship lens and the after-the-fact artist review must never disagree
|
|
169
|
+
// about what they cover. Widening COPY_VISUAL_MODULES must widen both at once,
|
|
170
|
+
// which it only does while there is one predicate.
|
|
171
|
+
const src = fs.readFileSync(path.join(ROOT, 'scripts', 'gds', 'ship-grade.js'), 'utf8');
|
|
172
|
+
assert.match(src, /hasCopyOrVisualWork/, 'the caller asks lifecycle for the answer');
|
|
173
|
+
assert.match(src, /panelFor\(\{ copyBearing \}\)/, 'and passes it down');
|
|
174
|
+
// RECURSIVE. A readdirSync of modules/grading alone skips grader-workers/,
|
|
175
|
+
// which holds reader.js — the file this assertion is most about. The first
|
|
176
|
+
// version of this test did exactly that and proved nothing it claimed.
|
|
177
|
+
const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
|
|
178
|
+
const full = path.join(dir, e.name);
|
|
179
|
+
return e.isDirectory() ? walk(full) : (e.name.endsWith('.js') ? [full] : []);
|
|
180
|
+
});
|
|
181
|
+
const gradingFiles = walk(path.join(ROOT, 'modules', 'grading'));
|
|
182
|
+
assert.ok(gradingFiles.some((f) => f.endsWith(`grader-workers${path.sep}reader.js`)),
|
|
183
|
+
'the scan reaches grader-workers/reader.js — otherwise it checks everything except the file in question');
|
|
184
|
+
for (const f of gradingFiles) {
|
|
185
|
+
const body = fs.readFileSync(f, 'utf8').replace(/^\s*\/\/.*$/gm, '');
|
|
186
|
+
assert.ok(!/COPY_VISUAL_MODULES|copy-proposal/.test(body),
|
|
187
|
+
`${path.relative(ROOT, f)} must not re-implement the copy predicate — grading cannot require lifecycle (ADR 0083), so the CALLER decides`);
|
|
188
|
+
}
|
|
189
|
+
// Sanity: the predicate really does discriminate.
|
|
190
|
+
assert.equal(hasCopyOrVisualWork({ discipline: 'artist' }), true);
|
|
191
|
+
assert.equal(hasCopyOrVisualWork({ module_key: 'hall-ui' }), true);
|
|
192
|
+
assert.equal(hasCopyOrVisualWork({ module_key: 'grading', discipline: 'engineer' }), false);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// ---- 6. STANDARD -----------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
test('the prompt sends the worker to the written guide, and forbids an uncited finding', () => {
|
|
198
|
+
const p = reader.PROMPT_HEADER;
|
|
199
|
+
assert.match(p, /docs\/voice\.md/, 'the rubric is the voice guide');
|
|
200
|
+
assert.match(p, /cites no rule id is not a finding/i, "the guide's own rule travels with it");
|
|
201
|
+
// The hard/judgement split is what stops an advisory lens recording taste as
|
|
202
|
+
// fact, so it is pinned rather than left to the prompt's good intentions.
|
|
203
|
+
assert.match(p, /V1-V5/, 'hard rules may be asserted');
|
|
204
|
+
assert.match(p, /V6-V11/, 'judgement rules may only be flagged');
|
|
205
|
+
assert.match(p, /ADVISORY/, 'the worker is told it cannot block');
|
|
206
|
+
assert.ok(!/blocker/.test(p.split('## Return ONLY JSON')[1] || ''),
|
|
207
|
+
'the JSON schema offers no blocker severity');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('docs/voice.md exists — the lens has a standard to judge against', () => {
|
|
211
|
+
// The hard dependency (task 1003976). A copy lens with no written standard
|
|
212
|
+
// records taste and calls it a finding.
|
|
213
|
+
const guide = fs.readFileSync(path.join(ROOT, 'docs', 'voice.md'), 'utf8');
|
|
214
|
+
for (const id of ['V1', 'V2', 'V3', 'V4', 'V5']) {
|
|
215
|
+
assert.ok(guide.includes(`## ${id} —`) || guide.includes(`## ${id} `), `${id} is a rule in the guide`);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// ---- 7. THE DONE-WHEN CLAUSE THAT LIVES IN ANOTHER FILE ---------------------
|
|
220
|
+
|
|
221
|
+
test('the artist review now coaches the reader, because there is a lens listening', () => {
|
|
222
|
+
// The task's own DONE WHEN: "Then STEP 1 of the artist review reverts to true
|
|
223
|
+
// coaching — see the comment in modules/lifecycle/cascade.js, which says so
|
|
224
|
+
// and asks not to be reverted early." The interim wording was explicitly a
|
|
225
|
+
// signal for which world we are in, so shipping the lens without turning it
|
|
226
|
+
// leaves the instruction to artists describing a panel that no longer exists.
|
|
227
|
+
const { artistReviewBody } = require('../modules/lifecycle/cascade.js');
|
|
228
|
+
const body = artistReviewBody({ id: '1234' });
|
|
229
|
+
assert.match(body, /COACH THE READER/, 'STEP 1 asks for coaching again');
|
|
230
|
+
assert.match(body, /docs\/voice\.md/, 'and names the standard to coach against');
|
|
231
|
+
assert.ok(!/none of them reads a sentence/.test(body),
|
|
232
|
+
'the interim claim that no lens reads a sentence is gone — it is no longer true');
|
|
233
|
+
// Still a request, never a gate (ADR 0241) — unchanged by this task.
|
|
234
|
+
assert.match(body, /REQUEST, not a gate/);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('the cascade and the copy lens cover the SAME set by construction', () => {
|
|
238
|
+
// Both key off hasCopyOrVisualWork, so an artist review exists for exactly
|
|
239
|
+
// the ships a reader ran on. If these ever diverge, an artist is asked to
|
|
240
|
+
// coach a lens that never looked.
|
|
241
|
+
const cascadeSrc = fs.readFileSync(path.join(ROOT, 'modules', 'lifecycle', 'cascade.js'), 'utf8');
|
|
242
|
+
const shipSrc = fs.readFileSync(path.join(ROOT, 'scripts', 'gds', 'ship-grade.js'), 'utf8');
|
|
243
|
+
assert.match(cascadeSrc, /hasCopyOrVisualWork/);
|
|
244
|
+
assert.match(shipSrc, /hasCopyOrVisualWork/);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ---- 8. COST ---------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
test('the Reader sits at the heavy timeout tier, so it cannot change worst-case wall time', () => {
|
|
250
|
+
// The four already sit at 360s and workers run in parallel, so the panel's
|
|
251
|
+
// wall time is max() and a fifth at the same budget does not move it.
|
|
252
|
+
const t = WORKERS.timeoutForWorker(reader, {});
|
|
253
|
+
assert.equal(t, 360_000);
|
|
254
|
+
for (const k of ['narc', 'quality', 'hacker', 'efficiency']) {
|
|
255
|
+
assert.equal(WORKERS.timeoutForWorker(WORKERS.REGISTRY[k], {}), t, `${k} shares the tier`);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// tests/status_default_version.mjs — /status resolves its default version at
|
|
2
|
+
// RUNTIME and never falls back to a sealed one (task 1004071).
|
|
3
|
+
//
|
|
4
|
+
// THE INCIDENT. `scripts/gds/status.js:29` read
|
|
5
|
+
// const DEFAULT_VERSION = resolveEnv('STATUS_DEFAULT_VERSION') || 'BONGOS-V1';
|
|
6
|
+
// while the comment three lines above stated the rule it was meant to keep:
|
|
7
|
+
// "the bare /status card should default to the live version, not a sealed one."
|
|
8
|
+
// BONGOS-V1 was later sealed and the literal was never bumped, so bare `/status`
|
|
9
|
+
// served a shipped version's closing scoreboard — 140/203 satisfied — as the
|
|
10
|
+
// project's CURRENT status. The output looks perfectly healthy, which is why it
|
|
11
|
+
// sat: nothing in the header says `shipped`, and /status is exactly what a
|
|
12
|
+
// session runs when it does NOT already know the answer and so cannot tell.
|
|
13
|
+
//
|
|
14
|
+
// WHY THE GUARD DID NOT CATCH IT. version-literal-guard.js already hunts this
|
|
15
|
+
// shape and already owns the remedy (version-select.js). status.js was on its
|
|
16
|
+
// allowlist, justified by "TWO versions are currently building — so 'most recent
|
|
17
|
+
// building' is not a derivable answer here." That was TRUE when written
|
|
18
|
+
// (BONGOS-V1 + CB-V1). It is false now: one version is building. The entry did
|
|
19
|
+
// not become wrong through anyone's error — the world moved under a justification
|
|
20
|
+
// that was a claim about live state, with nothing re-checking it. That staleness
|
|
21
|
+
// is the class defect, and `version_literals.mjs` now pins it.
|
|
22
|
+
//
|
|
23
|
+
// The rule these tests hold: an EXPLICIT choice always wins (flag, then env),
|
|
24
|
+
// a default is RESOLVED, and when nothing is building we stop rather than guess.
|
|
25
|
+
import { strict as assert } from 'node:assert';
|
|
26
|
+
import { test } from 'node:test';
|
|
27
|
+
import { createRequire } from 'node:module';
|
|
28
|
+
import path from 'node:path';
|
|
29
|
+
import { fileURLToPath } from 'node:url';
|
|
30
|
+
|
|
31
|
+
const require = createRequire(import.meta.url);
|
|
32
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
33
|
+
const status = require(path.join(ROOT, 'scripts', 'gds', 'status.js'));
|
|
34
|
+
const { parseArgs, resolveVersion } = status;
|
|
35
|
+
|
|
36
|
+
// A deps bundle with nothing configured: no env override, nothing building.
|
|
37
|
+
const deps = (over = {}) => ({
|
|
38
|
+
resolveEnv: () => null,
|
|
39
|
+
resolveDefaultVersion: async () => null,
|
|
40
|
+
...over,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ---- parseArgs no longer bakes the default in -----------------------------
|
|
44
|
+
test('parseArgs leaves version NULL when none is given, so main can resolve it', () => {
|
|
45
|
+
const o = parseArgs(['node', 'status.js']);
|
|
46
|
+
assert.equal(o.version, null, 'a literal default here is the bug this task fixes');
|
|
47
|
+
assert.equal(o.criterion, null, 'and nothing was mistaken for a criterion');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('an explicit --version is still carried through', () => {
|
|
51
|
+
const o = parseArgs(['node', 'status.js', '--version', 'BONGOS-V2']);
|
|
52
|
+
assert.equal(o.version, 'BONGOS-V2');
|
|
53
|
+
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('a bare version token is carried through too', () => {
|
|
57
|
+
const o = parseArgs(['node', 'status.js', 'BONGOS-V2']);
|
|
58
|
+
assert.equal(o.version, 'BONGOS-V2');
|
|
59
|
+
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('a bare Cn token is a criterion, not a version', () => {
|
|
63
|
+
const o = parseArgs(['node', 'status.js', 'C8']);
|
|
64
|
+
assert.equal(o.criterion, 'C8');
|
|
65
|
+
assert.equal(o.version, null, 'C8 must not be mistaken for a version id');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ---- the resolution order -------------------------------------------------
|
|
69
|
+
test('an explicit flag beats both the env override and the building lookup', async () => {
|
|
70
|
+
const r = await resolveVersion({ version: 'GDS-V3' }, deps({
|
|
71
|
+
resolveEnv: () => 'ENV-V9',
|
|
72
|
+
resolveDefaultVersion: async () => 'BONGOS-V2',
|
|
73
|
+
}));
|
|
74
|
+
assert.equal(r.version, 'GDS-V3');
|
|
75
|
+
assert.equal(r.defaulted, false, 'the user chose it, so nothing was defaulted');
|
|
76
|
+
assert.equal(r.source, 'flag');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('the env override beats the building lookup, and still counts as defaulted', async () => {
|
|
80
|
+
const r = await resolveVersion({ version: null }, deps({
|
|
81
|
+
resolveEnv: () => 'ENV-V9',
|
|
82
|
+
resolveDefaultVersion: async () => 'BONGOS-V2',
|
|
83
|
+
}));
|
|
84
|
+
assert.equal(r.version, 'ENV-V9');
|
|
85
|
+
assert.equal(r.source, 'env');
|
|
86
|
+
assert.equal(r.defaulted, true, 'the user did not name it on this invocation');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('with no flag and no env, the BUILDING version is resolved at runtime', async () => {
|
|
90
|
+
const r = await resolveVersion({ version: null }, deps({
|
|
91
|
+
resolveDefaultVersion: async () => 'BONGOS-V2',
|
|
92
|
+
}));
|
|
93
|
+
assert.equal(r.version, 'BONGOS-V2');
|
|
94
|
+
assert.equal(r.source, 'building');
|
|
95
|
+
assert.equal(r.defaulted, true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('THE INCIDENT: when nothing is building we return null, never a sealed guess', async () => {
|
|
99
|
+
// version-select.js already refuses to guess here, and status must not
|
|
100
|
+
// reintroduce the guess downstream of it. A sealed version's final scoreboard
|
|
101
|
+
// presented as current status is worse than an error, because it looks fine.
|
|
102
|
+
const r = await resolveVersion({ version: null }, deps());
|
|
103
|
+
assert.equal(r.version, null);
|
|
104
|
+
assert.equal(r.source, 'none');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('a resolved default never returns a SHIPPED version', async () => {
|
|
108
|
+
// Wired through the real selection rule rather than a stub, so this test fails
|
|
109
|
+
// if pickDefaultVersion is ever loosened to fall back to the newest-any-status.
|
|
110
|
+
const { pickDefaultVersion } = require(path.join(ROOT, 'scripts', 'gds', 'version-select.js'));
|
|
111
|
+
const versions = [
|
|
112
|
+
{ id: 'BONGOS-V1', status: 'shipped', started_at: '2026-06-20' },
|
|
113
|
+
{ id: 'CB-V1', status: 'shipped', started_at: '2026-06-21' },
|
|
114
|
+
{ id: 'BONGOS-V2', status: 'building', started_at: '2026-06-22' },
|
|
115
|
+
];
|
|
116
|
+
const r = await resolveVersion({ version: null }, deps({
|
|
117
|
+
resolveDefaultVersion: async () => pickDefaultVersion(versions),
|
|
118
|
+
}));
|
|
119
|
+
assert.equal(r.version, 'BONGOS-V2', 'the only building row wins over two newer-looking sealed ones');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ---- the drill-in must say which version it answered about ----------------
|
|
123
|
+
test('a DEFAULTED version is announced, so a cross-version answer cannot pass unnoticed', () => {
|
|
124
|
+
// Cn is version-scoped and the versions number independently: BONGOS-V1 C8 is
|
|
125
|
+
// `money-one-unit`, BONGOS-V2 C8 is `wa6-kernel-and-packs`. Asking for "C8"
|
|
126
|
+
// against a silently-defaulted version answers a different question than the
|
|
127
|
+
// one asked, and the old output gave the reader nothing to notice it by.
|
|
128
|
+
const note = status.defaultedVersionNote({ version: 'BONGOS-V2', defaulted: true, source: 'building' });
|
|
129
|
+
assert.match(note, /BONGOS-V2/, 'it names the version it resolved');
|
|
130
|
+
assert.match(note, /building/, 'and why that one');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('an explicitly chosen version is NOT announced — no noise when the user said it', () => {
|
|
134
|
+
const note = status.defaultedVersionNote({ version: 'BONGOS-V2', defaulted: false, source: 'flag' });
|
|
135
|
+
assert.equal(note, '', 'the user already knows which version they asked for');
|
|
136
|
+
});
|
|
@@ -199,3 +199,95 @@ test('ship-flow.js imports every cross-module function it calls', () => {
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
});
|
|
202
|
+
|
|
203
|
+
// ---- an exemption must not outlive its justification ----------------------
|
|
204
|
+
// Task 1004071. scripts/gds/status.js sat on VERSION_LITERAL_ALLOW justified by
|
|
205
|
+
// "TWO versions are currently building — so 'most recent building' is not a
|
|
206
|
+
// derivable answer here." That was true the day it was written (BONGOS-V1 and
|
|
207
|
+
// CB-V1). It is false now: one version builds. Nothing noticed, because the
|
|
208
|
+
// reason is a claim about LIVE STATE and an allowlist holds prose, which no CI
|
|
209
|
+
// step can re-check. The entry did not become wrong through anyone's error —
|
|
210
|
+
// the world moved under it. Hence the rule now written beside the Map: a reason
|
|
211
|
+
// may cite the CODE's shape, never the database's contents.
|
|
212
|
+
//
|
|
213
|
+
// SCANNING AN ALLOWLISTED PATH PROVES NOTHING. scanScripts applies the allowlist
|
|
214
|
+
// itself (`if (VERSION_LITERAL_ALLOW.has(rel)) continue`), so handing it an
|
|
215
|
+
// exempt path returns zero violations whatever the file says — the first draft
|
|
216
|
+
// of this test read that vacuous [] as "no literal" and reported every entry as
|
|
217
|
+
// dead, including grade-smoke.js, which plainly still carries two. The source is
|
|
218
|
+
// therefore re-scanned under a synthetic path that is on neither the allowlist
|
|
219
|
+
// nor isExempt's patterns, which is the only way to see what the guard WOULD say.
|
|
220
|
+
const PROBE = 'scripts/gds/__allowlist_probe__.js';
|
|
221
|
+
const scanUnexempt = (src) => g.scanScripts([PROBE], () => src);
|
|
222
|
+
|
|
223
|
+
test('the probe path really is unexempt — otherwise every check below is vacuous', () => {
|
|
224
|
+
assert.ok(!g.VERSION_LITERAL_ALLOW.has(PROBE), 'probe must not be allowlisted');
|
|
225
|
+
assert.ok(!g.isExempt(PROBE), 'probe must not match an isExempt pattern');
|
|
226
|
+
// And it must actually fire on a known-bad line, or a silent-empty scanner
|
|
227
|
+
// would let every assertion below pass for the wrong reason (learning: a
|
|
228
|
+
// static scan breaks by finding nothing, and green looks identical to working).
|
|
229
|
+
assert.equal(scanUnexempt(" version_id: 'PMS-V1',").violations.length, 1);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('no allowlist entry is DEAD — each exempt file still contains the shape it is exempt for', () => {
|
|
233
|
+
for (const [rel] of g.VERSION_LITERAL_ALLOW) {
|
|
234
|
+
let src = null;
|
|
235
|
+
try { src = read(...rel.split('/')); } catch { /* handled below */ }
|
|
236
|
+
assert.ok(src !== null, `${rel} is allowlisted but does not exist — drop the entry`);
|
|
237
|
+
assert.ok(
|
|
238
|
+
scanUnexempt(src).violations.length > 0,
|
|
239
|
+
`${rel} is on VERSION_LITERAL_ALLOW but no longer names a version literal — `
|
|
240
|
+
+ 'the exemption is dead and must be removed, or it will silently cover a '
|
|
241
|
+
+ 'literal someone reintroduces later.'
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('THE INCIDENT: status.js is no longer exempt, and names no version literal', () => {
|
|
247
|
+
// Both halves must hold. Dropping the entry while leaving the literal reds the
|
|
248
|
+
// guard; fixing the literal while leaving the entry leaves status.js covered by
|
|
249
|
+
// nothing for good. Scanned under the probe path so this stays honest even if
|
|
250
|
+
// someone re-adds the allowlist entry.
|
|
251
|
+
assert.ok(
|
|
252
|
+
!g.VERSION_LITERAL_ALLOW.has('scripts/gds/status.js'),
|
|
253
|
+
'status.js resolves its default at runtime now — it must not stay exempt'
|
|
254
|
+
);
|
|
255
|
+
assert.deepEqual(scanUnexempt(read('scripts', 'gds', 'status.js')).violations, []);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// ---- the fallback shape ASSIGNED could not see ----------------------------
|
|
259
|
+
test('THE BLIND SPOT: a version field falling back to a literal through || is caught', () => {
|
|
260
|
+
// The literal line that shipped, verbatim. ASSIGNED misses it because its
|
|
261
|
+
// `[^,;)]*` stops at the `)` of resolveEnv(...) — so wrapping a literal in any
|
|
262
|
+
// call defeated the guard, while the bare assignment was caught.
|
|
263
|
+
const bad = "const DEFAULT_VERSION = resolveEnv('STATUS_DEFAULT_VERSION') || 'BONGOS-V1';";
|
|
264
|
+
assert.equal(g.ASSIGNED.test(bad), false, 'documents WHY a third pattern was needed');
|
|
265
|
+
assert.equal(g.FALLBACK.test(bad), true);
|
|
266
|
+
assert.equal(scanUnexempt(bad).violations.length, 1, 'and the scanner reports it');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('the bare assignment ASSIGNED already caught still fails', () => {
|
|
270
|
+
assert.equal(scanUnexempt("const DEFAULT_VERSION = 'BONGOS-V1';").violations.length, 1);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test('FALLBACK stays narrow — it does not fire on a version field alone', () => {
|
|
274
|
+
// The over-broad source assertion this repo keeps relearning (learning 1000196).
|
|
275
|
+
// Loosening ASSIGNED to `[^;]*` would have flagged all of these.
|
|
276
|
+
for (const ok of [
|
|
277
|
+
" const v = versions.find((x) => x.version_id === id) || null;",
|
|
278
|
+
" if (!version_id) throw new Error('need a version');",
|
|
279
|
+
" console.log(`resolved ${version_id}`);",
|
|
280
|
+
" const label = version_id || 'unknown';",
|
|
281
|
+
]) {
|
|
282
|
+
assert.deepEqual(scanUnexempt(ok).violations, [], `must not flag: ${ok.trim()}`);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('a comment showing the old bad line does not re-trip the guard', () => {
|
|
287
|
+
// status.js now quotes its own former literal in the explanatory comment. If
|
|
288
|
+
// comment-stripping regressed, the file it documents would red the guard.
|
|
289
|
+
assert.deepEqual(
|
|
290
|
+
scanUnexempt("// const DEFAULT_VERSION = resolveEnv('X') || 'BONGOS-V1';").violations,
|
|
291
|
+
[]
|
|
292
|
+
);
|
|
293
|
+
});
|