@bongos/core 1.21.47 → 1.21.49

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 +50 -60
  2. package/.claude/skills/bongos-feedback/SKILL.md +5 -4
  3. package/bin/bongos.js +0 -0
  4. package/docs/adr/0087-bongos-app-architecture-and-handoff-contract.md +2 -2
  5. package/docs/copy-inventory.md +33 -31
  6. package/docs/copy-registry.json +103 -75
  7. package/docs/file-map.md +0 -1
  8. package/docs/module-api-changelog.md +4 -0
  9. package/docs/onboarding/primer.md +23 -16
  10. package/docs/page-readings.json +154 -145
  11. package/modules/hall-ui/public/commands-lib.js +1 -1
  12. package/modules/hall-ui/public/hall-render.js +13 -11
  13. package/modules/hall-ui/public/settings.html +4 -4
  14. package/modules/hall-ui/public/settings.js +1 -1
  15. package/modules/hall-ui/public/style.css +9 -0
  16. package/modules/onboarding/onboarding-state.js +5 -5
  17. package/package-lock.json +2 -2
  18. package/package.json +1 -1
  19. package/release-notes.json +16 -0
  20. package/scripts/gds/claude-materialize.js +3 -0
  21. package/scripts/gds/pack-env-prefix.js +73 -0
  22. package/scripts/gds/provision-repo.js +13 -0
  23. package/scripts/gds/provision-units.js +6 -17
  24. package/scripts/gds/provision.js +8 -2
  25. package/src/bongos/routes/auth.js +6 -5
  26. package/src/instance-config.js +28 -1
  27. package/src/module-api.js +1 -1
  28. package/tests/bongos_cli.mjs +2 -1
  29. package/tests/cli_allowlist_permissions.mjs +4 -0
  30. package/tests/cli_token_reissue.mjs +5 -3
  31. package/tests/hall_onboarding_bongos_setup.mjs +101 -0
  32. package/tests/instance_config.mjs +53 -0
  33. package/tests/instance_feedback.mjs +12 -0
  34. package/tests/provision.mjs +103 -0
  35. package/tests/skill_menu_ruling.mjs +1 -1
  36. package/.claude/skills/feedback/SKILL.md +0 -39
  37. package/scripts/bongos-mcp.js +0 -28
  38. package/scripts/gds/feedback-latest.js +0 -224
  39. package/tests/feedback_latest.mjs +0 -122
@@ -24,7 +24,7 @@
24
24
  {
25
25
  key: 'find', label: 'Finding things out and working with others',
26
26
  blurb: 'Looking up what the project already knows, asking for help, and passing work on.',
27
- names: ['status', 'recall', 'ask-for-help', 'collab-review', 'session-handoff', 'read-session-export', 'feedback'],
27
+ names: ['status', 'recall', 'ask-for-help', 'collab-review', 'session-handoff', 'read-session-export'],
28
28
  },
