@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
@@ -17,7 +17,7 @@ const {
17
17
  untrackedPaths, untrackedLeftBehind, untrackedWarningLines,
18
18
  } = require('../modules/lifecycle/ship-preflight.js');
19
19
  // task 1001436: the preflight step itself + the ship-input resolver it is handed.
20
- const { preflight, shipInputPaths } = require('../scripts/gds/ship-preflight-steps.js');
20
+ const { preflight, shipInputPaths, staleToolingFiles, TOOLING_PATHSPECS } = require('../scripts/gds/ship-preflight-steps.js');
21
21
 
22
22
  const tests = [];
23
23
  const test = (name, fn) => tests.push({ name, fn });
@@ -509,6 +509,72 @@ test('preflight (real git): nothing committed on the branch → no untracked war
509
509
  });
510
510
  });
511
511
 
512
+ // ---- stale ship-tooling guard (task 1004273) ----
513
+ // The guard named ship.js + grader.js only; two carves moved the logic into ship-*.js
514
+ // and grader-*.js / grader-workers/ / grade-engine.js, so a branch behind main on any
515
+ // of those shipped with a stale copy — the #662 shape the guard exists for.
516
+
517
+ test('stale-tooling guard: every tracked ship*.js and grader file is covered by TOOLING_PATHSPECS', () => {
518
+ const root = path.resolve(import.meta.dirname, '..');
519
+ const ls = (...spec) => execSync(`git ls-files -z -- ${spec.map((s) => `'${s}'`).join(' ')}`, { cwd: root, encoding: 'utf8' })
520
+ .split('\0').filter(Boolean);
521
+ // Chosen by an independent rule (the filename), then asked of git's own pathspec matcher.
522
+ const shipTooling = ls('scripts/gds', 'modules/grading').filter((f) =>
523
+ /^scripts\/gds\/ship[^/]*\.js$/.test(f) || /^modules\/grading\/(?:grader|grade-engine)/.test(f));
524
+ assert.ok(shipTooling.length >= 25, `only ${shipTooling.length} ship/grader file(s) found — the scan is reading the wrong tree`);
525
+ assert.ok(shipTooling.includes('scripts/gds/ship-land.js') && shipTooling.includes('modules/grading/grader-score.js'));
526
+ const covered = new Set(ls(...TOOLING_PATHSPECS));
527
+ const missed = shipTooling.filter((f) => !covered.has(f));
528
+ assert.deepEqual(missed, [], `ship/grade tooling the stale-tooling guard would not refuse: ${missed.join(', ')}`);
529
+ });
530
+
531
+ // Publish the throwaway repo's main to its local bare "origin".
532
+ const publishMain = (run) => run(['git', 'push', '-q', 'origin', 'main'].join(' '));
533
+
534
+ test('preflight (real git): main moved a carved ship-*.js and added a new grader file → refused stale-tooling', () => {
535
+ withPreflightRepo(({ work, run }) => {
536
+ fs.mkdirSync(path.join(work, 'scripts', 'gds'), { recursive: true });
537
+ fs.mkdirSync(path.join(work, 'modules', 'grading'), { recursive: true });
538
+ fs.writeFileSync(path.join(work, 'scripts', 'gds', 'ship-land.js'), '// v1\n');
539
+ fs.writeFileSync(path.join(work, 'modules', 'grading', 'grader-score.js'), '// v1\n');
540
+ run('git add -A && git commit -q -m tooling');
541
+ publishMain(run);
542
+ run('git checkout -q -b feature');
543
+ fs.writeFileSync(path.join(work, 'src.txt'), 'the branch\'s own work\n');
544
+ run('git add -A && git commit -q -m work');
545
+ // main moves: one carved file changes, one file appears that this checkout never had.
546
+ run('git checkout -q main');
547
+ fs.writeFileSync(path.join(work, 'scripts', 'gds', 'ship-land.js'), '// v2\n');
548
+ fs.writeFileSync(path.join(work, 'modules', 'grading', 'grader-new.js'), '// new carve\n');
549
+ run('git add -A && git commit -q -m "main moves the tooling"');
550
+ publishMain(run);
551
+ run('git checkout -q feature');
552
+ assert.ok(!fs.existsSync(path.join(work, 'modules', 'grading', 'grader-new.js')), 'the new file is on main only');
553
+
554
+ const { result, out } = capturePreflight({ task_id: 4244, head_sha: null }, {});
555
+ assert.equal(result.ok, false);
556
+ assert.equal(result.reason, 'stale-tooling');
557
+ assert.match(out, /- scripts\/gds\/ship-land\.js/, 'a carved ship-*.js is tooling');
558
+ assert.match(out, /- modules\/grading\/grader-new\.js/, 'a file only main has is still matched');
559
+ assert.deepEqual(staleToolingFiles({ cwd: work }).sort(), ['modules/grading/grader-new.js', 'scripts/gds/ship-land.js']);
560
+ });
561
+ });
562
+
563
+ test('preflight (real git): the branch\'s OWN edit to a ship-*.js is not stale tooling', () => {
564
+ withPreflightRepo(({ work, run }) => {
565
+ fs.mkdirSync(path.join(work, 'scripts', 'gds'), { recursive: true });
566
+ fs.writeFileSync(path.join(work, 'scripts', 'gds', 'ship-land.js'), '// v1\n');
567
+ run('git add -A && git commit -q -m tooling');
568
+ publishMain(run);
569
+ run('git checkout -q -b feature');
570
+ fs.writeFileSync(path.join(work, 'scripts', 'gds', 'ship-land.js'), '// the fix this task ships\n');
571
+ run('git add -A && git commit -q -m "fix the land"');
572
+ const { result } = capturePreflight({ task_id: 4245, head_sha: null }, {});
573
+ assert.equal(result.ok, true);
574
+ assert.deepEqual(staleToolingFiles({ cwd: work }), []);
575
+ });
576
+ });
577
+
512
578
  // ---- runner ----
