@bongos/core 1.21.61 → 1.21.63

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 (39) hide show
  1. package/.bongos-core.json +62 -42
  2. package/clients/bongos-client/index.d.ts +1 -1
  3. package/docs/adr/0366-hall-planning-is-forms-and-a-goal-made-there-carries-its-own-spec-task.md +85 -0
  4. package/docs/adr/README.md +1 -0
  5. package/docs/api/openapi.json +7 -3
  6. package/docs/api-reference.md +1 -1
  7. package/docs/copy-inventory.md +301 -301
  8. package/docs/copy-registry.json +342 -342
  9. package/docs/module-api-changelog.md +4 -0
  10. package/docs/onboarding/diagrams/assertions.json +1 -1
  11. package/docs/page-inventory.json +4 -1
  12. package/docs/page-readings.json +687 -670
  13. package/modules/lifecycle/merge-lock.js +53 -9
  14. package/modules/lifecycle/ship-preflight.js +40 -22
  15. package/modules/platform-identity/guilds.js +32 -0
  16. package/modules/platform-identity/routes/guilds.js +10 -3
  17. package/modules/public-landing/public/assets/cosmos.css +29 -3
  18. package/modules/public-landing/public/projects.html +294 -4
  19. package/modules/public-landing/public/projects.probes.json +18 -0
  20. package/modules/public-landing/public/projects.states.json +3 -0
  21. package/modules/ui-design/kit/fixtures/guilds.json +7 -0
  22. package/modules/ui-design/kit/fixtures/me__guild-requests.json +9 -0
  23. package/modules/ui-design/kit/serve.js +41 -0
  24. package/package-lock.json +2 -2
  25. package/package.json +1 -1
  26. package/release-notes.json +16 -0
  27. package/scripts/gds/ship-grade.js +13 -6
  28. package/scripts/gds/ship-merge-gate.js +26 -0
  29. package/scripts/gds/ship-merge.js +12 -3
  30. package/scripts/gds/ship-state.js +42 -5
  31. package/src/module-api.js +1 -1
  32. package/tests/guilds_api.mjs +31 -0
  33. package/tests/guilds_db.mjs +7 -0
  34. package/tests/hub_engagement_cards.mjs +9 -6
  35. package/tests/hub_guild_cards.mjs +392 -0
  36. package/tests/merge_lock.mjs +58 -0
  37. package/tests/ship_merge_gate.mjs +63 -0
  38. package/tests/ship_preflight.mjs +46 -1
  39. package/tests/ship_vendored_instance.mjs +259 -0
