@bongos/core 1.19.323 → 1.19.324
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 +36 -21
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/architect-audit.js +7 -1
- package/scripts/gds/audit-deps.js +11 -1
- package/scripts/gds/capture.js +4 -16
- package/scripts/gds/fitness.js +1 -1
- package/scripts/gds/session-eval.js +6 -1
- package/scripts/gds/ship-flow.js +4 -0
- package/scripts/gds/smoke-batch-claim.sh +9 -1
- package/scripts/gds/smoke-batch-release.sh +9 -1
- package/scripts/gds/smoke-claim-consistency.sh +10 -1
- package/scripts/gds/smoke-write.sh +10 -1
- package/scripts/gds/triage.js +4 -1
- package/scripts/gds/version-literal-guard.js +122 -0
- package/scripts/gds/version-select.js +79 -0
- package/src/module-api.js +3 -1
- package/tests/version_literals.mjs +201 -0
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.324",
|
|
6
|
+
"core_contract": "1.19.324",
|
|
7
|
+
"source_commit": "7448abdd2e984ab3e1d9acb627bb21e631f198c8",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-
|
|
9
|
+
"built_at": "2026-08-24T14:18:41.725Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 324,
|
|
13
13
|
"agent_docs_stubbed": 22,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1460,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1806,
|
|
20
|
+
"tree_sha256": "170e8d01edc7c386adfef5d3c45b916a749225cd999f6e7aef6c65e8c1307c24",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -4652,12 +4652,12 @@
|
|
|
4652
4652
|
{
|
|
4653
4653
|
"path": "package-lock.json",
|
|
4654
4654
|
"mode": "0000644",
|
|
4655
|
-
"sha256": "
|
|
4655
|
+
"sha256": "2bee07291c5d8ef2f9a3c4e0c7e10714c8e078d7a0e9bdc2321aecf4af7d9afe"
|
|
4656
4656
|
},
|
|
4657
4657
|
{
|
|
4658
4658
|
"path": "package.json",
|
|
4659
4659
|
"mode": "0000644",
|
|
4660
|
-
"sha256": "
|
|
4660
|
+
"sha256": "f7c47e473b34391b7357c799f632cfb8101bb723099baa3d41b2074deba44e37"
|
|
4661
4661
|
},
|
|
4662
4662
|
{
|
|
4663
4663
|
"path": "public-docs/index.html",
|
|
@@ -5017,7 +5017,7 @@
|
|
|
5017
5017
|
{
|
|
5018
5018
|
"path": "scripts/gds/architect-audit.js",
|
|
5019
5019
|
"mode": "0000644",
|
|
5020
|
-
"sha256": "
|
|
5020
|
+
"sha256": "62c32c421cc02af44ca47aab58ff857dcf6791450acd5b36e83fe175906e21f6"
|
|
5021
5021
|
},
|
|
5022
5022
|
{
|
|
5023
5023
|
"path": "scripts/gds/audit-authorship.sh",
|
|
@@ -5027,7 +5027,7 @@
|
|
|
5027
5027
|
{
|
|
5028
5028
|
"path": "scripts/gds/audit-deps.js",
|
|
5029
5029
|
"mode": "0000644",
|
|
5030
|
-
"sha256": "
|
|
5030
|
+
"sha256": "f0905213e5e8ba18ea4c3eab7e11bb7c00401a5653ac00990be15c65f361959d"
|
|
5031
5031
|
},
|
|
5032
5032
|
{
|
|
5033
5033
|
"path": "scripts/gds/audit-exports.js",
|
|
@@ -5112,7 +5112,7 @@
|
|
|
5112
5112
|
{
|
|
5113
5113
|
"path": "scripts/gds/capture.js",
|
|
5114
5114
|
"mode": "0000644",
|
|
5115
|
-
"sha256": "
|
|
5115
|
+
"sha256": "946fa97d020eb481b7da559f0b5c99bfa16dc24b680a01e0ff3d3faeaf5bd701"
|
|
5116
5116
|
},
|
|
5117
5117
|
{
|
|
5118
5118
|
"path": "scripts/gds/cf-tunnel.js",
|
|
@@ -5307,7 +5307,7 @@
|
|
|
5307
5307
|
{
|
|
5308
5308
|
"path": "scripts/gds/fitness.js",
|
|
5309
5309
|
"mode": "0000644",
|
|
5310
|
-
"sha256": "
|
|
5310
|
+
"sha256": "b5432b114aa075f790d056bc8cb090d3ba83fb52498d9dbee4a81161ac5bc019"
|
|
5311
5311
|
},
|
|
5312
5312
|
{
|
|
5313
5313
|
"path": "scripts/gds/gate-review.js",
|
|
@@ -5862,7 +5862,7 @@
|
|
|
5862
5862
|
{
|
|
5863
5863
|
"path": "scripts/gds/session-eval.js",
|
|
5864
5864
|
"mode": "0000644",
|
|
5865
|
-
"sha256": "
|
|
5865
|
+
"sha256": "9cdf1b5c6834568169cbe130cb2d28361baa747aa5bf7f358cc8e8631080790d"
|
|
5866
5866
|
},
|
|
5867
5867
|
{
|
|
5868
5868
|
"path": "scripts/gds/session.js",
|
|
@@ -5892,7 +5892,7 @@
|
|
|
5892
5892
|
{
|
|
5893
5893
|
"path": "scripts/gds/ship-flow.js",
|
|
5894
5894
|
"mode": "0000644",
|
|
5895
|
-
"sha256": "
|
|
5895
|
+
"sha256": "c63d7bcdf4c25de60ad4106495e09bccfd98a19b223111988e4339a4460558b8"
|
|
5896
5896
|
},
|
|
5897
5897
|
{
|
|
5898
5898
|
"path": "scripts/gds/ship-git.js",
|
|
@@ -5977,12 +5977,12 @@
|
|
|
5977
5977
|
{
|
|
5978
5978
|
"path": "scripts/gds/smoke-batch-claim.sh",
|
|
5979
5979
|
"mode": "0000644",
|
|
5980
|
-
"sha256": "
|
|
5980
|
+
"sha256": "53d3f4e28514aa6a3966e7a3db6223d4abe19074aaf44c39f61b920a0bdc35b0"
|
|
5981
5981
|
},
|
|
5982
5982
|
{
|
|
5983
5983
|
"path": "scripts/gds/smoke-batch-release.sh",
|
|
5984
5984
|
"mode": "0000644",
|
|
5985
|
-
"sha256": "
|
|
5985
|
+
"sha256": "68ea957b3e9e82917e1c1a2a870b4ebbd0e5ef80d101afa3cff39858c98a3a0f"
|
|
5986
5986
|
},
|
|
5987
5987
|
{
|
|
5988
5988
|
"path": "scripts/gds/smoke-box.sh",
|
|
@@ -5992,7 +5992,7 @@
|
|
|
5992
5992
|
{
|
|
5993
5993
|
"path": "scripts/gds/smoke-claim-consistency.sh",
|
|
5994
5994
|
"mode": "0000644",
|
|
5995
|
-
"sha256": "
|
|
5995
|
+
"sha256": "337d3f957cab32166b235a17c2604f9890d14067e31440a1400f345b21918eab"
|
|
5996
5996
|
},
|
|
5997
5997
|
{
|
|
5998
5998
|
"path": "scripts/gds/smoke-dependencies.sh",
|
|
@@ -6007,7 +6007,7 @@
|
|
|
6007
6007
|
{
|
|
6008
6008
|
"path": "scripts/gds/smoke-write.sh",
|
|
6009
6009
|
"mode": "0000644",
|
|
6010
|
-
"sha256": "
|
|
6010
|
+
"sha256": "5468e6fdf5330fa2a67aa9a027d747efdf09908f9d676c567f2e324fd55a5155"
|
|
6011
6011
|
},
|
|
6012
6012
|
{
|
|
6013
6013
|
"path": "scripts/gds/spark.js",
|
|
@@ -6037,7 +6037,7 @@
|
|
|
6037
6037
|
{
|
|
6038
6038
|
"path": "scripts/gds/triage.js",
|
|
6039
6039
|
"mode": "0000644",
|
|
6040
|
-
"sha256": "
|
|
6040
|
+
"sha256": "831de1914642a7dcf7c591c9cc69b37c917122e04428cb1e28fc00b07170b603"
|
|
6041
6041
|
},
|
|
6042
6042
|
{
|
|
6043
6043
|
"path": "scripts/gds/update-channel.js",
|
|
@@ -6059,6 +6059,16 @@
|
|
|
6059
6059
|
"mode": "0000644",
|
|
6060
6060
|
"sha256": "32e0c1f2a955d4cabeae00d3749b283abd70f05f34b5fcbd84be3f35e2e655c8"
|
|
6061
6061
|
},
|
|
6062
|
+
{
|
|
6063
|
+
"path": "scripts/gds/version-literal-guard.js",
|
|
6064
|
+
"mode": "0000644",
|
|
6065
|
+
"sha256": "7e27012820d6e2fb43c84140ae6f90d4ca9345658937aa75cc9f2c28cfdaf41e"
|
|
6066
|
+
},
|
|
6067
|
+
{
|
|
6068
|
+
"path": "scripts/gds/version-select.js",
|
|
6069
|
+
"mode": "0000644",
|
|
6070
|
+
"sha256": "be4fec619d5a0c76ba05bcc75042c1497f70da21b54a3d646884e2228f90b2ba"
|
|
6071
|
+
},
|
|
6062
6072
|
{
|
|
6063
6073
|
"path": "scripts/gds/worktree.js",
|
|
6064
6074
|
"mode": "0000644",
|
|
@@ -6427,7 +6437,7 @@
|
|
|
6427
6437
|
{
|
|
6428
6438
|
"path": "src/module-api.js",
|
|
6429
6439
|
"mode": "0000644",
|
|
6430
|
-
"sha256": "
|
|
6440
|
+
"sha256": "cbeef525ef673bbd61cd645ea63a3cbc5bc7946d9a8f9e7b15457aaae0869220"
|
|
6431
6441
|
},
|
|
6432
6442
|
{
|
|
6433
6443
|
"path": "src/module-loader/catalog.js",
|
|
@@ -8994,6 +9004,11 @@
|
|
|
8994
9004
|
"mode": "0000644",
|
|
8995
9005
|
"sha256": "372c80d42e122a70a4fae8fcfbaaa47459575d3e0a2b80fff8876dfc512c5aeb"
|
|
8996
9006
|
},
|
|
9007
|
+
{
|
|
9008
|
+
"path": "tests/version_literals.mjs",
|
|
9009
|
+
"mode": "0000644",
|
|
9010
|
+
"sha256": "17e872131c17bae7a061debc1fd3ee354ab704678b475edd6a8b1dfdd63ceb2f"
|
|
9011
|
+
},
|
|
8997
9012
|
{
|
|
8998
9013
|
"path": "tests/versions_route.mjs",
|
|
8999
9014
|
"mode": "0000644",
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.324",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.324",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.324",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -32,6 +32,7 @@ const fs = require('node:fs');
|
|
|
32
32
|
const path = require('node:path');
|
|
33
33
|
const { execFileSync } = require('node:child_process');
|
|
34
34
|
const { apiCall, cliClient, arg } = require('./cli-lib');
|
|
35
|
+
const { resolveDefaultVersion } = require('./version-select.js');
|
|
35
36
|
const grader = require('../../modules/grading/grader');
|
|
36
37
|
const llmCache = require('../../src/bongos/llm-cache'); // System 5 (task 1360): cache identical audits over an unchanged tree
|
|
37
38
|
const { branding } = require('../../src/branding');
|
|
@@ -183,7 +184,12 @@ async function main() {
|
|
|
183
184
|
const args = process.argv.slice(2);
|
|
184
185
|
const scope = arg('--scope', args);
|
|
185
186
|
const paths = arg('--paths', args);
|
|
186
|
-
|
|
187
|
+
// task 1003166 (G02): was 'GDS-V3', long since shipped.
|
|
188
|
+
const version = arg('--version', args) || await resolveDefaultVersion();
|
|
189
|
+
if (!version) {
|
|
190
|
+
console.error('architect-audit: no version is building; pass --version <id>.');
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
187
193
|
const dryRun = args.includes('--dry-run');
|
|
188
194
|
|
|
189
195
|
if (!scope || !paths) {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
const { touchesOverlap } = require('../../src/bongos/path-match.js');
|
|
23
23
|
const { cliClient } = require('./cli-lib');
|
|
24
|
+
const { resolveDefaultVersion } = require('./version-select.js');
|
|
24
25
|
// task 1003153 — the DEAD-edge detector core, shared with the claim refusal so
|
|
25
26
|
// "abandoned dependency" means one thing across the CLI, the route and this audit.
|
|
26
27
|
const { findStrandedTasks } = require('../../modules/lifecycle/dead-deps.js');
|
|
@@ -128,7 +129,16 @@ async function main() {
|
|
|
128
129
|
process.exitCode = await reportStranded({ asJson: argv.includes('--json') });
|
|
129
130
|
return;
|
|
130
131
|
}
|
|
131
|
-
|
|
132
|
+
// task 1003166 (G02): the default was 'GDS-V3', which has shipped — so a bare
|
|
133
|
+
// `audit-deps` audited a closed version and reported nothing useful.
|
|
134
|
+
const ver = argv.includes('--version')
|
|
135
|
+
? argv[argv.indexOf('--version') + 1]
|
|
136
|
+
: await resolveDefaultVersion();
|
|
137
|
+
if (!ver) {
|
|
138
|
+
console.error('audit-deps: no version is building; pass --version <id>.');
|
|
139
|
+
process.exitCode = 1;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
132
142
|
const minRank = argv.includes('--min') ? argv[argv.indexOf('--min') + 1] : 'low';
|
|
133
143
|
const asJson = argv.includes('--json');
|
|
134
144
|
const order = { high: 3, medium: 2, low: 1 };
|
package/scripts/gds/capture.js
CHANGED
|
@@ -54,22 +54,10 @@ const readline = require('node:readline');
|
|
|
54
54
|
const { cliClient, requireSession, arg, hasFlag } = require('./cli-lib');
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const versions = r.data?.versions ?? [];
|
|
62
|
-
// Most recent building version. Fall back to any building version if
|
|
63
|
-
// started_at is null on all of them.
|
|
64
|
-
const building = versions.filter((v) => v.status === 'building');
|
|
65
|
-
if (building.length === 0) return null;
|
|
66
|
-
building.sort((a, b) => {
|
|
67
|
-
const ta = a.started_at ? new Date(a.started_at).getTime() : 0;
|
|
68
|
-
const tb = b.started_at ? new Date(b.started_at).getTime() : 0;
|
|
69
|
-
return tb - ta;
|
|
70
|
-
});
|
|
71
|
-
return building[0].id;
|
|
72
|
-
}
|
|
57
|
+
// task 1003166 (G02): the selection rule moved to version-select.js so the nine
|
|
58
|
+
// scripts that used to hardcode a version id share ONE implementation. Kept as a
|
|
59
|
+
// local name so every call site below reads unchanged.
|
|
60
|
+
const { resolveDefaultVersion } = require('./version-select.js');
|
|
73
61
|
|
|
74
62
|
// Full JSON, not console.error's depth-2 inspect — a validation 400's nested
|
|
75
63
|
// details otherwise print as "[ [Object] ]" and hide which field failed.
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -1910,7 +1910,7 @@ const CHECKS = [
|
|
|
1910
1910
|
checkWriteRoutesValidated, // Check 18 — BV1 R12 / task 1999: every body-reading write route validates (ADR 0118)
|
|
1911
1911
|
require('./fitness-ratchets.js').checkQualityRatchets, // Check 20 — task 1003129: quality budgets only tighten (mechanics + knip CI step live in fitness-ratchets.js)
|
|
1912
1912
|
require('./doc-cli-guard.js').checkDocNamesRealCli, // Check 22 — task 1003165 / G01: a doc that names a CLI invocation must match the real CLI
|
|
1913
|
-
require('./skill-preflight.js').checkOpsSkillsDeclareMachine, //
|
|
1913
|
+
require('./skill-preflight.js').checkOpsSkillsDeclareMachine, require('./version-literal-guard.js').checkNoVersionLiterals, // Checks 23 (task 1003167/G03: a skill declares its machine) + 24 (task 1003166/G02: no script hardcodes a version id) — one line because fitness.js is 500 over the excess-lines ratchet and a new check must not cost a line (idea 1000810)
|
|
1914
1914
|
require('./control-manifest.js').checkControlsExist, // Check 21 — task 1003131: goal-1000076 controls assert their own existence (the ADR 0022 assurance-of-assurance)
|
|
1915
1915
|
];
|
|
1916
1916
|
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
// Pure helper (no I/O) — resolves the per-rank wandering default so the
|
|
20
20
|
// off-task signal below only fires for the CONSTRAINING ranks (task 1268).
|
|
21
21
|
const wanderingPrefs = require('../../modules/builder-settings/wandering-prefs'); // carved → modules/builder-settings/ (BV1.R80 / ADR 0093 §1)
|
|
22
|
+
const { resolveDefaultVersion } = require('./version-select.js');
|
|
22
23
|
|
|
23
24
|
// ---- classification vocabularies -----------------------------------------
|
|
24
25
|
|
|
@@ -547,6 +548,9 @@ if (require.main === module) {
|
|
|
547
548
|
: []);
|
|
548
549
|
let filed = 0;
|
|
549
550
|
let skipped = 0;
|
|
551
|
+
// task 1003166 (G02): resolved ONCE — it is invariant for the run, and this
|
|
552
|
+
// is a network round trip. The first version of this called it per finding.
|
|
553
|
+
const suggestedVersion = await resolveDefaultVersion();
|
|
550
554
|
for (const s of systemic) {
|
|
551
555
|
const tmpl = FINDING_TEMPLATES[s.type] || { title: s.type, body: '' };
|
|
552
556
|
const title = `${PREFIX} ${tmpl.title}`.slice(0, 200);
|
|
@@ -561,7 +565,8 @@ if (require.main === module) {
|
|
|
561
565
|
body: {
|
|
562
566
|
title,
|
|
563
567
|
body_md: bodyMd,
|
|
564
|
-
|
|
568
|
+
// task 1003166 (G02): was 'GDS-V4', which has shipped.
|
|
569
|
+
suggested_version: suggestedVersion,
|
|
565
570
|
},
|
|
566
571
|
}).catch(() => null);
|
|
567
572
|
if (r && r.ok) filed++;
|
package/scripts/gds/ship-flow.js
CHANGED
|
@@ -20,6 +20,10 @@ const { autoLinkifyChangedDocs, mergeMainIntoBranch, preflight } = require('./sh
|
|
|
20
20
|
const { applyBaseOverride, computeChangedFiles, computeGraderBaseline, runSmokeAsync } = require('./ship-grade.js');
|
|
21
21
|
const { buildPriorRoundContext, computeGradeResult, gradeAndRecord, printCreditsAndAchievements, printGradeOutcome } = require('./ship-grade-steps.js');
|
|
22
22
|
const { apiErrorLine, normalizeApiError } = require('./ship-io.js');
|
|
23
|
+
// task 1003205's carve left pushVia() used at regradeMain but never imported, so
|
|
24
|
+
// EVERY resume of a 'completed' task died with ReferenceError before resolving.
|
|
25
|
+
const { pushVia } = require('./ship-deploy-target.js');
|
|
26
|
+
|
|
23
27
|
const { diffstatFor, finishShip } = require('./ship-finish.js');
|
|
24
28
|
const { evaluateRegradeEligibility, evaluateResumeAction, resumeMerge } = require('./ship-resume.js');
|
|
25
29
|
const { noteServerReward } = require('./ship-state.js');
|
|
@@ -24,6 +24,14 @@ source "$(dirname "$0")/lib.sh"
|
|
|
24
24
|
API="$(gds_api_base)"
|
|
25
25
|
TOKEN="$(gds_resolve_token)" || exit $?
|
|
26
26
|
|
|
27
|
+
# task 1003166 (G02): resolve the version at RUNTIME rather than naming one. A
|
|
28
|
+
# literal works until that version ships, then `version_closed` refuses every
|
|
29
|
+
# create and the smoke fails as if the code broke (idea 1000823).
|
|
30
|
+
VERSION_ID=$(node "$(dirname "$0")/version-select.js") || {
|
|
31
|
+
echo "FAIL: could not resolve a building version — see version-select.js" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
ts=$(date +%Y%m%d-%H%M%S)
|
|
28
36
|
|
|
29
37
|
# Wrapper: METHOD PATH [BODY] → echoes "HTTP_CODE\nPAYLOAD". Never exits.
|
|
@@ -49,7 +57,7 @@ mk_task() {
|
|
|
49
57
|
import json,sys
|
|
50
58
|
t = '${touches_csv}'.split(',')
|
|
51
59
|
print(json.dumps({
|
|
52
|
-
'version_id': '
|
|
60
|
+
'version_id': '${VERSION_ID}',
|
|
53
61
|
'title': '__smoke__ batch-claim ${ts}-${suffix}',
|
|
54
62
|
'description': 'smoke',
|
|
55
63
|
'status': 'ready',
|
|
@@ -31,6 +31,14 @@ source "$(dirname "$0")/lib.sh"
|
|
|
31
31
|
API="$(gds_api_base)"
|
|
32
32
|
TOKEN="$(gds_resolve_token)" || exit $?
|
|
33
33
|
|
|
34
|
+
# task 1003166 (G02): resolve the version at RUNTIME rather than naming one. A
|
|
35
|
+
# literal works until that version ships, then `version_closed` refuses every
|
|
36
|
+
# create and the smoke fails as if the code broke (idea 1000823).
|
|
37
|
+
VERSION_ID=$(node "$(dirname "$0")/version-select.js") || {
|
|
38
|
+
echo "FAIL: could not resolve a building version — see version-select.js" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
ts=$(date +%Y%m%d-%H%M%S)
|
|
35
43
|
|
|
36
44
|
# Wrapper: METHOD PATH [BODY] → echoes "PAYLOAD\nHTTP_CODE". Never exits.
|
|
@@ -55,7 +63,7 @@ mk_task() {
|
|
|
55
63
|
import json,sys
|
|
56
64
|
t = '${touches_csv}'.split(',')
|
|
57
65
|
print(json.dumps({
|
|
58
|
-
'version_id': '
|
|
66
|
+
'version_id': '${VERSION_ID}',
|
|
59
67
|
'title': '__smoke__ batch-release ${ts}-${suffix}',
|
|
60
68
|
'description': 'smoke',
|
|
61
69
|
'status': 'ready',
|
|
@@ -86,7 +86,7 @@ create_task() {
|
|
|
86
86
|
body=$(python3 -c "
|
|
87
87
|
import json
|
|
88
88
|
print(json.dumps({
|
|
89
|
-
'version_id': '
|
|
89
|
+
'version_id': '${VERSION_ID}',
|
|
90
90
|
'title': '${title}',
|
|
91
91
|
'description': 'Synthetic smoke task. Auto-cleaned by smoke-claim-consistency.sh.',
|
|
92
92
|
'status': 'ready',
|
|
@@ -124,6 +124,15 @@ echo "API: ${API}"
|
|
|
124
124
|
echo "Titles: ${SMOKE_TITLE_A} | ${SMOKE_TITLE_B}"
|
|
125
125
|
echo
|
|
126
126
|
|
|
127
|
+
# task 1003166 (G02): resolve the version at RUNTIME. This file used to hardcode
|
|
128
|
+
# one; that version shipped, `version_closed` refused every create, and the smoke
|
|
129
|
+
# could not pass while /merge-mode still advertised it green (idea 1000823).
|
|
130
|
+
VERSION_ID=$(node "$(dirname "$0")/version-select.js") || {
|
|
131
|
+
echo "FAIL: could not resolve a building version — see version-select.js" >&2
|
|
132
|
+
exit 1
|
|
133
|
+
}
|
|
134
|
+
echo "Version: ${VERSION_ID}"
|
|
135
|
+
|
|
127
136
|
echo "--- 1. create 2 smoke tasks ---"
|
|
128
137
|
TASK_A=$(create_task "${SMOKE_TITLE_A}" "A")
|
|
129
138
|
TASK_B=$(create_task "${SMOKE_TITLE_B}" "B")
|
|
@@ -58,6 +58,15 @@ echo "API: ${API}"
|
|
|
58
58
|
echo "Title: ${SMOKE_TITLE}"
|
|
59
59
|
echo
|
|
60
60
|
|
|
61
|
+
# task 1003166 (G02): resolve the version at RUNTIME. This file used to hardcode
|
|
62
|
+
# one; that version shipped, `version_closed` refused every create, and the smoke
|
|
63
|
+
# could not pass while /merge-mode still advertised it green (idea 1000823).
|
|
64
|
+
VERSION_ID=$(node "$(dirname "$0")/version-select.js") || {
|
|
65
|
+
echo "FAIL: could not resolve a building version — see version-select.js" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
}
|
|
68
|
+
echo "Version: ${VERSION_ID}"
|
|
69
|
+
|
|
61
70
|
echo "--- 0. snapshot progress before ---"
|
|
62
71
|
PROGRESS_BEFORE=$(snapshot_progress)
|
|
63
72
|
echo "${PROGRESS_BEFORE}"
|
|
@@ -67,7 +76,7 @@ echo "--- 1. create task ---"
|
|
|
67
76
|
CREATE_BODY=$(python3 -c "
|
|
68
77
|
import json
|
|
69
78
|
print(json.dumps({
|
|
70
|
-
'version_id': '
|
|
79
|
+
'version_id': '${VERSION_ID}',
|
|
71
80
|
'title': '${SMOKE_TITLE}',
|
|
72
81
|
'description': 'Synthetic smoke task. Auto-shipped by smoke-write.sh.',
|
|
73
82
|
'status': 'ready',
|
package/scripts/gds/triage.js
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
const readline = require('node:readline');
|
|
39
39
|
const fs = require('node:fs');
|
|
40
40
|
const { cliClient, requireSession } = require('./cli-lib');
|
|
41
|
+
const { resolveDefaultVersion } = require('./version-select.js');
|
|
41
42
|
|
|
42
43
|
function prompt(rl, question) {
|
|
43
44
|
return new Promise((resolve) => rl.question(question, (a) => resolve(a)));
|
|
@@ -384,7 +385,9 @@ async function main() {
|
|
|
384
385
|
|
|
385
386
|
if (ans === 'p' || ans === 'promote') {
|
|
386
387
|
// Promote: version, priority, title, goal — each defaulted from the idea.
|
|
387
|
-
|
|
388
|
+
// task 1003166 (G02): the fallback was 'PMS-V2', which has shipped —
|
|
389
|
+
// promoting into it would have been refused as version_closed.
|
|
390
|
+
const defaultVersion = idea.suggested_version || await resolveDefaultVersion();
|
|
388
391
|
const versionAns = (await prompt(rl, ` version [${defaultVersion}]: `)).trim();
|
|
389
392
|
const versionId = versionAns || defaultVersion;
|
|
390
393
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gds/version-literal-guard.js — no ops script names a version id
|
|
3
|
+
// (task 1003166 / G02, goal 1000077; criterion guards-no-hardcoded-version).
|
|
4
|
+
//
|
|
5
|
+
// THE INCIDENT. smoke-write.sh hardcoded 'PMS-V1'. That version shipped, so
|
|
6
|
+
// `version_closed` refused every create and the write-path smoke COULD NOT PASS
|
|
7
|
+
// — while /merge-mode Step 5 went on telling the operator "4/4 expected". The
|
|
8
|
+
// failure reads as a broken smoke, not a stale constant, which is why it sat
|
|
9
|
+
// (idea 1000823). Nine scripts carried the same bomb; four of the versions they
|
|
10
|
+
// named (PMS-V1, PMS-V2, GDS-V3, GDS-V4) had ALREADY shipped when this ran.
|
|
11
|
+
//
|
|
12
|
+
// WHAT IT CHECKS, narrowly: a version-shaped literal ASSIGNED TO A VERSION FIELD
|
|
13
|
+
// or used as a version DEFAULT, in an executable script. Not any mention of a
|
|
14
|
+
// version anywhere — smoke-claim-consistency.sh legitimately says "PMS-V2 P6 not
|
|
15
|
+
// live yet" in a comment and an echo, and flagging that would be the over-broad
|
|
16
|
+
// source assertion this repo has learned to avoid (learning 1000196). Comment
|
|
17
|
+
// lines are stripped before matching for the same reason.
|
|
18
|
+
//
|
|
19
|
+
// EXCLUDED BY PATH, not by allowlist: seeders and tests. A seeder's whole subject
|
|
20
|
+
// IS a specific historical version — `seed-v3-tasks.js` naming GDS-V3 is correct,
|
|
21
|
+
// and asking it to resolve one at runtime would break it.
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const { execFileSync } = require('node:child_process');
|
|
28
|
+
|
|
29
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
30
|
+
|
|
31
|
+
// Any prefix, so a version scheme invented next year is covered too — better
|
|
32
|
+
// than enumerating PMS/GDS/BONGOS/CB and finding out later that OTB-V2 slipped.
|
|
33
|
+
const VERSION_LITERAL = String.raw`['"][A-Z][A-Z0-9]*-V\d+['"]`;
|
|
34
|
+
// A version FIELD or a version DEFAULT — the two shapes that actually rot.
|
|
35
|
+
const VERSION_FIELD = String.raw`version_id|versionId|suggested_version|target_version|DEFAULT_VERSION|VERSION_ID`;
|
|
36
|
+
const ASSIGNED = new RegExp(`(?:${VERSION_FIELD})\\s*['"]?\\s*[:=][^,;)]*${VERSION_LITERAL}`);
|
|
37
|
+
const DEFAULTED = new RegExp(`--version[^\\n]*?\\|\\|\\s*${VERSION_LITERAL}`);
|
|
38
|
+
|
|
39
|
+
// path::reason. A justified entry, one line, like every other guard here.
|
|
40
|
+
const VERSION_LITERAL_ALLOW = new Map([
|
|
41
|
+
['scripts/gds/grade-smoke.js',
|
|
42
|
+
'GOOD_TASK / BAD_TASK are in-memory fixtures handed to grader.gradeSubagent; they are never POSTed, so no version_closed can refuse them and the literal cannot rot into a failure.'],
|
|
43
|
+
['scripts/gds/status.js',
|
|
44
|
+
'DEFAULT_VERSION is an owner-facing dashboard default with an env override, and TWO versions are currently building — so "most recent building" is not a derivable answer here. Retargeting what /status shows by default is an owner decision, not a refactor.'],
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
function isExempt(rel) {
|
|
48
|
+
return /(^|\/)seed-[^/]*\.js$/.test(rel) // a seeder's subject IS that version
|
|
49
|
+
|| /\.test\.js$/.test(rel) // fixtures pinned to a historical version
|
|
50
|
+
|| /(^|\/)fixtures\//.test(rel)
|
|
51
|
+
|| /(^|\/)version-literal-guard\.js$/.test(rel); // this file names the shapes it hunts
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Drop comment lines only — never inline content, which is where real code lives.
|
|
55
|
+
function stripComments(src, rel) {
|
|
56
|
+
const lineComment = rel.endsWith('.sh') ? /^\s*#/ : /^\s*(\/\/|\*|\/\*)/;
|
|
57
|
+
return String(src).split('\n').map((l) => (lineComment.test(l) ? '' : l));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function scanScripts(files, read) {
|
|
61
|
+
const violations = [];
|
|
62
|
+
let scanned = 0;
|
|
63
|
+
for (const rel of files) {
|
|
64
|
+
if (isExempt(rel)) continue;
|
|
65
|
+
const src = read(rel);
|
|
66
|
+
if (src === null) continue;
|
|
67
|
+
scanned += 1;
|
|
68
|
+
if (VERSION_LITERAL_ALLOW.has(rel)) continue;
|
|
69
|
+
stripComments(src, rel).forEach((line, i) => {
|
|
70
|
+
if (ASSIGNED.test(line) || DEFAULTED.test(line)) {
|
|
71
|
+
violations.push(
|
|
72
|
+
`${rel}:${i + 1} names a version id where one should be RESOLVED: ${line.trim().slice(0, 90)}`
|
|
73
|
+
+ ' — a literal works until that version ships, then version_closed refuses every write and the'
|
|
74
|
+
+ ' failure reads as broken code. Use resolveDefaultVersion() from version-select.js'
|
|
75
|
+
+ ' (bash: VERSION_ID=$(node scripts/gds/version-select.js)), or allowlist it with a reason.'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return { violations, scanned };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function trackedScripts() {
|
|
84
|
+
const out = execFileSync('git', ['ls-files', 'scripts/*.js', 'scripts/*.sh', 'scripts/**/*.js', 'scripts/**/*.sh'], {
|
|
85
|
+
cwd: REPO_ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
86
|
+
});
|
|
87
|
+
return String(out || '').split('\n').map((f) => f.trim()).filter(Boolean);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const NAME = 'no ops script hardcodes a version id (G02)';
|
|
91
|
+
|
|
92
|
+
function checkNoVersionLiterals({ files = null, read = null } = {}) {
|
|
93
|
+
const list = files || trackedScripts();
|
|
94
|
+
if (!list.length) {
|
|
95
|
+
return { name: NAME, ok: false, hardFail: true,
|
|
96
|
+
violations: ['scan defect — enumerated 0 tracked scripts; a broken enumeration, not a clean result.'],
|
|
97
|
+
warnings: [], note: 'static scan of scripts/ for hardcoded version ids.' };
|
|
98
|
+
}
|
|
99
|
+
const reader = read || ((rel) => {
|
|
100
|
+
try { return fs.readFileSync(path.join(REPO_ROOT, rel), 'utf8'); } catch { return null; }
|
|
101
|
+
});
|
|
102
|
+
const { violations, scanned } = scanScripts(list, reader);
|
|
103
|
+
return {
|
|
104
|
+
name: NAME,
|
|
105
|
+
ok: violations.length === 0,
|
|
106
|
+
hardFail: violations.length > 0,
|
|
107
|
+
violations,
|
|
108
|
+
warnings: [],
|
|
109
|
+
note: `${scanned} executable script(s) under scripts/ scanned: none may assign a version-shaped literal to a version field or use one as a --version default. Seeders, *.test.js and fixtures are exempt by path (their subject IS a historical version); ${VERSION_LITERAL_ALLOW.size} justified allowlist entr${VERSION_LITERAL_ALLOW.size === 1 ? 'y' : 'ies'}.`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function main() {
|
|
114
|
+
const r = checkNoVersionLiterals();
|
|
115
|
+
for (const v of r.violations) console.error(`✗ ${v}`);
|
|
116
|
+
console.log(`${r.hardFail ? 'FAIL' : 'PASS'} ${r.note}`);
|
|
117
|
+
process.exit(r.hardFail ? 1 : 0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (require.main === module) main();
|
|
121
|
+
|
|
122
|
+
module.exports = { checkNoVersionLiterals, scanScripts, stripComments, isExempt, VERSION_LITERAL_ALLOW, ASSIGNED, DEFAULTED };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gds/version-select.js — resolve "the version to use" at RUNTIME
|
|
3
|
+
// (task 1003166 / G02, goal 1000077; criterion guards-no-hardcoded-version).
|
|
4
|
+
//
|
|
5
|
+
// THE BUG THIS ENDS. scripts/gds/smoke-write.sh hardcoded 'PMS-V1'. That version
|
|
6
|
+
// shipped, so `version_closed` correctly refuses the create — meaning the
|
|
7
|
+
// write-path smoke CANNOT pass — while /merge-mode Step 5 still tells the
|
|
8
|
+
// operator "4/4 expected". A literal version id is a time bomb with a long fuse:
|
|
9
|
+
// it works until the version ships, and then it fails in a way that reads as a
|
|
10
|
+
// broken smoke rather than a stale constant (idea 1000823).
|
|
11
|
+
//
|
|
12
|
+
// Nine scripts carried one. The rule they now share is capture.js's, which was
|
|
13
|
+
// already right: the most recent BUILDING version. Extracted here so there is one
|
|
14
|
+
// implementation rather than nine, and exposed as a CLI so the bash smokes get
|
|
15
|
+
// the same answer as the JS callers instead of reimplementing the sort in python.
|
|
16
|
+
//
|
|
17
|
+
// const { resolveDefaultVersion } = require('./version-select.js'); // JS
|
|
18
|
+
// VERSION_ID=$(node scripts/gds/version-select.js) || exit 1 # bash
|
|
19
|
+
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
const { cliExit } = require('./cli-lib');
|
|
23
|
+
|
|
24
|
+
// PURE, so the selection rule is unit-tested without a session or a network.
|
|
25
|
+
// Returns a version id, or null when nothing is building — never a guess.
|
|
26
|
+
function pickDefaultVersion(versions) {
|
|
27
|
+
if (!Array.isArray(versions)) return null;
|
|
28
|
+
const building = versions.filter((v) => v && v.status === 'building');
|
|
29
|
+
if (building.length === 0) return null;
|
|
30
|
+
// Most recent by started_at. A null started_at sorts last rather than
|
|
31
|
+
// throwing, so a half-filled row cannot decide the answer (capture.js's
|
|
32
|
+
// original behaviour, kept deliberately).
|
|
33
|
+
building.sort((a, b) => {
|
|
34
|
+
const ta = a.started_at ? new Date(a.started_at).getTime() : 0;
|
|
35
|
+
const tb = b.started_at ? new Date(b.started_at).getTime() : 0;
|
|
36
|
+
return tb - ta;
|
|
37
|
+
});
|
|
38
|
+
return building[0].id;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function resolveDefaultVersion(deps = {}) {
|
|
42
|
+
const client = deps.cliClient || require('./cli-lib').cliClient;
|
|
43
|
+
const api = await client();
|
|
44
|
+
const r = await api.versions.getVersions();
|
|
45
|
+
if (!r.ok) return null;
|
|
46
|
+
return pickDefaultVersion(r.data?.versions ?? []);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// A version id is interpolated by bash callers into a quoted Python string, so
|
|
50
|
+
// the ONE place that can guarantee it is safe is here, at the source, rather than
|
|
51
|
+
// four escaping fixes downstream. Ids are `[A-Za-z][A-Za-z0-9._-]*` in practice;
|
|
52
|
+
// anything else is refused rather than printed. Defense in depth: writing a
|
|
53
|
+
// versions.id is already a privileged operation.
|
|
54
|
+
const SAFE_ID = /^[A-Za-z][A-Za-z0-9._-]*$/;
|
|
55
|
+
|
|
56
|
+
async function main() {
|
|
57
|
+
const id = await resolveDefaultVersion();
|
|
58
|
+
if (!id) {
|
|
59
|
+
// Exit non-zero rather than printing something a caller would interpolate:
|
|
60
|
+
// a smoke that creates a task against the empty string is worse than one
|
|
61
|
+
// that stops and says why.
|
|
62
|
+
console.error('version-select: no version is currently `building` — cannot resolve a default.');
|
|
63
|
+
console.error(' Open one, or pass an explicit --version to the caller.');
|
|
64
|
+
return cliExit(1);
|
|
65
|
+
}
|
|
66
|
+
if (!SAFE_ID.test(id)) {
|
|
67
|
+
console.error(`version-select: refusing to emit ${JSON.stringify(id)} — not a plain version id.`);
|
|
68
|
+
console.error(' Callers splice this into a quoted string; an id with a quote or backslash could break out.');
|
|
69
|
+
return cliExit(1);
|
|
70
|
+
}
|
|
71
|
+
process.stdout.write(id);
|
|
72
|
+
return cliExit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (require.main === module) {
|
|
76
|
+
main().catch((err) => { console.error(`version-select: ${err && err.message}`); cliExit(1); });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { pickDefaultVersion, resolveDefaultVersion, SAFE_ID };
|
package/src/module-api.js
CHANGED
|
@@ -1073,8 +1073,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
1073
1073
|
// landed since 1.19.321 with no explicit bump. run 32679604360. (task 1002620)
|
|
1074
1074
|
// 1.19.323 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1075
1075
|
// landed since 1.19.322 with no explicit bump. run 32680138776. (task 1002620)
|
|
1076
|
+
// 1.19.324 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1077
|
+
// landed since 1.19.323 with no explicit bump. run 32737868129. (task 1002620)
|
|
1076
1078
|
// ---------------------------------------------------------------------------
|
|
1077
|
-
const CORE_VERSION = '1.19.
|
|
1079
|
+
const CORE_VERSION = '1.19.324'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
1078
1080
|
|
|
1079
1081
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
1080
1082
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// tests/version_literals.mjs — no ops script hardcodes a version id
|
|
2
|
+
// (task 1003166 / G02, goal 1000077, criterion guards-no-hardcoded-version).
|
|
3
|
+
//
|
|
4
|
+
// THE INCIDENT: smoke-write.sh named 'PMS-V1'. That version shipped, so
|
|
5
|
+
// version_closed refused every create and the write-path smoke could not pass —
|
|
6
|
+
// while /merge-mode Step 5 still said "4/4 expected". FOUR of the versions the
|
|
7
|
+
// nine offending scripts named had already shipped when this was written.
|
|
8
|
+
//
|
|
9
|
+
// The tests care as much about what the guard must NOT flag as what it must: a
|
|
10
|
+
// scan that fires on a comment mentioning an old version would be the over-broad
|
|
11
|
+
// source assertion this repo keeps relearning (learning 1000196).
|
|
12
|
+
import { strict as assert } from 'node:assert';
|
|
13
|
+
import { test } from 'node:test';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
21
|
+
const g = require(path.join(ROOT, 'scripts', 'gds', 'version-literal-guard.js'));
|
|
22
|
+
const { pickDefaultVersion } = require(path.join(ROOT, 'scripts', 'gds', 'version-select.js'));
|
|
23
|
+
const read = (...p) => fs.readFileSync(path.join(ROOT, ...p), 'utf8');
|
|
24
|
+
const scan = (files) => g.scanScripts(Object.keys(files), (f) => files[f]);
|
|
25
|
+
|
|
26
|
+
// ---- the selection rule ---------------------------------------------------
|
|
27
|
+
test('the newest BUILDING version wins; shipped ones are never chosen', () => {
|
|
28
|
+
const vs = [
|
|
29
|
+
{ id: 'PMS-V1', status: 'shipped', started_at: '2026-05-09' },
|
|
30
|
+
{ id: 'GDS-V4', status: 'shipped', started_at: '2026-05-30' },
|
|
31
|
+
{ id: 'BONGOS-V1', status: 'building', started_at: '2026-06-20' },
|
|
32
|
+
{ id: 'CB-V1', status: 'building', started_at: '2026-06-21' },
|
|
33
|
+
];
|
|
34
|
+
assert.equal(pickDefaultVersion(vs), 'CB-V1');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('no building version yields NULL, never a guess', () => {
|
|
38
|
+
// The caller must stop and say so; interpolating '' into a create is worse.
|
|
39
|
+
assert.equal(pickDefaultVersion([{ id: 'X', status: 'shipped', started_at: '2026-01-01' }]), null);
|
|
40
|
+
assert.equal(pickDefaultVersion([]), null);
|
|
41
|
+
assert.equal(pickDefaultVersion(null), null);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('a null started_at cannot decide the answer', () => {
|
|
45
|
+
const vs = [
|
|
46
|
+
{ id: 'OLD', status: 'building', started_at: null },
|
|
47
|
+
{ id: 'NEW', status: 'building', started_at: '2026-06-21' },
|
|
48
|
+
];
|
|
49
|
+
assert.equal(pickDefaultVersion(vs), 'NEW', 'a half-filled row sorts last, it does not win');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// ---- what the guard MUST catch -------------------------------------------
|
|
53
|
+
test('THE INCIDENT: a version field assigned a literal is caught', () => {
|
|
54
|
+
const r = scan({ 'scripts/gds/smoke.sh': " 'version_id': 'PMS-V1'," });
|
|
55
|
+
assert.equal(r.violations.length, 1);
|
|
56
|
+
assert.match(r.violations[0], /RESOLVED/);
|
|
57
|
+
assert.match(r.violations[0], /version-select/, 'and it names the remedy');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('a --version default is caught', () => {
|
|
61
|
+
const r = scan({ 'scripts/gds/a.js': " const v = arg('--version', args) || 'GDS-V3';" });
|
|
62
|
+
assert.equal(r.violations.length, 1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('a DEFAULT_VERSION constant is caught', () => {
|
|
66
|
+
const r = scan({ 'scripts/gds/b.js': "const DEFAULT_VERSION = process.env.X || 'BONGOS-V1';" });
|
|
67
|
+
assert.equal(r.violations.length, 1);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('an unknown future prefix is caught too', () => {
|
|
71
|
+
// Enumerating PMS/GDS/BONGOS/CB would let the next scheme through.
|
|
72
|
+
const r = scan({ 'scripts/gds/c.js': " version_id: 'ACME-V7'," });
|
|
73
|
+
assert.equal(r.violations.length, 1, 'the shape is matched, not a fixed list');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// ---- what the guard must NOT catch ---------------------------------------
|
|
77
|
+
test('a COMMENT naming an old version is not a violation', () => {
|
|
78
|
+
// smoke-claim-consistency.sh legitimately says "PMS-V2 P6 not live yet".
|
|
79
|
+
const r = scan({
|
|
80
|
+
'scripts/gds/d.sh': '# TODO: once PMS-V2 P6 ships, drop this',
|
|
81
|
+
'scripts/gds/e.js': "// version_id: 'GDS-V3' used to live here",
|
|
82
|
+
});
|
|
83
|
+
assert.deepEqual(r.violations, []);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('prose printed to the operator is not a violation', () => {
|
|
87
|
+
const r = scan({ 'scripts/gds/f.sh': ' echo "SKIP — PMS-V2 P6 not live yet"' });
|
|
88
|
+
assert.deepEqual(r.violations, []);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('a RESOLVED assignment is not a violation', () => {
|
|
92
|
+
const r = scan({ 'scripts/gds/g.js': ' version_id: await resolveDefaultVersion(),' });
|
|
93
|
+
assert.deepEqual(r.violations, []);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('seeders and tests are exempt BY PATH, not by allowlist', () => {
|
|
97
|
+
// A seeder's whole subject IS a historical version; asking it to resolve one
|
|
98
|
+
// at runtime would break it.
|
|
99
|
+
for (const p of ['scripts/gds/seed-v3-tasks.js', 'scripts/gds/newcomer-floor.test.js', 'scripts/hall-preview/fixtures/x.js']) {
|
|
100
|
+
assert.equal(g.isExempt(p), true, `${p} must be exempt`);
|
|
101
|
+
}
|
|
102
|
+
assert.equal(g.isExempt('scripts/gds/smoke-write.sh'), false, 'an ops script is not exempt');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ---- the live tree --------------------------------------------------------
|
|
106
|
+
test('the live tree passes', () => {
|
|
107
|
+
const r = g.checkNoVersionLiterals();
|
|
108
|
+
assert.equal(r.hardFail, false, `unexpected:\n ${r.violations.join('\n ')}`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('an empty enumeration FAILS rather than reporting all-clear', () => {
|
|
112
|
+
const r = g.checkNoVersionLiterals({ files: [] });
|
|
113
|
+
assert.equal(r.hardFail, true);
|
|
114
|
+
assert.match(r.violations[0], /scan defect/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('every allowlist entry carries a real justification', () => {
|
|
118
|
+
assert.ok(g.VERSION_LITERAL_ALLOW.size > 0);
|
|
119
|
+
for (const [k, why] of g.VERSION_LITERAL_ALLOW) {
|
|
120
|
+
assert.match(k, /^scripts\//, `${k} is a script path`);
|
|
121
|
+
assert.ok(String(why).length > 60, `${k} needs a reason, not a label`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// ---- the reported bug is actually fixed ----------------------------------
|
|
126
|
+
test('smoke-write.sh resolves its version instead of naming one', () => {
|
|
127
|
+
const sh = read('scripts', 'gds', 'smoke-write.sh');
|
|
128
|
+
assert.ok(!/'version_id': '[A-Z]/.test(sh), 'no literal remains');
|
|
129
|
+
// The invocation is `$(node "$(dirname "$0")/version-select.js")`, so the
|
|
130
|
+
// closing paren is preceded by a quote AND the inner $(dirname "$0") contains
|
|
131
|
+
// its own ')', so neither `.js)` nor [^)]* matches. Match to end-of-line.
|
|
132
|
+
assert.match(sh, /VERSION_ID=\$\(node[^\n]*version-select\.js/, 'it resolves at runtime');
|
|
133
|
+
assert.match(sh, /exit 1/, 'and stops if it cannot');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('all four bash smokes resolve, and none names a version', () => {
|
|
137
|
+
for (const f of ['smoke-write.sh', 'smoke-claim-consistency.sh', 'smoke-batch-release.sh', 'smoke-batch-claim.sh']) {
|
|
138
|
+
const sh = read('scripts', 'gds', f);
|
|
139
|
+
assert.match(sh, /version-select\.js/, `${f} resolves`);
|
|
140
|
+
assert.match(sh, /'version_id': '\$\{VERSION_ID\}'/, `${f} uses the resolved id`);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('capture.js no longer keeps its own copy of the rule', () => {
|
|
145
|
+
// It was the precedent; now it is a caller, so there is ONE implementation.
|
|
146
|
+
const src = read('scripts', 'gds', 'capture.js');
|
|
147
|
+
assert.match(src, /require\('\.\/version-select\.js'\)/);
|
|
148
|
+
assert.ok(!/async function resolveDefaultVersion/.test(src), 'the private copy is gone');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// ---- the four grader findings from the first pass -------------------------
|
|
152
|
+
test('an unsafe version id is REFUSED, not printed', () => {
|
|
153
|
+
// Bash callers splice this into a quoted Python string, so the one place that
|
|
154
|
+
// can guarantee safety is the source. Ids with a quote or backslash could
|
|
155
|
+
// otherwise break out of the literal (the grader's defense-in-depth finding).
|
|
156
|
+
const { SAFE_ID } = require(path.join(ROOT, 'scripts', 'gds', 'version-select.js'));
|
|
157
|
+
for (const bad of ["CB-V1'; import os", 'CB\\V1', "'", '', '1-V1', 'a b']) {
|
|
158
|
+
assert.equal(SAFE_ID.test(bad), false, `${JSON.stringify(bad)} must be refused`);
|
|
159
|
+
}
|
|
160
|
+
for (const good of ['CB-V1', 'BONGOS-V1', 'GDS-V3', 'v2.1_beta']) {
|
|
161
|
+
assert.equal(SAFE_ID.test(good), true, `${good} must be accepted`);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('session-eval resolves the version ONCE, not per finding', () => {
|
|
166
|
+
// It was called inside `for (const s of systemic)` — a network round trip per
|
|
167
|
+
// finding, for a value invariant across the run.
|
|
168
|
+
const src = read('scripts', 'gds', 'session-eval.js');
|
|
169
|
+
// Anchored on the CALL COUNT rather than on a loop position: the file has TWO
|
|
170
|
+
// `for (const s of systemic)` loops, so an indexOf-based test picked the wrong
|
|
171
|
+
// one and passed vacuously. Exactly one call site is the real invariant.
|
|
172
|
+
const calls = src.match(/await resolveDefaultVersion\(\)/g) || [];
|
|
173
|
+
assert.equal(calls.length, 1, `resolved once per run, not per finding (found ${calls.length})`);
|
|
174
|
+
assert.match(src, /const suggestedVersion = await resolveDefaultVersion\(\)/, 'hoisted to a const');
|
|
175
|
+
assert.match(src, /suggested_version: suggestedVersion,/, 'and the filing uses it');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('version-select exits through cliExit, not process.exit', () => {
|
|
179
|
+
// CLAUDE.md: cliExit is the always-correct move for handle closure.
|
|
180
|
+
const src = read('scripts', 'gds', 'version-select.js');
|
|
181
|
+
assert.match(src, /require\('\.\/cli-lib'\)/);
|
|
182
|
+
assert.ok(!/process\.exit\(/.test(src), 'no raw process.exit remains');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('ship-flow.js imports every cross-module function it calls', () => {
|
|
186
|
+
// task 1003205's carve left pushVia() called in regradeMain but never imported,
|
|
187
|
+
// so EVERY resume of a task at 'completed' died with ReferenceError before the
|
|
188
|
+
// claim resolved — found by hitting it while shipping this task. Guarding the
|
|
189
|
+
// one name is not enough; assert the import exists for anything it calls from
|
|
190
|
+
// its sibling deploy-target module.
|
|
191
|
+
const src = read('scripts', 'gds', 'ship-flow.js');
|
|
192
|
+
const deployTargetExports = ['pushVia', 'resolveDeployMode', 'mainIsProtected', 'resolveDeployTarget'];
|
|
193
|
+
const imported = /require\('\.\/ship-deploy-target\.js'\)/.test(src)
|
|
194
|
+
? (src.match(/const \{([^}]*)\} = require\('\.\/ship-deploy-target\.js'\)/) || [, ''])[1]
|
|
195
|
+
: '';
|
|
196
|
+
for (const name of deployTargetExports) {
|
|
197
|
+
if (new RegExp(`(?<![\\w.])${name}\\(`).test(src)) {
|
|
198
|
+
assert.ok(imported.includes(name), `ship-flow.js calls ${name}() but does not import it`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|