@bongos/core 1.19.1033 → 1.19.1034

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.
Files changed (100) hide show
  1. package/.bongos-core.json +186 -86
  2. package/.claude/skills/builder-start/SKILL.md +1 -1
  3. package/.claude/skills/grade-recover/SKILL.md +2 -1
  4. package/.claude/skills/grader-health/SKILL.md +4 -0
  5. package/.claude/skills/merge-mode/SKILL.md +1 -1
  6. package/docs/adr/0089-modular-multi-model-adversarial-grader.md +15 -0
  7. package/docs/adr/README.md +1 -1
  8. package/docs/api/openapi.json +1 -1
  9. package/docs/architecture.md +3 -2
  10. package/docs/canonical-permissions.md +2 -2
  11. package/docs/copy-inventory.md +34 -30
  12. package/docs/copy-registry.json +69 -33
  13. package/docs/module-api-changelog.md +3 -1
  14. package/docs/recipes/local-dev.md +45 -3
  15. package/docs/recipes/packaging-the-core.md +1 -1
  16. package/modules/autonomy/cadence.js +52 -0
  17. package/modules/government/protected-surfaces.json +1 -0
  18. package/modules/grading/grade-engine.js +267 -0
  19. package/modules/grading/grader-subagent.js +4 -3
  20. package/modules/grading/grader-workers/index.js +67 -11
  21. package/modules/grading/grader-workers/worker-verdict.js +32 -2
  22. package/modules/grading/grader.js +109 -88
  23. package/modules/hall-ui/public/deploy.js +35 -0
  24. package/modules/hall-ui/public/project-settings.css +7 -0
  25. package/modules/lifecycle/routes/tasks.js +19 -4
  26. package/modules/provisioning/control-plane-guard.js +14 -13
  27. package/modules/provisioning/core-upgrade.js +30 -17
  28. package/modules/provisioning/public-refusal.js +44 -0
  29. package/modules/provisioning/routes/disconnect.js +2 -1
  30. package/modules/provisioning/routes/env-manifest.js +5 -4
  31. package/modules/provisioning/routes/provisioning.js +16 -15
  32. package/modules/provisioning/routes/repo-private.js +2 -1
  33. package/package-lock.json +2 -2
  34. package/package.json +1 -1
  35. package/release-notes.json +70 -0
  36. package/scripts/gds/autobongos-run.js +10 -2
  37. package/scripts/gds/backfill-cost.js +20 -9
  38. package/scripts/gds/claim.js +2 -2
  39. package/scripts/gds/credential-presence.js +281 -0
  40. package/scripts/gds/deploy-main-audit-gate.sh +11 -4
  41. package/scripts/gds/dev-lib.js +212 -5
  42. package/scripts/gds/dev.js +102 -50
  43. package/scripts/gds/doctor.js +3 -2
  44. package/scripts/gds/grade-correlation-audit.js +280 -0
  45. package/scripts/gds/grade-replay.js +237 -0
  46. package/scripts/gds/hooks/pre-push.js +7 -3
  47. package/scripts/gds/main-audit.js +19 -10
  48. package/scripts/gds/memory-map.js +3 -1
  49. package/scripts/gds/onboarding-config.js +3 -2
  50. package/scripts/gds/plain-cards.js +4 -0
  51. package/scripts/gds/preflight.js +12 -4
  52. package/scripts/gds/provision-core-upgrade.js +155 -4
  53. package/scripts/gds/provision-repo-auth.js +15 -0
  54. package/scripts/gds/provision.js +1 -1
  55. package/scripts/gds/publish-credential-check.js +3 -0
  56. package/scripts/gds/push-main.js +14 -3
  57. package/scripts/gds/push-path-brief.js +3 -2
  58. package/scripts/gds/run-routine.js +18 -5
  59. package/scripts/gds/run-unit-tests.js +14 -3
  60. package/scripts/gds/session-cost.js +12 -3
  61. package/scripts/gds/ship-deploy-target.js +11 -6
  62. package/scripts/gds/ship-grade-steps.js +33 -0
  63. package/scripts/gds/ship-land.js +157 -11
  64. package/scripts/gds/ship-merge.js +3 -2
  65. package/scripts/gds/ship-preflight-steps.js +30 -8
  66. package/scripts/gds/ship.js +1 -1
  67. package/scripts/gds/upgrade.js +3 -3
  68. package/scripts/hall-preview/README.md +20 -3
  69. package/scripts/hall-preview/live-identity.js +1 -1
  70. package/scripts/hall-preview/refresh-fixtures.js +71 -11
  71. package/scripts/hall-preview/scrub-identity.js +296 -0
  72. package/src/bongos/routes/security.js +3 -3
  73. package/src/build-info.js +4 -2
  74. package/src/module-api.js +1 -1
  75. package/tests/adr_0089_scope.mjs +70 -0
  76. package/tests/autobongos_cadence.mjs +74 -0
  77. package/tests/consumer_layout_boot.mjs +363 -31
  78. package/tests/core_upgrade_door.mjs +40 -6
  79. package/tests/core_upgrade_runner.mjs +240 -4
  80. package/tests/credential_presence.mjs +153 -0
  81. package/tests/deploy_audit_gate.mjs +5 -2
  82. package/tests/deploy_page_projects.mjs +63 -2
  83. package/tests/dev_lib.mjs +2 -1
  84. package/tests/dev_windows.mjs +324 -0
  85. package/tests/env_knobs_neutral_prefix.mjs +203 -0
  86. package/tests/fixtures/grade-replay/recorded-panel-grades.json +27 -0
  87. package/tests/government_protected_surfaces.mjs +10 -0
  88. package/tests/grade_correlation_audit.mjs +159 -0
  89. package/tests/grade_engine.mjs +249 -0
  90. package/tests/grade_engine_replay.mjs +99 -0
  91. package/tests/grader_prompt_window_bound.mjs +132 -0
  92. package/tests/grader_unbacked_score.mjs +194 -0
  93. package/tests/hall_preview_scrub.mjs +214 -0
  94. package/tests/integration_parked_ratchet.mjs +24 -1
  95. package/tests/provisioning_public_refusal.mjs +195 -0
  96. package/tests/publish_status_branch.mjs +128 -0
  97. package/tests/ship_land_red_pr.mjs +236 -0
  98. package/tests/ship_preflight.mjs +67 -1
  99. package/tests/start_env_guard.mjs +16 -0
  100. package/tests/upgrade.mjs +20 -1
