@bongos/core 1.18.0 → 1.18.3

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 (113) hide show
  1. package/.bongos-core.json +132 -322
  2. package/README.md +1 -1
  3. package/config/branding.neutral.json +5 -1
  4. package/docs/adr/0142-project-context-is-prose-only.md +41 -0
  5. package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
  6. package/docs/adr/README.md +5 -2
  7. package/docs/api/openapi.json +1789 -450
  8. package/docs/api-reference.md +52 -5
  9. package/docs/file-map.md +7 -7
  10. package/docs/recipes/packaging-the-core.md +13 -0
  11. package/docs/recipes/upgrading-the-core.md +5 -2
  12. package/migrations/039_newcomer_friendly_column.sql +5 -0
  13. package/migrations/102_remove_box_first_provision_approval.sql +4 -0
  14. package/migrations/core_192_github_oauth_tokens.sql +33 -0
  15. package/modules/hall-ui/public/my-projects.html +78 -0
  16. package/modules/hall-ui/public/my-projects.js +142 -0
  17. package/modules/hall-ui/public/scouting.html +45 -0
  18. package/modules/hall-ui/public/scouting.js +73 -0
  19. package/modules/status-ui/public/explore.html +54 -0
  20. package/modules/status-ui/public/explore.js +67 -0
  21. package/package-lock.json +2 -2
  22. package/package.json +1 -1
  23. package/public-landing/create.html +211 -14
  24. package/scripts/gds/cli-lib.js +11 -5
  25. package/scripts/gds/lint-migrations.js +102 -0
  26. package/scripts/gds/login.js +42 -10
  27. package/scripts/gds/mirror-redact.js +5 -0
  28. package/scripts/gds/package-core.js +263 -4
  29. package/scripts/gds/provision.js +145 -7
  30. package/scripts/gds/publish-manifest.js +15 -0
  31. package/scripts/gds/run-unit-tests.js +38 -14
  32. package/scripts/gds/setup.js +41 -0
  33. package/scripts/gds/ship.js +115 -31
  34. package/scripts/gds/upgrade.js +147 -5
  35. package/scripts/migrate.sh +10 -0
  36. package/src/bongos/auth.js +362 -5
  37. package/src/bongos/db.js +66 -0
  38. package/src/bongos/github-repos.js +93 -0
  39. package/src/bongos/routes/auth.js +93 -11
  40. package/src/bongos/routes/github.js +90 -0
  41. package/src/bongos/routes/instance.js +15 -3
  42. package/src/bongos/routes.js +2 -0
  43. package/src/branding.js +17 -2
  44. package/src/instance-config.js +16 -0
  45. package/src/module-api.js +29 -1
  46. package/tests/branding.mjs +29 -23
  47. package/tests/github_repos_helpers.mjs +91 -0
  48. package/tests/github_token_store.mjs +121 -0
  49. package/tests/idp_device_exchange.mjs +105 -0
  50. package/tests/idp_federation.mjs +100 -0
  51. package/tests/migrations_forward_safe.mjs +86 -0
  52. package/tests/module_api.mjs +1 -1
  53. package/tests/package_core.mjs +246 -1
  54. package/tests/provision.mjs +157 -0
  55. package/tests/pruned_lockfile_integrity.mjs +79 -0
  56. package/tests/ship_ci_deploy.mjs +60 -0
  57. package/tests/upgrade.mjs +254 -5
  58. package/.gitignore +0 -91
  59. package/docs/adr/0139-platform-history-migration-to-cloudbongos.md +0 -94
  60. package/docs/adr/0140-cloudbongos-standalone-reprovision.md +0 -43
  61. package/scripts/hall-preview/fixtures/access-requests.json +0 -20
  62. package/scripts/hall-preview/fixtures/achievements.json +0 -76
  63. package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
  64. package/scripts/hall-preview/fixtures/audit-log.json +0 -68
  65. package/scripts/hall-preview/fixtures/blockers__detail.json +0 -12
  66. package/scripts/hall-preview/fixtures/box__me.json +0 -5
  67. package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -69
  68. package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
  69. package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
  70. package/scripts/hall-preview/fixtures/builders__roster.json +0 -48
  71. package/scripts/hall-preview/fixtures/goals.json +0 -502
  72. package/scripts/hall-preview/fixtures/goals__25.json +0 -96
  73. package/scripts/hall-preview/fixtures/goals__26.json +0 -67
  74. package/scripts/hall-preview/fixtures/goals__3.json +0 -103
  75. package/scripts/hall-preview/fixtures/goals__35.json +0 -49
  76. package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
  77. package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
  78. package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
  79. package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
  80. package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -32
  81. package/scripts/hall-preview/fixtures/inbox.json +0 -330
  82. package/scripts/hall-preview/fixtures/inbox__detail.json +0 -15
  83. package/scripts/hall-preview/fixtures/instance.json +0 -124
  84. package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
  85. package/scripts/hall-preview/fixtures/me.json +0 -476
  86. package/scripts/hall-preview/fixtures/me__art-key__own.json +0 -6
  87. package/scripts/hall-preview/fixtures/me__event-sound-prefs.json +0 -209
  88. package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
  89. package/scripts/hall-preview/fixtures/me__render.json +0 -5
  90. package/scripts/hall-preview/fixtures/me__skill-prefs.json +0 -143
  91. package/scripts/hall-preview/fixtures/me__sound-prefs.json +0 -67
  92. package/scripts/hall-preview/fixtures/me__wandering.json +0 -43
  93. package/scripts/hall-preview/fixtures/override-requests.json +0 -15
  94. package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
  95. package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
  96. package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
  97. package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
  98. package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
  99. package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
  100. package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
  101. package/scripts/hall-preview/fixtures/public__grades.json +0 -50
  102. package/scripts/hall-preview/fixtures/public__progress.json +0 -124
  103. package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
  104. package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
  105. package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
  106. package/scripts/hall-preview/fixtures/security__docs.json +0 -10
  107. package/scripts/hall-preview/fixtures/security__reports.json +0 -31
  108. package/scripts/hall-preview/fixtures/sessions__search.json +0 -9
  109. package/scripts/hall-preview/fixtures/tasks.json +0 -2420
  110. package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
  111. package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
  112. package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
  113. package/scripts/hall-preview/fixtures/versions.json +0 -89
package/tests/upgrade.mjs CHANGED
@@ -16,10 +16,11 @@ import { strict as assert } from 'node:assert';
16
16
  import { createRequire } from 'node:module';
17
17
  import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
18
18
  import { tmpdir } from 'node:os';
19
- import { join } from 'node:path';
19
+ import { join, dirname } from 'node:path';
20
20
 
21
21
  const require = createRequire(import.meta.url);
22
22
  const u = require('../scripts/gds/upgrade.js');