@@ -0,0 +1,259 @@
1
+ // tests/ship_vendored_instance.mjs
2
+ //
3
+ // task 1004584 — `bongos ship` could not land a task in a VENDORED instance (the
4
+ // core installed as node_modules/@bongos/core). Reported from mercury on core
5
+ // 1.21.20, Windows, laptop deploy mode. One REPO_ROOT meant both "the instance
6
+ // repo" (merge target, lock) and "where the core's scripts live" (smoke), and in a
7
+ // vendored instance those are two different folders:
8
+ //
9
+ // 1. no worktree had `main` checked out → REPO_ROOT fell back to the package
10
+ // dir → the merge lock opened node_modules/@bongos/core/.git/… → ENOENT;
11
+ // 2. `main` checked out in a LINKED worktree → <root>/.git is a FILE → the same
12
+ // ENOENT on the lock;
13
+ // 3. `main` in the primary checkout → the merge succeeded, then the post-merge
14
+ // smoke ran `node scripts/gds/smoke-gds.js` cwd-relative to the instance →
15
+ // MODULE_NOT_FOUND → ship reset main and the task stuck at `confirmed`.
16
+ //
17
+ // This builds the real layout and runs ship's real local merge leg in it: a bare
18
+ // origin, an instance clone whose node_modules/@bongos/core holds a COPY of the
19
+ // core files ship loads, a per-task linked worktree on the task branch, and a
20
+ // child process that requires the vendored copy of ship-merge.js and calls
21
+ // mergeLocally from the feature worktree — the shape `bongos ship` runs in.
22
+ //
23
+ // What is stubbed, and why: smoke-gds.js (the real one probes a live server — the
24
+ // stub only proves it was found at the CORE path and run), and cli-lib's
25
+ // cliClient (the land-proof POST). The deploy mode is pinned to laptop by env so
26
+ // no gh probe runs. Everything else — git, the lock, the fitness gate, the push
27
+ // to origin — is the real code against real repos.
28
+ //
29
+ // Run: node tests/ship_vendored_instance.mjs
30
+
31
+ import { strict as assert } from 'node:assert';
32
+ import { createRequire } from 'node:module';
33
+ import { execFileSync, spawnSync } from 'node:child_process';
34
+ import fs from 'node:fs';
35
+ import os from 'node:os';
36
+ import path from 'node:path';
37
+ import { fileURLToPath } from 'node:url';
38
+
39
+ const require = createRequire(import.meta.url);
40
+ const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
41
+ const TASK = 4242;
42
+ const BRANCH = `task-${TASK}`;
43
+
44
+ // The core as an install would carry it: every tracked file under the paths the
45
+ // package ships code from (media skipped — nothing on the merge leg reads it).
46
+ // Read from the working tree, so the test exercises the code being changed.
47
+ // Built ONCE into a template and copied into each fixture.
48
+ const CORE_FILES = execFileSync('git', ['ls-files', '-z', 'src', 'scripts', 'modules', 'config', 'package.json', '.claude/sounds'], {
49
+ cwd: REPO, encoding: 'utf8',
50
+ }).split('\0').filter((f) => f && !/\.(png|jpe?g|webp|gif|svg|mp3|wav|ogg)$/i.test(f) && fs.existsSync(path.join(REPO, f)));
51
+ const CORE_TEMPLATE = fs.mkdtempSync(path.join(os.tmpdir(), 'bongos-vendored-core-'));
52
+ for (const rel of CORE_FILES) {
53
+ const dst = path.join(CORE_TEMPLATE, rel);
54
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
55
+ fs.copyFileSync(path.join(REPO, rel), dst);
56
+ }
57
+
58
+ // A git env that ignores the developer's own config (hooks, signing, drivers).
59
+ const emptyCfg = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'bongos-vendored-cfg-')), 'gitconfig');
60
+ fs.writeFileSync(emptyCfg, '');
61
+ const GIT_ENV = {
62
+ ...process.env,
63
+ GIT_CONFIG_NOSYSTEM: '1',
64
+ GIT_CONFIG_GLOBAL: emptyCfg,
65
+ GIT_AUTHOR_NAME: 'Vendored Test', GIT_AUTHOR_EMAIL: 'vendored@example.test',
66
+ GIT_COMMITTER_NAME: 'Vendored Test', GIT_COMMITTER_EMAIL: 'vendored@example.test',
67
+ };
68
+ function git(cwd, ...args) {
69
+ return execFileSync('git', args, { cwd, env: GIT_ENV, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
70
+ }
71
+
72
+ function writeFile(p, body) {
73
+ fs.mkdirSync(path.dirname(p), { recursive: true });
74
+ fs.writeFileSync(p, body);
75
+ }
76
+
77
+ // Build one fixture. `mainAt`: 'primary' | 'linked' | 'none'.
78
+ function buildFixture(mainAt) {
79
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), `bongos-vendored-${mainAt}-`));
80
+ const origin = path.join(root, 'origin.git');
81
+ const inst = path.join(root, 'instance');
82
+ git(root, 'init', '--bare', '-b', 'main', origin);
83
+ git(root, 'clone', '-q', origin, inst);
84
+ git(inst, 'checkout', '-q', '-b', 'main');
85
+ writeFile(path.join(inst, '.gitignore'), 'node_modules/\n');
86
+ writeFile(path.join(inst, 'README.md'), '# a vendored instance\n');
87
+ git(inst, 'add', '.');
88
+ git(inst, 'commit', '-q', '-m', 'instance: initial');
89
+ git(inst, 'push', '-q', '-u', 'origin', 'main');
90
+
91
+ // The vendored core, where `npm install` would put it.
92
+ const core = path.join(inst, 'node_modules', '@bongos', 'core');
93
+ fs.cpSync(CORE_TEMPLATE, core, { recursive: true });
94
+ // The smoke stub records WHERE it was run from — the bug was it not being found.
95
+ writeFile(path.join(core, 'scripts', 'gds', 'smoke-gds.js'),
96
+ "require('fs').writeFileSync(process.env.SMOKE_MARKER, JSON.stringify({ script: __filename, cwd: process.cwd() }));\n");
97
+
98
+ // The task's own worktree + branch, carrying one commit.
99
+ const feature = path.join(root, 'wt-task');
100
+ git(inst, 'worktree', 'add', '-q', '-b', BRANCH, feature, 'main');
101
+ writeFile(path.join(feature, 'feature.txt'), 'the work\n');
102
+ git(feature, 'add', 'feature.txt');
103
+ git(feature, 'commit', '-q', '-m', 'the task work');
104
+
105
+ // Where `main` lives.
106
+ let mainDir = inst;
107
+ if (mainAt !== 'primary') {
108
+ git(inst, 'checkout', '-q', '-b', 'scratch'); // the primary checkout leaves main
109
+ if (mainAt === 'linked') {
110
+ mainDir = path.join(root, 'wt-main');
111
+ git(inst, 'worktree', 'add', '-q', mainDir, 'main');
112
+ } else {
113
+ mainDir = null;
114
+ }
115
+ }
116
+ return { root, origin, inst, core, feature, mainDir };
117
+ }
118
+
119
+ // Run ship's local merge leg from the vendored copy, in the feature worktree.
120
+ function runMergeLeg(fx) {
121
+ const marker = path.join(fx.root, 'smoke-ran.json');
122
+ const runner = path.join(fx.root, 'run-merge.js');
123
+ fs.writeFileSync(runner, `
124
+ const path = require('path');
125
+ const core = ${JSON.stringify(fx.core)};
126
+ const cli = require(path.join(core, 'scripts', 'gds', 'cli-lib.js'));
127
+ cli.cliClient = async () => ({ tasks: { postTasksIdAttestGate: async () => ({ ok: true, status: 200, data: { tip_recorded: true } }) } });
128
+ const state = require(path.join(core, 'scripts', 'gds', 'ship-state.js'));
129
+ const { mergeLocally } = require(path.join(core, 'scripts', 'gds', 'ship-merge.js'));
130
+ mergeLocally(${TASK}, 'vendored land').then(
131
+ (o) => { console.log('ROOTS ' + JSON.stringify({ core: state.CORE_ROOT, instance: state.INSTANCE_ROOT, main: state.MAIN_CHECKED_OUT })); console.log('RESULT ' + JSON.stringify(o)); },
132
+ (e) => { console.log('THREW ' + JSON.stringify({ code: e.code, message: e.message })); process.exitCode = 1; },
133
+ );
134
+ `);
135
+ const env = { ...GIT_ENV, SMOKE_MARKER: marker, BONGOS_DEPLOY_MODE: 'laptop'};
136
+ for (const k of ['BONGOS_DEPLOY_SSH_TARGET', 'OTB_DEPLOY_SSH_TARGET', 'BONGOS_DEPLOY_HEALTHZ_URL', 'OTB_DEPLOY_HEALTHZ_URL']) delete env[k];
137
+ const r = spawnSync(process.execPath, [runner], { cwd: fx.feature, env, encoding: 'utf8', timeout: 120000 });
138
+ const out = `${r.stdout || ''}\n${r.stderr || ''}`;
139
+ const line = (tag) => {
140
+ const l = out.split('\n').find((s) => s.startsWith(`${tag} `));
141
+ return l ? JSON.parse(l.slice(tag.length + 1)) : null;
142
+ };
143
+ return { out, result: line('RESULT'), roots: line('ROOTS'), threw: line('THREW'), marker };
144
+ }
145
+
146
+ // The two failure signatures from the report. (Not a blanket ENOENT match: ship's
147
+ // best-effort doc regenerators also run on this leg and grumble about the core's
148
+ // docs/ folder in a vendored layout — non-fatal by design, and not this bug.)
149
+ const LOCK_ENOENT = /ENOENT[^\n]*otb-automerge\.lock/;
150
+ const SMOKE_MISSING = /Cannot find module[^\n]*smoke-gds/;
151
+ const same = (a, b) => path.resolve(a).toLowerCase() === path.resolve(b).toLowerCase();
152
+ const commonLock = (fx) => path.join(fx.inst, '.git', 'otb-automerge.lock');
153
+
154
+ let passed = 0;
155
+ let failed = 0;
156
+ const fixtures = [];
157
+ async function t(name, fn) {
158
+ try { await fn(); passed++; console.log(` PASS ${name}`); }
159
+ catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
160
+ }
161
+
162
+ function assertLanded(fx, run) {
163
+ assert.equal(run.threw, null, `the merge leg threw: ${JSON.stringify(run.threw)}\n${run.out}`);
164
+ assert.ok(run.result, `no result line:\n${run.out}`);
165
+ assert.equal(run.result.ok, true, `expected a land, got ${JSON.stringify(run.result)}\n${run.out}`);
166
+ assert.ok(!LOCK_ENOENT.test(run.out), `the merge lock hit ENOENT:\n${run.out}`);
167
+ assert.ok(!SMOKE_MISSING.test(run.out), `smoke-gds.js was not found:\n${run.out}`);
168
+ // the smoke ran — from the CORE package, with the instance checkout as cwd
169
+ const smoke = JSON.parse(fs.readFileSync(run.marker, 'utf8'));
170
+ assert.ok(same(path.dirname(smoke.script), path.join(fx.core, 'scripts', 'gds')), `smoke ran from ${smoke.script}`);
171
+ assert.ok(same(smoke.cwd, fx.mainDir), `smoke cwd was ${smoke.cwd}, expected the main checkout ${fx.mainDir}`);
172
+ // the work is on origin/main, and the lock is gone
173
+ git(fx.inst, 'fetch', '-q', 'origin');
174
+ const files = git(fx.inst, 'ls-tree', '--name-only', 'origin/main');
175
+ assert.ok(files.split('\n').includes('feature.txt'), `origin/main lacks the task's file:\n${files}`);
176
+ assert.ok(!fs.existsSync(commonLock(fx)), 'the merge lock was left behind');
177
+ }
178
+
179
+ console.log('ship merge leg in a vendored instance (task 1004584)');
180
+
181
+ // ---- resolveRoots, pure: git answers injected, no repo needed ----
182
+ const { resolveRoots } = require(path.join(REPO, 'scripts', 'gds', 'ship-state.js'));
183
+ const VENDORED_DIR = path.join('/inst', 'node_modules', '@bongos', 'core', 'scripts', 'gds');
184
+ const VENDORED_CORE = path.resolve(VENDORED_DIR, '..', '..'); // drive-qualified on Windows, as resolveRoots returns it
185
+
186
+ await t('resolveRoots: CORE_ROOT is the package dir, INSTANCE_ROOT the worktree with main', () => {
187
+ const r = resolveRoots({ dirname: VENDORED_DIR, cwd: '/wt-task', findMain: () => '/wt-main', findPrimary: () => '/inst' });
188
+ assert.deepEqual(r, { coreRoot: VENDORED_CORE, instanceRoot: '/wt-main', mainCheckedOut: true });
189
+ });
190
+
191
+ await t('resolveRoots: asks from the script dir first, then the process cwd', () => {
192
+ const asked = [];
193
+ const r = resolveRoots({
194
+ dirname: VENDORED_DIR, cwd: '/wt-task',
195
+ findMain: (d) => { asked.push(d); return d === '/wt-task' ? '/inst' : null; },
196
+ findPrimary: () => { throw new Error('primary must not be consulted when main was found'); },
197
+ });
198
+ assert.deepEqual(asked, [VENDORED_DIR, '/wt-task']);
199
+ assert.equal(r.instanceRoot, '/inst');
200
+ });
201
+
202
+ await t('resolveRoots: no main anywhere → the PRIMARY checkout, flagged, never the package dir', () => {
203
+ const r = resolveRoots({ dirname: VENDORED_DIR, cwd: '/wt-task', findMain: () => null, findPrimary: () => '/inst' });
204
+ assert.deepEqual(r, { coreRoot: VENDORED_CORE, instanceRoot: '/inst', mainCheckedOut: false });
205
+ });
206
+
207
+ await t('resolveRoots: git cannot answer at all → the package dir (the core repo itself, pre-git)', () => {
208
+ const r = resolveRoots({ dirname: VENDORED_DIR, cwd: '/x', findMain: () => null, findPrimary: () => null });
209
+ assert.deepEqual(r, { coreRoot: VENDORED_CORE, instanceRoot: VENDORED_CORE, mainCheckedOut: false });
210
+ });
211
+
212
+ // ---- the real merge leg, in a real vendored layout ----
213
+
214
+ await t('main in the PRIMARY checkout: lands, smoke found under the core package', () => {
215
+ const fx = buildFixture('primary'); fixtures.push(fx);
216
+ const run = runMergeLeg(fx);
217
+ assert.ok(same(run.roots.core, fx.core), `CORE_ROOT ${run.roots.core}`);
218
+ assert.ok(same(run.roots.instance, fx.inst), `INSTANCE_ROOT ${run.roots.instance}`);
219
+ assertLanded(fx, run);
220
+ });
221
+
222
+ await t('main in a LINKED worktree: lands, lock lives in the common git dir', () => {
223
+ const fx = buildFixture('linked'); fixtures.push(fx);
224
+ const run = runMergeLeg(fx);
225
+ assert.ok(same(run.roots.instance, fx.mainDir), `INSTANCE_ROOT ${run.roots.instance}, expected ${fx.mainDir}`);
226
+ assert.equal(run.roots.main, true);
227
+ assertLanded(fx, run);
228
+ // and the lock path ship used is the one the merge-lock CLI uses from anywhere
229
+ const lock = require(path.join(fx.core, 'modules', 'lifecycle', 'merge-lock.js'));
230
+ assert.ok(same(lock.lockPath(fx.mainDir), commonLock(fx)), `lockPath(linked main) = ${lock.lockPath(fx.mainDir)}`);
231
+ assert.ok(same(lock.lockPath(fx.inst), commonLock(fx)));
232
+ });
233
+
234
+ await t('NO worktree has main: a clear bail naming the fix, never ENOENT, nothing pushed', () => {
235
+ const fx = buildFixture('none'); fixtures.push(fx);
236
+ const run = runMergeLeg(fx);
237
+ assert.equal(run.threw, null, `the merge leg threw: ${JSON.stringify(run.threw)}\n${run.out}`);
238
+ assert.ok(!LOCK_ENOENT.test(run.out), `the merge lock hit ENOENT:\n${run.out}`);
239
+ assert.equal(run.roots.main, false);
240
+ assert.ok(same(run.roots.instance, fx.inst), `fell back to ${run.roots.instance}, not the primary checkout`);
241
+ assert.ok(!same(run.roots.instance, fx.core), 'fell back to the core package dir');
242
+ assert.equal(run.result.ok, false);
243
+ assert.match(run.result.reason, /no worktree of this repo has main checked out/);
244
+ assert.match(run.result.nextStep, /checkout main/);
245
+ assert.ok(!fs.existsSync(run.marker), 'smoke ran although nothing was merged');
246
+ const files = git(fx.inst, 'ls-tree', '--name-only', 'origin/main');
247
+ assert.ok(!files.split('\n').includes('feature.txt'), 'origin/main changed on a bail');
248
+ assert.ok(!fs.existsSync(commonLock(fx)), 'the merge lock was left behind');
249
+ });
250
+
251
+ for (const fx of fixtures) {
252
+ try { fs.rmSync(fx.root, { recursive: true, force: true }); } catch (_) { /* best-effort */ }
253
+ }
254
+ for (const dir of [path.dirname(emptyCfg), CORE_TEMPLATE]) {
255
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_) { /* best-effort */ }
256
+ }
257
+
258
+ console.log(`\n${passed} passed, ${failed} failed`);
259
+ process.exit(failed ? 1 : 0);