@@ -24,6 +24,7 @@ const api = require('../../../src/module-api');
24
24
  const { pool, validateOrRespond, parseId } = api;
25
25
  const provisioning = require('../provisioning');
26
26
  const envManifest = require('../env-manifest');
27
+ const { failFrom } = require('../public-refusal');
27
28
 
28
29
  const log = api.logger('provisioning');
29
30
 
@@ -119,7 +120,7 @@ module.exports = function buildEnvManifestRouter() {
119
120
  });
120
121
  } catch (err) {
121
122
  log.error('[provisioning] GET /provisioning/instances/:id/env-manifest', err);
122
- res.fail('env_manifest_failed', { status: 500, message: 'internal error' });
123
+ failFrom(res, err, 'env_manifest_failed');
123
124
  }
124
125
  });
125
126
 
@@ -147,7 +148,7 @@ module.exports = function buildEnvManifestRouter() {
147
148
  res.status(created ? 201 : 200).json({ ok: true, created, env_var: row, values_held: false });
148
149
  } catch (err) {
149
150
  log.error('[provisioning] POST /provisioning/instances/:id/env-manifest', err);
150
- res.fail('env_manifest_failed', { status: 500, message: 'internal error' });
151
+ failFrom(res, err, 'env_manifest_failed');
151
152
  }
152
153
  });
153
154
 
@@ -174,7 +175,7 @@ module.exports = function buildEnvManifestRouter() {
174
175
  res.json({ ok: true, env_var: row, values_held: false });
175
176
  } catch (err) {
176
177
  log.error('[provisioning] PATCH /provisioning/instances/:id/env-manifest/:varId', err);
177
- res.fail('env_manifest_failed', { status: 500, message: 'internal error' });
178
+ failFrom(res, err, 'env_manifest_failed');
178
179
  }
179
180
  });
180
181
 
@@ -193,7 +194,7 @@ module.exports = function buildEnvManifestRouter() {
193
194
  message: 'Declaration removed. The variable\'s value, wherever you keep it, is untouched — this platform never had it.' });