513
579
 
514
580
  let pass = 0;
@@ -118,6 +118,22 @@ t('a hosted origin says the core installs with no token, and never re-offers the
118
118
  assert.doesNotMatch(card, /\bprivate\b/i, 'must not call the package private');
119
119
  });
120
120
 
121
+ // task 1004288: the card above was corrected when the core went public, but the SKILL that
122
+ // routes the same fresh-clone wall kept teaching the old posture — "a PRIVATE package", "404s
123
+ // on any machine with no NPM_TOKEN", "the expected tokenless outcome". An agent that believes
124
+ // that reads a real 404 (a version never published, a mistyped name) as normal and moves on.
125
+ t('the builder-start skill agrees with the card: the core is public, and a 404 is not normal', () => {
126
+ const { readFileSync } = require('node:fs');
127
+ const path = require('node:path');
128
+ const skill = readFileSync(path.join(import.meta.dirname, '..', '.claude', 'skills', 'builder-start', 'SKILL.md'), 'utf8');
129
+ assert.doesNotMatch(skill, /PRIVATE package|private package/i, 'must not call @bongos/core a private package (ADR 0283)');
130
+ assert.doesNotMatch(skill, /expected tokenless outcome/i, 'must not present a tokenless 404 as expected');
131
+ assert.doesNotMatch(skill, /404s on any machine with no `?NPM_TOKEN/i, 'must not tell the agent a tokenless install 404s');
132
+ assert.match(skill, /PUBLIC npm package/, 'names the core as public');
133
+ assert.match(skill, /ADR 0283/, 'cites the decision that made it public');
134
+ assert.match(skill, /404 on `@bongos\/core` is therefore \*\*not\*\* a normal outcome/, 'says a 404 now means something is wrong');
135
+ });
136
+
121
137
  t('isLocalOrigin: loopback is local, everything else is remote', () => {
122
138
  for (const o of ['http://localhost:3000', 'http://app.localhost:3000', 'http://127.0.0.1:3000', 'http://[::1]:3000']) {
123
139
  assert.equal(isLocalOrigin(o), true, `${o} should be local`);
package/tests/upgrade.mjs CHANGED
@@ -1543,7 +1543,7 @@ t('persistPin: a refused origin push on a WIRED private instance is loud — nam
1543
1543
  assert.equal(r.refused, 'read-only-deploy-key');
1544
1544
  // The tokenless refusal is a plain sentence, not the bare status line.
1545
1545
  assert.match(r.error, /read-only deploy key at \/srv\/bongos\/acme\/\.git\/cloudbongos-deploy-key/, 'names the key');
1546
- assert.match(r.error, /no owner GitHub token is stored/, 'and why nothing else was tried');
1546
+ assert.match(r.error, /no owner GitHub token was available/, 'and why nothing else was tried');
1547
1547
  assert.notEqual(r.error, 'git push failed');
1548
1548
  const text = warnings.join('\n');
1549
1549
  assert.match(text, /PIN NOT PUSHED/, 'shouts');
@@ -1655,6 +1655,25 @@ t('resolvePinPushAuth: the owner token is spent only when one is stored AND the
1655
1655
  assert.equal(await u.resolvePinPushAuth(dir, { log: hush, instance: inst, getGithubToken: async () => { throw new Error('secret-box off'); } }, at('git@github.com:acme/widgets.git')), null);
1656
1656
  });
1657
1657
 
1658
+ t('resolvePinPushAuth: on a hosted project\u2019s own box a missing row SAYS WHY, instead of falling back silently (task 1004065)', async () => {
1659
+ // The project's own database has no provisioning table (ADR 0281), so the lookup
1660
+ // answers null there — and on a checkout wired with the read-only deploy key that is
1661
+ // not "the origin path", it is a push that cannot succeed.
1662
+ const dir = { instanceDir: '/srv/bongos/acme' };
1663
+ const wired = { config: { status: 0, stdout: 'ssh -F /dev/null -i /srv/bongos/acme/.git/cloudbongos-deploy-key -o IdentitiesOnly=yes\n' }, remote: { status: 0, stdout: 'git@github.com:acme/widgets.git\n' } };
1664
+ const said = [];
1665
+ const r = await u.resolvePinPushAuth(dir, { log: (m) => said.push(m), getInstanceBySlug: async () => null }, pinRun(wired));
1666
+ assert.equal(r, null, 'still the remote path — there is no token to use');
1667
+ const line = said.join('\n');
1668
+ assert.match(line, /no provisioning row is readable from this database/, 'names what is missing');
1669
+ assert.match(line, /read-only deploy key at \/srv\/bongos\/acme\/\.git\/cloudbongos-deploy-key/, 'and why the fallback cannot work');
1670
+ assert.match(line, /pushes the pin from the hub/, 'and where the push actually happens');
1671
+ // An UNWIRED checkout with no row (a laptop, a public instance) is the real origin path: quiet.
1672
+ const quiet = [];
1673
+ await u.resolvePinPushAuth(dir, { log: (m) => quiet.push(m), getInstanceBySlug: async () => null }, pinRun({ remote: wired.remote }));
1674
+ assert.deepEqual(quiet, [], 'no noise where the fallback is correct');
1675
+ });
1676
+
1658
1677
  t('runUpgrade --commit-pin: on a wired private instance the pin lands via the owner token, end to end', async () => {
1659
1678
  const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
1660
1679
  const src = mkdtempSync(join(tmpdir(), 'otb-upgrade-src-'));