29
29
  {
30
30
  key: 'owner', label: 'Owner routines',
@@ -808,11 +808,11 @@
808
808
  ? `Stuck on any step? <a href="${API}/auth/discord/start">Join our Discord</a> and ask in <strong>#support</strong>.`
809
809
  : `Stuck on any step? See the project's README and docs.`;
810
810
  const setupSteps = `
811
- <p class="setup__intro">Build from your own machine — clone the repository, run the platform locally, and sign in from the terminal.</p>
811
+ <p class="setup__intro">Build from your own machine — get a copy of the project, sign in once, then work in Claude.</p>
812
812
  <ol class="setup__steps">
813
813
  <li><strong>Clone the repository</strong> and install dependencies (<code>npm install</code>).</li>
814
- <li><strong><a href="https://claude.com/download" target="_blank" rel="noopener noreferrer">Download Claude</a></strong> (or use the Claude Code CLI) and open the project.</li>
815
- <li><strong>Run <code>/builder-setup</code></strong> to authenticate this machine, then <code>/builder-start</code> to claim your first task.</li>
814
+ <li><strong>Run <code>npx bongos setup</code></strong> in that folder to sign this machine in.</li>
815
+ <li><strong><a href="https://claude.com/download" target="_blank" rel="noopener noreferrer">Download Claude</a></strong> (or use the Claude Code CLI), open the project, and run <code>/builder-start</code> to claim your first task.</li>
816
816
  </ol>
817
817
  `;
818
818
  // task 469: a "Set up your machine / get the code" door. The steps above
@@ -821,24 +821,26 @@
821
821
  // how do I get it onto my own laptop?". It's the hall mirror of the primer's
822
822
  // "Step 0 — get the project onto your machine". Collapsed by default so it
823
823
  // doesn't crowd the recommended path; the clone block is click-to-copy
824
- // (copy-on-click.js picks up .welcome__paste). The commands mirror
825
- // scripts/setup-builder.sh's own header verbatim so they can't drift.
824
+ // (copy-on-click.js picks up .welcome__paste). Sign-in is `npx bongos setup`
825
+ // (bin/bongos.js → scripts/gds/setup.js), one line on every OS — bare
826
+ // `bongos` is not on a fresh clone's PATH, `npx` finds the repo's own bin.
826
827
  // The clone commands come from the instance's own repo coords (loaded from
827
828
  // GET /api/bongos/instance), not a hardcoded repo, so a fresh instance's newcomer
828
829
  // clones ITS project (ADR 0062 §3, task 1705). Falls back to generic guidance
829
830
  // until the coords load (or if they're unavailable).
830
831
  const cloneBlock = ctx.instanceRepo
831
- ? `git clone ${escapeHtml(ctx.instanceRepo.url)}.git\ncd ${escapeHtml(ctx.instanceRepo.repo)}\n./scripts/setup-builder.sh`
832
- : `# clone your instance's repository, cd into it, then run:\n./scripts/setup-builder.sh`;
832
+ ? `git clone ${escapeHtml(ctx.instanceRepo.url)}.git\ncd ${escapeHtml(ctx.instanceRepo.repo)}\nnpm install\nnpx bongos setup`
833
+ : `# clone your instance's repository, cd into it, then run:\nnpm install\nnpx bongos setup`;
833
834
  const getCode = `
834
835
  <details class="setup__getcode">
835
836
  <summary class="setup__getcode-summary">Set up your machine / get the code</summary>
836
837
  <div class="setup__getcode-body">
837
- <p class="setup__hint">First install <a href="https://git-scm.com/downloads" target="_blank" rel="noopener noreferrer">Git</a>
838
+ <p class="setup__hint">First install <a href="https://git-scm.com/downloads" target="_blank" rel="noopener noreferrer">Git</a>,
839
+ <a href="https://nodejs.org" target="_blank" rel="noopener noreferrer">Node</a> (version 22 or newer)
838
840
  and <a href="https://claude.com/download" target="_blank" rel="noopener noreferrer">Claude Code</a> — that's all the tooling you need up front.</p>
839
- <p class="setup__getcode-lede">Then copy the project onto your machine and run the one-shot bootstrap (it checks your setup, installs dependencies, and signs you in):</p>
841
+ <p class="setup__getcode-lede">Then copy the project onto your machine, install its dependencies, and sign in:</p>
840
842
  <code class="welcome__paste">${cloneBlock}</code>
841
- <p class="setup__hint">On Windows, run <code>node scripts/setup-builder.js</code> instead of the last line. New to the steps? The
843
+ <p class="setup__hint">The same commands work on Mac, Windows and Linux. New to the steps? The
842
844
  <a href="/primer">primer</a> walks the whole path in plain language.</p>
843
845
  </div>
844
846
  </details>`;
@@ -898,7 +900,7 @@
898
900
  <span class="onboarding-item__mark" aria-hidden="true">›</span>
899
901
  <div class="onboarding-item__body">
900
902
  <p class="onboarding-item__label">First — obtain the repo</p>
901
- <p class="onboarding-item__hint">Clone the project to your machine. The build commands live inside the repo, so this comes first. The remaining steps below are tracked automatically.</p>
903
+ <p class="onboarding-item__hint">Clone the project to your machine and run <code>npm install</code> in it. The build commands live inside the repo, so this comes first. The remaining steps below are tracked automatically.</p>
902
904
  </div>
903
905
  </li>`;
904
906
 
@@ -140,13 +140,13 @@
140
140
  <section class="scroll" id="cli-scroll" aria-labelledby="cli-h" data-group="access" hidden>
141
141
  <h2 id="cli-h" class="scroll__h">CLI access</h2>
142
142
  <p class="scroll__lede">
143
- Mint a fresh CLI token using this browser session and paste it into
144
- your terminal — the quick way back in when a <code>/builder-*</code>
145
- command says your session expired.
143
+ Mint a fresh CLI token using this browser session and paste the
144
+ command it gives you into a terminal in your project folder — the quick
145
+ way back in when a <code>/builder-*</code> command says your session expired.
146
146
  </p>
147
147
  <div class="settings-card">
148
148
  <button type="button" class="settings-btn" id="cli-reissue-btn">Re-issue CLI token</button>
149
- <span class="settings-card__hint">paste it into your terminal — bypasses Device Flow.</span>
149
+ <span class="settings-card__hint">no GitHub code to type in.</span>
150
150
  <div id="cli-reissue-result" class="cli-result" hidden></div>
151
151
  </div>
152
152
  <p class="section-note">Tokens refresh on use; re-issuing never signs you out anywhere else.</p>
@@ -792,7 +792,7 @@
792
792
  // three out of the copy registry, blinding the artist's queue to this panel.
793
793
  const el = (tag, cls) => Object.assign(document.createElement(tag), { className: cls });
794
794
  const say = el('p', 'cli-result__say');
795
- say.textContent = 'Fresh CLI token issued. Paste it in your terminal:';
795
+ say.textContent = 'Fresh CLI token issued. Paste this into a terminal in your project folder:';
796
796
  const eyebrow = el('p', 'quote__eyebrow');
797
797
  eyebrow.textContent = 'paste command';
798
798
  const pre = el('pre', 'quote__pre');
@@ -3206,6 +3206,15 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
3206
3206
  padding: 8px 12px;
3207
3207
  overflow-x: auto;
3208
3208
  }
3209
+ /* The get-the-code block is several commands, one per line. Without `pre` the
3210
+ line breaks collapse on screen AND in copy-on-click (it copies innerText),
3211
+ so a builder pasted every command onto one line. Both axes named: see the
3212
+ one-axis-strip rule in this module's CLAUDE.md. */
3213
+ .setup__getcode .welcome__paste {
3214
+ display: block;
3215
+ white-space: pre;
3216
+ overflow-y: hidden;
3217
+ }
3209
3218
 
3210
3219
  .setup { margin-top: 12px; }
3211
3220
  .setup__intro { font-size: 13px; color: var(--ink-soft); }
@@ -92,7 +92,7 @@ const ALL_STAGES = [...STAGES, GRADUATED];
92
92
  const PRE_AUTH_STEP = {
93
93
  id: 'obtain_repo',
94
94
  label: 'Obtain the repo',
95
- hint: 'Clone the project to thy machine. The build commands live inside the repo, so this comes first.',
95
+ hint: 'Clone the project to thy machine and run npm install in it. The build commands live inside the repo, so this comes first.',
96
96
  informational: true,
97
97
  };
98
98
 
@@ -126,7 +126,7 @@ const STAGE_LABELS = {
126
126
  // onboarding model never named that step, so the hint now points at it ahead of
127
127
  // the sign-in itself. See PRE_AUTH_STEP below for the standalone representation.
128
128
  const STAGE_HINTS = {
129
- auth_complete: 'First obtain the repo — clone it — then sign in via GitHub Device Flow or the web sign-in.',
129
+ auth_complete: 'First obtain the repo — clone it — then sign in with npx bongos setup in that folder.',
130
130
  pick_disciplines: 'Pick the work that calls thee — engineer, artist, ideator, or any mix.',
131
131
  first_start: 'Run /builder-start to see claimable works.',
132
132
  first_claim: 'Claim a task with /builder-claim N — it is thine alone until thou shippest it.',
@@ -153,7 +153,7 @@ function isKnownStage(stage) {
153
153
  // clears the step. `source: 'discord'`.
154
154
  // 2. pick_disciplines — the real engine stage, cleared by the craft picker.
155
155
  // 3. cli_setup — cleared once the builder has authenticated the CLI
156
- // (ran /builder-setup, which mints a source='cli'
156
+ // (ran `npx bongos setup`, which mints a source='cli'
157
157
  // session). Detected via db.hasCliSession; passed in as
158
158
  // `cliAuthed`. `source: 'cli'`.
159
159
  // 4. ship_three — the real engine stage. Auto, live "N of 3" counter,
@@ -178,14 +178,14 @@ const DISPLAY_STEPS = [
178
178
  const DISPLAY_LABELS = {
179
179
  join_discord: 'Join the Discord server',
180
180
  pick_disciplines: 'Choose thy craft — engineer, artist, or ideator',
181
- cli_setup: 'Run /builder-setup',
181
+ cli_setup: 'Run npx bongos setup',
182
182
  ship_three: 'Ship three works — and rise to Thetes',
183
183
  };
184
184
 
185
185
  const DISPLAY_HINTS = {
186
186
  join_discord: 'Connect Discord — thou art auto-joined to the city\'s server and given thy rank.',
187
187
  pick_disciplines: 'Pick the work that calls thee — engineer, artist, ideator, or any mix.',
188
- cli_setup: 'Sign the CLI in with /builder-setup — this ticks itself once thou art signed in.',
188
+ cli_setup: 'In thy copy of the project, run npx bongos setup — this ticks itself once thou art signed in.',
189
189
  ship_three: 'Carry three tasks through with /builder-ship — the third makes thee a Thetes.',
190
190
  };
191
191
 
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.47",
3
+ "version": "1.21.49",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.47",
9
+ "version": "1.21.49",
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.21.47",
3
+ "version": "1.21.49",
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",
@@ -9046,5 +9046,21 @@
9046
9046
  "id": "1004562",
9047
9047
  "text": "Walked every building and floor desk of the Governor City in light, dark and on a phone, and fixed what did not match the design: number tiles now say what they count, a cleared floor no longer shows a stale count, counts read"
9048
9048
  }
9049
+ ],
9050
+ "1.21.48": [
9051
+ {
9052
+ "id": "1004576",
9053
+ "text": "Saying \"Bongos feedback: …\" or suggesting how Bongos should behave now reliably sends it to the Bongos team; an unused leftover skill that confused this was removed."
9054
+ },
9055
+ {
9056
+ "id": "1004573",
9057
+ "text": "A project brought in from an existing repo now always picks up the sign-in and settings the platform gives it, instead of silently ignoring them."
9058
+ }
9059
+ ],
9060
+ "1.21.49": [
9061
+ {
9062
+ "id": "1004575",
9063
+ "text": "New builders get setup steps that actually work end to end: the sign-in commands in Settings and the primer now run from a fresh copy of the project, the primer's terminal commands display properly, and the setup no longer lea"
9064
+ }
9049
9065
  ]
9050
9066
  }
@@ -346,6 +346,9 @@ const STARTER_COMMANDS = [
346
346
  'bongos release:*',
347
347
  'bongos reauth:*',
348
348
  'bongos paste-token:*',
349
+ // What Settings → CLI access hands out since task 1004575: a clone has no
350
+ // bare `bongos` on PATH, so the copied command runs through npx.
351
+ 'npx bongos paste-token:*',
349
352
  'bongos exec scripts/gds/api.js:*',
350
353
  // The read-only subset (task 1003992, ADR 0304). `git branch` and `git fetch`
351
354
  // are exact forms on purpose: a flag turns either into a write or an exec. No
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ // scripts/gds/pack-env-prefix.js — which env-var names a hosted project reads, and
4
+ // whether its own pack agrees with what the provisioning runner writes (task 1004573).
5
+ //
6
+ // WHY. The runner writes every hosted project's /etc/<slug>/web.env under ONE prefix
7
+ // (provisionedEnvPrefix). A project ADOPTED from an existing repo brings its own
8
+ // config/branding.json, and mercury's said envPrefix CLOUDBONGOS while web.env carried
9
+ // BONGOS_* — so the project read none of its sign-in credentials, its address or any
10
+ // setting, and the only symptom anyone saw was "did not report sign-in ready".
11
+ //
12
+ // Its own file because both provision-units.js (web.env) and provision-repo.js (the
13
+ // adopt scaffold) need it, and provision-units already requires provision-repo — a
14
+ // helper living in either would make a require cycle. Requires only provision-config.
15
+
16
+ const fs = require('node:fs');
17
+ const path = require('node:path');
18
+ const { REPO_ROOT } = require('./provision-config.js');
19
+
20
+ // The pack whose envPrefix a provisioned instance inherits unless its own pack says
21
+ // otherwise (see the long note on ENV_PREFIX_PACK's history in provision-units.js).
22
+ const ENV_PREFIX_PACK = 'config/branding.neutral.json';
23
+
24
+ // The env-var prefix a provisioned instance actually RESOLVES — i.e. the envPrefix
25
+ // of the pack it loads (ENV_PREFIX_PACK). src/instance-config.resolveEnv
26
+ // reads <envPrefix>_<SUFFIX>, and src/branding.resolveBranding applies
27
+ // <envPrefix>_{PUBLIC,BUILDERS,…}_ORIGIN overrides — so a web.env var only takes
28
+ // effect under THIS prefix. Read from the pack (fail-soft to the vanilla default)
29
+ // so it stays in lockstep instead of a magic string. (The prior slug-based prefix
30
+ // was a latent bug: it never matched the neutral pack's CLOUDBONGOS envPrefix, so
31
+ // the OAuth creds were silently unreadable — task 1972.)
32
+ function provisionedEnvPrefix() {
33
+ const { FALLBACK_ENV_PREFIX } = require('../../src/instance-config'); // eslint-disable-line global-require
34
+ try {
35
+ const pack = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, ENV_PREFIX_PACK), 'utf8'));
36
+ return pack.envPrefix || FALLBACK_ENV_PREFIX;
37
+ } catch { return FALLBACK_ENV_PREFIX; }
38
+ }
39
+
40
+ // The envPrefix a pack's text names, or null (no pack, not JSON, no envPrefix).
41
+ function packEnvPrefix(packText) {
42
+ try {
43
+ const pack = JSON.parse(String(packText || ''));
44
+ return pack && typeof pack.envPrefix === 'string' ? pack.envPrefix : null;
45
+ } catch { return null; }
46
+ }
47
+
48
+ // Does the instance's OWN pack read the names this runner writes? PURE: give it the
49
+ // pack's text (or null when it could not be read) and the prefix written; it returns
50
+ // a sentence naming the mismatch, or null when the names agree or the pack says nothing.
51
+ function envPrefixMismatch(packText, written = provisionedEnvPrefix()) {
52
+ const own = packEnvPrefix(packText);
53
+ if (!own || own === written) return null;
54
+ return `the project's config/branding.json reads settings named ${own}_*, but the platform wrote ${written}_* — set its envPrefix to "${written}"`;
55
+ }
56
+
57
+ // The adopt scaffold's half of the fix: rewrite ONLY the envPrefix value in an adopted
58
+ // repo's config/branding.json so the project reads the names this runner writes,
59
+ // leaving every other byte of the owner's file as they wrote it (a JSON round-trip
60
+ // would reflow it). PURE. Returns { text, from } when it changed something, or null
61
+ // when the names already agree, the pack names no prefix, or it is not JSON.
62
+ function normalizePackEnvPrefix(packText, written = provisionedEnvPrefix()) {
63
+ const raw = String(packText || '');
64
+ const own = packEnvPrefix(raw);
65
+ if (!own || own === written) return null;
66
+ const re = /("envPrefix"\s*:\s*)"[^"]*"/;
67
+ if (!re.test(raw)) return null;
68
+ const text = raw.replace(re, (_m, key) => `${key}"${written}"`);
69
+ if (packEnvPrefix(text) !== written) return null;
70
+ return { text, from: own };
71
+ }
72
+
73
+ module.exports = { ENV_PREFIX_PACK, provisionedEnvPrefix, envPrefixMismatch, normalizePackEnvPrefix };
@@ -19,6 +19,7 @@ const { CONFIG, REPO_ROOT, coreVersionSafe, provisionerBotEmail, provisionerBotN
19
19
  // upgrade.js can reach these three without pulling provision*.js into the merge
20
20
  // resolver's executed closure; re-exported below so every existing caller is unchanged.
21
21
  const { instanceRepoRemote, parseTargetRef, resolveOwnerGithubToken } = require('./provision-repo-auth.js');
22
+ const { normalizePackEnvPrefix } = require('./pack-env-prefix.js'); // an adopted pack's envPrefix (task 1004573)
22
23
  const { CORE_PKG, compareSemver, isStable } = require('./update-channel.js');
23
24
 
24
25
  function dbName(inst) { return inst.db_name || inst.slug; }
@@ -832,6 +833,18 @@ async function scaffoldStandaloneRepo(inst, deps) {
832
833
  } else {
833
834
  writeFile(giPath, INSTANCE_GITIGNORE);
834
835
  }
836
+ // Adopt layers config only where ABSENT, so an owner's existing config/branding.json
837
+ // keeps whatever envPrefix it carried — and web.env is written under the platform's.
838
+ // Mercury's said CLOUDBONGOS: it read none of its credentials or settings (task 1004573).
839
+ // Rewrite just that one value, in this same commit, so the repo and web.env agree.
840
+ const packPath = path.join(root, 'config', 'branding.json');
841
+ if (mode === 'adopt' && fs.existsSync(packPath)) {
842
+ const fixed = normalizePackEnvPrefix(fs.readFileSync(packPath, 'utf8'));
843
+ if (fixed) {
844
+ writeFile(packPath, fixed.text);
845
+ log(` [scaffold] config/branding.json envPrefix ${fixed.from} → the platform's, so the project reads the settings it is given`);
846
+ }
847
+ }
835
848
  exec(`git -C ${root} config user.name "${provisionerBotName()}"`);
836
849
  exec(`git -C ${root} config user.email "${provisionerBotEmail()}"`);
837
850
  exec(`git -C ${root} add -A`);
@@ -162,23 +162,10 @@ WantedBy=timers.target
162
162
  // branding-file env line is written at all. Reading the neutral pack HERE is still correct: it is
163
163
  // the prefix floor an instance inherits unless its own pack overrides envPrefix, and every live
164
164
  // co-tenant pack pins `envPrefix: "CLOUDBONGOS"` explicitly.
165
- const ENV_PREFIX_PACK = 'config/branding.neutral.json';
166
-
167
- // The env-var prefix a provisioned instance actually RESOLVES — i.e. the envPrefix
168
- // of the pack it loads (ENV_PREFIX_PACK). src/instance-config.resolveEnv
169
- // reads <envPrefix>_<SUFFIX>, and src/branding.resolveBranding applies
170
- // <envPrefix>_{PUBLIC,BUILDERS,…}_ORIGIN overrides — so a web.env var only takes
171
- // effect under THIS prefix. Read from the pack (fail-soft to the vanilla default)
172
- // so it stays in lockstep instead of a magic string. (The prior slug-based prefix
173
- // was a latent bug: it never matched the neutral pack's CLOUDBONGOS envPrefix, so
174
- // the OAuth creds were silently unreadable — task 1972.)
175
- function provisionedEnvPrefix() {
176
- const { FALLBACK_ENV_PREFIX } = require('../../src/instance-config');
177
- try {
178
- const pack = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, ENV_PREFIX_PACK), 'utf8'));
179
- return pack.envPrefix || FALLBACK_ENV_PREFIX;
180
- } catch { return FALLBACK_ENV_PREFIX; }
181
- }
165
+ // ENV_PREFIX_PACK + provisionedEnvPrefix (and the task-1004573 pack checks) live in
166
+ // pack-env-prefix.js — provision-repo.js needs them too, and it cannot require this file
167
+ // (this file requires it). Re-exported below so every existing caller is unchanged.
168
+ const { ENV_PREFIX_PACK, provisionedEnvPrefix, envPrefixMismatch, normalizePackEnvPrefix } = require('./pack-env-prefix.js');
182
169
 