23
+ const pc = require('../scripts/gds/package-core.js'); // the packager — reuse its EXACT hash fns to build reference pins
23
24
 
24
25
  let passed = 0, failed = 0;
25
26
  const pending = [];
@@ -159,7 +160,9 @@ t('full: pre-flight → pin → install → migrate → materialize → restart
159
160
  let materialized = null;
160
161
  let regenArgs = null;
161
162
  const res = await u.runUpgrade(
162
- { to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'emersonian.service' },
163
+ // healthCheck:false — this test exercises the full pipeline, not the health poll (which would need
164
+ // a fetch mock); a managed-restart bump now requires a --health-url unless health is opted out (H9).
165
+ { to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'emersonian.service', healthCheck: false },
163
166
  {
164
167
  log: () => {}, err: () => {},
165
168
  run: fakeRun(calls, { dirty: '', installTo: '1.15.0', dir }),
@@ -398,7 +401,7 @@ t('runUpgrade --registry (full): pins the exact version + writes the env-fed .np
398
401
  const dir = scratchConsumer({ pinned: '1.16.0', installed: '1.16.0' });
399
402
  const calls = [];
400
403
  const res = await u.runUpgrade(
401
- { to: '1.17.0', registry: true, instance: dir, service: 'demo.service' },
404
+ { to: '1.17.0', registry: true, instance: dir, service: 'demo.service', healthCheck: false }, // H9: opt out (no health poll under test)
402
405
  {
403
406
  log: () => {}, err: () => {},
404
407
  run: fakeRun(calls, { dirty: '', installTo: '1.17.0', dir }),
@@ -479,7 +482,7 @@ t('rollback on migrate failure: reverts pin + reinstalls previous core + rolled_
479
482
  const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
480
483
  const calls = [], queries = [];
481
484
  const res = await u.runUpgrade(
482
- { to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service' },
485
+ { to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service', healthCheck: false }, // H9: opt out — this test is about migrate-failure rollback, not health
483
486
  {
484
487
  log: () => {}, err: () => {},
485
488
  run: pinAwareRun(calls, { dir, failMigrate: true }),
@@ -502,7 +505,7 @@ t('rollback on npm-install failure: reverts pin + reinstalls previous core', asy
502
505
  const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
503
506
  const calls = [], queries = [];
504
507
  const res = await u.runUpgrade(
505
- { to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service' },
508
+ { to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service', healthCheck: false }, // H9: opt out — this test is about install-failure rollback, not health
506
509
  {
507
510
  log: () => {}, err: () => {},
508
511
  run: pinAwareRun(calls, { dir, failInstallForVersion: '1.15.0' }),
@@ -547,6 +550,252 @@ t('restorePin: writes a previous dep string back verbatim (file: or registry)',
547
550
  assert.equal(JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).dependencies['@bongos/core'], '^1.16.0');
548
551
  });
549
552
 
553
+ // ---- 7. integrity-pin verification (task 1002216 / audit H8, finding F1) ----
554
+ // Build a reference pin exactly as the packager does — canonicalTreeHash over the pinned
555
+ // files' sha256, so a matching installed tree recomputes byte-identically.
556
+ function pinManifestFor(files, { tarballSha = null, tarballBytes = 0 } = {}) {
557
+ const meta = files
558
+ .map((f) => ({ path: f.path, mode: f.mode || '0000644', sha256: pc.sha256hex(Buffer.from(f.content)) }))
559
+ .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
560
+ const m = { artifact: 'bongos-core', tree_sha256: pc.canonicalTreeHash(meta), files: meta };
561
+ if (tarballSha) m.tarball = { name: 'bongos-core.tgz', sha256: tarballSha, bytes: tarballBytes };
562
+ return m;
563
+ }
564
+ function writeCoreTree(coreDir, files) {
565
+ for (const f of files) {
566
+ const abs = join(coreDir, ...f.path.split('/'));
567
+ mkdirSync(dirname(abs), { recursive: true });
568
+ writeFileSync(abs, f.content);
569
+ }
570
+ }
571
+ const PIN_FILES = [{ path: 'a.js', content: 'console.log(1)\n' }, { path: 'sub/b.json', content: '{"x":1}\n' }];
572
+
573
+ t('verifyInstalledPin: matching installed tree + tarball bytes → ok (both checks)', () => {
574
+ const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
575
+ const coreDir = join(dir, 'node_modules', '@bongos', 'core');
576
+ writeCoreTree(coreDir, PIN_FILES);
577
+ const tgzBytes = Buffer.from('deterministic-fake-tgz');
578
+ const tgz = join(dir, 'vendor', 'bongos-core-1.15.0.tgz');
579
+ mkdirSync(dirname(tgz), { recursive: true });
580
+ writeFileSync(tgz, tgzBytes);
581
+ const m = pinManifestFor(PIN_FILES, { tarballSha: pc.sha256hex(tgzBytes), tarballBytes: tgzBytes.length });
582
+ const res = u.verifyInstalledPin({ coreDir, referenceManifest: m, tgzPath: tgz });
583
+ assert.equal(res.ran, true);
584
+ assert.equal(res.ok, true, res.problems.join('; '));
585
+ assert.deepEqual(res.checks.map((c) => c.name).sort(), ['tarball.sha256', 'tree_sha256']);
586
+ });
587
+
588
+ t('verifyInstalledPin: a corrupted installed file → mismatch (ran, not ok)', () => {
589
+ const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
590
+ const coreDir = join(dir, 'node_modules', '@bongos', 'core');
591
+ writeCoreTree(coreDir, PIN_FILES);
592
+ const m = pinManifestFor(PIN_FILES);
593
+ writeFileSync(join(coreDir, 'a.js'), 'console.log(999) // tampered\n'); // corrupt AFTER pinning
594
+ const res = u.verifyInstalledPin({ coreDir, referenceManifest: m, tgzPath: null });
595
+ assert.equal(res.ran, true);
596
+ assert.equal(res.ok, false);
597
+ assert.ok(res.problems.some((p) => /content mismatch: a\.js/.test(p)), res.problems.join('; '));
598
+ });
599
+
600
+ t('verifyInstalledPin: a missing installed file → mismatch', () => {
601
+ const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
602
+ const coreDir = join(dir, 'node_modules', '@bongos', 'core');
603
+ writeCoreTree(coreDir, [PIN_FILES[0]]); // omit sub/b.json
604
+ const res = u.verifyInstalledPin({ coreDir, referenceManifest: pinManifestFor(PIN_FILES), tgzPath: null });
605
+ assert.equal(res.ok, false);
606
+ assert.ok(res.problems.some((p) => /missing installed file: sub\/b\.json/.test(p)), res.problems.join('; '));
607
+ });
608
+
609
+ t('verifyInstalledPin: tarball bytes differ from the pin → mismatch', () => {
610
+ const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
611
+ const coreDir = join(dir, 'node_modules', '@bongos', 'core');
612
+ writeCoreTree(coreDir, PIN_FILES);
613
+ const tgz = join(dir, 'vendor', 'bongos-core-1.15.0.tgz');
614
+ mkdirSync(dirname(tgz), { recursive: true });
615
+ writeFileSync(tgz, Buffer.from('the-actual-bytes'));
616
+ const m = pinManifestFor(PIN_FILES, { tarballSha: pc.sha256hex(Buffer.from('DIFFERENT-bytes')), tarballBytes: 9 });
617
+ const res = u.verifyInstalledPin({ coreDir, referenceManifest: m, tgzPath: tgz });
618
+ assert.equal(res.ok, false);
619
+ assert.ok(res.problems.some((p) => /tarball bytes/.test(p)), res.problems.join('; '));
620
+ });
621
+
622
+ t('verifyInstalledPin: no pin in the reference manifest → ran:false (nothing to check)', () => {
623
+ const res = u.verifyInstalledPin({ coreDir: '/nonexistent', referenceManifest: {}, tgzPath: null });
624
+ assert.equal(res.ran, false);
625
+ assert.equal(res.ok, false);
626
+ });
627
+
628
+ t('resolveReferenceManifest: prefers the committed vendored side-car (strong)', () => {
629
+ const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
630
+ const coreDir = join(dir, 'node_modules', '@bongos', 'core');
631
+ mkdirSync(join(dir, 'vendor'), { recursive: true });
632
+ writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.manifest.json'), JSON.stringify(pinManifestFor(PIN_FILES)));
633
+ writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'x');
634
+ const ref = u.resolveReferenceManifest({ instanceDir: dir, coreDir, targetVersion: '1.15.0' });
635
+ assert.equal(ref.source, 'vendored side-car manifest');
636
+ assert.equal(ref.strong, true);
637
+ assert.ok(ref.tgzPath && existsSync(ref.tgzPath));
638
+ });
639
+
640
+ t('resolveReferenceManifest: falls back to the installed inner manifest (not strong)', () => {
641
+ const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
642
+ const coreDir = join(dir, 'node_modules', '@bongos', 'core');
643
+ mkdirSync(coreDir, { recursive: true });
644
+ writeFileSync(join(coreDir, '.bongos-core.json'), JSON.stringify(pinManifestFor(PIN_FILES)));
645
+ const ref = u.resolveReferenceManifest({ instanceDir: dir, coreDir, targetVersion: '1.15.0' });
646
+ assert.equal(ref.source, 'installed inner manifest');
647
+ assert.equal(ref.strong, false);
648
+ assert.equal(ref.tgzPath, null);
649
+ });
650
+
651
+ // A spawn shim whose `npm install` writes an installed core tree (+ version) so the
652
+ // real pin-verify step can run against it. `files` is what actually lands on disk.
653
+ function installWritingTree(calls, { dir, version, files }) {
654
+ return (cmd, args) => {
655
+ calls.push(`${cmd} ${(args || []).join(' ')}`.trim());
656
+ if (cmd === 'git' && args[0] === 'status') return { status: 0, stdout: '' };
657
+ if (cmd === 'npm' && args[0] === 'install') {
658
+ const coreDir = join(dir, 'node_modules', '@bongos', 'core');
659
+ writeCoreTree(coreDir, files);
660
+ writeFileSync(join(coreDir, 'package.json'), JSON.stringify({ name: '@bongos/core', version }) + '\n');
661
+ }
662
+ return { status: 0 };
663
+ };
664
+ }
665
+
666
+ t('runUpgrade: a matching pin passes verification (upgrade proceeds)', async () => {
667
+ const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
668
+ mkdirSync(join(dir, 'vendor'), { recursive: true });
669
+ writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'fake-tgz');
670
+ writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.manifest.json'),
671
+ JSON.stringify(pinManifestFor(PIN_FILES, { tarballSha: pc.sha256hex(Buffer.from('fake-tgz')), tarballBytes: 8 })));
672
+ const logs = [];
673
+ const res = await u.runUpgrade(
674
+ { to: '1.15.0', instance: dir },
675
+ {
676
+ log: (m) => logs.push(m), err: () => {},
677
+ run: installWritingTree([], { dir, version: '1.15.0', files: PIN_FILES }),
678
+ materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
679
+ }
680
+ );
681
+ assert.equal(res.ok, true, res.error);
682
+ assert.ok(logs.some((m) => /integrity pin verified.*tarball\.sha256.*tree_sha256/.test(m)), logs.join('\n'));
683
+ });
684
+
685
+ t('runUpgrade: a pin MISMATCH after install fails and auto-rolls-back', async () => {
686
+ const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
687
+ mkdirSync(join(dir, 'vendor'), { recursive: true });
688
+ writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'fake-tgz');
689
+ // The reference pin is for PIN_FILES, but install writes DIFFERENT content → tree mismatch.
690
+ writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.manifest.json'), JSON.stringify(pinManifestFor(PIN_FILES)));
691
+ const corrupted = [{ path: 'a.js', content: 'console.log(666)\n' }, { path: 'sub/b.json', content: '{"x":1}\n' }];
692
+ const res = await u.runUpgrade(
693
+ { to: '1.15.0', instance: dir },
694
+ {
695
+ log: () => {}, err: () => {},
696
+ run: installWritingTree([], { dir, version: '1.15.0', files: corrupted }),
697
+ materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
698
+ }
699
+ );
700
+ assert.equal(res.ok, false);
701
+ assert.ok(/integrity pin MISMATCH/.test(res.error || ''), res.error);
702
+ assert.equal(res.rolledBack, true);
703
+ assert.equal(u.readPinnedCoreVersion(dir), '1.14.1', 'pin reverted to the previous version on rollback');
704
+ });
705
+
706
+ t('runUpgrade: --require-pin makes a MISSING reference manifest fatal', async () => {
707
+ const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
708
+ mkdirSync(join(dir, 'vendor'), { recursive: true });
709
+ writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'fake-tgz'); // tgz present, but NO manifest
710
+ const res = await u.runUpgrade(
711
+ { to: '1.15.0', instance: dir, requirePin: true },
712
+ {
713
+ log: () => {}, err: () => {},
714
+ run: installWritingTree([], { dir, version: '1.15.0', files: PIN_FILES }),
715
+ materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
716
+ }
717
+ );
718
+ assert.equal(res.ok, false);
719
+ assert.ok(/no reference manifest/.test(res.error || ''), res.error);
720
+ assert.equal(res.rolledBack, true, 'unverifiable pin rolls back under --require-pin');
721
+ });
722
+
723
+ t('runUpgrade: --skip-pin-verify bypasses the check (no manifest, still succeeds)', async () => {
724
+ const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
725
+ mkdirSync(join(dir, 'vendor'), { recursive: true });
726
+ writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'fake-tgz');
727
+ const res = await u.runUpgrade(
728
+ { to: '1.15.0', instance: dir, skipPinVerify: true },
729
+ {
730
+ log: () => {}, err: () => {},
731
+ run: installWritingTree([], { dir, version: '1.15.0', files: PIN_FILES }),
732
+ materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
733
+ }
734
+ );
735
+ assert.equal(res.ok, true, res.error);
736
+ });
737
+
738
+ // ---- 8. mandatory post-restart health check (task 1002222 / audit H9, finding F5) ----
739
+ t('runUpgrade: a managed-restart bump WITHOUT --health-url is refused up front (H9 mandatory)', async () => {
740
+ const src = mkdtempSync(join(tmpdir(), 'src-'));
741
+ writeFileSync(join(src, 'bongos-core-1.15.0.tgz'), 'TGZ');
742
+ const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
743
+ const calls = [];
744
+ const res = await u.runUpgrade(
745
+ { to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service' }, // restarts, no --health-url, not opted out
746
+ { log: () => {}, err: () => {}, run: fakeRun(calls, { dirty: '', installTo: '1.15.0', dir }), materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }) }
747
+ );
748
+ assert.equal(res.ok, false);
749
+ assert.match(res.error, /health check is mandatory/);
750
+ assert.equal(u.readPinnedCoreVersion(dir), '1.14.1', 'refused in pre-flight → pin untouched');
751
+ assert.ok(!calls.some((c) => /npm install/.test(c)), 'refused before any install');
752
+ });
753
+
754
+ t('runUpgrade: --no-health-check lets a managed-restart bump proceed without a URL', async () => {
755
+ const src = mkdtempSync(join(tmpdir(), 'src-'));
756
+ writeFileSync(join(src, 'bongos-core-1.15.0.tgz'), 'TGZ');
757
+ const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
758
+ const logs = [];
759
+ const res = await u.runUpgrade(
760
+ { to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service', healthCheck: false },
761
+ { log: (m) => logs.push(m), err: () => {}, run: fakeRun([], { dirty: '', installTo: '1.15.0', dir }), materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }) }
762
+ );
763
+ assert.equal(res.ok, true, res.error);
764
+ assert.ok(logs.some((m) => /health check skipped \(--no-health-check\)/.test(m)), logs.join('\n'));
765
+ });
766
+
767
+ t('runUpgrade: a managed-restart bump WITH a healthy --health-url confirms + succeeds', async () => {
768
+ const src = mkdtempSync(join(tmpdir(), 'src-'));
769
+ writeFileSync(join(src, 'bongos-core-1.15.0.tgz'), 'TGZ');
770
+ const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
771
+ const logs = [];
772
+ const res = await u.runUpgrade(
773
+ { to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service', healthUrl: 'http://x/healthz' },
774
+ {
775
+ log: (m) => logs.push(m), err: () => {}, run: fakeRun([], { dirty: '', installTo: '1.15.0', dir }),
776
+ materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
777
+ fetch: async () => ({ ok: true, status: 200 }), sleep: async () => {}, now: () => 0,
778
+ }
779
+ );
780
+ assert.equal(res.ok, true, res.error);
781
+ assert.equal(res.healthOk, true);
782
+ assert.ok(logs.some((m) => /health http:\/\/x\/healthz → 200/.test(m)), logs.join('\n'));
783
+ });
784
+
785
+ t('runUpgrade: no managed restart + no --health-url proceeds but flags the bump as unconfirmed', async () => {
786
+ const src = mkdtempSync(join(tmpdir(), 'src-'));
787
+ writeFileSync(join(src, 'bongos-core-1.15.0.tgz'), 'TGZ');
788
+ const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
789
+ const logs = [];
790
+ const res = await u.runUpgrade(
791
+ { to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir }, // no service/restart-cmd, no --health-url
792
+ { log: (m) => logs.push(m), err: () => {}, run: fakeRun([], { dirty: '', installTo: '1.15.0', dir }), materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }) }
793
+ );
794
+ assert.equal(res.ok, true, res.error);
795
+ assert.equal(res.healthOk, null);
796
+ assert.ok(logs.some((m) => /not confirmed serving/.test(m)), logs.join('\n'));
797
+ });
798
+
550
799
  await Promise.all(pending);
551
800
  console.log(`\nupgrade: ${passed} passed, ${failed} failed`);
552
801
  process.exit(failed ? 1 : 0);
package/.gitignore DELETED
@@ -1,91 +0,0 @@
1
- # Dependencies
2
- node_modules/
3
-
4
- # Per-session git worktrees live under here; each is itself a git checkout
5
- # and must NEVER be committed (would create embedded-repo references).
6
- .claude/worktrees/
7
-
8
- # Environment / secrets
9
- .env
10
- .env.local
11
- .env.*.local
12
-
13
- # Google Chat OAuth credentials — must NEVER be committed.
14
- # Live on the droplet at /etc/amazonprimea/ instead.
15
- google-chat-credentials.json
16
- google-chat-refresh-token.json
17
- *.credentials.json
18
-
19
- # Build output
20
- dist/
21
- build/
22
-
23
- # Logs
24
- *.log
25
- npm-debug.log*
26
-
27
- # OS files
28
- .DS_Store
29
-
30
- # Pixel art generation artifacts (iterations are huge; keep references + finals only)
31
- modules/art-pipeline/iterations/*
32
- # ...but keep the anti-grid FFT baselines tracked (idea 27): they're tiny + the smoke suite
33
- # (tests/anti_grid.mjs) compares against them. `modules/art-pipeline/iterations/*` (not the bare
34
- # dir) lets this negation re-include the file — git can't re-include under a fully-excluded dir.
35
- !modules/art-pipeline/iterations/anti_grid_baselines.json
36
- modules/art-pipeline/generated/raw/
37
- modules/art-pipeline/generated/*/raw/
38
- # Derived from references/ via slice_references.py — easy to regenerate, no need in git
39
- modules/art-pipeline/template/ref_tiles/
40
- modules/art-pipeline/template/ref_clusters/
41
- # Autotile cross-verify snapshot — regenerated on every smoke_test run.
42
- modules/art-pipeline/pipeline/_autotile_tables.json
43
- __pycache__/
44
- *.pyc
45
-
46
-
47
- # World snapshots — generated on the droplet by scripts/snapshots/render-world.mjs.
48
- # Untracked so git reset --hard on deploy doesn't wipe them.
49
- public-status/snapshots/*.png
50
- public-status/snapshots/manifest.json
51
-
52
- # Repo Atlas data — generated on the droplet by scripts/gds/gen-atlas.js on every
53
- # deploy (ADR 0094). Untracked so git reset --hard doesn't wipe it; the deploy
54
- # step rewrites it fresh each time, so a committed copy would only go stale.
55
- # (The hall surface carved to modules/hall-ui/public/ in BV1.R85.)
56
- modules/hall-ui/public/atlas.json
57
-
58
- # Whole-file generated navigation docs — untracked since ADR 0110 (task 1926), the
59
- # same reasoning as the atlas above. docs/repo-map.md (symbol skeleton, ADR 0063) and
60
- # docs/session-log-index.md (session index, ADR 0062 §8) are 100%-generated and were
61
- # regenerated + committed on EVERY ship, so two parallel builder branches collided on
62
- # them constantly. Untracked + regenerated at deploy (scripts/deploy/deploy-prod.sh) and
63
- # at box-source-fetch, they can never appear in a branch diff → the conflict class is
64
- # gone at the source. Their generators are dual-duty: the nested CLAUDE.md symbol blocks
65
- # and the CLAUDE.md §13 snippet stay TRACKED and CI-gated (gen-*.js --check-block).
66
- docs/repo-map.md
67
- docs/session-log-index.md
68
-
69
- # Onboarding diagram RENDERS — untracked since ADR 0110 pt.3 / ADR 0122 (task 2027),
70
- # the deferred diagram half of ADR 0110. The .svg/.png are regenerated + committed on
71
- # every render-capable ship, so parallel builder branches collided on them (same class
72
- # as the nav docs above). Untracked + re-rendered at deploy (scripts/deploy/deploy-prod.sh
73
- # runs scripts/render-diagrams.sh against the tracked .mmd, using the host's installed
74
- # Chrome — never puppeteer's own droplet-stalling download) and best-effort at
75
- # box-source-fetch. The .mmd SOURCES + assertions.json stay TRACKED (git reset --hard
76
- # restores them; the render regenerates the images from them), so the nightly
77
- # diagram-drift cron (reads assertions.json) is unaffected.
78
- docs/onboarding/diagrams/*.svg
79
- docs/onboarding/diagrams/*.png
80
-
81
- # Re-hosted #bugs screenshots (task #936). Downloaded from Discord and served
82
- # by routes/bug-attachments.js. Outside git so a deploy never wipes them.
83
- var/bug-attachments/
84
-
85
- # Custom merge-driver scratch files. The regen merge driver
86
- # (scripts/gds/git-merge-regen.js, installed by scripts/gds/install-git-hooks.js)
87
- # writes `.merge_file_*` temp files while resolving conflicts. They are pure
88
- # scratch — never source — but a `git add -A` once staged one onto main
89
- # (.merge_file_IKo9KW, merge 3e9f1f81). Ignore the pattern so it can't recur.
90
- .merge_file_*
91
- scratchpad/
@@ -1,94 +0,0 @@
1
- # ADR 0139 — Migrate the platform work-history from the Off the Boats instance DB to the cloudbongos DB
2
-
3
- - **Status:** Accepted — **MIGRATION COMPLETE 2026-07-11** (owner greenlit the production run + the cut). Load into cloudbongos + verification done; TIER-2 valuable-history sweep done; and the WS-6 clean cut **fired + verified on live amazonprimea** (now game-only). The CB↔OTB split is complete (code + data). See the **Execution record** at the end.
4
- - **Date:** 2026-07-10
5
- - **Track:** internal (platform)
6
- - **Decides:** whether — and how — the Cloud Bongos *platform* work-history (tasks, goals, done-when criteria, the credit ledger, builder identities + balances) is physically relocated from the `amazonprimea` (Off the Boats "customer zero") Postgres database to the `cloudbongos` database, so a builder signing into the cloudbongos hall with the same GitHub sees their earned balance.
7
- - **Builds on:** ADR 0103 (core-first extraction; cloud-bongos is the trunk), ADR 0108 (configurable-root; the code half), ADR 0100 §3 (per-repo GDS), ADR 0016 (server-enforced permissions / trust boundary).
8
- - **Supersedes framing of:** ADR 0107 §3, which *deferred* cross-instance identity/payout as an open question, and the goal-41 "identity federated, authority per-project" design string. This ADR is the deliberate, bounded decision those left open — see Context.
9
- - **Companion:** the **code half** of the CB↔OTB split (amazonprimea task 1728 / R89 cutover; cloud-bongos re-seeded to the authoritative source by amazonprimea task 2190). This ADR is the **data half**.
10
-
11
- ## Context
12
-
13
- Off the Boats (`amazonprimea` DB) is where Cloud Bongos itself was built — "customer zero." The R89 cutover already moved the platform **code** out: amazonprimea now consumes `@bongos/core` as a vendored dependency, and cloud-bongos is the authoritative editable core source. What remains inside the `amazonprimea` database is the platform's entire **work-history**: ~2,000+ platform tasks/goals/criteria, the credit ledger, and the builder identities that earned those credits — interleaved with the game's own ~57 tasks and game-earned credits.
14
-
15
- The requirement: a builder who signs into the cloudbongos hall with the same GitHub account should see the platform balance they earned. The platform's *own* design deliberately drew a line against this — goal-41's design says "identity is federated, authority (ranks/credits) is **not**," and ADR 0107 §3 explicitly defers cross-instance payout. So **relocating balances runs counter to that line and therefore needs its own decision record** — this ADR. It is *not* the future federation hub (goal 41), and it is not "finishing" an existing plan; it is a one-time, supervised data move.
16
-
17
- No cross-instance data-mover exists today. The only tooling is per-instance `pg_dump` backup; provisioning creates an *empty* DB; the public-mirror export moves *source code*, not data. The mover (WS-2) is therefore bespoke and is the bulk of the effort.
18
-
19
- ## Decision
20
-
21
- ### Locked decisions (owner)
22
-
23
- 1. **Clean move, not copy.** The platform history *leaves* Off the Boats. No frozen dual copy — two diverging ledgers would double-count balances and rot.
24
- 2. **Game credits stay at Off the Boats.** Everything else — platform credits, including orphan/no-task session & bonus awards — moves.
25
- 3. **Identity is keyed by `builders.github_id`** (a stable global value present in both DBs). The migrated balance surfaces automatically on the builder's *existing* per-instance GitHub sign-in — **no federation hub is required**.
26
- 4. **Currency: the number moves, the label adapts.** The reward unit is the integer `credits`; the label ("drachmae" at OTB, "credits" at the neutral starter) is pure instance config (`branding.currency.label`, ADR-referenced `<redacted>`). A 1,000 balance arrives as 1,000 in cloudbongos's unit automatically. *(Open: whether cloudbongos relabels to "drachmae" — see Open Questions.)*
27
- 5. **The cloudbongos DB is near-empty → clean load, not a merge.** Only `lars589` (auto-seated founding Archon) and one CB-V1 test task exist; preserve them.
28
- 6. **The ETL runs one-time, supervised, from the owner's Mac over SSH**, reaching both droplets' databases. It is not a builder-facing tool.
29
-
30
- ### Classification is decided at the version level
31
-
32
- Every task/goal/criterion/credit inherits its **version's** label. `versions.id` is a text PK, so version rows copy without renumbering.
33
-
34
- | MOVE → cloudbongos (platform) | STAY at Off the Boats (game) |
35
- |---|---|
36
- | PMS-V1, PMS-V2, GDS-V3, GDS-V4 → BONGOS-V1 and all their tasks/goals/criteria/credits | Off the Boats V1/V2/V3 and their tasks/goals/pixel-art |
37
- | The dependency graph among those records | Credits earned on game work |
38
- | A copy of each builder identity (keyed by github_id) carrying the platform balance | The live game world + Colyseus server (untouched) |
39
-
40
- Edge cases: work that *built* the art pipeline = platform (moves); *running* it to draw tiles = game (stays) — WS-1 includes a human review pass. Orphan credits (`task_id = NULL`) move with the platform (reversible if the owner disagrees).
41
-
42
- ## Workstreams (filed under switchover goal 26; execution owner-gated)
43
-
44
- - **WS-0 — Re-provision cloudbongos.com onto core 1.17.3 on a fresh DB, FIRST.** The box still runs a lift-and-shift clone whose deploy script references R89-removed paths; a re-provision *after* a load would wipe it. Must precede any data load. Preserve/re-seat the `lars589` Archon.
45
- - **WS-1 — Label every version game vs platform** (the single line that decides the move-set) + human pass over art-pipeline-style edge cases.
46
- - **WS-2 — Build the one-time mover (most effort).** Renumber bigint PKs; match builders by `github_id`; remap every FK; handle the polymorphic `dependencies` table per-kind; load with sync/promotion triggers OFF; preserve the `credit_log` idempotency key. **Prove on a throwaway copy first — never run blind on live.**
47
- - **WS-3 — This ADR.**
48
- - **WS-4 — Move day (OWNER GREENLIGHT REQUIRED).** Brief platform-write freeze at amazonprimea; full DB snapshots of both sides; run the mover into cloudbongos; rebuild balances from the ledger. Still reversible — amazonprimea untouched until WS-6.
49
- - **WS-5 — Prove it.** Reconcile record counts both sides; sample balances to the drachma; and the real test — a live GitHub sign-in at cloudbongos shows the right person with the right balance.
50
- - **WS-6 — Clean cut at Off the Boats.** Only after WS-5: delete the moved records from amazonprimea (reverse FK/dependency order), rebuild amazonprimea balances to game-only, repoint platform doc-links to cloudbongos.
51
- - **WS-7 — Close the switchover.** Tie off the goal; tell builders where their history now lives.
52
-
53
- ## DB mechanics (the production hazards the mover must respect)
54
-
55
- - **Schema lives in `@bongos/core`** under `package/migrations/*.sql` (not in the amazonprimea repo anymore). `003_pms.sql` is foundational.
56
- - **Bigserial PK collision.** Both DBs allocate 1,2,3…; a naive copy collides on PK and *silently mis-links* FKs. Collide: `builders/tasks/claims/goals/credit_log/cost_log/karma_log/done_when_criteria/builder_sessions/idea_inbox/learnings/goal_membership_requests/core_upgrades`. Only text/natural PKs copy cleanly (`versions`, `achievements`). People are re-keyed off the natural `builders.github_id` (UNIQUE); everything else gets a fresh offset, and **every FK is rewritten** to the remapped ids.
57
- - **Polymorphic `dependencies` table — highest risk.** `(from_kind, from_id, to_kind, to_id)` over {task, goal, criterion} has **NO foreign keys** — ids are bare bigints disambiguated only by `*_kind`. The mover must remap `from_id`/`to_id` **per-kind**, or edges silently repoint. (The older `020_task_dependencies` task→task list also needs remapping.)
58
- - **`credit_log` double-count trap.** `credit_log` is an append-only ledger; `builders.total_credits` is a trigger-maintained cache (`trg_credit_log_apply` → `+= delta` on INSERT). Authoritative balance = `SUM(credit_log.delta)` per builder. **Load with triggers OFF, then rebuild `total_credits` from `SUM(delta)`** — else balances double. Same shape for `karma_log` → `builders.karma`.
59
- - **Idempotency key must be preserved.** A partial unique index on `credit_log(session_id) WHERE reason='session.token_reward'` enforces one token-reward payout per session. If the index or `session_id` values are lost, the token-reward path **double-pays on replay**.
60
- - **Promotion triggers also fire on status writes** (auto-promote backlog→ready on ship/satisfy/achieve). Load with all triggers disabled, then re-enable.
61
-
62
- ## Consequences
63
-
64
- - **Positive:** the platform's work-history + rewards live at their true home; builders keep their earned balance across the sign-in they already use; the CB↔OTB separation is completed (data + code); no standing federation infrastructure is incurred.
65
- - **Reversibility:** everything through WS-5 is reversible — amazonprimea is untouched until WS-6, and both sides are snapshotted on move day. The point of no return is WS-6 (the clean cut).
66
- - **Risk:** a mistimed trigger or a missed FK/idempotency remap corrupts balances. Mitigation: prove the mover on a throwaway clone (WS-2), triggers-off load + ledger rebuild (WS-4), and full reconciliation before the cut (WS-5).
67
-
68
- ## Open questions (owner)
69
-
70
- 1. Confirm the exact game/platform version line (the single decision that fixes the move-set).
71
- 2. Currency label at cloudbongos — keep "credits" or relabel to "drachmae" (one-line config; balances identical either way).
72
- 3. Confirm tracking home for WS-0…WS-7 (proposed: switchover goal 26).
73
- 4. Greenlight to *execute* (WS-4 onward) — nothing runs against production until then.
74
- 5. WS-0 reality check — confirm cloudbongos.com was fully re-provisioned onto core 1.17.3 on a fresh DB, not merely code-updated.
75
- 6. Orphan credits (`task_id = NULL`) — confirm the "move with platform" rule.
76
-
77
- ### Open questions — RESOLVED (owner, 2026-07-11)
78
-
79
- 1. Version line: **game V1/V2/V3 stay; PMS-V1, PMS-V2, GDS-V3, GDS-V4, BONGOS-V1 move** (as tabled). 2. Currency: **keep label "credits" at cloudbongos, 1:1 with drachmae** (no relabel). 3. Tracking home: **cloudbongos**. 4. **Greenlight given** — execute through production. 5. WS-0 reality check delegated to the executor → **verified: cloudbongos.com already runs @bongos/core 1.17.4 on the dependency model; no re-provision performed** (would have wiped legit native data). 6. Orphan credits: **move with platform** (confirmed).
80
-
81
- ## Execution record (2026-07-11)
82
-
83
- Executed from the cloudbongos droplet (the ETL host): reads amazonprimea via the live `cohost-db-tunnel` (`127.0.0.1:5433`), writes the local cloudbongos DB. Scripts + full snapshots persist at `/home/lars/mig/` (`mover.js`, `verify.js`, `cut.js`, both pg_dumps, a pre-load cloudbongos snapshot). Tracked as amazonprimea switchover tasks 2192–2198.
84
-
85
- - **WS-0 (2192):** verified already-provisioned (core 1.17.4); no re-provision.
86
- - **WS-1 (2193):** per-table move-set classified (14 platform-history tables; ~50 OTB-instance-infra tables correctly stay).
87
- - **WS-2 (2194):** mover built (Node ETL: +1,000,000 PK offset; builders merged by `github_id`; versions upsert; deletes the target's core-seeded placeholder platform goals/criteria first; triggers-off load then rebuild `total_credits`/`karma` from `SUM(delta)`; `credit_log` `ON CONFLICT` on the token-reward partial index `DO NOTHING`). **Rehearsed on a throwaway copy: 25/25 checks pass.**
88
- - **WS-4 (2195):** **live load into cloudbongos COMMITTED** — 1,865 tasks, 27 goals, 69 criteria, 1,678 claims, 1,373 credit rows (1 token-reward idempotency skip), + full history; FK-check clean. Pre-load snapshot taken; amazonprimea untouched (fully reversible).
89
- - **WS-5 (2196):** **verified on live cloudbongos — 25/25.** All table counts + all six builder balances reconcile (lars589 **37,072**; jaxri 5,341; scootermack 2,389; IAMMASTERQUA 195; LarsCode 44; kingaonline 36). Game/native preserved; 0 FK orphans. `cloudbongos.com` leaderboard serves the migrated builders.
90
- - **WS-5b (2199) — TIER-2 valuable sweep (post-audit):** a full 59-table completeness audit confirmed the core move was 100% complete (2 flagged gaps were false positives), and surfaced ~13k deferred TIER-2 rows. Owner chose the valuable sweep → `sweep2.js` carried builder_memory, grading history, session_records/BFG, vulnerability_reports, builder_sessions (tokens redacted; restored 1,442 nulled claim links), + governance rows to cloudbongos; **verified 18/18**. Only the rebuildable `search_chunks` retrieval index was intentionally skipped.
91
- - **WS-6 (2197) — clean cut: FIRED + verified on live amazonprimea.** Pre-fire review found the first-draft cut too aggressive (it would delete live-game learnings/ideas/blockers), so it was rewritten **surgical** (`cut2.js`): deletes only platform-version-scoped work + the platform/orphan credit ledger, and preserves game + unlinked methodology, karma, builder-level tables, and all auth/game data. Run as the non-superuser app user (no trigger-disable) with children-before-parents + FK enforcement (safer). Rehearsed clean on a fresh copy, then fired after a final backup. Result: amazonprimea is game-only (versions V1/V2/V3, 157 game tasks, 123 game claims + auth intact, lars589 38,919→**1,824**, ledger reconciles, 0 platform residue, 0 FK orphans, app healthy).
92
- - **WS-7 (2198):** data + cut complete. Sole remaining owner step: announce to builders (Discord) where their history now lives.
93
-
94
- **Notes:** the migration's own bookkeeping tasks (2192–2199, all BONGOS-V1) moved to cloudbongos and were then removed from amazonprimea by the cut — self-referential, expected; their record lives here + in session memory. The 56-credit token-reward for the shared session `1b57dcb4` is dropped by the idempotency skip. Minor residue intentionally kept on amazonprimea (harmless, also on cloudbongos): platform karma, unlinked ideas/learnings, and vuln/achievement rows with nulled platform refs. Doc-links to platform tasks in the OTB repo now resolve on the cloudbongos hall. Scripts + backups: `/home/lars/mig/` on the cloudbongos droplet.
@@ -1,43 +0,0 @@
1
- # ADR 0140 — cloudbongos.com re-provisioned to the standalone model, DB-preserving (closes ADR 0139 WS-0)
2
-
3
- - **Status:** Accepted — executed 2026-07-12
4
- - **Date:** 2026-07-12
5
- - **Deciders:** Lars (owner)
6
- - **Track:** internal (platform / infra)
7
- - **Builds on:** ADR 0108 (configurable-root / standalone consumer model), ADR 0126 (dedicated cloudbongos.com droplet), ADR 0129 (pull-timer deploy), ADR 0139 (CB↔OTB data migration — this closes its WS-0)
8
-
9
- ## Context
10
-
11
- cloudbongos.com ran as a **lift-and-shift clone of the `amazonprimea` game repo** (ADR 0126): the `cloudbongos` systemd service executed `src/platform-server.js` out of `/home/lars/cloudbongos` (a checkout whose `origin` was `lars589/amazonprimea`), branded via `GDS_BRANDING_FILE`, against the separate `cloudbongos` DB.
12
-
13
- ADR 0139's **WS-0** called for re-provisioning cloudbongos.com onto the ADR 0108 standalone `@bongos/core` model *first, on a fresh DB*. WS-0 was recorded as "verified already-provisioned; no re-provision performed" — but that verification was wrong: the box kept running the amazonprimea clone. The data-half workstreams (WS-4/5/6) then loaded the platform history into the `cloudbongos` DB and cut OTB.
14
-
15
- The gap surfaced on 2026-07-12: `builders.cloudbongos.com` served a bare, unstyled shell — every `/builders/*` asset 404'd. Root cause: the running process was frozen on stale amazonprimea code (its pull-deploy silently no-op'd because `origin` tracked the unchanged amazonprimea repo, not a cloudbongos instance). The site had **never run the restored code**.
16
-
17
- The key constraint that inverts WS-0's original plan: the `cloudbongos` DB is now **full** — the migrated work-history and credit balances (e.g. `lars589` = 37,072). WS-0's "fresh DB" re-provision would wipe it. So the re-provision had to be **DB-preserving**.
18
-
19
- ## Decision
20
-
21
- We will convert cloudbongos.com to a proper ADR 0108 **standalone instance**, preserving the existing `cloudbongos` DB, via a **build-alongside-then-flip** procedure:
22
-
23
- 1. **Its own instance repo** — `cloudbongos/cloudbongos-instance` (private): lean `config/` (`branding.json` = the real cloudbongos.com domains over the neutral "Cloud Bongos" brand; `modules.json` = game-less + `provisioning`), a `bongos.config.json` instance-root pointer, and a **pinned `@bongos/core@1.17.3`** dependency. The engine, hall, and all platform code come from the package.
24
- 2. **Materialize beside the old checkout** — clone + `npm ci` into a new dir (`/home/lars/cloudbongos-instance`); the old `/home/lars/cloudbongos` stays untouched as rollback.
25
- 3. **Prove before flipping** — boot on a spare port against the live DB; confirm the hall serves and the leaderboard reads migrated balances; confirm `migrate` is a **no-op** (the DB already carries all 112 of 1.17.3's migrations).
26
- 4. **Flip the service** — `cloudbongos.service` `WorkingDirectory` → the instance, `ExecStart` → `node_modules/@bongos/core/src/platform-server.js`; drop `GDS_BRANDING_FILE`; add `CLOUDBONGOS_INSTANCE_ROOT`; keep `PGDATABASE` + OAuth via `/etc/cloudbongos/web.env`. Back up the old unit first.
27
- 5. **Repoint the ADR 0129 pull-deploy** — rewrite `/home/lars/deploy.sh` to fetch/reset the instance repo → `npm ci` (lockfile-gated) → core-package `migrate` → restart → healthz.
28
-
29
- The engine **source stays authoritative at `lars589/cloud-bongos`** (not relocated); the instance consumes `@bongos/core` from npm. A **verified DB backup precedes all changes**, and the old checkout + old unit + old deploy script are retained for instant rollback.
30
-
31
- ## Alternatives considered
32
-
33
- - **Run cloudbongos.com directly from the `cloud-bongos` engine repo.** Rejected: it forces one host's identity into the shared/authoritative engine (the very anti-pattern that entangled cloudbongos with the amazonprimea repo), and it would ride the engine's development `main` with no stable pin — any work-in-progress commit could take the live site down.
34
- - **Keep the amazonprimea lift-and-shift; just restart / repoint that checkout.** Rejected: leaves the intended standalone model unfinished and keeps cloudbongos coupled to the OTB repo.
35
- - **Vanilla WS-0 (fresh-DB re-provision).** Rejected now: it would wipe the freshly-migrated history and balances — the whole point of ADR 0139. Hence the DB-preserving variant.
36
- - **Reuse an existing repo for the site** — `cloud-bongos` (the engine) or `cloudbongos/bongos` (the public AGPL source mirror). Rejected: both hold load-bearing roles; a dedicated `cloudbongos/cloudbongos-instance` keeps host identity out of both.
37
-
38
- ## Consequences
39
-
40
- - cloudbongos.com runs the clean standalone model: hall renders, all three hosts (apex/builders/status) healthy, DB + balances intact. Future platform updates are a one-line `@bongos/core` pin bump, auto-deployed within ~2 min by the repointed pull-timer.
41
- - **Closes ADR 0139 WS-0** (belatedly, in DB-preserving form).
42
- - Deploy keys had to be **enabled org-wide on the `cloudbongos` org** so the box's read-only deploy key could attach to an org repo (they were disabled by default).
43
- - Follow-ups (non-blocking): retire `/home/lars/cloudbongos` + the backups after a soak; version `/home/lars/deploy.sh` into the instance repo and restore the ADR 0129 self-heal step; the packaged engine's `CORE_VERSION` constant lags its `package.json` (reports 1.17.2 while running 1.17.3) — cosmetic, flag upstream.
@@ -1,20 +0,0 @@
1
- {
2
- "requests": [
3
- {
4
- "id": "901",
5
- "display_name": "Preview Applicant",
6
- "github_login": "preview-applicant",
7
- "note": "Synthetic access request for the preview harness.",
8
- "created_at": "2026-07-08T14:02:00.000Z",
9
- "status": "pending"
10
- },
11
- {
12
- "id": "902",
13
- "display_name": null,
14
- "github_login": "sample-builder",
15
- "note": null,
16
- "created_at": "2026-07-09T09:41:00.000Z",
17
- "status": "pending"
18
- }
19
- ]
20
- }
@@ -1,76 +0,0 @@
1
- {
2
- "achievements": [
3
- {
4
- "id": "first-ship",
5
- "name": "First Ship",
6
- "description": "Ship your first task.",
7
- "icon": "📦",
8
- "credit_bonus": 1,
9
- "sort_order": 10
10
- },
11
- {
12
- "id": "first-claim",
13
- "name": "First Claim",
14
- "description": "Claim your first task.",
15
- "icon": "🪙",
16
- "credit_bonus": 1,
17
- "sort_order": 20
18
- },
19
- {
20
- "id": "first-parallel-claim",
21
- "name": "First Parallel Claim",
22
- "description": "Hold two simultaneous claims; ship both successfully.",
23
- "icon": "⚓",
24
- "credit_bonus": 2,
25
- "sort_order": 30
26
- },
27
- {
28
- "id": "streak-3",
29
- "name": "Streak: 3 Nights",
30
- "description": "Ship a task in 3 consecutive local-time nights (>=15min; cleanup/refactor now counts).",
31
- "icon": "🔥",
32
- "credit_bonus": 2,
33
- "sort_order": 40
34
- },
35
- {
36
- "id": "streak-7",
37
- "name": "Streak: 7 Nights",
38
- "description": "Ship a task in 7 consecutive local-time nights (>=15min; cleanup/refactor now counts).",
39
- "icon": "🌋",
40
- "credit_bonus": 5,
41
- "sort_order": 50
42
- },
43
- {
44
- "id": "art-pipeline-shipper",
45
- "name": "Art Pipeline Shipper",
46
- "description": "Ship a task whose touches[] include the art/ pipeline.",
47
- "icon": "🎨",
48
- "credit_bonus": 2,
49
- "sort_order": 60
50
- },
51
- {
52
- "id": "subtractor",
53
- "name": "Subtractor",
54
- "description": "Earn the net-negative ship bonus on 3 ships — across your work you have deleted more than you have added.",
55
- "icon": "🪓",
56
- "credit_bonus": 2,
57
- "sort_order": 65
58
- },
59
- {
60
- "id": "gds-shipper",
61
- "name": "GDS Shipper",
62
- "description": "Ship a task whose touches[] include src/gds/ or scripts/gds/.",
63
- "icon": "⚙️",
64
- "credit_bonus": 2,
65
- "sort_order": 70
66
- },
67
- {
68
- "id": "breaking-the-100c-bar",
69
- "name": "Breaking the 100c Bar",
70
- "description": "Cross 100 total credits.",
71
- "icon": "💯",
72
- "credit_bonus": 5,
73
- "sort_order": 80
74
- }
75
- ]
76
- }