194
195
  } catch (err) {
195
196
  log.error('[provisioning] DELETE /provisioning/instances/:id/env-manifest/:varId', err);
196
- res.fail('env_manifest_failed', { status: 500, message: 'internal error' });
197
+ failFrom(res, err, 'env_manifest_failed');
197
198
  }
198
199
  });
199
200
 
@@ -62,6 +62,7 @@ const capacity = require('../capacity'); // the box's slot ceiling (ADR 0285,
62
62
  const { callbackPage } = require('./callback-page');
63
63
  // the LOUD body validators (task 1003504) — lifted out when this file hit the size ratchet
64
64
  const { badProjectDetail, badModuleSelection, badProjectType } = require('./body-validators');
65
+ const { failFrom } = require('../public-refusal'); // every catch: a declared refusal reaches the owner, anything else stays opaque (task 1004126)
65
66
  // the kernel ports this module provides (moved out at the size ratchet, task 1003578)
66
67
  const { registerProvisioningSeams } = require('../seams');
67
68
  // task 1003208: structured logging (pino via the doorway) — was console.*.
@@ -211,7 +212,7 @@ module.exports = function provisioningRoutes() {
211
212
  res.json({ instances: rows.map(fleetInstance), capacity: await capacity.fleetCapacity(pool).catch(() => null) });
212
213
  } catch (err) {
213
214
  log.error('[provisioning] GET /provisioning/fleet', err);
214
- res.fail('fleet_failed', { status: 500, message: 'internal error' });
215
+ failFrom(res, err, 'fleet_failed');
215
216
  }
216
217
  });
217
218
 
@@ -226,7 +227,7 @@ module.exports = function provisioningRoutes() {
226
227
  res.json(out);
227
228
  } catch (err) {
228
229
  log.error('[provisioning] GET /provisioning/fleet/cost-ledger', err);
229
- res.fail('cost_ledger_failed', { status: 500, message: 'internal error' });
230
+ failFrom(res, err, 'cost_ledger_failed');
230
231
  }
231
232
  });
232
233
 
@@ -279,7 +280,7 @@ module.exports = function provisioningRoutes() {
279
280
  message: r.created ? 'The instance will be torn down shortly.' : 'A teardown is already queued for that instance.' });
280
281
  } catch (err) {
281
282
  log.error('[provisioning] POST /provisioning/instances/:id/force-teardown', err);
282
- res.fail('teardown_failed', { status: 500, message: 'internal error' });
283
+ failFrom(res, err, 'teardown_failed');
283
284
  }
284
285
  });
285
286
 
@@ -518,7 +519,7 @@ module.exports = function provisioningRoutes() {
518
519
  // Another instance took the address between the check above and the INSERT (task 1002901).
519
520
  if (provisioning.isDomainTaken(err)) return res.fail('domain_taken', { status: 409, message: 'That address is already held by another instance.' });
520
521
  log.error('[provisioning] POST /provisioning/instances', err);
521
- res.fail('request_failed', { status: 500, message: 'internal error' });
522
+ failFrom(res, err, 'request_failed');
522
523
  }
523
524
  });
524
525
 
@@ -566,7 +567,7 @@ module.exports = function provisioningRoutes() {
566
567
  res.json({ slug, available: reason === null, reason, address: address || null });
567
568
  } catch (err) {
568
569
  log.error('[provisioning] GET /provisioning/slug-available', err);
569
- res.fail('check_failed', { status: 500, message: 'internal error' });
570
+ failFrom(res, err, 'check_failed');
570
571
  }
571
572
  });
572
573
 
@@ -586,7 +587,7 @@ module.exports = function provisioningRoutes() {
586
587
  res.json({ instances });
587
588
  } catch (err) {
588
589
  log.error('[provisioning] GET /provisioning/instances', err);
589
- res.fail('list_failed', { status: 500, message: 'internal error' });
590
+ failFrom(res, err, 'list_failed');
590
591
  }
591
592
  });
592
593
 
@@ -630,7 +631,7 @@ module.exports = function provisioningRoutes() {
630
631
  });
631
632
  } catch (err) {
632
633
  log.error('[provisioning] GET /provisioning/instances/:id', err);
633
- res.fail('read_failed', { status: 500, message: 'internal error' });
634
+ failFrom(res, err, 'read_failed');
634
635
  }
635
636
  });
636
637
 
@@ -750,7 +751,7 @@ module.exports = function provisioningRoutes() {
750
751
  });