183
170
  // The per-instance secrets file body (the standup runbook's /etc/<inst>/web.env).
184
171
  // The OAuth client id/secret are GUIDED/manual inputs (no GitHub create-API) — the
@@ -430,6 +417,8 @@ function renamedEnvCarry(body) {
430
417
 
431
418
  module.exports = {
432
419
  ENV_PREFIX_PACK,
420
+ envPrefixMismatch,
421
+ normalizePackEnvPrefix,
433
422
  backupScriptPath,
434
423
  backupService,
435
424
  backupServicePath,
@@ -50,7 +50,7 @@ const crypto = require('node:crypto');
50
50
  const retry = require('./intent-retry.js'); // when a failed intent is tried again (task 1004447)
51
51
  const { CONFIG, MANIFEST_UA, softFailResult, MANIFEST_VERIFY_INTERVAL_MS, MANIFEST_VERIFY_TRIES, MAX_INTENT_ATTEMPTS, REPO_ROOT, coreVersionSafe, hasFlag, loadDeps, oauthSecret, provisionerBotEmail } = require('./provision-config.js');
52
52
  const { accountPreflight, appUser, alreadyScaffolded, buildCorePinRefreshCommit, coreCheckoutHasGit, dbCreateCmd, dbName, dbRoleCmd, dbRoleCmdShown, dbRoleGrantsCmd, ensureInstanceUserCmd, generateDbPassword, grantInstanceRepoReadCmd, instanceDbRole, instanceStateDir, instanceUser, deployKeyPath, deployKeyTitle, ensurePrivateRepoAccess, installedCorePackDir, installedCoreTarball, instanceInitSpec, instanceRepoRemote, migrateCmd, onboardMode, ownerLoginOf, parseTargetRef, planCorePinRefresh, refreshStandaloneCorePin, resolveOwnerGithubToken, resolveVendorableCoreTarball, safeVersionLabel, scaffoldStandaloneRepo, seedFirstVersionCmd, standaloneInstallCmd, standaloneMigrateCmd, standalonePullCmd, standaloneRegenDocsCmd, standaloneRoot } = require('./provision-repo.js');
53
- const { backupScriptPath, backupService, backupServicePath, backupTimer, backupTimerPath, backupUnitName, instanceManifestCmd, originEnvVarsFor, serviceUnit, serviceUnitPath, settingsConsumed, settingsEnvVarsFor, renamedEnvCarry, upsertEnvVars, webEnvBody, webEnvPath } = require('./provision-units.js');
53
+ const { backupScriptPath, backupService, backupServicePath, backupTimer, backupTimerPath, backupUnitName, envPrefixMismatch, instanceManifestCmd, originEnvVarsFor, serviceUnit, serviceUnitPath, settingsConsumed, settingsEnvVarsFor, renamedEnvCarry, upsertEnvVars, webEnvBody, webEnvPath } = require('./provision-units.js');
54
54
  const { caddyBlock, caddySnippetPath, checkCaddyWiring, checkDnsTokenScope, classifyZoneScope, dnsReleaseEnv, dnsUpsertEnv, federateInstance, federationHubOrigin, healthzCmd, identityCmd, identityVerdict, ingressPlaneGap, ingressPreflight, shouldFederate, zoneForDomain } = require('./provision-net.js');
55
55
  const { resolveProcessManager } = require('./provision-process.js');
56
56
  const { capacityPreflight, statusCapacityLine } = require('./provision-capacity.js');
@@ -1097,7 +1097,13 @@ async function placeManifestCreds(inst, mrow, deps) {
1097
1097
  if (!authConfigured) {
1098
1098
  // The App now EXISTS on the owner's account — keep its id + slug on the row so
1099
1099
  // the wizard can name it and say why "try again" makes a second one (task 1004572).
1100
- return { ok: false, codeSpent: true, appId: creds.appId, appSlug: creds.appSlug, error: 'the credentials are placed, but the project did not report sign-in ready in time' };
1100
+ // Say WHY when we can see it: an adopted repo's own pack may read other names (task 1004573).
1101
+ // A cloud-host checkout lives on the control plane (the scaffold reads it the same way), so
1102
+ // this is a plain file read — no shell. An unreadable or non-JSON pack names nothing.
1103
+ let packText = null;
1104
+ if (standalone) { try { packText = (deps.readFile || fs.readFileSync)(path.join(standaloneRoot(inst), 'config', 'branding.json'), 'utf8'); } catch { packText = null; } }
1105
+ const why = envPrefixMismatch(packText);
1106
+ return { ok: false, codeSpent: true, appId: creds.appId, appSlug: creds.appSlug, error: `the credentials are placed, but the project did not report sign-in ready in time${why ? ` — ${why}` : ''}` };
1101
1107
  }
1102
1108
  log(' [oauth-manifest] ✓ /healthz auth_configured=true — sign-in is live');
1103
1109
  } else {
@@ -1306,17 +1306,18 @@ module.exports = function buildAuthRouter() {
1306
1306
  // THERE — on an instance, where the core is an installed dependency and
1307
1307
  // `scripts/gds/` does not exist, so the previous
1308
1308
  // `node scripts/gds/paste-token.js <token>` named a file the builder did not
1309
- // have and the copy button handed out a command that could not run. `bongos`
1310
- // is on PATH in any installed instance, so the verb form is layout- and
1311
- // directory-independent.
1309
+ // have and the copy button handed out a command that could not run. The verb
1310
+ // is run through `npx` (task 1004575): a builder's clone does not put bare
1311
+ // `bongos` on PATH, and `npx bongos` resolves the project's own bin there.
1312
+ // The hall tells them to paste it in their project folder.
1312
1313
  //
1313
1314
  // The --api-base flag carries the issuer above. Omitted when this instance
1314
1315
  // cannot state its own origin, because a flag with an empty value would be
1315
1316
  // worse than none: paste-token would take '' as an explicit instruction and
1316
1317
  // stop falling back to the resolution that works today.
1317
1318
  paste_command: issuer
1318
- ? `bongos paste-token ${token} --api-base ${issuer}`
1319
- : `bongos paste-token ${token}`,
1319
+ ? `npx bongos paste-token ${token} --api-base ${issuer}`
1320
+ : `npx bongos paste-token ${token}`,
1320
1321
  });
1321
1322
  } catch (err) {
1322
1323
  log.error('[gds] POST /auth/cli-token/issue', err);
@@ -190,7 +190,7 @@ function readConfigFileSync(name, { encoding = 'utf8', b = safeBrand() } = {}) {
190
190
  // per process naming the old spelling, the new one, and the release the old
191
191
  // stops working in; see warnLegacyEnv below. `warn` is injectable so a test can
192
192
  // assert the warning fires rather than only that the value resolves.
193
- function resolveEnv(suffix, { env = process.env, prefix = envPrefix(), legacy = LEGACY_ENV_PREFIXES, warn = warnLegacyEnv } = {}) {
193
+ function resolveEnv(suffix, { env = process.env, prefix = envPrefix(), legacy = LEGACY_ENV_PREFIXES, warn = warnLegacyEnv, warnPack = warnPackPrefix } = {}) {
194
194
  const canonical = `${prefix}_${suffix}`;
195
195
  const canonicalValue = prefix ? env[canonical] : undefined;
196
196
  const canonicalSet = canonicalValue !== undefined && canonicalValue !== '';
@@ -200,6 +200,22 @@ function resolveEnv(suffix, { env = process.env, prefix = envPrefix(), legacy =
200
200
  // on the winner would leave the "both set" operator with no notice at all.
201
201
  const seen = new Set([prefix].filter(Boolean));
202
202
  let winner = canonicalSet ? canonicalValue : undefined;
203
+ // THE PLATFORM'S SPELLING IS ALWAYS HEARD (task 1004573). The platform writes
204
+ // every hosted project's web.env as BONGOS_* whatever that project's own pack
205
+ // says. A project adopted from a repo whose config/branding.json still carried
206
+ // envPrefix CLOUDBONGOS read none of it — not its sign-in credentials, not its
207
+ // address, not one setting its owner chose — and nothing said so (mercury,
208
+ // 2026-10-04). So when the pack names another prefix, BONGOS_* is read right
209
+ // after it, ahead of the legacy list. The pack's own spelling still wins.
210
+ if (prefix && prefix !== FALLBACK_ENV_PREFIX) {
211
+ seen.add(FALLBACK_ENV_PREFIX);
212
+ const name = `${FALLBACK_ENV_PREFIX}_${suffix}`;
213
+ const v = env[name];
214
+ if (!canonicalSet && v !== undefined && v !== '') {
215
+ winner = v;
216
+ warnPack({ prefix, name });
217
+ }
218
+ }
203
219
  for (const pfx of legacy) {
204
220
  if (!pfx || seen.has(pfx)) continue;
205
221
  seen.add(pfx);
@@ -229,6 +245,17 @@ function warnLegacyEnv({ legacyName, canonicalName, shadowed, differs, log = con
229
245
  return true;
230
246
  }
231
247
 
248
+ // One line per process when a value had to come from the platform's BONGOS_*
249
+ // spelling because this project's pack names another prefix. Not a deprecation —
250
+ // BONGOS_ is the canonical name — so it says what to change instead: the pack.
251
+ let _packPrefixWarned = false;
252
+ function warnPackPrefix({ prefix, name, log = console } = {}) {
253
+ if (_packPrefixWarned) return false;
254
+ _packPrefixWarned = true;
255
+ log.warn(`[bongos] config/branding.json sets envPrefix "${prefix}", but ${name} (the platform's spelling) is what is set — reading it. Set envPrefix to "${FALLBACK_ENV_PREFIX}" to match.`);
256
+ return true;
257
+ }
258
+
232
259
  // NOT exported, deliberately: warnLegacyEnv, LEGACY_ENV_SUNSET_RELEASE and the
233
260
  // ledger are internals. resolveEnv's injectable `warn` is the whole test seam a
234
261
  // caller needs, and a test of the DEFAULT path captures console.warn — which
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.21.47'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.49'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -75,7 +75,8 @@ test('the server emits exactly the verb the CLI registers', () => {
75
75
  const cmds = [...tail.matchAll(/`([^`]*bongos paste-token[^`]*)`/g)].map((x) => x[1]);
76
76
  assert.ok(cmds.length > 0, 'auth.js should emit at least one paste_command template literal');
77
77
  for (const cmd of cmds) {
78
- const verb = cmd.split(' ')[1];
78
+ // `npx bongos <verb> …` since task 1004575 — a clone has no bare `bongos` on PATH.
79
+ const verb = cmd.replace(/^npx /, '').split(' ')[1];
79
80
  assert.ok(cli.VERBS[verb], `server emits "bongos ${verb}" but the CLI has no such verb`);
80
81
  }
81
82
  });
@@ -210,6 +210,10 @@ const GIT_GH_COMMANDS = [
210
210
  'git branch --list', 'git branch --show-current', 'git branch -a', 'git branch -vv',
211
211
  'git fetch', 'git fetch origin', 'git fetch origin main',
212
212
  'gh pr view:*', 'gh pr checks:*', 'gh pr list:*', 'gh run view:*', 'gh run list:*',
213
+ // Not git/gh, but the one non-script verb a session is handed to paste: Settings →
214
+ // CLI access gives `npx bongos paste-token <token>` (task 1004575), a blessed writer
215
+ // (see BLESSED_WRITERS) spelled the way a clone can run it.
216
+ 'npx bongos paste-token:*',
213
217
  ];
214
218
  // gds CLIs audited read-only under EVERY flag they take — a prefix rule allows them all.
215
219
  // Audited and left OUT: backlog-review.js (POSTs from its default walk) and
@@ -176,8 +176,10 @@ t('paste_command names a CLI VERB, never a filesystem path', () => {
176
176
  const cmds = [...tail.matchAll(/`([^`]*bongos paste-token[^`]*)`/g)].map((m) => m[1]);
177
177
  assert.equal(cmds.length, 2, `expected both ternary branches, got ${cmds.length}`);
178
178
  for (const cmd of cmds) {
179
- assert.ok(/^bongos paste-token /.test(cmd),
180
- `paste_command must start with the verb form, got: ${cmd}`);
179
+ // Through npx (task 1004575): a builder's clone does not put bare `bongos` on
180
+ // PATH, so a bare verb was "command not found" when pasted in the project folder.
181
+ assert.ok(/^npx bongos paste-token /.test(cmd),
182
+ `paste_command must start with the npx verb form, got: ${cmd}`);
181
183
  // The invariant is "names no FILE ON THE RECIPIENT'S DISK", which the original
182
184
  // spelling approximated as "contains no slash". That was too broad once the command
183
185
  // had to carry the issuer URL (task 1003722) — `https://host` is not a filesystem
@@ -208,7 +210,7 @@ t('paste_command carries the issuing instance, so the CLI cannot guess wrong', (
208
210
  t('an instance that cannot state its own origin omits the flag rather than sending an empty one', () => {
209
211
  // `--api-base ''` would read at the far end as an explicit instruction and suppress
210
212
  // the fallback that works today — strictly worse than sending no flag at all.
211
- assert.ok(/:\s*`bongos paste-token \$\{token\}`/.test(issueBlock),
213
+ assert.ok(/:\s*`npx bongos paste-token \$\{token\}`/.test(issueBlock),
212
214
  'there must be a flagless branch for the empty-issuer case');
213
215
  });
214
216