@bongos/core 1.19.323 → 1.19.325

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 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.323",
6
- "core_contract": "1.19.323",
7
- "source_commit": "6b1fb52bfb9b8d899ffc44c0b8a22fd08cab9379",
5
+ "core_version": "1.19.325",
6
+ "core_contract": "1.19.325",
7
+ "source_commit": "9a1a1c67f80e8d01c562568c4565b9c2b4fb9420",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-24T01:33:26.263Z",
9
+ "built_at": "2026-08-24T14:28:39.280Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 324,
13
13
  "agent_docs_stubbed": 22,
14
- "functional_verbatim": 1457,
14
+ "functional_verbatim": 1460,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1803,
20
- "tree_sha256": "6534c0e9c341ea8d670e2f26984693052bc41041c70f99e671d5abd7ca3448dc",
19
+ "file_count": 1806,
20
+ "tree_sha256": "d455b7b84d748804468edea9e50ab188ef60da26a3538ede625b0766ca673f9b",
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": "670e665f41fbb4d1c4154b2e2a08dcb64383583a8162188101dd0807ae849caa"
4655
+ "sha256": "1a460f0c695ba6d590ee9bab9b281626de4747e164ca416098658ae054843ad6"
4656
4656
  },
4657
4657
  {
4658
4658
  "path": "package.json",
4659
4659
  "mode": "0000644",
4660
- "sha256": "27ab832e2bac7f7baac4016571d5536ac884b4b0f125a4b6f6a2dbae9b73112f"
4660
+ "sha256": "7cc398a8e7f341aee2272da252afc4abd5213349ece88962764c6bde65f27f25"
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": "bb4d1723f6668116e5c4783bb39efbfe5249e3dd8ed9e1739f3488f92fd37b00"
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": "84275f8b3c8981717c1648f346954a55742a0fecd2f6617257c4f77894609e12"
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": "950a65cf4766f795622c6780dcfa9a1b3f9aefe38f406ec6759b129028958aa9"
5115
+ "sha256": "946fa97d020eb481b7da559f0b5c99bfa16dc24b680a01e0ff3d3faeaf5bd701"
5116
5116
  },
