@bongos/core 1.19.334 → 1.19.336

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.334",
6
- "core_contract": "1.19.334",
7
- "source_commit": "17d7b23f872fb3f64737ae5ffe270b91ace779a0",
5
+ "core_version": "1.19.336",
6
+ "core_contract": "1.19.336",
7
+ "source_commit": "85d561ede2a6c0ab512de0e97780813e9fea4112",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-25T00:36:48.394Z",
9
+ "built_at": "2026-08-25T00:56:17.081Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 325,
13
13
  "agent_docs_stubbed": 22,
14
- "functional_verbatim": 1490,
14
+ "functional_verbatim": 1483,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1837,
20
- "tree_sha256": "15089adb0fd9a51245746cbd141dd57dceadee53a5e640beb0d7059c7f219610",
19
+ "file_count": 1830,
20
+ "tree_sha256": "5a51e5573725c512980eafa27efcd8c38c63d1ffddf8d108ca0803f5a3ebac68",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -287,7 +287,7 @@
287
287
  {
288
288
  "path": ".devcontainer/post-create.sh",
289
289
  "mode": "0000644",
290
- "sha256": "db5db43fc7a692008eb1f70c6bec4345d6812939e9f09b302e2692621bbaece9"
290
+ "sha256": "69aef5fb55abc1bcd7c5f437df9bf7a38459eb52e1e4cc687b81d801f9c911ab"
291
291
  },