751
752
  } catch (err) {
752
753
  log.error('[provisioning] PATCH /provisioning/instances/:id/detail', err);
753
- res.fail('detail_update_failed', { status: 500, message: 'internal error' });
754
+ failFrom(res, err, 'detail_update_failed');
754
755
  }
755
756
  });
756
757
 
@@ -805,7 +806,7 @@ module.exports = function provisioningRoutes() {
805
806
  res.json({ suggested, seeking: invites.seeking, reason: invites.reason });
806
807
  } catch (err) {
807
808
  log.error('[provisioning] GET /provisioning/instances/:id/invite-suggestions', err);
808
- res.fail('invite_suggestions_failed', { status: 500, message: 'internal error' });
809
+ failFrom(res, err, 'invite_suggestions_failed');
809
810
  }
810
811
  });
811
812
 
@@ -830,7 +831,7 @@ module.exports = function provisioningRoutes() {
830
831
  options: { ...provisioning.SETTINGS_VOCAB, ...provisioning.PLANET_VOCAB } });
831
832
  } catch (err) {
832
833
  log.error('[provisioning] GET /provisioning/instances/:id/settings', err);
833
- res.fail('settings_read_failed', { status: 500, message: 'internal error' });
834
+ failFrom(res, err, 'settings_read_failed');
834
835
  }
835
836
  });
836
837
 
@@ -983,7 +984,7 @@ module.exports = function provisioningRoutes() {
983
984
  res.json({ ok: true, saved: provisioning.effectiveSettings(updated), planet: provisioning.planetChoice(updated), push });
984
985
  } catch (err) {
985
986
  log.error('[provisioning] PATCH /provisioning/instances/:id/settings', err);
986
- res.fail('settings_update_failed', { status: 500, message: 'internal error' });
987
+ failFrom(res, err, 'settings_update_failed');
987
988
  }
988
989
  });
989
990
 
@@ -1020,7 +1021,7 @@ module.exports = function provisioningRoutes() {
1020
1021
  message: r.created ? 'Your instance will be torn down shortly.' : 'A teardown is already queued for that instance.' });
1021
1022
  } catch (err) {
1022
1023
  log.error('[provisioning] POST /provisioning/instances/:id/teardown', err);
1023
- res.fail('teardown_failed', { status: 500, message: 'internal error' });
1024
+ failFrom(res, err, 'teardown_failed');
1024
1025
  }
1025
1026
  });
1026
1027
 
@@ -1079,7 +1080,7 @@ module.exports = function provisioningRoutes() {
1079
1080
  : 'A restart is already under way for this project.' });
1080
1081
  } catch (err) {
1081
1082
  log.error('[provisioning] POST /provisioning/instances/:id/restart', err);
1082
- res.fail('restart_failed', { status: 500, message: 'internal error' });
1083
+ failFrom(res, err, 'restart_failed');
1083
1084
  }
1084
1085
  });
1085
1086
 
@@ -1224,7 +1225,7 @@ module.exports = function provisioningRoutes() {
1224
1225
  // Same race on the attach side: the index refuses the UPDATE, so nothing is recorded and nothing is enqueued.
1225
1226
  if (provisioning.isDomainTaken(err)) return res.fail('domain_taken', { status: 409, message: 'That address is already in use by another instance.' });
1226
1227
  log.error('[provisioning] POST /provisioning/instances/:id/domain', err);
1227
- res.fail('domain_attach_failed', { status: 500, message: 'internal error' });
1228
+ failFrom(res, err, 'domain_attach_failed');
1228
1229
  }
1229
1230
  });
1230
1231
 
@@ -1282,7 +1283,7 @@ module.exports = function provisioningRoutes() {
1282
1283
  });
1283
1284
  } catch (err) {
1284
1285
  log.error('[provisioning] POST /provisioning/instances/:id/github-app', err);
1285
- res.fail('github_app_manifest_failed', { status: 500, message: 'internal error' });
1286
+ failFrom(res, err, 'github_app_manifest_failed');
1286
1287
  }
1287
1288
  });
1288
1289
 
@@ -16,6 +16,7 @@ const api = require('../../../src/module-api');
16
16
  const { pool, validateOrRespond, parseId } = api;
17
17
  const provisioning = require('../provisioning');
18
18
  const repoPrivate = require('../repo-private');
19
+ const { failFrom } = require('../public-refusal');
19
20
 
