@bongos/core 1.17.1 → 1.17.2

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 (164) hide show
  1. package/.bongos-core.json +244 -264
  2. package/.claude/skills/blocker-review/SKILL.md +1 -1
  3. package/.claude/skills/builder-claim/SKILL.md +2 -0
  4. package/.claude/skills/builder-exit/SKILL.md +2 -2
  5. package/.claude/skills/builder-redteam/SKILL.md +3 -3
  6. package/.claude/skills/builder-setup/SKILL.md +2 -2
  7. package/.claude/skills/builder-start/SKILL.md +1 -1
  8. package/.claude/skills/dev/SKILL.md +1 -1
  9. package/.claude/skills/idea-triage/SKILL.md +2 -2
  10. package/.claude/skills/ideate/SKILL.md +1 -1
  11. package/.claude/skills/merge-mode/SKILL.md +1 -1
  12. package/.claude/skills/new-project/SKILL.md +3 -0
  13. package/.claude/skills/paint/SKILL.md +1 -1
  14. package/GOVERNANCE.md +1 -1
  15. package/README.md +1 -1
  16. package/docs/adr/0001-stack-choice-single-vps.md +1 -1
  17. package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
  18. package/docs/adr/0019-red-team-bounty-payout-table.md +3 -3
  19. package/docs/adr/0020-security-model-public-stub.md +1 -1
  20. package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +1 -1
  21. package/docs/adr/0024-cloneable-repo-local-first-memory.md +1 -1
  22. package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +4 -4
  23. package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +3 -3
  24. package/docs/adr/0030-builders-hall-live-updates.md +1 -1
  25. package/docs/adr/0031-cloud-dev-environments-for-builders.md +4 -4
  26. package/docs/adr/0032-discord-mirror-of-gds.md +2 -2
  27. package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
  28. package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
  29. package/docs/adr/0044-per-box-live-game-preview.md +1 -1
  30. package/docs/adr/0054-session-token-reward-cost-plus.md +10 -10
  31. package/docs/adr/0057-container-cost-ledger.md +3 -3
  32. package/docs/adr/0062-medusa-instance-model-decoupling.md +4 -4
  33. package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +2 -2
  34. package/docs/adr/0085-remove-default-per-builder-spend-cap.md +1 -1
  35. package/docs/adr/0086-goal-scoped-work-hierarchy.md +1 -1
  36. package/docs/adr/0096-require-reward-before-workable.md +9 -9
  37. package/docs/adr/0097-retroactive-reward-backfill.md +3 -3
  38. package/docs/adr/0100-two-repo-instance-model.md +1 -1
  39. package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +3 -3
  40. package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +3 -3
  41. package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +1 -1
  42. package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +6 -6
  43. package/docs/adr/0136-update-channel-subscription-policy.md +51 -0
  44. package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +40 -0
  45. package/docs/adr/0138-bundled-module-deps-declared-per-module.md +45 -0
  46. package/docs/adr/README.md +9 -6
  47. package/docs/architecture.md +2 -2
  48. package/docs/branding-contract.md +5 -4
  49. package/docs/canonical-permissions.md +1 -1
  50. package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
  51. package/docs/file-map.md +18 -17
  52. package/docs/handoff-template.md +2 -2
  53. package/docs/modules-contract.md +6 -1
  54. package/docs/onboarding/diagrams/README.md +7 -7
  55. package/docs/onboarding/drachmae-and-karma.md +18 -18
  56. package/docs/onboarding/primer.md +9 -9
  57. package/docs/recipes/audit-replay.md +2 -2
  58. package/docs/recipes/builder-box-lifecycle.md +2 -2
  59. package/docs/recipes/claude-design-loop.md +1 -1
  60. package/docs/recipes/ops-gotchas.md +1 -1
  61. package/docs/recipes/private-npm-distribution.md +17 -0
  62. package/docs/recipes/staging-environment.md +1 -1
  63. package/docs/recipes/upgrading-the-core.md +55 -0
  64. package/docs/research/cursor-competitive-analysis.md +1 -1
  65. package/docs/research/llm-model-agnostic-and-cheaper-grader.md +1 -1
  66. package/modules/discord/module.json +2 -1
  67. package/modules/economy/achievements.js +9 -1
  68. package/modules/economy/credits.js +10 -1
  69. package/modules/hall-ui/public/builders.js +71 -4
  70. package/modules/hall-ui/public/goal-inbox.js +191 -0
  71. package/modules/hall-ui/public/hall-widgets.js +14 -1
  72. package/modules/hall-ui/public/index.html +3 -0
  73. package/modules/hall-ui/public/new-project-wizard.js +1 -0
  74. package/modules/hall-ui/public/style.css +43 -0
  75. package/modules/provisioning/CLAUDE.md +15 -0
  76. package/modules/provisioning/migrations/provisioning_001_tables.sql +160 -0
  77. package/modules/provisioning/migrations/provisioning_002_standalone_shape.sql +11 -0
  78. package/modules/provisioning/migrations/provisioning_003_github_app_manifest.sql +95 -0
  79. package/modules/provisioning/module.json +17 -0
  80. package/modules/provisioning/onboard-plan.js +37 -0
  81. package/modules/provisioning/provisioning.js +539 -0
  82. package/modules/provisioning/rate-limit.js +35 -0
  83. package/modules/provisioning/routes/provisioning.js +607 -0
  84. package/modules/provisioning/tests/provisioning.mjs +232 -0
  85. package/modules/provisioning/ui/.gitkeep +0 -0
  86. package/modules/security/secret-scrub.js +10 -0
  87. package/package-lock.json +2 -2
  88. package/package.json +7 -2
  89. package/scripts/discord/README.md +1 -1
  90. package/scripts/gds/claim.js +98 -48
  91. package/scripts/gds/claude-materialize.js +30 -9
  92. package/scripts/gds/cutover-codemod.js +241 -0
  93. package/scripts/gds/fitness.js +54 -0
  94. package/scripts/gds/init.js +59 -3
  95. package/scripts/gds/lint-infra-exec.js +70 -0
  96. package/scripts/gds/lint-workflow-exec.js +63 -0
  97. package/scripts/gds/mirror-lineage-check.js +14 -5
  98. package/scripts/gds/mirror-redact.js +8 -0
  99. package/scripts/gds/onboard.js +4 -0
  100. package/scripts/gds/package-core.js +18 -3
  101. package/scripts/gds/publish-manifest.js +60 -2
  102. package/scripts/gds/run-routine.js +51 -13
  103. package/scripts/gds/run-unit-tests.js +4 -0
  104. package/scripts/gds/update-channel.js +186 -0
  105. package/scripts/gds/upgrade.js +116 -9
  106. package/src/bongos/auth.js +14 -1
  107. package/src/bongos/serve-internal.js +10 -1
  108. package/src/branding.js +1 -0
  109. package/src/module-api.js +12 -1
  110. package/src/module-loader/loader.js +36 -4
  111. package/src/module-loader/manifest-schema.js +16 -1
  112. package/tests/branding.mjs +11 -0
  113. package/tests/claim_error_surface.mjs +107 -0
  114. package/tests/claude_materialize.mjs +20 -0
  115. package/tests/consumer_layout_boot.mjs +302 -0
  116. package/tests/cutover_codemod.mjs +184 -0
  117. package/tests/device_flow_start.mjs +25 -2
  118. package/tests/hook_core_resolve.mjs +125 -0
  119. package/tests/infra_exec_paths.mjs +104 -0
  120. package/tests/init.mjs +47 -0
  121. package/tests/mirror_lineage_proof.mjs +14 -0
  122. package/tests/module_loader.mjs +42 -0
  123. package/tests/module_manifest.mjs +19 -0
  124. package/tests/onboard.mjs +13 -0
  125. package/tests/package_core.mjs +8 -0
  126. package/tests/provisioning_github_app_routes.mjs +15 -0
  127. package/tests/publish_manifest.mjs +52 -1
  128. package/tests/run_routine.mjs +26 -1
  129. package/tests/run_routine_two_root.mjs +77 -0
  130. package/tests/scrubber_corpus.mjs +17 -0
  131. package/tests/update_channel.mjs +186 -0
  132. package/tests/upgrade.mjs +131 -0
  133. package/tests/workflow_exec_paths.mjs +98 -0
  134. package/docs/design/container-exterior-tiles.md +0 -49
  135. package/docs/design/container-exterior.md +0 -81
  136. package/docs/design/container-interior.md +0 -77
  137. package/docs/design/world-map-scroll.md +0 -41
  138. package/docs/recipes/claude-gemini-bongos-doors.md +0 -155
  139. package/docs/recipes/cloudbongos-staging-gate.md +0 -139
  140. package/docs/recipes/cloudbongos-standup.md +0 -111
  141. package/docs/recipes/parallel-load-results-2026-05-30.md +0 -63
  142. package/docs/recipes/parallel-load-test.md +0 -32
  143. package/docs/recipes/pixel-art-pipeline.md +0 -411
  144. package/docs/recipes/staging-data.md +0 -50
  145. package/migrations/instance/018_pms_v3_planning_seed.sql +0 -131
  146. package/migrations/instance/080_gds_v4_planning_seed.sql +0 -75
  147. package/migrations/instance/110_medusa_planning_seed.sql +0 -70
  148. package/migrations/instance/120_bongos_v1_planning_seed.sql +0 -69
  149. package/migrations/instance/144_bongos_v1_module_system_criterion.sql +0 -26
  150. package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +0 -29
  151. package/migrations/instance/172_v2_world_planning_seed.sql +0 -163
  152. package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +0 -33
  153. package/migrations/instance/182_gdsv4_bongos_consolidation.sql +0 -132
  154. package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +0 -116
  155. package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +0 -69
  156. package/scripts/gds/art-stage.js +0 -152
  157. package/server.js +0 -212
  158. package/tests/art_key.mjs +0 -121
  159. package/tests/day_cycle.mjs +0 -197
  160. package/tests/snapshot_picker.mjs +0 -233
  161. package/tests/sprite_facing.mjs +0 -35
  162. package/tests/terrain_picker.mjs +0 -298
  163. package/tests/turn_in_place.mjs +0 -280
  164. package/tests/walkability.mjs +0 -188
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // cutover-codemod.js — rewrite HOST files' core-internal require()s to the @bongos/core
4
+ // package subpaths at cutover (R89 / task 2159 [F1], task 2161 [F3]).
5
+ //
6
+ // WHY. Post-split (task 1728) a host file lives at `<instance>/…` with NO co-located `src/`.
7
+ // Its relative core require()s — `require('../../src/module-api')` (host modules, F1),
8
+ // `require('./src/bongos/…')` (the game entrypoint server.js, F3) — point at the DELETED
9
+ // instance `src/` → boot crash (the 2026-07-10 rehearsal, findings F1+F3:
10
+ // docs/reviews/2026-07-10-amazonprimea-decouple-r89-rehearsal.md). Core modules are UNAFFECTED:
11
+ // they ship INSIDE @bongos/core, where `src/` sits next to `modules/`, so their identical
12
+ // relative requires still resolve. The fix is to rewrite the host files' core requires to the
13
+ // package's EXPORTED subpaths (`@bongos/core/module-api`, `@bongos/core/serve-internal`, …).
14
+ //
15
+ // WHY A CUTOVER TRANSFORM, NOT AN IN-PLACE MONOLITH EDIT. In the monolith `@bongos/core` is not
16
+ // a dependency (the specifier resolves nowhere) and fitness's "modules import only
17
+ // src/module-api.js" check would fail the rewritten form. So this must run AT CUTOVER on the
18
+ // partitioned consumer tree — never on the committed monolith source. Mirrors the `.claude/hooks`
19
+ // `_core-resolve.js` step (task 2139); `claude-materialize.js` rewrites `.claude/`, this rewrites
20
+ // host-module + server.js source — the gap that rehearsal F1/F3 named.
21
+ //
22
+ // TWO GUARDS make it structurally impossible to corrupt the monolith:
23
+ // 1. SCOPE ORACLE = publish-manifest. A file is rewritten only when `isPublishable(rel) === false`
24
+ // (host: game / art-pipeline / character-anim / demo + server.js). Core files (publishable)
25
+ // ship with the package and are left untouched — reusing the exact partition logic as the
26
+ // core-vs-host oracle, so this can never drift from what the cutover actually deletes/keeps.
27
+ // 2. PRECONDITION = post-split consumer. Refuse unless the instance core doorway is gone
28
+ // (`src/module-api.js` absent at the root) — the definitive signal core was partitioned out.
29
+ // In the monolith that file exists, so the codemod refuses. `--force` overrides for a
30
+ // read-only preview only.
31
+ //
32
+ // The rewrite is TABLE-DRIVEN (CORE_INTERNAL_MAP), not a blind `src/X → @bongos/core/X` path swap,
33
+ // because the core's export names do not all mirror the src layout: `src/bongos/serve-internal.js`
34
+ // is exported as `./serve-internal`, not `./bongos/serve-internal`. The table encodes each mapping
35
+ // so a rewrite can never produce a specifier the package doesn't export. Idempotent (a rewritten
36
+ // `@bongos/core/*` require no longer matches the relative-into-src pattern) and quote-preserving.
37
+
38
+ const fs = require('node:fs');
39
+ const path = require('node:path');
40
+ const { execFileSync } = require('node:child_process');
41
+ const { isPublishable } = require('./publish-manifest');
42
+
43
+ const CORE_PKG = '@bongos/core';
44
+
45
+ // <subpath after `src/` in the relative require> → <the @bongos/core export subpath>.
46
+ // The core exports map (scripts/gds/package-core.js buildPackageJson) MUST export every value
47
+ // here, or the rewritten require won't resolve. Kept deliberately small + explicit.
48
+ // F1 (task 2159): the host-module doorway.
49
+ // F3 (task 2161): the six core internals the game entrypoint server.js wires — added there,
50
+ // in lockstep with widening the core exports map, so a rewrite never outruns an export.
51
+ const CORE_INTERNAL_MAP = {
52
+ // F1 — every host module imports the core doorway; the ONLY core import a module is allowed.
53
+ 'module-api': 'module-api',
54
+ // F3 (task 2161) — the six core internals the game entrypoint server.js wires. Each LHS is the
55
+ // subpath after `src/` in server.js's relative require; each RHS is the core's EXPORTED subpath
56
+ // (widened in package-core.js buildPackageJson in lockstep). Note the divergence: the file at
57
+ // `src/bongos/serve-internal.js` is exported as `./serve-internal` (a curated name), NOT
58
+ // `./bongos/serve-internal` — this table encodes that so the rewrite can't emit an unexported
59
+ // specifier. The other five mirror their src/ path.
60
+ 'bongos/api-path-404': 'bongos/api-path-404',
61
+ 'bongos/uptime-poller': 'bongos/uptime-poller',
62
+ 'bongos/serve-internal': 'serve-internal',
63
+ 'module-loader/loader': 'module-loader/loader',
64
+ 'modules': 'modules',
65
+ 'module-seams': 'module-seams',
66
+ };
67
+
68
+ // require('<./ or ../…>src/<key>') — a RELATIVE require that reaches into a `src/` dir.
69
+ // Group 1 = quote (preserved), group 2 = the `./`/`../…` prefix, group 3 = the key after `src/`.
70
+ // An already-rewritten `require('@bongos/core/…')` has no `../src/` and never matches → idempotent.
71
+ const REQUIRE_RE = /require\((['"])((?:\.\.?\/)+)src\/([^'"]+)\1\)/g;
72
+
73
+ // Pure. Rewrite the core-internal require()s in one file's text per `map`.
74
+ // Returns { text, changes:[{from,to,key}], unmapped:[{key,spec}] }.
75
+ // - changes: requires rewritten to `@bongos/core/<mapped>`.
76
+ // - unmapped: relative-into-`src/` requires whose key is NOT in the table — left UNCHANGED and
77
+ // surfaced so a new core internal a host file starts depending on can't be silently missed
78
+ // (it would still crash at boot; the codemod flags "add an export + a table entry").
79
+ function rewriteCoreRequires(text, map = CORE_INTERNAL_MAP) {
80
+ const changes = [];
81
+ const unmapped = [];
82
+ const out = String(text == null ? '' : text).replace(REQUIRE_RE, (whole, q, _prefix, key) => {
83
+ const mapped = Object.prototype.hasOwnProperty.call(map, key) ? map[key] : null;
84
+ if (mapped == null) {
85
+ unmapped.push({ key, spec: whole });
86
+ return whole; // leave unknown core internals untouched — never guess a specifier
87
+ }
88
+ const to = `require(${q}${CORE_PKG}/${mapped}${q})`;
89
+ changes.push({ from: whole, to, key });
90
+ return to;
91
+ });
92
+ return { text: out, changes, unmapped };
93
+ }
94
+
95
+ // A file is in scope iff the partition classifies it HOST (not shipped in the core package).
96
+ function isHostFile(rel, isPublishableFn = isPublishable) {
97
+ return isPublishableFn(rel) === false;
98
+ }
99
+
100
+ // Post-split consumer signal: the instance's own core doorway is gone. In the monolith
101
+ // `src/module-api.js` exists, so this is false and the CLI refuses (protecting the monolith).
102
+ function looksLikeConsumer(root, { existsFn = fs.existsSync } = {}) {
103
+ return !existsFn(path.join(root, 'src', 'module-api.js'));
104
+ }
105
+
106
+ // Skip vendored deps, VCS metadata, the `.claude/` topology surface (materialize's job), and
107
+ // build output. `dist/` in particular holds an extracted @bongos/core tarball whose bundled
108
+ // core modules carry the SAME `../../src/module-api` requires — scanning it would be noise
109
+ // (it's gitignored throwaway, never part of the consumer).
110
+ const SKIP_DIRS = new Set(['node_modules', '.git', '.claude', 'dist', 'coverage']);
111
+
112
+ // Preferred source scope: git-TRACKED *.js files. A consumer tree is a git repo at cutover, and
113
+ // tracking is the honest "source" oracle — it excludes every gitignored build artifact (dist/,
114
+ // coverage/, …) without enumerating them. Returns repo-relative POSIX paths, or null when git is
115
+ // unavailable / this isn't a repo (→ caller falls back to the filesystem walk).
116
+ function gitTrackedJsFiles(root, { runGit } = {}) {
117
+ const run = runGit || ((args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }));
118
+ let out;
119
+ try { out = run(['ls-files', '-z', '*.js']); } catch { return null; }
120
+ if (typeof out !== 'string') return null;
121
+ return out.split('\0').filter(Boolean).map((p) => p.split(path.sep).join('/')).sort();
122
+ }
123
+
124
+ // Recursively collect *.js files under `root`, skipping SKIP_DIRS. Returns repo-relative POSIX
125
+ // paths (what isPublishable expects). The fallback when git tracking is unavailable.
126
+ function collectJsFiles(root, { fsImpl = fs } = {}) {
127
+ const found = [];
128
+ const walk = (absDir) => {
129
+ let entries;
130
+ try { entries = fsImpl.readdirSync(absDir, { withFileTypes: true }); } catch { return; }
131
+ for (const ent of entries) {
132
+ if (ent.isDirectory()) {
133
+ if (SKIP_DIRS.has(ent.name)) continue;
134
+ walk(path.join(absDir, ent.name));
135
+ } else if (ent.isFile() && ent.name.endsWith('.js')) {
136
+ found.push(path.relative(root, path.join(absDir, ent.name)).split(path.sep).join('/'));
137
+ }
138
+ }
139
+ };
140
+ walk(root);
141
+ return found.sort();
142
+ }
143
+
144
+ function underSkipDir(rel) {
145
+ return SKIP_DIRS.has(rel.split('/')[0]);
146
+ }
147
+
148
+ // The candidate *.js set to consider: git-tracked source when available, else the filesystem walk.
149
+ // SKIP_DIRS is enforced on BOTH paths — git ls-files does not know about it, and `.claude/` in
150
+ // particular is materialize's + _core-resolve.js's domain (task 2139), never the codemod's.
151
+ function candidateJsFiles(root, { fsImpl = fs, runGit } = {}) {
152
+ const tracked = gitTrackedJsFiles(root, { runGit });
153
+ const list = tracked && tracked.length ? tracked : collectJsFiles(root, { fsImpl });
154
+ return list.filter((rel) => !underSkipDir(rel));
155
+ }
156
+
157
+ // Plan the codemod over a consumer `root`: which host files change, and any unmapped core requires.
158
+ // Pure-ish (reads disk via injectable deps for tests). Does NOT write.
159
+ function planCodemod({ root, fsImpl = fs, runGit, isPublishableFn = isPublishable, map = CORE_INTERNAL_MAP } = {}) {
160
+ const files = [];
161
+ const unmapped = [];
162
+ for (const rel of candidateJsFiles(root, { fsImpl, runGit })) {
163
+ if (!isHostFile(rel, isPublishableFn)) continue; // core file → ships with the package, leave it
164
+ let raw;
165
+ try { raw = fsImpl.readFileSync(path.join(root, rel), 'utf8'); } catch { continue; }
166
+ const res = rewriteCoreRequires(raw, map);
167
+ if (res.unmapped.length) for (const u of res.unmapped) unmapped.push({ rel, ...u });
168
+ if (res.changes.length) files.push({ rel, changes: res.changes, newText: res.text });
169
+ }
170
+ return { files, unmapped };
171
+ }
172
+
173
+ // ---- CLI -------------------------------------------------------------------
174
+ function parseArgs(argv) {
175
+ const a = { root: process.cwd(), apply: false, force: false };
176
+ for (let i = 0; i < argv.length; i++) {
177
+ if (argv[i] === '--root') a.root = argv[++i];
178
+ else if (argv[i] === '--apply') a.apply = true;
179
+ else if (argv[i] === '--force') a.force = true;
180
+ }
181
+ return a;
182
+ }
183
+
184
+ function main(argv = process.argv.slice(2), { log = console.log, err = console.error } = {}) {
185
+ const args = parseArgs(argv);
186
+ const root = path.resolve(args.root);
187
+
188
+ if (!looksLikeConsumer(root) && !args.force) {
189
+ err(`[cutover-codemod] REFUSING: ${path.join(root, 'src/module-api.js')} still exists — this looks like the monolith,`);
190
+ err(' not a post-split consumer. This codemod is a CUTOVER transform and must not edit the monolith');
191
+ err(' (there @bongos/core is not a dep and fitness would reject the rewrite). Run it after the partition,');
192
+ err(' or pass --force for a read-only preview only.');
193
+ return 2;
194
+ }
195
+
196
+ const plan = planCodemod({ root });
197
+
198
+ for (const u of plan.unmapped) {
199
+ err(`[cutover-codemod] WARNING ${u.rel}: unmapped core-internal require ${u.spec} — add a CORE_INTERNAL_MAP`);
200
+ err(` entry + a matching core export before cutover, or this require will crash at boot.`);
201
+ }
202
+
203
+ if (!plan.files.length) {
204
+ log('[cutover-codemod] no host files need rewriting (already converted, or none present).');
205
+ return 0;
206
+ }
207
+
208
+ const verb = args.apply ? 'rewrote' : 'would rewrite';
209
+ for (const f of plan.files) {
210
+ log(`[cutover-codemod] ${verb} ${f.rel} (${f.changes.length} require${f.changes.length === 1 ? '' : 's'}):`);
211
+ for (const c of f.changes) log(` ${c.from} → ${c.to}`);
212
+ if (args.apply) fs.writeFileSync(path.join(root, f.rel), f.newText);
213
+ }
214
+
215
+ const total = plan.files.reduce((n, f) => n + f.changes.length, 0);
216
+ if (args.apply) {
217
+ log(`[cutover-codemod] ✓ rewrote ${total} require(s) across ${plan.files.length} host file(s).`);
218
+ } else {
219
+ log(`[cutover-codemod] dry-run: ${total} require(s) across ${plan.files.length} host file(s). Re-run with --apply to write.`);
220
+ }
221
+ return 0;
222
+ }
223
+
224
+ module.exports = {
225
+ CORE_PKG,
226
+ CORE_INTERNAL_MAP,
227
+ REQUIRE_RE,
228
+ rewriteCoreRequires,
229
+ isHostFile,
230
+ looksLikeConsumer,
231
+ gitTrackedJsFiles,
232
+ collectJsFiles,
233
+ candidateJsFiles,
234
+ planCodemod,
235
+ parseArgs,
236
+ main,
237
+ };
238
+
239
+ if (require.main === module) {
240
+ process.exit(main());
241
+ }
@@ -1170,6 +1170,59 @@ function checkCoreRequiresExcludedConfig() {
1170
1170
  };
1171
1171
  }
1172
1172
 
1173
+ // ============================================================================
1174
+ // Check 16b — core code must not require an instance-EXCLUDED module (task 2135)
1175
+ // ============================================================================
1176
+ //
1177
+ // Sibling of Check 16 for the code→MODULE class. A publishable core file that
1178
+ // require()s a boundary-EXCLUDED module (modules/game, modules/art-pipeline,
1179
+ // modules/character-anim, …) throws MODULE_NOT_FOUND once that module lives in the
1180
+ // consumer's instance repo rather than the core package (R89 split). The cure is
1181
+ // either to reclassify the module INTO core (if it belongs there — e.g. provisioning,
1182
+ // sibling of dev-box) or to exclude the requiring file with the feature it serves
1183
+ // (art tooling). Catches BOTH require forms: a direct literal
1184
+ // require('../modules/<key>/…') AND a require(path.join(<root>, 'modules/<key>/…')).
1185
+ // Only an EXCLUDED module required from a PUBLISHABLE file is a violation; requiring a
1186
+ // publishable (core) module is fine, and the loader's DYNAMIC dispatch
1187
+ // (path.join(dir,'routes',rk) — no literal modules/<key>/ string) is invisible here.
1188
+ const MODULE_PATH_IN_STRING_RE = /['"](?:[^'"]*\/)?modules\/([a-z0-9][a-z0-9-]*)\/[^'"]*['"]/g;
1189
+
1190
+ function checkCoreRequiresExcludedModule() {
1191
+ const { isPublishable } = require('./publish-manifest');
1192
+ const violations = [];
1193
+ const seen = new Set();
1194
+ const allAbs = [
1195
+ ...['src', 'scripts'].flatMap((d) => walkJs(path.join(CORE_ROOT, d))),
1196
+ ...walkJsAllRoots('modules'),
1197
+ ];
1198
+ for (const abs of allAbs) {
1199
+ const relFile = rel(abs);
1200
+ if (seen.has(relFile)) continue;
1201
+ seen.add(relFile);
1202
+ if (!isPublishable(relFile)) continue; // only CORE (publishable) code can crash a vanilla boot
1203
+ for (const raw of fs.readFileSync(abs, 'utf8').split('\n')) {
1204
+ const trimmed = raw.trim();
1205
+ if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
1206
+ if (!/require\s*\(/.test(raw)) continue; // only lines that actually require()
1207
+ let m;
1208
+ MODULE_PATH_IN_STRING_RE.lastIndex = 0;
1209
+ while ((m = MODULE_PATH_IN_STRING_RE.exec(raw))) {
1210
+ const key = m[1];
1211
+ if (isPublishable(`modules/${key}/module.json`)) continue; // requiring a CORE module is fine
1212
+ violations.push(`${relFile} → require(...'modules/${key}/…') pulls in an instance-EXCLUDED module — reclassify it into core (if it belongs there) or exclude ${relFile} with the feature it serves, so a fresh core boots (task 2135).`);
1213
+ }
1214
+ }
1215
+ }
1216
+ return {
1217
+ name: 'core never requires an excluded module (task 2135)',
1218
+ ok: violations.length === 0,
1219
+ hardFail: violations.length > 0,
1220
+ violations,
1221
+ warnings: [],
1222
+ note: "a publishable src/·scripts/·modules/ .js must not require('…modules/<key>/…') when that module is boundary-excluded — MODULE_NOT_FOUND post-split (R89).",
1223
+ };
1224
+ }
1225
+
1173
1226
  // ============================================================================
1174
1227
  // Check 17 — no route hand-rolls a FLAT error (BV1 R03 / task 1990 / ADR 0116)
1175
1228
  // ============================================================================
@@ -1360,6 +1413,7 @@ const CHECKS = [
1360
1413
  checkKernelPromptsNoHardcodedIdentity, // BV1.R76 — kernel prompts read identity from branding()
1361
1414
  checkHallSectionRegistry,
1362
1415
  checkCoreRequiresExcludedConfig, // task 1824 — core→instance-config require guard
1416
+ checkCoreRequiresExcludedModule, // Check 16b — task 2135: core→excluded-module require guard (R89 split)
1363
1417
  checkErrorEnvelope, // Check 17 — BV1 R03 / task 1990: no route hand-rolls a flat error (ADR 0116)
1364
1418
  checkWriteRoutesValidated, // Check 18 — BV1 R12 / task 1999: every body-reading write route validates (ADR 0118)
1365
1419
  ];
@@ -133,7 +133,13 @@ const INSTANCE_MIGRATE_SCRIPT = `bash ${INSTANCE_CORE_PKG_DIR}/scripts/migrate.s
133
133
  // standalone instance runs. Preserves any pre-existing fields + scripts (fills only what
134
134
  // is missing); greenfield OWNS the core dep, so it is always (re)set to `coreDep`. Never
135
135
  // spawns npm — generating the lockfile is a separate imperative step in main().
136
- function buildInstancePackageJson(existing, { name = 'cloudbongos-instance', coreDep } = {}) {
136
+ // `moduleDeps` (ADR 0138 / task 2160): the merged external npm deps of the modules this
137
+ // instance ENABLES. @bongos/core ships lean (express+pg, ADR 0108), so a bundled module that
138
+ // needs more (e.g. discord → discord.js) declares it in its module.json `dependencies`, and the
139
+ // consumer's package.json must carry it or boot crashes on the missing module (rehearsal F2).
140
+ // Precedence: module deps FILL gaps, an explicit pre-existing pin WINS over them, @bongos/core
141
+ // is always (re)set. With no moduleDeps this is byte-identical to before.
142
+ function buildInstancePackageJson(existing, { name = 'cloudbongos-instance', coreDep, moduleDeps } = {}) {
137
143
  const dep = coreDep || coreDepRange();
138
144
  const pkg = isObj(existing) ? { ...existing } : {};
139
145
  if (!pkg.name) pkg.name = name;
@@ -141,10 +147,37 @@ function buildInstancePackageJson(existing, { name = 'cloudbongos-instance', cor
141
147
  pkg.scripts = { ...(isObj(pkg.scripts) ? pkg.scripts : {}) };
142
148
  if (!pkg.scripts.start) pkg.scripts.start = INSTANCE_START_SCRIPT;
143
149
  if (!pkg.scripts.migrate) pkg.scripts.migrate = INSTANCE_MIGRATE_SCRIPT;
144
- pkg.dependencies = { ...(isObj(pkg.dependencies) ? pkg.dependencies : {}), [CORE_PKG]: dep };
150
+ pkg.dependencies = {
151
+ ...(isObj(moduleDeps) ? moduleDeps : {}),
152
+ ...(isObj(pkg.dependencies) ? pkg.dependencies : {}),
153
+ [CORE_PKG]: dep,
154
+ };
145
155
  return pkg;
146
156
  }
147
157
 
158
+ // The external npm deps a single module declares in its module.json `dependencies` (ADR 0138).
159
+ // I/O (reads the core-package module manifest); fail-soft → {} when absent/unreadable.
160
+ function moduleDeclaredDeps(moduleKey, { coreRoot = resolveCoreRoot(), fsImpl = fs } = {}) {
161
+ try {
162
+ const mj = JSON.parse(fsImpl.readFileSync(path.join(coreRoot, 'modules', moduleKey, 'module.json'), 'utf8'));
163
+ return isObj(mj.dependencies) ? mj.dependencies : {};
164
+ } catch {
165
+ return {};
166
+ }
167
+ }
168
+
169
+ // Merge the declared deps of every ENABLED module (modulesConfig = { modules: { key: bool } })
170
+ // into one object. PURE — `readDeps(key)` is injected (defaults to moduleDeclaredDeps) so this is
171
+ // testable with no disk. A later-enabled module's range wins on a key collision (rare).
172
+ function enabledModuleDeps(modulesConfig, { readDeps = (k) => moduleDeclaredDeps(k) } = {}) {
173
+ const enabled = isObj(modulesConfig) && isObj(modulesConfig.modules) ? modulesConfig.modules : {};
174
+ const out = {};
175
+ for (const [key, on] of Object.entries(enabled)) {
176
+ if (on === true) Object.assign(out, readDeps(key));
177
+ }
178
+ return out;
179
+ }
180
+
148
181
  // ---------------------------------------------------------------------------
149
182
  // Pure builders (exported for tests — no I/O, no network)
150
183
  // ---------------------------------------------------------------------------
@@ -525,7 +558,11 @@ function writeConfigs(spec, { dir, force, dryRun, coreDep, log = console.log })
525
558
  const pkgName = slugify((spec.identity && spec.identity.productName) || '', 'cloudbongos-instance');
526
559
  let existingPkg = null;
527
560
  if (fs.existsSync(pkgPath)) { try { existingPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); } catch { existingPkg = null; } }
528
- const pkg = buildInstancePackageJson(existingPkg, { name: pkgName, coreDep });
561
+ // Compose the deps of the ENABLED modules (ADR 0138 / task 2160) so an instance that turns a
562
+ // bundled module on gets that module's npm deps (e.g. discord → discord.js) in its package.json,
563
+ // rather than crashing at boot on a dep @bongos/core deliberately doesn't carry (rehearsal F2).
564
+ const moduleDeps = enabledModuleDeps(modules);
565
+ const pkg = buildInstancePackageJson(existingPkg, { name: pkgName, coreDep, moduleDeps });
529
566
  const pinNote = pkg.dependencies[CORE_PKG];
530
567
 
531
568
  if (dryRun) {
@@ -908,6 +945,23 @@ async function main(argv) {
908
945
  console.log('\nMaterializing the .claude/ methodology surface:');
909
946
  materializeClaude({ coreRoot: resolveCoreRoot(), instanceDir: dir, dryRun, log: console.log });
910
947
 
948
+ // Regenerate the per-instance API artifacts (OpenAPI spec + api-reference + typed
949
+ // client) from the PINNED core's routes so this fresh consumer's COMMITTED artifacts
950
+ // match its core from day one — not the core package's build-time snapshot (task 2141).
951
+ // They ship as CORE starters (task 2078 — the hall/docs must render before first regen)
952
+ // yet are per-instance GENERATED; `bongos upgrade` does the same on a bump, this closes
953
+ // the gap at standup. Best-effort: a hiccup warns, never fails init — the instance-root
954
+ // drift guard (fitness `gen-api-docs --check` + the api_client byte-identity test)
955
+ // catches any lag on the consumer's first CI.
956
+ if (dryRun) {
957
+ console.log('\n[dry-run] would regenerate the API artifacts (OpenAPI spec + reference + typed client) from the pinned core');
958
+ } else {
959
+ console.log('\nRegenerating the API artifacts from the pinned core (OpenAPI spec + reference + typed client):');
960
+ const regen = upgrade.regenerateApiArtifacts({ instanceDir: dir, coreRoot: resolveCoreRoot() });
961
+ if (regen.ok) console.log(` ✓ regenerated (${regen.ran.join(', ')}) → docs/api/ + docs/api-reference.md + clients/bongos-client/`);
962
+ else console.error(` ! API-artifact regen skipped (${regen.reason}) — regenerate in ${dir} by hand; the instance-root fitness gate flags the lag otherwise.`);
963
+ }
964
+
911
965
  let seedResult = {};
912
966
  if (!noSeed) seedResult = (await seedViaApi(spec, { dryRun })) || {};
913
967
  else console.log(' (--no-seed: skipped version/task seeding)');
@@ -933,6 +987,8 @@ module.exports = {
933
987
  KNOWN_MODULES, HOSTING_SHAPES, CORE_PKG,
934
988
  // greenfield installability (ADR 0108 gap 1/4, task 2053)
935
989
  buildInstancePackageJson, coreVersionSafe, INSTANCE_START_SCRIPT, INSTANCE_MIGRATE_SCRIPT,
990
+ // enabled-module dep composition (ADR 0138 / task 2160)
991
+ moduleDeclaredDeps, enabledModuleDeps,
936
992
  };
937
993
 
938
994
  if (require.main === module) {
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // lint-infra-exec.js — task 2137 (ADR 0108 configurable-root). The dry-run/lint that proves every
4
+ // host systemd unit running a CORE script still resolves AFTER the R89 split.
5
+ //
6
+ // The units (box-*, cohost-*, provision-*, search-index-db) keep living in the instance repo
7
+ // (infra/ is host topology) while scripts/gds/ moves into node_modules/@bongos/core, so each
8
+ // ExecStart runs through infra/core-node.sh, which resolves the script under the instance root OR
9
+ // its node_modules/@bongos/core (both layouts). "Resolves in a consumer layout" is provable from
10
+ // the package manifest: the referenced core script must be a PUBLISHABLE core file (isPublishable →
11
+ // it ships inside @bongos/core → present at node_modules/@bongos/core/<rel> post-install). We also
12
+ // assert it exists at the core root today (monolith resolution + a typo catch) and that NO unit
13
+ // still uses the bare repo-relative `node scripts/…` form (which resolves nothing post-split).
14
+ //
15
+ // Usage: node scripts/gds/lint-infra-exec.js (exit 0 = every unit resolves; 1 = violation)
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const { isPublishable } = require('./publish-manifest');
20
+ const { resolveInstanceRoot, resolveCoreRoot } = require('../../src/instance-config');
21
+
22
+ const LAUNCHER = 'infra/core-node.sh';
23
+ // Repointed form: ExecStart=/usr/bin/env bash infra/core-node.sh <core-rel> [args]
24
+ const LAUNCHER_EXEC = /^ExecStart=.*\binfra\/core-node\.sh\s+(\S+)/;
25
+ // Old bare form: ExecStart=<node> scripts/… — must no longer appear on a core-script unit.
26
+ const BARE_EXEC = /^ExecStart=\S*node\s+(scripts\/\S+)/;
27
+
28
+ // Pure-ish (reads the fs): classify every infra/*.service ExecStart. Returns
29
+ // { ok, checked:[{unit,rel}], violations:[{unit,rel,why}] }. Roots are injectable for tests.
30
+ function lintInfraExec({ instanceRoot, coreRoot } = {}) {
31
+ const iRoot = instanceRoot || resolveInstanceRoot();
32
+ const cRoot = coreRoot || resolveCoreRoot();
33
+ const dir = path.join(iRoot, 'infra');
34
+ const checked = [];
35
+ const violations = [];
36
+ const files = fs.existsSync(dir) ? fs.readdirSync(dir).filter((f) => f.endsWith('.service')).sort() : [];
37
+ for (const f of files) {
38
+ for (const raw of fs.readFileSync(path.join(dir, f), 'utf8').split('\n')) {
39
+ const L = raw.trim();
40
+ if (!L.startsWith('ExecStart=')) continue;
41
+ const viaLauncher = LAUNCHER_EXEC.exec(L);
42
+ if (viaLauncher) {
43
+ const rel = viaLauncher[1];
44
+ if (!isPublishable(rel)) {
45
+ violations.push({ unit: f, rel, why: 'not a publishable core file — ABSENT from node_modules/@bongos/core in a consumer layout' });
46
+ } else if (!fs.existsSync(path.join(cRoot, rel))) {
47
+ violations.push({ unit: f, rel, why: `does not exist at the core root (${cRoot})` });
48
+ } else {
49
+ checked.push({ unit: f, rel });
50
+ }
51
+ continue;
52
+ }
53
+ const bare = BARE_EXEC.exec(L);
54
+ if (bare && bare[1].startsWith('scripts/')) {
55
+ violations.push({ unit: f, rel: bare[1], why: `bare repo-relative ExecStart — route it through ${LAUNCHER}` });
56
+ }
57
+ }
58
+ }
59
+ return { ok: violations.length === 0, checked, violations };
60
+ }
61
+
62
+ if (require.main === module) {
63
+ const r = lintInfraExec();
64
+ for (const c of r.checked) console.log(` ✓ ${c.unit.padEnd(38)} ${c.rel} (publishable → resolves in a consumer layout)`);
65
+ for (const v of r.violations) console.error(` ✗ ${v.unit.padEnd(38)} ${v.rel} — ${v.why}`);
66
+ console.log(`\nlint-infra-exec: ${r.checked.length} core-script unit(s) resolve; ${r.violations.length} violation(s)`);
67
+ process.exit(r.ok ? 0 : 1);
68
+ }
69
+
70
+ module.exports = { lintInfraExec };
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // lint-workflow-exec.js — task 2136 (ADR 0108 configurable-root). The dry-run/lint that proves
4
+ // every .github/workflows/*.yml invocation of a CORE script still resolves AFTER the R89 split.
5
+ //
6
+ // Workflows keep living in the instance repo (.github/ is host topology) while scripts/gds/* +
7
+ // scripts/migrate.sh move into @bongos/core, so each core invocation runs through the both-layouts
8
+ // resolver: infra/core-node.sh for node targets, infra/core-path.sh for bash targets. "Resolves in
9
+ // a consumer layout" is provable from the package manifest: the referenced core script must be a
10
+ // PUBLISHABLE core file (ships in @bongos/core → present at node_modules/@bongos/core post-install)
11
+ // AND exist at the core root today. A BARE `node scripts/gds/x.js` / `bash scripts/migrate.sh` (not
12
+ // routed through the resolver) is a violation — it resolves nothing from the instance root
13
+ // post-split. Host scripts (scripts/art/*) stay at the instance root, so they are NOT flagged.
14
+ //
15
+ // Usage: node scripts/gds/lint-workflow-exec.js (exit 0 = all routed + resolve; 1 = violation)
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const { isPublishable } = require('./publish-manifest');
20
+ const { resolveInstanceRoot, resolveCoreRoot } = require('../../src/instance-config');
21
+
22
+ // Routed: `… core-node.sh <rel>` / `… core-path.sh <rel>` (rel = a scripts/… core path).
23
+ const ROUTED = /core-(?:node|path)\.sh\s+(scripts\/[^\s"')]+)/g;
24
+ // Bare core invocation the resolver should have wrapped. Core scripts = scripts/gds/*.{js,sh} +
25
+ // scripts/migrate.sh; host scripts/art/* deliberately excluded (they stay at the instance root).
26
+ const BARE = /(?:^|[^/\w])(?:node|bash)\s+(scripts\/(?:gds\/[^\s"']+\.(?:js|sh)|migrate\.sh))/;
27
+
28
+ function lintWorkflowExec({ instanceRoot, coreRoot } = {}) {
29
+ const iRoot = instanceRoot || resolveInstanceRoot();
30
+ const cRoot = coreRoot || resolveCoreRoot();
31
+ const dir = path.join(iRoot, '.github', 'workflows');
32
+ const checked = [];
33
+ const violations = [];
34
+ const files = fs.existsSync(dir) ? fs.readdirSync(dir).filter((f) => /\.ya?ml$/.test(f)).sort() : [];
35
+ for (const f of files) {
36
+ for (const raw of fs.readFileSync(path.join(dir, f), 'utf8').split('\n')) {
37
+ const line = raw.replace(/\s+$/, '');
38
+ if (line.trim().startsWith('#')) continue; // a YAML/shell comment — reference, not an invocation
39
+ let m;
40
+ ROUTED.lastIndex = 0;
41
+ while ((m = ROUTED.exec(line)) !== null) {
42
+ const rel = m[1];
43
+ if (!isPublishable(rel)) violations.push({ file: f, rel, why: 'routed but NOT a publishable core file — absent from @bongos/core in a consumer layout' });
44
+ else if (!fs.existsSync(path.join(cRoot, rel))) violations.push({ file: f, rel, why: `routed but does not exist at the core root (${rel})` });
45
+ else checked.push({ file: f, rel });
46
+ }
47
+ if (/core-(?:node|path)\.sh/.test(line)) continue; // routed line — the arg was handled above
48
+ const bare = BARE.exec(line);
49
+ if (bare) violations.push({ file: f, rel: bare[1], why: 'bare core invocation — route it through infra/core-node.sh (node) or infra/core-path.sh (bash)' });
50
+ }
51
+ }
52
+ return { ok: violations.length === 0, checked, violations };
53
+ }
54
+
55
+ if (require.main === module) {
56
+ const r = lintWorkflowExec();
57
+ for (const c of r.checked) console.log(` ✓ ${c.file.padEnd(26)} ${c.rel} (publishable → resolves in a consumer layout)`);
58
+ for (const v of r.violations) console.error(` ✗ ${v.file.padEnd(26)} ${v.rel} — ${v.why}`);
59
+ console.log(`\nlint-workflow-exec: ${r.checked.length} routed core invocation(s) resolve; ${r.violations.length} violation(s)`);
60
+ process.exit(r.ok ? 0 : 1);
61
+ }
62
+
63
+ module.exports = { lintWorkflowExec };
@@ -55,9 +55,11 @@ const { nextFree } = require(path.join(REPO_ROOT, 'scripts/gds/next-number.js'))
55
55
  // deterministic regardless of file order.
56
56
  const KNOWN_SECRET_SHAPES = SECRET_PATTERNS.map((re) => new RegExp(re.source, re.flags.replace('g', '')));
57
57
 
58
- // Core migrations are migrations/NNN_slug.sql (3+ digit prefix). Module migrations
59
- // live under modules/<key>/migrations/ and are numbered independently — the
60
- // claim-time allocator only reads the core dir, so only core numbering can diverge.
58
+ // Core migrations are migrations/NNN_slug.sql (3+ digit prefix) DIRECTLY under
59
+ // migrations/. Module migrations live under modules/<key>/migrations/ and instance
60
+ // SEEDS under migrations/instance/ (ADR 0105) — both numbered independently and
61
+ // excluded from the publishable mirror; the claim-time allocator only reads the
62
+ // top-level core dir, so only core numbering can diverge.
61
63
  const MIGRATION_BASENAME_RE = /^(\d{3,})_.*\.sql$/;
62
64
 
63
65
  // ---- pure helpers (exported for tests) ------------------------------------
@@ -67,8 +69,15 @@ function migrationNumbersFromPaths(paths) {
67
69
  const out = new Set();
68
70
  for (const p of paths || []) {
69
71
  const s = String(p);
70
- if (!s.startsWith('migrations/')) continue; // core dir only
71
- const m = MIGRATION_BASENAME_RE.exec(s.split('/').pop());
72
+ if (!s.startsWith('migrations/')) continue; // the migrations dir …
73
+ // … and ONLY its top level. migrations/instance/NNN_*.sql are this instance's host
74
+ // seeds (ADR 0105), excluded from the publishable mirror (task 2140) — counting them
75
+ // here put every instance seed in the PRIVATE set but not the MIRROR set, a false
76
+ // private⇄mirror divergence that red-lined the unit gate (task 2152). A path with a
77
+ // further slash is a subdir, never a core migration.
78
+ const rest = s.slice('migrations/'.length);
79
+ if (rest.includes('/')) continue;
80
+ const m = MIGRATION_BASENAME_RE.exec(rest);
72
81
  if (m) out.add(parseInt(m[1], 10));
73
82
  }
74
83
  return out;
@@ -171,6 +171,14 @@ function deriveInstanceLiterals(branding = {}, extra = {}) {
171
171
  add(owners, branding.firstAdmin);
172
172
  const identity = branding.identity || {};
173
173
  add(tokens, identity.worldName);
174
+ // The PRODUCT name is instance identity too. It was leaking into the published core (the
175
+ // onboarding primer described this instance's product as the reader's own project) because
176
+ // only worldName was derived — the redactor never saw it AND findLeaks couldn't gate it.
177
+ // Deriving it here closes both in one place. (task 2153)
178
+ add(tokens, identity.productName);
179
+ // The reward-currency label is per-instance identity as well; a vanilla core must not ship
180
+ // another instance's currency name (the currency/karma onboarding doc carried it verbatim).
181
+ add(tokens, (branding.currency || {}).label);
174
182
  // The fulfillment/parent company is instance identity too (a coined single word like
175
183
  // "Acme"); redact its name + its site's registrable label.
176
184
  const company = identity.company || {};
@@ -163,6 +163,10 @@ async function run() {
163
163
  log(' RECOMMENDED — one click: in the control-plane hall, open “Start a project” →');
164
164
  log(' “Set up GitHub sign-in”. Approve the pre-filled GitHub App; the control plane');
165
165
  log(' places the credentials automatically (no secret to paste). Then skip to ⑦.');
166
+ log(' ⚠ Terminal CLI only: the one-click App cannot enable Device Flow via the manifest,');
167
+ log(' but `bongos login` needs it. After approving, open the app (github.com → Settings');
168
+ log(' → Developer settings → GitHub Apps → your app), check "Enable Device Flow", save.');
169
+ log(' Web sign-in works without this.');
166
170
  log(' — or, to create an OAuth app BY HAND —');
167
171
  log(` ${oa.location}`);
168
172
  log(' (Developer settings is under your ACCOUNT, not the repository.)');
@@ -240,13 +240,28 @@ function buildPackageJson({ version, sourceDeps, sourceOverrides }) {
240
240
  main: 'src/platform-server.js',
241
241
  bin: { bongos: 'bin/bongos.js' },
242
242
  // Named exports a consumer imports directly, alongside the default entry
243
- // (buildPlatformApp, from `main`): mountInternalSurfaces (the shared
244
- // host-routing surface both server.js and platform-server.js call) and the
245
- // module-api doorway a module's routes/seams are authored against.
243
+ // (buildPlatformApp, from `main`). Two groups:
244
+ // • the platform surface — serve-internal (the shared host-routing surface
245
+ // both server.js and platform-server.js call) + the module-api doorway a
246
+ // module's routes/seams are authored against.
247
+ // • the GAME-HOST KIT (rehearsal F3, task 2161) — the core internals a
248
+ // game-bearing instance's own entrypoint (server.js, kept host per task
249
+ // 2134) wires directly: the API-404 shaping, the uptime poller, the module
250
+ // loader, the enabled-module gate, and the seam registry. A game-less
251
+ // instance boots via `.`/platform-server.js and never imports these; a game
252
+ // instance's server.js requires them, and the cutover codemod
253
+ // (scripts/gds/cutover-codemod.js) rewrites its relative `./src/*` requires
254
+ // to exactly these subpaths. Every subpath the codemod's CORE_INTERNAL_MAP
255
+ // targets MUST appear here or the rewritten require won't resolve.
246
256
  exports: {
247
257
  '.': './src/platform-server.js',
248
258
  './serve-internal': './src/bongos/serve-internal.js',
249
259
  './module-api': './src/module-api.js',
260
+ './bongos/api-path-404': './src/bongos/api-path-404.js',
261
+ './bongos/uptime-poller': './src/bongos/uptime-poller.js',
262
+ './module-loader/loader': './src/module-loader/loader.js',
263
+ './modules': './src/modules.js',
264
+ './module-seams': './src/module-seams.js',
250
265
  },
251
266
  engines: { node: '>=22' },
252
267
  // Pre-launch distribution posture (task 2089 / ADR 0134): publish PRIVATE by