292
292
  {
293
293
  "path": ".dockerignore",
@@ -472,12 +472,12 @@
472
472
  {
473
473
  "path": "docs/adr/0011-terrain-id-rendering-model.md",
474
474
  "mode": "0000644",
475
- "sha256": "68e9189dd2d99428b599d02011f8c88649151e9acaf9d8912c98b231eed4507e"
475
+ "sha256": "b3c9291fda6fd5e19601a9b79a97359682d26bb316b357f28db12dbb281d79cf"
476
476
  },
477
477
  {
478
478
  "path": "docs/adr/0012-ground-object-cell-split.md",
479
479
  "mode": "0000644",
480
- "sha256": "bb26e441c48117377bbd451ea679a864bd58d5ac3a3a775cf20bc7219e5090e1"
480
+ "sha256": "a724bcd8c583d7f53515809da27202a7e512bad7862a8f77c8d8bd07c001792d"
481
481
  },
482
482
  {
483
483
  "path": "docs/adr/0013-paths-as-object-overlays-with-autotile-dispatch.md",
@@ -2987,7 +2987,7 @@
2987
2987
  {
2988
2988
  "path": "modules/economy/achievements.js",
2989
2989
  "mode": "0000644",
2990
- "sha256": "387c37df8b65bab755e8f006722e6665d9e5aafea29eabf868d1e2dfc4720e99"
2990
+ "sha256": "fc270efbe9439442d2f51490a8a292313e3f66c7b5d33c28cd9e7f929f56b307"
2991
2991
  },
2992
2992
  {
2993
2993
  "path": "modules/economy/cost-classify.js",
@@ -3007,7 +3007,7 @@
3007
3007
  {
3008
3008
  "path": "modules/economy/credits.js",
3009
3009
  "mode": "0000644",
3010
- "sha256": "7c42fc848a1b7139d7ffa59cc3d81d89db84329b7beae9d5b8c5bc0afefc6f41"
3010
+ "sha256": "a3d9d82cb5f19fdb8520c4df74022219886c767ae626dccf27728e07bf6d36f2"
3011
3011
  },
3012
3012
  {
3013
3013
  "path": "modules/economy/migrations/economy_001_fix_gds_shipper_description.sql",
@@ -3019,6 +3019,11 @@
3019
3019
  "mode": "0000644",
3020
3020
  "sha256": "b8b3a4add551b32bc625dca0390afc2176dcd9e433b24d21128dd6e249e70623"
3021
3021
  },
3022
+ {
3023
+ "path": "modules/economy/policy.js",
3024
+ "mode": "0000644",
3025
+ "sha256": "cc953acd69f844d158c3a79ea4de3b12d652ea0247c878c6a6c33d5abf68eab3"
3026
+ },
3022
3027
  {
3023
3028
  "path": "modules/economy/reward.js",
3024
3029
  "mode": "0000644",
@@ -4722,12 +4727,12 @@
4722
4727
  {
4723
4728
  "path": "package-lock.json",
4724
4729
  "mode": "0000644",
4725
- "sha256": "27cc593724db16a7e292e6f8287d3fadfbda3a6eaff49c1c97a08fe5bf175ce5"
4730
+ "sha256": "2aec5aabe3c60deec1e8f210e69d070f95b0e8d8a1a8fd55e9344399d67c66e5"
4726
4731
  },
4727
4732
  {
4728
4733
  "path": "package.json",
4729
4734
  "mode": "0000644",
4730
- "sha256": "5d2f06a29143a871ca7dd2f20d46ba2b05389a57aafddc6891147baa2a00e124"
4735
+ "sha256": "417a1f2c9a79ec8492ee09886edc0e2cbefa35a1ba9686bc81a9dabe4527ce45"
4731
4736
  },
4732
4737
  {
4733
4738
  "path": "public-docs/index.html",
@@ -5377,7 +5382,7 @@
5377
5382
  {
5378
5383
  "path": "scripts/gds/fitness-checks-identity.js",
5379
5384
  "mode": "0000644",
5380
- "sha256": "5d731cf4703ca47be3dfd272f4bc74f6f49471f6b027474285cca4221fac47d5"
5385
+ "sha256": "f87265779d60ae2d642e370f7af2d1d70fa6a1354a7389835d887b0dfe3c5dfc"
5381
5386
  },
5382
5387
  {
5383
5388
  "path": "scripts/gds/fitness-checks-vocabulary.js",
@@ -5397,7 +5402,7 @@
5397
5402
  {
5398
5403
  "path": "scripts/gds/fitness-ratchets.js",
5399
5404
  "mode": "0000644",
5400
- "sha256": "3f32080357abdecb0f0860c5c9313f12283f01774c8367ac22c1c12d0d176411"
5405
+ "sha256": "1732691382d3ec82f4f59b44250fce6367179a44283395dc92c78cfd67493ed2"
5401
5406
  },
5402
5407
  {
5403
5408
  "path": "scripts/gds/fitness.js",
@@ -5802,7 +5807,7 @@
5802
5807
  {
5803
5808
  "path": "scripts/gds/run-unit-tests.js",
5804
5809
  "mode": "0000644",
5805
- "sha256": "c96e50e533f4e20dba91c6a26ac6efd2c6ad33ffcdc856eeb46af21a17e67975"
5810
+ "sha256": "b3d816af249491e6187676ad084a60aaa72881615f8824a0263c31505d57a149"
5806
5811
  },
5807
5812
  {
5808
5813
  "path": "scripts/gds/sandbox-stage.js",
@@ -5949,11 +5954,6 @@
5949
5954
  "mode": "0000644",
5950
5955
  "sha256": "14825fa0500e600efcc518e7120dd4b64fdae9235509e7a39d3865f179754f5f"
5951
5956
  },
5952
- {
5953
- "path": "scripts/gds/seed-v3-tasks.js",
5954
- "mode": "0000644",
5955
- "sha256": "a0d8c1530cfc80f5604fb4d520d131daa2a9001f0a28f5e184dd0386034af6e0"
5956
- },
5957
5957
  {
5958
5958
  "path": "scripts/gds/sequence.js",
5959
5959
  "mode": "0000644",
@@ -6239,26 +6239,6 @@
6239
6239
  "mode": "0000644",
6240
6240
  "sha256": "5860bf9daef3dd83a5ff4d3ee5e017e3cbd465ebda0c00df4ca622d629468ec4"
6241
6241
  },
6242
- {
6243
- "path": "scripts/snapshots/backfill-history.mjs",
6244
- "mode": "0000644",
6245
- "sha256": "536c5dce00bb8fc04d895396dfe593cfef1317c6d5666d76a0e0586f08f08987"
6246
- },
6247
- {
6248
- "path": "scripts/snapshots/maybe-render-on-deploy.sh",
6249
- "mode": "0000644",
6250
- "sha256": "e8392d7dc47f583c75acdf87bdd8ba9cae19fd348dd8f559802955690938545d"
6251
- },
6252
- {
6253
- "path": "scripts/snapshots/render-pre-art-eras.mjs",
6254
- "mode": "0000644",
6255
- "sha256": "a5e5498067075212e7a38eda2faf1d4cdf5f98421a71bed85e46a2c0f9dc882c"
6256
- },
6257
- {
6258
- "path": "scripts/snapshots/render-world.mjs",
6259
- "mode": "0000644",
6260
- "sha256": "deb5f6169f53771bc119cdbaa3164112b8cf480b49c1adf0d65914b7c96ee84f"
6261
- },
6262
6242
  {
6263
6243
  "path": "scripts/status-mirror-sync.js",
6264
6244
  "mode": "0000644",
@@ -6577,7 +6557,7 @@
6577
6557
  {
6578
6558
  "path": "src/module-api.js",
6579
6559
  "mode": "0000644",
6580
- "sha256": "717fc58e2725b43ec1bc0a0e995172992ed0b42cba603c68482292b70da67c57"
6560
+ "sha256": "0a68c727fa467947704e21c886c0de2839aea5ecad7bf8b67f72867b7028ffb8"
6581
6561
  },
6582
6562
  {
6583
6563
  "path": "src/module-loader/catalog.js",
@@ -6654,11 +6634,6 @@
6654
6634
  "mode": "0000644",
6655
6635
  "sha256": "125edcec8c7d7536365b9675daa1fceb9fe072a30e2a4a68ee2911cd258a8c10"
6656
6636
  },
6657
- {
6658
- "path": "tests/anti_grid.mjs",
6659
- "mode": "0000644",
6660
- "sha256": "2d78ef9e446efa0aa66e716b362d72432024773b28d94f5768bcbaaf79c99dad"
6661
- },
6662
6637
  {
6663
6638
  "path": "tests/api_cli_flags.mjs",
6664
6639
  "mode": "0000644",
@@ -6824,16 +6799,6 @@
6824
6799
  "mode": "0000644",
6825
6800
  "sha256": "034114a63f2d8a431755f6cf35a7e66b1cbb3b7931bb251e3a8f235ff5057e29"
6826
6801
  },
6827
- {
6828
- "path": "tests/autotile_variants.mjs",
6829
- "mode": "0000644",
6830
- "sha256": "0cd3167548071272f13a3072a87d6071c46540a1fc0f810ca4584ac3f93693f6"
6831
- },
6832
- {
6833
- "path": "tests/autotile_xverify.mjs",
6834
- "mode": "0000644",
6835
- "sha256": "4986f25a93ef89f0312eeb1f4ff32cd82e2f8e7bcc9749143443537e734d5193"
6836
- },
6837
6802
  {
6838
6803
  "path": "tests/backfill_sessions.mjs",
6839
6804
  "mode": "0000644",
@@ -8072,7 +8037,7 @@
8072
8037
  {
8073
8038
  "path": "tests/lib_sh_resolution.mjs",
8074
8039
  "mode": "0000644",
8075
- "sha256": "4ec5ac626d9f8a72fe0e0066b266feb1f87b38c0dd8ac52f107c23e37a6a2e97"
8040
+ "sha256": "97630ec5029469ec99c6a87160822f319833857f12a79e42b4a465e362836ef0"
8076
8041
  },
8077
8042
  {
8078
8043
  "path": "tests/lifecycle-card.mjs",
@@ -8712,7 +8677,7 @@
8712
8677
  {
8713
8678
  "path": "tests/search_isolation.mjs",
8714
8679
  "mode": "0000644",
8715
- "sha256": "4832744832280a72cc84b8c4b7a4b836612fbec733972bedaa1810124a90de8b"
8680
+ "sha256": "21a352003eac3ef3975aecf758163bd90fb58c34daef643055b2423ea2ffdc17"
8716
8681
  },
8717
8682
  {
8718
8683
  "path": "tests/search_route.mjs",
@@ -102,13 +102,9 @@ echo
102
102
 
103
103
  # --- 3. Full-stack verification -----------------------------------------------
104
104
  say "4/4 Verifying the box is ready"
105
- # sharp: the native image dep — proves npm ci produced a working binary on this
106
- # arch (x64 or arm64).
107
- if node -e "require('sharp'); console.log('sharp', require('sharp/package.json').version)" >/dev/null 2>&1; then
108
- echo " ${green}ok${reset} sharp loads ($(node -e "console.log(require('sharp/package.json').version)" 2>/dev/null))"
109
- else
110
- echo " ${yellow}!!${reset} sharp failed to load — the game/art build will break"
111
- fi
105
+ # sharp was verified here as "the native image dep". Task 1003207 removed it: its
106
+ # only consumers were the game-era snapshot renderers and an art-pipeline test,
107
+ # all of which left this core with the game. Nothing native remains to prove.
112
108
  # The five art libs must import in the venv.
113
109
  if python3 -c "import PIL, numpy, scipy, sklearn, certifi" >/dev/null 2>&1; then
114
110
  echo " ${green}ok${reset} art libs import (Pillow, numpy, scipy, scikit-learn, certifi)"
@@ -94,7 +94,8 @@ chooses the foam variant at the boundary. One less hand-tuned terrain
94
94
  in the map.
95
95
 
96
96
  A Python↔JS cross-verification smoke test
97
- ([`tests/autotile_xverify.mjs`](../../tests/autotile_xverify.mjs) plus
97
+ (`tests/autotile_xverify.mjs` — deleted by task 1003207 with the rest of the
98
+ game-era art pipeline; named here as the historical record, not a live file — plus
98
99
  its emitter [`art/pipeline/autotile_xverify.py`](../../art/pipeline/autotile_xverify.py))
99
100
  asserts that all 256 blob47 inputs and all 16 path16 inputs map to the
100
101
  same tile index in both implementations. Wired into
@@ -113,7 +113,8 @@ server module directly to guard against drift.
113
113
  ### Cross-verify safety
114
114
 
115
115
  The Python↔JS autotile lookup table cross-verifier
116
- ([`tests/autotile_xverify.mjs`](../../tests/autotile_xverify.mjs))
116
+ (`tests/autotile_xverify.mjs`, deleted by task 1003207 with the game-era art
117
+ pipeline — named here as the historical record, not a live file)
117
118
  tests `selectBlob47` / `selectPath16` directly — unchanged by this
118
119
  ADR. Picker behavior change is captured in the new
119
120
  [`tests/terrain_picker.mjs`](../../tests/terrain_picker.mjs), which
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { SUBTRACTOR_THRESHOLD } = require('./policy');
4
+
3
5
  // Achievement evaluation — runs inside existing DB transactions so unlocks
4
6
  // are atomic with the action that triggered them.
5
7
  //
@@ -318,10 +320,9 @@ async function eligible_breaking100c(client, builderId) {
318
320
 
319
321
  async function eligible_subtractor(client, builderId) {
320
322
  // Mirror credits.awardNetNegativeBonus: subtractor unlocks at SUBTRACTOR_THRESHOLD
321
- // net-negative ship bonuses. Lazy-require the threshold (credits.js requires
322
- // this module, so a top-level require would be circular — same lazy pattern
323
- // renderProgressText uses for ./streak).
324
- const { SUBTRACTOR_THRESHOLD } = require('./credits');
323
+ // net-negative ship bonuses. The threshold used to be lazy-required from
324
+ // credits.js to dodge a circular import; task 1003207 moved it to ./policy.js,
325
+ // which both files read, so the cycle is gone rather than deferred.
325
326
  const r = await client.query(
326
327
  `SELECT COUNT(*)::int AS n FROM credit_log
327
328
  WHERE builder_id = $1 AND reason = 'ship.net_negative'`,
@@ -712,7 +712,9 @@ async function awardSessionTokenReward(client, builderId, sessionId, modelUsage,
712
712
  // ship's bonus row is inserted — and that insert lives here. Doing the unlock
713
713
  // inline keeps the read-then-unlock atomic with the insert in one transaction.
714
714
  const NET_NEGATIVE_BONUS = 1;
715
- const SUBTRACTOR_THRESHOLD = 3; // net-negative ships needed to unlock Subtractor
715
+ // task 1003207: moved to ./policy.js so achievements.js can read it without a
716
+ // lazy require back into this file (that circularity was a tracked cycle).
717
+ const { SUBTRACTOR_THRESHOLD } = require('./policy');
716
718
  function netNegativeBonusAmount(diffStats) {
717
719
  if (!diffStats) return 0;
718
720
  const added = Number(diffStats.lines_added) || 0;
@@ -0,0 +1,22 @@
1
+ // modules/economy/policy.js — economy policy constants that BOTH the credit
2
+ // writers and the achievement predicates need.
3
+ //
4
+ // Extracted by task 1003207 (goal 1000079) to break a genuine require cycle:
5
+ // credits.js requires achievements.js at load, and achievements.js needed
6
+ // SUBTRACTOR_THRESHOLD back from credits.js. The workaround was a lazy
7
+ // require() inside the function body, with a comment explaining the circularity
8
+ // — a cycle papered over rather than removed, and one of the five the
9
+ // require_cycle_count ratchet was holding.
10
+ //
11
+ // A shared constant is the honest seam: the threshold is neither a credit-writing
12
+ // concern nor an achievement concern, it is the POLICY both read. credits.js
13
+ // still re-exports it, so no caller outside this module changed.
14
+
15
+ 'use strict';
16
+
17
+ // Net-negative ships needed to unlock Subtractor. Read by credits.js when it
18
+ // awards the bonus, and by achievements.js when it decides the badge is earned —
19
+ // the two must agree, which is why it lives in one place.
20
+ const SUBTRACTOR_THRESHOLD = 3;
21
+
22
+ module.exports = { SUBTRACTOR_THRESHOLD };
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.334",
3
+ "version": "1.19.336",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.334",
9
+ "version": "1.19.336",
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.334",
3
+ "version": "1.19.336",
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",
@@ -101,7 +101,6 @@ const IDENTITY_SCAN_ALLOWED = {
101
101
  // when the monolith was split, so the detector's self-carve-out moved with them.
102
102
  'scripts/gds/fitness-checks-identity.js': 2,
103
103
  'scripts/gds/package-core.js': 1,
104
- 'scripts/gds/seed-v3-tasks.js': 14,
105
104
  // audit-replay.sh + db-backup-{nightly,offsite}.sh were carved out here as
106
105
  // "host-pinned ops"; task 1002757 RETIRED that justification for them — each
107
106
  // now resolves its database from the branding pack (the migrate.sh pattern) and
@@ -143,9 +143,19 @@ function requireGraph(contentsByFile) {
143
143
  const edges = new Map();
144
144
  for (const [file, text] of contentsByFile) {
145
145
  const deps = [];
146
- for (const m of text.matchAll(REQUIRE_RE)) {
147
- const target = resolveRelative(file, m[2], exists);
148
- if (target && target !== file) deps.push(target);
146
+ // Scan line by line and skip WHOLE-LINE comments. A comment that merely
147
+ // NAMES a require — "// re-exports every name, so no require('./db') call
148
+ // site had to change" — used to create a phantom edge, and during the task
149
+ // 1003204 carve one such header comment on twelve new files fabricated a
150
+ // twelve-file cycle and reddened CI. Only a `//`- or `*`-leading line is
151
+ // dropped, so an inline trailing comment still counts, which is the safe
152
+ // direction: a missed real edge would hide a cycle.
153
+ for (const line of text.split('\n')) {
154
+ if (/^\s*(\/\/|\*)/.test(line)) continue;
155
+ for (const m of line.matchAll(REQUIRE_RE)) {
156
+ const target = resolveRelative(file, m[2], exists);
157
+ if (target && target !== file) deps.push(target);
158
+ }
149
159
  }
150
160
  edges.set(file, deps);
151
161
  }
@@ -49,7 +49,11 @@ const MODULE_TESTS = {
49
49
  'terrain_picker', 'local_preview',
50
50
  ],
51
51
  'art-pipeline': [
52
- 'anti_grid', 'autotile_variants', 'autotile_xverify', 'art_key', 'art_stage',
52
+ // task 1003207 deleted anti_grid / autotile_variants / autotile_xverify — the
53
+ // art pipeline is not a module of this core any more, so they targeted a
54
+ // directory that no longer exists. art_key / art_stage still gate on the
55
+ // module being enabled, which is what this map is for.
56
+ 'art_key', 'art_stage',
53
57
  ],
54
58
  };
55
59
 
@@ -116,21 +120,17 @@ const INTEGRATION = new Set([
116
120
  ]);
117
121
 
118
122
  // Excluded so this gate is trustworthy in a CLEAN checkout (the CI condition):
119
- // - autotile_variants / autotile_xverify: depend on a python-generated artifact
120
- // (modules/art-pipeline/pipeline/_autotile_tables.json) / build output absent in a fresh clone,
121
- // so they exit non-zero standalone. Belong in an art-pipeline gate, not here.
122
- // - anti_grid: runs modules/art-pipeline/pipeline/anti_grid_eval.py, which needs numpy (absent in
123
- // the Node-only CI runner) -> ModuleNotFoundError. Same python-artifact class
124
- // as autotile; belongs in an art-pipeline gate (task 859 / ADR 0042 cutover).
123
+ // - autotile_variants / autotile_xverify / anti_grid were parked here because they
124
+ // needed modules/art-pipeline artifacts. Task 1003207 DELETED all three: the
125
+ // art pipeline left this core with the game, so they targeted a module that no
126
+ // longer exists and could never have gone green again. A permanently-parked
127
+ // test is not a gate, it is a file that looks like one.
125
128
  //
126
129
  // grader_panel was excluded here (2 "failing" assertions, idea #225) until #954
127
130
  // (V4.R16) diagnosed them as STALE — they predated #481's Narc-advisory change,
128
131
  // NOT a grader regression. Now green and back under this gate so the quality-
129
132
  // gate brain can't silently regress.
130
133
  const KNOWN_FAILING = new Set([
131
- 'autotile_variants',
132
- 'autotile_xverify',
133
- 'anti_grid',
134
134
  // #1002617: red in every clean environment (Linux CI and a Windows checkout
135
135
  // alike), surfaced by the unit lane's first-ever runs. Parked so the `unit`
136
136
  // check can be required (a gate that's already red gates nothing); per-test
package/src/module-api.js CHANGED
@@ -1095,8 +1095,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
1095
1095
  // landed since 1.19.332 with no explicit bump. run 32793666555. (task 1002620)
1096
1096
  // 1.19.334 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1097
1097
  // landed since 1.19.333 with no explicit bump. run 32794305277. (task 1002620)
1098
+ // 1.19.335 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1099
+ // landed since 1.19.334 with no explicit bump. run 32795232997. (task 1002620)
1100
+ // 1.19.336 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1101
+ // landed since 1.19.335 with no explicit bump. run 32795564021. (task 1002620)
1098
1102
  // ---------------------------------------------------------------------------
1099
- const CORE_VERSION = '1.19.334'; // CI auto-patch carrier (ADR 0161); see changelog above
1103
+ const CORE_VERSION = '1.19.336'; // CI auto-patch carrier (ADR 0161); see changelog above
1100
1104
 
1101
1105
  // A namespaced logger so a module's log lines are attributable + consistent.
1102
1106
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -26,6 +26,7 @@ import { test } from 'node:test';
26
26
  import { createRequire } from 'node:module';
27
27
  import { spawnSync } from 'node:child_process';
28
28
  import fs from 'node:fs';
29
+ import os from 'node:os';
29
30
  import path from 'node:path';
30
31
  import { fileURLToPath } from 'node:url';
31
32
  import { fitnessSource } from './helpers.mjs';
@@ -152,3 +153,144 @@ test('the whole host-identity check is green on this tree', () => {
152
153
  const r = fitness.checkKernelPromptsNoHardcodedIdentity();
153
154
  assert.equal(r.ok, true, `violations:\n${r.violations.join('\n')}`);
154
155
  });
156
+
157
+ // ---- 4. the SAME rule applies to the baked box scripts (task 1003252) ------
158
+ //
159
+ // THE SECOND BUG, same class, different file group. The sweep above fixed
160
+ // scripts/gds/*.sh but never looked at infra/box-*.sh — the scripts baked onto a
161
+ // dev box. Those kept reading a hardcoded ~/.config/otb/gds-session.json while
162
+ // paste-token.js writes to ~/.config/<branding.configDir>/. On this instance
163
+ // (configDir "cloudbongos") that meant every /builder-reauth silently stranded the
164
+ // box: the 5-min heartbeat, the terminal/host-key/version reporters and the
165
+ // GDS-backed git credential helper all kept presenting a DEAD token and failing
166
+ // with a misleading "session expired — re-auth on the box", which is exactly what
167
+ // the builder had just done. Observed live: four reporters 401ing every tick and a
168
+ // checkout 311 commits behind origin/main.
169
+ //
170
+ // The fix mirrors instance-config.js's read-side fallback in bash: prefer the most
171
+ // recently written gds-session.json across ~/.config/*/, since branding is not
172
+ // readable from a script that must also run before any clone exists.
173
+
174
+ const BOX_SCRIPTS = [
175
+ 'box-heartbeat.sh',
176
+ 'box-report-terminal.sh',
177
+ 'box-report-host-keys.sh',
178
+ 'box-report-version.sh',
179
+ ];
180
+
181
+ const INFRA = path.join(ROOT, 'infra');
182
+ const boxCode = (f) => fitness.stripCommentsForIdentity(fs.readFileSync(path.join(INFRA, f), 'utf8'), '.sh');
183
+
184
+ test('every baked box script resolves the session across config dirs, not just the legacy one', () => {
185
+ for (const f of BOX_SCRIPTS) {
186
+ const src = boxCode(f);
187
+ assert.match(src, /\.config\/\*\/gds-session\.json/,
188
+ `${f} must fall back across ~/.config/*/ — without it a /builder-reauth into a `
189
+ + 'non-"otb" configDir strands the box on a dead token');
190
+ // The legacy literal may survive ONLY as the last-resort default, never as the
191
+ // sole source — i.e. it must be preceded by the glob scan.
192
+ // The scan widens what these scripts will read, so each candidate must be owned
193
+ // by the box user -- otherwise a file dropped by another local account could win
194
+ // simply by being newest.
195
+ assert.match(src, /\[ -O "\$_sess" \]/,
196
+ `${f} must require candidate session files to be owned by the current user`);
197
+ const globAt = src.indexOf('.config/*/gds-session.json');
198
+ const legacyAt = src.indexOf('.config/otb/gds-session.json');
199
+ if (legacyAt !== -1) {
200
+ assert.ok(globAt < legacyAt,
201
+ `${f} reaches the legacy path before scanning ~/.config/*/ — the fallback must come first`);
202
+ }
203
+ }
204
+ });
205
+
206
+ test('every baked box script passes bash -n', (t) => {
207
+ const probe = bash(['-n', 'infra/box-heartbeat.sh']);
208
+ if (!probe) return t.skip('bash not available on this machine');
209
+ for (const f of BOX_SCRIPTS) {
210
+ const r = bash(['-n', `infra/${f}`]);
211
+ assert.equal(r.status, 0, `bash -n infra/${f} failed:\n${r.stderr}`);
212
+ }
213
+ });
214
+
215
+ // Pull the resolver straight out of the committed file so this exercises the real
216
+ // code, not a copy that could drift away from it.
217
+ function resolverBlock(f) {
218
+ const src = fs.readFileSync(path.join(INFRA, f), 'utf8');
219
+ const start = src.indexOf('if [ -z "${GDS_SESSION:-}" ]; then');
220
+ const endMark = 'GDS_SESSION="${GDS_SESSION:-$HOME/.config/otb/gds-session.json}"';
221
+ const end = src.indexOf(endMark, start);
222
+ assert.ok(start !== -1 && end !== -1, `could not locate the resolver block in ${f}`);
223
+ return src.slice(start, end + endMark.length);
224
+ }
225
+
226
+ test('the resolver prefers the most recently written session across config dirs', (t) => {
227
+ const probe = bash(['-n', 'infra/box-report-version.sh']);
228
+ if (!probe) return t.skip('bash not available on this machine');
229
+
230
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), 'boxsess-'));
231
+ const legacy = path.join(home, '.config', 'otb');
232
+ const configured = path.join(home, '.config', 'cloudbongos');
233
+ fs.mkdirSync(legacy, { recursive: true });
234
+ fs.mkdirSync(configured, { recursive: true });
235
+ fs.writeFileSync(path.join(legacy, 'gds-session.json'), '{"token":"stale"}');
236
+ fs.writeFileSync(path.join(configured, 'gds-session.json'), '{"token":"fresh"}');
237
+ // Make the legacy copy unambiguously older than the freshly re-authed one.
238
+ const old = new Date(Date.now() - 60_000);
239
+ fs.utimesSync(path.join(legacy, 'gds-session.json'), old, old);
240
+
241
+ const block = resolverBlock('box-report-version.sh');
242
+ const run = (env) => spawnSync('bash', ['-c', `set -uo pipefail\n${block}\nprintf '%s' "$GDS_SESSION"`],
243
+ { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: home, ...env } });
244
+
245
+ // 1. the freshly written session wins over the stale legacy one
246
+ assert.equal(run({}).stdout, path.join(configured, 'gds-session.json'));
247
+
248
+ // 2. an explicit override still beats the scan
249
+ assert.equal(run({ GDS_SESSION: '/explicit/path.json' }).stdout, '/explicit/path.json');
250
+
251
+ // 3. with nothing on disk it falls back to the historical path, so the caller's
252
+ // "no session at ..." message still names somewhere sensible
253
+ const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'boxsess-empty-'));
254
+ const r3 = spawnSync('bash', ['-c', `set -uo pipefail\n${block}\nprintf '%s' "$GDS_SESSION"`],
255
+ { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: empty } });
256
+ assert.equal(r3.stdout, path.join(empty, '.config', 'otb', 'gds-session.json'));
257
+
258
+ fs.rmSync(home, { recursive: true, force: true });
259
+ fs.rmSync(empty, { recursive: true, force: true });
260
+ });
261
+
262
+ // box-heartbeat.sh is the odd one out: its override variable (OTB_GDS_SESSION) is
263
+ // NOT the variable it resolves into (SF). A first cut of the fix guarded on the
264
+ // override but assigned to SF, so a caller that SET the override had it silently
265
+ // dropped and fell through to the legacy default — the exact bug being fixed,
266
+ // reintroduced through the back door. Pin all three branches.
267
+ test('box-heartbeat.sh honors OTB_GDS_SESSION, scans, then falls back', (t) => {
268
+ const probe = bash(['-n', 'infra/box-heartbeat.sh']);
269
+ if (!probe) return t.skip('bash not available on this machine');
270
+
271
+ const src = fs.readFileSync(path.join(INFRA, 'box-heartbeat.sh'), 'utf8');
272
+ const start = src.indexOf('SF="${OTB_GDS_SESSION:-}"');
273
+ const endMark = 'SF="${SF:-$HOME/.config/otb/gds-session.json}"';
274
+ const end = src.indexOf(endMark, start);
275
+ assert.ok(start !== -1 && end !== -1, 'could not locate the SF resolver in box-heartbeat.sh');
276
+ const block = src.slice(start, end + endMark.length);
277
+
278
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), 'hbsess-'));
279
+ const configured = path.join(home, '.config', 'cloudbongos');
280
+ fs.mkdirSync(configured, { recursive: true });
281
+ fs.writeFileSync(path.join(configured, 'gds-session.json'), '{"token":"fresh"}');
282
+
283
+ const run = (env, h = home) => spawnSync('bash',
284
+ ['-c', `set -euo pipefail\n${block}\nprintf '%s' "$SF"`],
285
+ { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: h, ...env } }).stdout;
286
+
287
+ assert.equal(run({ OTB_GDS_SESSION: '/explicit/override.json' }), '/explicit/override.json',
288
+ 'the OTB_GDS_SESSION override must win — guarding on it but assigning elsewhere drops it');
289
+ assert.equal(run({}), path.join(configured, 'gds-session.json'));
290
+
291
+ const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'hbsess-empty-'));
292
+ assert.equal(run({}, empty), path.join(empty, '.config', 'otb', 'gds-session.json'));
293
+
294
+ fs.rmSync(home, { recursive: true, force: true });
295
+ fs.rmSync(empty, { recursive: true, force: true });
296
+ });
@@ -143,7 +143,10 @@ console.log('\nLayer C — real searchChunks against a live DB (skips if unreach
143
143
 
144
144
  let pool = null;
145
145
  try {
146
- ({ pool } = require('../src/db'));
146
+ // task 1003207: this said `../src/db`, which has not existed since the kernel
147
+ // carve — so the require ALWAYS threw, pool stayed null, and Layer C skipped
148
+ // even against a live Postgres. It was a dead layer reporting as a skip.
149
+ ({ pool } = require('../src/bongos/pool'));
147
150
  } catch { pool = null; }
148
151
 
149
152
  async function dbReachable() {