20
21
  const log = api.logger('provisioning');
21
22
 
@@ -74,7 +75,7 @@ module.exports = function buildRepoPrivateRouter() {
74
75
  : 'Making the repository private is already under way for this project.' });
75
76
  } catch (err) {
76
77
  log.error('[provisioning] POST /provisioning/instances/:id/repo-private', err);
77
- res.fail('repo_private_failed', { status: 500, message: 'internal error' });
78
+ failFrom(res, err, 'repo_private_failed');
78
79
  }
79
80
  });
80
81
 
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.1033",
3
+ "version": "1.19.1034",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.1033",
9
+ "version": "1.19.1034",
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.1033",
3
+ "version": "1.19.1034",
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",
@@ -7379,6 +7379,46 @@
7379
7379
  }
7380
7380
  ],
7381
7381
  "1.19.1033": [
7382
+ {
7383
+ "id": "1004179",
7384
+ "text": "If the overnight builder can't start its worker at all, it now notices and slows down instead of quietly working through your whole task list breaking things."
7385
+ },
7386
+ {
7387
+ "id": "1003648",
7388
+ "text": "If a change is ever too big for one of the automatic reviewers, it now tries again with less attached material instead of silently failing — and the failure is recognised for what it is."
7389
+ },
7390
+ {
7391
+ "id": "1002863",
7392
+ "text": "Every change to the core is now automatically installed the way a brand-new project installs it, given a fresh empty database, and checked that the app really runs on that database — so a release that cannot set up its own dat"
7393
+ },
7394
+ {
7395
+ "id": "1004126",
7396
+ "text": "When the platform refuses a project action (for example, taking its own server offline), the owner now sees the real reason instead of \"internal error\"."
7397
+ },
7398
+ {
7399
+ "id": "1004287",
7400
+ "text": "A code comment in the shipping tool now describes what one of its helpers actually returns; nothing a user sees changes."
7401
+ },
7402
+ {
7403
+ "id": "1001441",
7404
+ "text": "A new Cloud Bongos project can now configure its build-and-ship settings with its own standard names, instead of names inherited from the original project; the old names still work."
7405
+ },
7406
+ {
7407
+ "id": "1002839",
7408
+ "text": "The grader that checks every shipped task now runs through one central engine that future kinds of work can plug into; its verdicts are unchanged, checked against 376 real past grades."
7409
+ },
7410
+ {
7411
+ "id": "1003647",
7412
+ "text": "The sample data used to preview the builder website no longer contains real people's names, photos, accounts or money figures; stand-ins are written instead, automatically, every time the samples are refreshed."
7413
+ },
7414
+ {
7415
+ "id": "1002903",
7416
+ "text": "When an automatic reviewer gives work a low mark without naming a problem, the builder is now told exactly that — nothing to fix, ask again — instead of being told to fix problems that do not exist."
7417
+ },
7418
+ {
7419
+ "id": "1003764",
7420
+ "text": "When a finished task is stuck waiting to be added to the project, the tools now say which automatic test failed and how to fix it, instead of just saying \"waiting\" — and the status check no longer errors out when you ask about"
7421
+ },
7382
7422
  {
7383
7423
  "id": "1004218",
7384
7424
  "text": "Every new core release now carries its own list of what changed, so the Deploy page on any project can show what an upgrade brings in, starting with the next release."
@@ -7407,5 +7447,35 @@
7407
7447
  "id": "1004170",
7408
7448
  "text": "Builders on a Mac no longer get a permanently failing local test run: the database backup scripts and two server cleanup scripts now work the same on a Mac as on the server."
7409
7449
  }
7450
+ ],
7451
+ "1.19.1034": [
7452
+ {
7453
+ "id": "1004273",
7454
+ "text": "The shipping tool once again refuses to run from an out-of-date copy of itself, now that its code is split across many files, so a builder no longer gets the same unexplained ship failure over and over."
7455
+ },
7456
+ {
7457
+ "id": "1002707",
7458
+ "text": "Owners on Windows can now start their project on their own computer with one command, bongos dev, instead of being told to set up Linux or Docker first; it finds Postgres and Git Bash itself and explains any missing step in pl"
7459
+ },
7460
+ {
7461
+ "id": "1002880",
7462
+ "text": "There is now one page listing every password and key the project needs, what each is for and where it is kept, plus a one-line check the owner can run on the server to see which are already in place, so nobody is asked to crea"
7463
+ },
7464
+ {
7465
+ "id": "1002843",
7466
+ "text": "A new read-only report measures whether the four AI reviewers that grade each ship actually disagree with each other, and it refuses to give a number when there are too few real decisions to trust one."
7467
+ },
7468
+ {
7469
+ "id": "1002844",
7470
+ "text": "The grading design document now says plainly which four tools are not part of the grader and why, with a test that fails if that ever stops being true."
7471
+ },
7472
+ {
7473
+ "id": "1004065",
7474
+ "text": "When a hosted project is upgraded, the platform now tries to save the new version to the project's GitHub repository itself, and the Deploy page says plainly when it could not, instead of reporting a clean success."
7475
+ },
7476
+ {
7477
+ "id": "1004288",
7478
+ "text": "The setup instructions an AI assistant follows on a fresh copy of a project no longer claim the core package is private, so a real download failure is no longer mistaken for normal."
7479
+ }
7410
7480
  ]