5117
5117
  {
5118
5118
  "path": "scripts/gds/cf-tunnel.js",
@@ -5182,7 +5182,7 @@
5182
5182
  {
5183
5183
  "path": "scripts/gds/control-manifest.js",
5184
5184
  "mode": "0000644",
5185
- "sha256": "0d5b0c5e4bc15ff914d34849607ad2f01d1843d4dafeafdaf4191c2f365300bb"
5185
+ "sha256": "189caa1e5c263beb5d2c251d924abfe6ea84b7f8b26409a93633c8466bd5801c"
5186
5186
  },
5187
5187
  {
5188
5188
  "path": "scripts/gds/copy-inventory.js",
@@ -5267,7 +5267,7 @@
5267
5267
  {
5268
5268
  "path": "scripts/gds/doc-cli-guard.js",
5269
5269
  "mode": "0000644",
5270
- "sha256": "6f157f0b3a5618bcc2372b6d3a2805ff212013b421720470b443da35aa72cf26"
5270
+ "sha256": "9cf6c8ead8129799d17a18eadedf0423828cb457fa07fd317dcf2853254861e9"
5271
5271
  },
5272
5272
  {
5273
5273
  "path": "scripts/gds/docs-entropy.js",
@@ -5307,7 +5307,7 @@
5307
5307
  {
5308
5308
  "path": "scripts/gds/fitness.js",
5309
5309
  "mode": "0000644",
5310
- "sha256": "2de9f3b0e2c488ac1170aa90eab53c850b37c332676e36c4c8f214b219019352"
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": "6e236a05fa0a6459104bd16e8f2da5092c412b0c518511823eae0b39570eb678"
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": "efad88d4ead86356f27650b81c5ee28362bc94695dede92958e5398e9e71748c"
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": "d27deba8997676ed2224d2261316b3e60c0668bcde585f7efd0443d6ab150d89"
5980
+ "sha256": "53d3f4e28514aa6a3966e7a3db6223d4abe19074aaf44c39f61b920a0bdc35b0"
5981
5981
  },
5982
5982
  {
5983
5983
  "path": "scripts/gds/smoke-batch-release.sh",
5984
5984
  "mode": "0000644",
5985
- "sha256": "57e6f1ce66cd4e5cb7d6bbbd409f074b137344ba7c281a122f8ad42243d76029"
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": "55aa25f1d7143edec57833dcd582f05d1a1b7261bd73d51044fc48dff3532ee1"
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": "9c3a382def8c074a37bad66d7e3deef41c7f2bb9406f02f107be0e9a7dc7bfb3"
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": "5255bafa71699cadd345c5bb2ba6e99700cdd5d90fd165b949ef6595c5a83386"
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": "8624156a5587eaca3430325ef66420fb96b9567ed19af27dce361dc8af08611f"
6440
+ "sha256": "cc999c875729b68c2b78cf1f16a34184412b4d2b87b82be7b182627d313e9628"
6431
6441
  },
6432
6442
  {
6433
6443
  "path": "src/module-loader/catalog.js",
@@ -6977,7 +6987,7 @@
6977
6987
  {
6978
6988
  "path": "tests/control_manifest.mjs",
6979
6989
  "mode": "0000644",
6980
- "sha256": "2465159282594723a49d03ed2ca9dbb10e95ddb7af96d145c45f7d7c2a8b8cee"
6990
+ "sha256": "46ec55dbdb06b33cbe4b3dff44580cbb90d2daa0b225b810352a887e941e4798"
6981
6991
  },
6982
6992
  {
6983
6993
  "path": "tests/copy_desk_flags_db.mjs",
@@ -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.323",
3
+ "version": "1.19.325",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.323",
9
+ "version": "1.19.325",
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.323",
3
+ "version": "1.19.325",
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
- const version = arg('--version', args) || 'GDS-V3';
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
- const ver = argv.includes('--version') ? argv[argv.indexOf('--version') + 1] : 'GDS-V3';
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 };
@@ -54,22 +54,10 @@ const readline = require('node:readline');
54
54
  const { cliClient, requireSession, arg, hasFlag } = require('./cli-lib');
55
55
 
56
56
 
57
- async function resolveDefaultVersion() {
58
- const api = await cliClient();
59
- const r = await api.versions.getVersions();
60
- if (!r.ok) return null;
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.
@@ -74,6 +74,47 @@ const CONTROLS = [
74
74
  wiredIn: { file: '.claude/scheduled-tasks/redteam-weekly/SKILL.md', needle: 'redteam-patrol.js' },
75
75
  alsoExists: ['scripts/gds/redteam-patrol.js'],
76
76
  },
77
+
78
+ // ── goal 1000077's four guards (task 1003169 / G05) ──────────────────────
79
+ //
80
+ // Same reasoning as the goal-1000076 controls above, applied to the goal that
81
+ // exists BECAUSE prose documented four failure modes and nothing refused them.
82
+ // A guard that can silently stop existing is not a guard — and these four are
83
+ // exactly the kind that can: three are one line in fitness.js's CHECKS array
84
+ // (delete the line and the check simply never runs, with nothing red), and the
85
+ // fourth is a module the ship pipeline requires.
86
+ //
87
+ // `alsoExists` names each guard's TEST on purpose. A check whose test is gone
88
+ // still runs, but nothing then proves it can still fail — and this session
89
+ // produced three tests that were green for the wrong reason, so an untested
90
+ // guard is a guard on trust.
91
+ {
92
+ label: 'guard: a doc that names a CLI matches the real CLI (G01)',
93
+ file: 'scripts/gds/doc-cli-guard.js',
94
+ wiredIn: { file: 'scripts/gds/fitness.js', needle: 'doc-cli-guard' },
95
+ alsoExists: ['tests/doc_cli_guard.mjs'],
96
+ },
97
+ {
98
+ label: 'guard: no ops script hardcodes a version id (G02)',
99
+ file: 'scripts/gds/version-literal-guard.js',
100
+ wiredIn: { file: 'scripts/gds/fitness.js', needle: 'version-literal-guard' },
101
+ alsoExists: ['tests/version_literals.mjs', 'scripts/gds/version-select.js'],
102
+ },
103
+ {
104
+ label: 'guard: an ops skill declares the machine it needs (G03)',
105
+ file: 'scripts/gds/skill-preflight.js',
106
+ wiredIn: { file: 'scripts/gds/fitness.js', needle: 'skill-preflight' },
107
+ alsoExists: ['tests/skill_preflight.mjs'],
108
+ },
109
+ {
110
+ // Not a fitness check: a module the ship pipeline requires. Wired in
111
+ // ship-flow.js, which is where the carve left the resume path — and where a
112
+ // missing import silently broke every regrade until task 1003166 hit it.
113
+ label: 'guard: a ship cannot lie about how far it got (G04)',
114
+ file: 'scripts/gds/ship-honesty.js',
115
+ wiredIn: { file: 'scripts/gds/ship-flow.js', needle: 'ship-honesty' },
116
+ alsoExists: ['tests/ship_cannot_lie.mjs'],
117
+ },
77
118
  ];
78
119
 
79
120
  // Pure evaluation — exported for the test. deps.exists/deps.read override fs.
@@ -107,7 +148,7 @@ function checkControlsExist() {
107
148
  warnings: [],
108
149
  note: violations.length
109
150
  ? 'a shipped security/quality control disappeared or came unwired — restore it or update control-manifest.js in the same change'
110
- : `all ${CONTROLS.length} goal-1000076 controls present and wired (the assurance-of-assurance the ADR 0022 audit found missing)`,
151
+ : `all ${CONTROLS.length} controls present and wired — goal 1000076's plus goal 1000077's four guards (task 1003169). The assurance-of-assurance the ADR 0022 audit found missing.`,
111
152
  };
112
153
  }
113
154
 
@@ -33,34 +33,9 @@ const { execFileSync } = require('node:child_process');
33
33
 
34
34
  const REPO_ROOT = path.resolve(__dirname, '..', '..');
35
35
 
36
- // ---------------------------------------------------------------------------
37
- // Check 22 — a doc that NAMES a CLI invocation must match the real CLI
38
- // (task 1003165 / G01, goal 1000077).
39
- //
40
- // WHY. Task 1002931 shipped a skill instructing `capture.js --full --fields-file`
41
- // while those flags lived on an unlanded branch. Its test passed, because the test
42
- // pinned the skill's PROSE — it asserted the file said the words, never that the
43
- // words worked. So the skill confidently told agents to run a command that
44
- // silently ignored both flags and filed the wrong thing. That is the whole class:
45
- // AN UNRECOGNISED FLAG IS NOT AN ERROR. `arg('--x')`/`hasFlag('--x')` just return
46
- // null and the command proceeds, so a doc can name a flag that does nothing and
47
- // every test stays green (learning 1000198).
48
- //
49
- // It happened TWICE in one session: the second time, /workspace ran an older
50
- // capture.js and the same documented flags were dropped on the floor again.
51
- //
52
- // WHAT IT CHECKS, deliberately narrow: for every `node scripts/gds/<x>.js` in a
53
- // tracked .md, (a) the script exists, and (b) each `--flag` written next to it is
54
- // parsed by that script. Nothing else — this is not a linter for prose.
55
- //
56
- // STATIC ONLY, NEVER EXECUTED. The task says so and the reason matters: running
57
- // every documented CLI from a fitness check would be slow, would need a session,
58
- // and a command that blocks on a prompt would hang CI forever — the exact failure
59
- // R151's TTY gate exists to prevent.
60
- // Key is `<doc path>::<token>`, where token is either the script path (the whole
61
- // invocation is tolerated) or `--flag` (just that flag). Every entry carries its
62
- // reason on the same line: an unexplained entry is a silent hole, and the point of
63
- // this check is that a doc's claims are checkable.
36
+ // Check 22 in fitness.js's CHECKS. The why, the narrowness and the static-only
37
+ // rule are all in the file header above — not restated here.
38
+
64
39
  const DOC_CLI_ALLOW = new Map([
65
40
  ['.claude/skills/paint/SKILL.md::scripts/gds/art-stage.js',
66
41
  'art-stage.js ships with the art-pipeline HOST module, which publish-manifest.js excludes from core — absent from a core-only checkout by design; the skill says so inline.'],
@@ -120,6 +95,14 @@ function scanDocs(docs, { readDoc, readScript, allow = DOC_CLI_ALLOW } = {}) {
120
95
  if (!srcCache.has(rel)) srcCache.set(rel, readScript(rel));
121
96
  return srcCache.get(rel);
122
97
  };
98
+ // Memoized alongside srcCache: stripLineComments is two regex passes over a
99
+ // whole script, and it was being recomputed for EVERY flag on every invocation
100
+ // of that script. Invariant per file, so compute it once (grader, task 1003165).
101
+ const codeCache = new Map();
102
+ const codeOnly = (rel, src) => {
103
+ if (!codeCache.has(rel)) codeCache.set(rel, stripLineComments(src));
104
+ return codeCache.get(rel);
105
+ };
123
106
 
124
107
  for (const doc of docs) {
125
108
  const text = readDoc(doc);
@@ -156,7 +139,7 @@ function scanDocs(docs, { readDoc, readScript, allow = DOC_CLI_ALLOW } = {}) {
156
139
  // rather than failing is the deliberate choice: a usage banner is the
157
140
  // ONLY place some scripts name a flag they genuinely handle via a helper,
158
141
  // so failing here would resurrect the false positives that sank draft 1.
159
- if (!scriptMentionsFlag(stripLineComments(src), flag)) {
142
+ if (!scriptMentionsFlag(codeOnly(scriptRel, src), flag)) {
160
143
  warnings.push(
161
144
  `${doc} → \`node ${scriptRel} --${flag}\`: ${scriptRel} names \`--${flag}\` only in comments/usage text, `
162
145
  + 'never in code — it may be a flag whose implementation was deleted. Verify it is still handled.'
@@ -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, // Check 23 — task 1003167 / G03: an ops skill declares the machine it needs
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
- suggested_version: 'GDS-V4',
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++;
@@ -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': 'BONGOS-V1',
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': 'BONGOS-V1',
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': 'PMS-V2',
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': 'PMS-V1',
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',
@@ -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
- const defaultVersion = idea.suggested_version || 'PMS-V2';
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,12 @@ 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)
1078
+ // 1.19.325 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1079
+ // landed since 1.19.324 with no explicit bump. run 32738897943. (task 1002620)
1076
1080
  // ---------------------------------------------------------------------------
1077
- const CORE_VERSION = '1.19.323'; // CI auto-patch carrier (ADR 0161); see changelog above
1081
+ const CORE_VERSION = '1.19.325'; // CI auto-patch carrier (ADR 0161); see changelog above
1078
1082
 
1079
1083
  // A namespaced logger so a module's log lines are attributable + consistent.
1080
1084
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -58,3 +58,63 @@ test('all-present + all-wired yields zero violations', () => {
58
58
  const v = cm.evaluate(controls, { exists: () => true, read: () => 'has NEEDLE inside' });
59
59
  assert.deepEqual(v, []);
60
60
  });
61
+
62
+ // ── goal 1000077's four guards (task 1003169 / G05) ────────────────────────
63
+ // The goal exists because prose documented four failure modes and nothing
64
+ // refused them. These assert the guards themselves cannot quietly vanish —
65
+ // three are ONE LINE in fitness.js's CHECKS array, so deleting that line makes
66
+ // the check never run with nothing going red.
67
+
68
+ const GUARD_LABELS = ['G01', 'G02', 'G03', 'G04'];
69
+
70
+ test('all four goal-1000077 guards are in the manifest', () => {
71
+ for (const g of GUARD_LABELS) {
72
+ const row = cm.CONTROLS.find((c) => c.label.includes(`(${g})`));
73
+ assert.ok(row, `${g} has a manifest entry`);
74
+ assert.ok(row.file, `${g} names its file`);
75
+ assert.ok(row.wiredIn && row.wiredIn.file && row.wiredIn.needle, `${g} names where it is wired`);
76
+ assert.ok(Array.isArray(row.alsoExists) && row.alsoExists.some((f) => f.startsWith('tests/')),
77
+ `${g} names its TEST — a check whose test is gone still runs, but nothing proves it can fail`);
78
+ }
79
+ });
80
+
81
+ test('each guard file really exists and is really wired', () => {
82
+ // Not a source-text assertion: run the manifest's own evaluator over the
83
+ // real tree for just these rows.
84
+ const rows = cm.CONTROLS.filter((c) => GUARD_LABELS.some((g) => c.label.includes(`(${g})`)));
85
+ assert.equal(rows.length, 4);
86
+ assert.deepEqual(cm.evaluate(rows), [], 'no violations on the live tree');
87
+ });
88
+
89
+ test('a DELETED guard file is caught', () => {
90
+ const row = cm.CONTROLS.find((c) => c.label.includes('(G01)'));
91
+ const out = cm.evaluate([row], { exists: () => false, read: () => '' });
92
+ assert.equal(out.length, 1);
93
+ assert.match(out[0], /missing/i);
94
+ });
95
+
96
+ test('a guard UNWIRED from fitness.js is caught — the one-line deletion', () => {
97
+ // The realistic rot: someone removes the CHECKS entry and the guard sits on
98
+ // disk, never running, with a green build.
99
+ for (const g of ['G01', 'G02', 'G03']) {
100
+ const row = cm.CONTROLS.find((c) => c.label.includes(`(${g})`));
101
+ const out = cm.evaluate([row], { exists: () => true, read: () => 'const CHECKS = [];' });
102
+ assert.equal(out.length, 1, `${g} must be caught when unwired`);
103
+ }
104
+ });
105
+
106
+ test("G04 is wired via ship-flow, not fitness — it is a module, not a check", () => {
107
+ const row = cm.CONTROLS.find((c) => c.label.includes('(G04)'));
108
+ assert.equal(row.wiredIn.file, 'scripts/gds/ship-flow.js');
109
+ const out = cm.evaluate([row], { exists: () => true, read: () => 'nothing requires it' });
110
+ assert.equal(out.length, 1, 'an unrequired ship-honesty is caught');
111
+ });
112
+
113
+ test('a guard whose TEST vanished is caught', () => {
114
+ const row = cm.CONTROLS.find((c) => c.label.includes('(G02)'));
115
+ const out = cm.evaluate([row], {
116
+ exists: (f) => !String(f).startsWith('tests/'),
117
+ read: () => "require('./version-literal-guard.js')",
118
+ });
119
+ assert.ok(out.length >= 1, 'a missing test is a violation, not a shrug');
120
+ });
@@ -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
+ });