7411
7481
  }
@@ -49,6 +49,7 @@ const fence = require('../../modules/autonomy/fence.js');
49
49
  // where it would not be inside modules/ — which is exactly why fence.js takes the
50
50
  // hits as an argument instead of matching them itself.
51
51
  const { matchProtected } = require('../../src/bongos/permission-path-check');
52
+ const { resolveEnv, envPrefix, LEGACY_ENV_PREFIXES } = require('../../src/instance-config');
52
53
  const cadence = require('../../modules/autonomy/cadence.js');
53
54
 
54
55
  const REPO_ROOT = path.resolve(__dirname, '..', '..');
@@ -206,11 +207,18 @@ async function pickTask(goals, deps = {}) {
206
207
  // own required vars), to reach the instance (BONGOS_API_BASE), and to be told it
207
208
  // is a subagent. It does NOT need a git push credential — the road to main is the
208
209
  // server-mediated pipeline (ADR 0307 §6) — so none is passed.
210
+ function claudeBinEnvNames() {
211
+ return [...new Set([envPrefix(), ...LEGACY_ENV_PREFIXES].map((p) => `${p}_CLAUDE_BIN`)), 'CLAUDE_BIN'];
212
+ }
213
+
209
214
  const WORKER_ENV_ALLOW = [
210
215
  'PATH', 'HOME', 'SHELL', 'LANG', 'LC_ALL', 'TZ', 'TMPDIR', 'USER', 'LOGNAME',
211
216
  'SystemRoot', 'windir', 'COMSPEC', 'PATHEXT', 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'TEMP', 'TMP',
212
217
  'BONGOS_API_BASE', 'CLOUDBONGOS_API_BASE',
213
- 'OTB_CLAUDE_BIN', 'CLAUDE_BIN',
218
+ // Every spelling resolveWorkerBin below can read (task 1001441): the branded
219
+ // <PREFIX>_CLAUDE_BIN, the legacy ones, and the unprefixed CLAUDE_BIN. A name
220
+ // missing here would be set on the host and silently absent in the worker.
221
+ ...claudeBinEnvNames(),
214
222
  'XDG_CONFIG_HOME',
215
223
  // THE WORKER'S OWN CREDENTIAL, and it has to be here (task 1003907, found by
216
224
  // running it). On a desktop, the claude CLI authenticates from the credential
@@ -257,7 +265,7 @@ const MAX_WORKER_OUTPUT = 8 * 1024 * 1024; // a misbehaving worker must not gr
257
265
  // laid-out install should still run, and a corrupted schema surfaces as a failed
258
266
  // worker rather than as a refusal to start at all.
259
267
  function resolveWorkerBin(env = process.env, platform = process.platform) {
260
- const override = env.OTB_CLAUDE_BIN || env.CLAUDE_BIN;
268
+ const override = resolveEnv('CLAUDE_BIN', { env }) || env.CLAUDE_BIN;
261
269
  if (override) return { bin: override, shell: platform === 'win32' && /\.(cmd|bat)$/i.test(override) };
262
270
  if (platform !== 'win32') return { bin: 'claude', shell: false };
263
271
  for (const dir of String(env.PATH || env.Path || '').split(path.delimiter)) {
@@ -12,23 +12,28 @@
12
12
  //
13
13
  // If no path is given, the script searches for the ledger in the
14
14
  // expected locations (current art/iterations/, sibling worktrees, or
15
- // $OTB_COST_LEDGER env var).
15
+ // $<PREFIX>_COST_LEDGER env var — BONGOS_COST_LEDGER on a vanilla instance, the
16
+ // legacy OTB_COST_LEDGER still read).
16
17
 
17
18
  const fs = require('node:fs');
18
19
  const path = require('node:path');
19
20
  const { cliClient, requireSession, fmtUsd } = require('./cli-lib');
21
+ const { resolveEnv, envPrefix } = require('../../src/instance-config');
20
22
  // Cost-attribution audit carved into modules/economy/cost-classify.js (BV1.R77 /
21
23
  // ADR 0093 §1-2). A CLI may require a module directly (the memory/grading-carve
22
24
  // precedent — grade-smoke.js requires modules/grading/grader).
23
25
  const { auditCostAttribution, formatAuditReport } = require('../../modules/economy/cost-classify');
24
26
 
25
- const KNOWN_LEDGER_PATHS = [
26
- process.env.OTB_COST_LEDGER,
27
+ // The places a ledger is looked for, env override first. A function so the test can
28
+ // hand it an env; resolved once below for the CLI.
29
+ const knownLedgerPaths = (env = process.env) => [
30
+ resolveEnv('COST_LEDGER', { env }),
27
31
  path.resolve(__dirname, '../../art/iterations/cost_ledger.jsonl'),
28
32
  // Two historical absolute-path fallbacks (under the old Desktop folder name)
29
33
  // were dropped after the project-folder rename. Pass the ledger path as a CLI
30
- // arg or set OTB_COST_LEDGER to point at any non-default location.
34
+ // arg or set <PREFIX>_COST_LEDGER to point at any non-default location.
31
35
  ];
36
+ const KNOWN_LEDGER_PATHS = knownLedgerPaths();
32
37
 
33
38
  function findLedger(argPath) {
34
39
  const candidates = argPath ? [argPath, ...KNOWN_LEDGER_PATHS] : KNOWN_LEDGER_PATHS;
@@ -106,7 +111,7 @@ async function main() {
106
111
 
107
112
  const ledgerPath = findLedger(argPath);
108
113
  if (!ledgerPath) {
109
- console.error('No cost_ledger.jsonl found. Pass a path or set OTB_COST_LEDGER.');
114
+ console.error(`No cost_ledger.jsonl found. Pass a path or set ${envPrefix()}_COST_LEDGER.`);
110
115
  console.error('Searched:');
111
116
  for (const p of KNOWN_LEDGER_PATHS) if (p) console.error(` - ${p}`);
112
117
  process.exit(2);
@@ -159,7 +164,13 @@ async function main() {
159
164
  process.exit(failed > 0 ? 1 : 0);
160
165
  }
161
166
 
162
- main().catch((err) => {
163
- console.error('backfill-cost.js failed:', err);
164
- process.exit(1);
165
- });
167
+ // Run only when invoked directly: requiring this file (the test does, for
168
+ // knownLedgerPaths) must never start a backfill.
169
+ if (require.main === module) {
170
+ main().catch((err) => {
171
+ console.error('backfill-cost.js failed:', err);
172
+ process.exit(1);
173
+ });
174
+ }
175
+
176
+ module.exports = { knownLedgerPaths };
@@ -225,8 +225,8 @@ function captureHeadSha() {
225
225
  // origin/main. The completion cards + skills are CLI-script features that ride
226
226
  // the LOCAL checkout, not the deployed server — a worktree branched from a stale
227
227
  // main silently lacks them (the root cause behind the missing cards in session
228
- // 1c1e12d7: claimed/shipped task 1274 on old scripts). Mirrors ship.js's
229
- // TOOLING_PATHS preflight, but as a NUDGE — claiming on old scripts is fine, you
228
+ // 1c1e12d7: claimed/shipped task 1274 on old scripts). Mirrors the ship's
229
+ // TOOLING_PATHSPECS preflight (ship-preflight-steps.js), but as a NUDGE — claiming on old scripts is fine, you
230
230
  // just miss features. Strictly best-effort + fail-open (never break/slow a claim).
231
231
  const STALE_TOOLING_DIRS = ['scripts/gds', 'src/bongos', '.claude/skills'];
232
232
  function warnIfToolingStale() {