@bongos/core 1.17.0 → 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 (230) hide show
  1. package/.bongos-core.json +363 -312
  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/CLAUDE.md +11 -238
  15. package/GOVERNANCE.md +1 -1
  16. package/README.md +1 -1
  17. package/bin/bongos.js +1 -0
  18. package/clients/bongos-client/README.md +4 -4
  19. package/clients/bongos-client/bongos-client.global.js +14 -1
  20. package/clients/bongos-client/index.cjs +14 -1
  21. package/clients/bongos-client/index.d.ts +19 -2
  22. package/clients/bongos-client/index.mjs +11 -1
  23. package/clients/bongos-client/package.json +1 -1
  24. package/docs/adr/0001-stack-choice-single-vps.md +1 -1
  25. package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
  26. package/docs/adr/0019-red-team-bounty-payout-table.md +3 -3
  27. package/docs/adr/0020-security-model-public-stub.md +1 -1
  28. package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +1 -1
  29. package/docs/adr/0024-cloneable-repo-local-first-memory.md +1 -1
  30. package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +4 -4
  31. package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +3 -3
  32. package/docs/adr/0030-builders-hall-live-updates.md +1 -1
  33. package/docs/adr/0031-cloud-dev-environments-for-builders.md +4 -4
  34. package/docs/adr/0032-discord-mirror-of-gds.md +2 -2
  35. package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
  36. package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
  37. package/docs/adr/0044-per-box-live-game-preview.md +1 -1
  38. package/docs/adr/0054-session-token-reward-cost-plus.md +10 -10
  39. package/docs/adr/0057-container-cost-ledger.md +3 -3
  40. package/docs/adr/0062-medusa-instance-model-decoupling.md +4 -4
  41. package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +2 -2
  42. package/docs/adr/0085-remove-default-per-builder-spend-cap.md +1 -1
  43. package/docs/adr/0086-goal-scoped-work-hierarchy.md +1 -1
  44. package/docs/adr/0096-require-reward-before-workable.md +9 -9
  45. package/docs/adr/0097-retroactive-reward-backfill.md +3 -3
  46. package/docs/adr/0100-two-repo-instance-model.md +1 -1
  47. package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +3 -3
  48. package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +3 -3
  49. package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +1 -1
  50. package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +6 -6
  51. package/docs/adr/0133-github-app-manifest-onboarding-signin.md +2 -0
  52. package/docs/adr/0134-private-first-npm-distribution.md +20 -6
  53. package/docs/adr/0135-module-upstream-submission-interim-queue.md +34 -0
  54. package/docs/adr/0136-update-channel-subscription-policy.md +51 -0
  55. package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +40 -0
  56. package/docs/adr/0138-bundled-module-deps-declared-per-module.md +45 -0
  57. package/docs/adr/README.md +14 -10
  58. package/docs/api/openapi.json +315 -6
  59. package/docs/api-reference.md +10 -5
  60. package/docs/architecture.md +3 -3
  61. package/docs/branding-contract.md +5 -4
  62. package/docs/canonical-permissions.md +1 -1
  63. package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
  64. package/docs/file-map.md +20 -19
  65. package/docs/handoff-template.md +2 -2
  66. package/docs/modules-contract.md +8 -1
  67. package/docs/onboarding/diagrams/README.md +7 -7
  68. package/docs/onboarding/drachmae-and-karma.md +18 -18
  69. package/docs/onboarding/primer.md +9 -9
  70. package/docs/recipes/audit-replay.md +2 -2
  71. package/docs/recipes/builder-box-lifecycle.md +2 -2
  72. package/docs/recipes/claude-design-loop.md +1 -1
  73. package/docs/recipes/instance-deploy.md +4 -4
  74. package/docs/recipes/instance-first-run.md +51 -0
  75. package/docs/recipes/ops-gotchas.md +1 -1
  76. package/docs/recipes/packaging-the-core.md +5 -5
  77. package/docs/recipes/private-npm-distribution.md +39 -12
  78. package/docs/recipes/self-host.md +1 -1
  79. package/docs/recipes/staging-environment.md +1 -1
  80. package/docs/recipes/upgrading-the-core.md +63 -8
  81. package/docs/research/cursor-competitive-analysis.md +1 -1
  82. package/docs/research/llm-model-agnostic-and-cheaper-grader.md +1 -1
  83. package/migrations/core_191_module_submissions.sql +45 -0
  84. package/modules/autonomy/CLAUDE.md +11 -45
  85. package/modules/builder-settings/CLAUDE.md +11 -54
  86. package/modules/dev-box/CLAUDE.md +11 -34
  87. package/modules/discord/CLAUDE.md +11 -46
  88. package/modules/discord/module.json +2 -1
  89. package/modules/economy/CLAUDE.md +11 -44
  90. package/modules/economy/achievements.js +9 -1
  91. package/modules/economy/credits.js +10 -1
  92. package/modules/grading/CLAUDE.md +11 -38
  93. package/modules/hall-ui/CLAUDE.md +11 -57
  94. package/modules/hall-ui/public/builders.js +71 -4
  95. package/modules/hall-ui/public/goal-inbox.js +191 -0
  96. package/modules/hall-ui/public/goals-page.js +227 -22
  97. package/modules/hall-ui/public/goals.html +16 -0
  98. package/modules/hall-ui/public/hall-widgets.js +14 -1
  99. package/modules/hall-ui/public/index.html +3 -0
  100. package/modules/hall-ui/public/new-project-wizard.js +6 -4
  101. package/modules/hall-ui/public/style.css +43 -0
  102. package/modules/ideas/CLAUDE.md +11 -33
  103. package/modules/lifecycle/CLAUDE.md +11 -59
  104. package/modules/lifecycle/routes/goals.js +80 -0
  105. package/modules/memory/CLAUDE.md +11 -36
  106. package/modules/onboarding/CLAUDE.md +11 -51
  107. package/modules/provisioning/CLAUDE.md +15 -0
  108. package/modules/provisioning/migrations/provisioning_001_tables.sql +160 -0
  109. package/modules/provisioning/migrations/provisioning_002_standalone_shape.sql +11 -0
  110. package/modules/provisioning/migrations/provisioning_003_github_app_manifest.sql +95 -0
  111. package/modules/provisioning/module.json +17 -0
  112. package/modules/provisioning/onboard-plan.js +37 -0
  113. package/modules/provisioning/provisioning.js +539 -0
  114. package/modules/provisioning/rate-limit.js +35 -0
  115. package/modules/provisioning/routes/provisioning.js +607 -0
  116. package/modules/provisioning/tests/provisioning.mjs +232 -0
  117. package/modules/provisioning/ui/.gitkeep +0 -0
  118. package/modules/security/CLAUDE.md +11 -40
  119. package/modules/security/secret-scrub.js +10 -0
  120. package/modules/sessions/CLAUDE.md +11 -35
  121. package/modules/status-ui/CLAUDE.md +11 -43
  122. package/package-lock.json +2 -2
  123. package/package.json +7 -2
  124. package/public-landing/create.html +58 -12
  125. package/public-landing/index.html +40 -21
  126. package/scripts/discord/README.md +1 -1
  127. package/scripts/gds/CLAUDE.md +11 -140
  128. package/scripts/gds/claim.js +98 -48
  129. package/scripts/gds/claude-materialize.js +174 -20
  130. package/scripts/gds/claude-md-template.js +84 -0
  131. package/scripts/gds/cli-lib.js +112 -2
  132. package/scripts/gds/cutover-codemod.js +241 -0
  133. package/scripts/gds/fitness.js +54 -0
  134. package/scripts/gds/gen-api-client.js +10 -10
  135. package/scripts/gds/init.js +112 -16
  136. package/scripts/gds/lint-infra-exec.js +70 -0
  137. package/scripts/gds/lint-workflow-exec.js +63 -0
  138. package/scripts/gds/migration-namespace.js +1 -1
  139. package/scripts/gds/mirror-lineage-check.js +14 -5
  140. package/scripts/gds/mirror-redact.js +8 -0
  141. package/scripts/gds/module.js +118 -4
  142. package/scripts/gds/npmrc.js +93 -0
  143. package/scripts/gds/onboard.js +17 -24
  144. package/scripts/gds/package-core.js +74 -17
  145. package/scripts/gds/provision.js +102 -17
  146. package/scripts/gds/publish-manifest.js +60 -2
  147. package/scripts/gds/run-routine.js +51 -13
  148. package/scripts/gds/run-unit-tests.js +4 -0
  149. package/scripts/gds/seed-adopt-tasks.js +1 -1
  150. package/scripts/gds/status.js +6 -1
  151. package/scripts/gds/task.js +166 -0
  152. package/scripts/gds/update-channel.js +186 -0
  153. package/scripts/gds/upgrade.js +172 -25
  154. package/src/bongos/CLAUDE.md +11 -60
  155. package/src/bongos/auth.js +24 -0
  156. package/src/bongos/module-submissions.js +56 -0
  157. package/src/bongos/route-rank-check.js +6 -0
  158. package/src/bongos/routes/CLAUDE.md +11 -39
  159. package/src/bongos/routes/auth.js +6 -1
  160. package/src/bongos/routes/modules.js +123 -21
  161. package/src/bongos/serve-internal.js +10 -1
  162. package/src/branding.js +1 -0
  163. package/src/instance-config.js +1 -1
  164. package/src/module-api.js +24 -1
  165. package/src/module-loader/loader.js +36 -4
  166. package/src/module-loader/manifest-schema.js +16 -1
  167. package/tests/branding.mjs +11 -0
  168. package/tests/claim_error_surface.mjs +107 -0
  169. package/tests/claude_materialize.mjs +125 -0
  170. package/tests/consumer_layout_boot.mjs +302 -0
  171. package/tests/cutover_codemod.mjs +184 -0
  172. package/tests/device_flow_start.mjs +96 -0
  173. package/tests/first_run_engine_check.mjs +79 -0
  174. package/tests/goal_membership_requests.mjs +33 -3
  175. package/tests/hook_core_resolve.mjs +125 -0
  176. package/tests/hook_user_agents.mjs +62 -0
  177. package/tests/infra_exec_paths.mjs +104 -0
  178. package/tests/init.mjs +106 -6
  179. package/tests/instance_mismatch_guard.mjs +101 -0
  180. package/tests/mirror_lineage_proof.mjs +14 -0
  181. package/tests/module_cli.mjs +130 -2
  182. package/tests/module_loader.mjs +42 -0
  183. package/tests/module_manifest.mjs +19 -0
  184. package/tests/module_submissions.mjs +135 -0
  185. package/tests/npmrc.mjs +105 -0
  186. package/tests/onboard.mjs +62 -0
  187. package/tests/package_core.mjs +76 -22
  188. package/tests/platform_boot.mjs +5 -2
  189. package/tests/provision.mjs +107 -8
  190. package/tests/provisioning_github_app_routes.mjs +44 -2
  191. package/tests/publish_manifest.mjs +52 -1
  192. package/tests/run_routine.mjs +26 -1
  193. package/tests/run_routine_two_root.mjs +77 -0
  194. package/tests/scrubber_corpus.mjs +17 -0
  195. package/tests/served_paths_shipped.mjs +2 -2
  196. package/tests/task_cli.mjs +99 -0
  197. package/tests/update_channel.mjs +186 -0
  198. package/tests/upgrade.mjs +220 -25
  199. package/tests/workflow_exec_paths.mjs +98 -0
  200. package/docs/design/container-exterior-tiles.md +0 -49
  201. package/docs/design/container-exterior.md +0 -81
  202. package/docs/design/container-interior.md +0 -77
  203. package/docs/design/world-map-scroll.md +0 -41
  204. package/docs/recipes/claude-gemini-bongos-doors.md +0 -155
  205. package/docs/recipes/cloudbongos-staging-gate.md +0 -139
  206. package/docs/recipes/cloudbongos-standup.md +0 -111
  207. package/docs/recipes/parallel-load-results-2026-05-30.md +0 -63
  208. package/docs/recipes/parallel-load-test.md +0 -32
  209. package/docs/recipes/pixel-art-pipeline.md +0 -411
  210. package/docs/recipes/staging-data.md +0 -50
  211. package/migrations/instance/018_pms_v3_planning_seed.sql +0 -131
  212. package/migrations/instance/080_gds_v4_planning_seed.sql +0 -75
  213. package/migrations/instance/110_medusa_planning_seed.sql +0 -70
  214. package/migrations/instance/120_bongos_v1_planning_seed.sql +0 -69
  215. package/migrations/instance/144_bongos_v1_module_system_criterion.sql +0 -26
  216. package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +0 -29
  217. package/migrations/instance/172_v2_world_planning_seed.sql +0 -163
  218. package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +0 -33
  219. package/migrations/instance/182_gdsv4_bongos_consolidation.sql +0 -132
  220. package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +0 -116
  221. package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +0 -69
  222. package/scripts/gds/art-stage.js +0 -152
  223. package/server.js +0 -212
  224. package/tests/art_key.mjs +0 -121
  225. package/tests/day_cycle.mjs +0 -197
  226. package/tests/snapshot_picker.mjs +0 -233
  227. package/tests/sprite_facing.mjs +0 -35
  228. package/tests/terrain_picker.mjs +0 -298
  229. package/tests/turn_in_place.mjs +0 -280
  230. package/tests/walkability.mjs +0 -188
@@ -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 };
@@ -13,7 +13,7 @@
13
13
  //
14
14
  // DISCIPLINE. Every migration SOURCE owns a distinct filename namespace, so stems can
15
15
  // never collide across sources and each source's allocator counts only its own numbers:
16
- // • CORE (the @cloudbongos/core package's migrations/*.sql) → core_NNN_slug.sql
16
+ // • CORE (the @bongos/core package's migrations/*.sql) → core_NNN_slug.sql
17
17
  // • MODULE (modules/<key>/migrations/*.sql) → <key>_NNN_slug.sql (key: dev-box → dev_box_)
18
18
  // • INSTANCE (the host repo's migrations/instance/*.sql) → NNN_slug.sql (BARE — the instance owns the bare namespace)
19
19
  //
@@ -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 || {};
@@ -351,6 +351,40 @@ function recordSignOff(key, signedBy, { modulesDir = MODULES_DIR } = {}) {
351
351
  return { ...record, path: path.relative(REPO_ROOT, file) };
352
352
  }
353
353
 
354
+ // Reads a previously-recorded sign-off (recordSignOff above), or null if the
355
+ // module has never been signed off. Never throws on a missing/garbled file —
356
+ // "no sign-off yet" is the expected common case, not an error.
357
+ function readSignOff(key, { modulesDir = MODULES_DIR } = {}) {
358
+ return readJson(path.join(modulesDir, key, '.upstream-signoff.json'));
359
+ }
360
+
361
+ // ---------------------------------------------------------------------------
362
+ // bongos module submit <key> — ADR 0107 §1 upstreaming submission
363
+ // ---------------------------------------------------------------------------
364
+
365
+ // Packages a module for the upstreaming queue: its manifest + a full file
366
+ // snapshot (base64, so binary assets round-trip identically) + whatever
367
+ // sign-off is on disk. Pure fs read — no pre-check, no network; the caller
368
+ // (cmdSubmit locally, the POST /modules/:key/submit route server-side) is
369
+ // responsible for having already run checkModulePublishability() and for
370
+ // deciding whether a missing sign-off is fatal.
371
+ function packageModuleForSubmission(key, { modulesDir = MODULES_DIR } = {}) {
372
+ const dir = path.join(modulesDir, key);
373
+ const manifest = readJson(path.join(dir, 'module.json'));
374
+ if (!manifest) throw new Error(`modules/${key}/module.json missing or unreadable`);
375
+ let totalBytes = 0;
376
+ const files = listModuleFiles(dir)
377
+ .map((abs) => {
378
+ const rel = path.relative(dir, abs).split(path.sep).join('/');
379
+ const buf = fs.readFileSync(abs);
380
+ totalBytes += buf.length;
381
+ return { path: rel, contentBase64: buf.toString('base64'), bytes: buf.length };
382
+ })
383
+ .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
384
+ const signOff = readSignOff(key, { modulesDir });
385
+ return { key, manifest, files, totalBytes, signOff };
386
+ }
387
+
354
388
  // ---------------------------------------------------------------------------
355
389
  // Commands
356
390
  // ---------------------------------------------------------------------------
@@ -445,6 +479,79 @@ function cmdCheck(args, { log = console.log, errlog = console.error, check = che
445
479
  return 0;
446
480
  }
447
481
 
482
+ // `bongos module submit <key>` — ADR 0107 §1. Runs the §4 pre-check + requires
483
+ // a sign-off (locally, for fast refusal without a network round trip), then
484
+ // POSTs to POST /modules/:key/submit — the SAME route the hall "Modules" tab's
485
+ // submit button calls (src/bongos/routes/modules.js). That route re-runs the
486
+ // pre-check against ITS OWN on-disk copy of the module and is the authoritative
487
+ // gate: a local pass here is a courtesy, not a guarantee, if the module's
488
+ // changes aren't committed + shipped + deployed yet.
489
+ async function cmdSubmit(args, {
490
+ log = console.log, errlog = console.error,
491
+ check = checkModulePublishability, sign = recordSignOff, readSign = readSignOff,
492
+ submit,
493
+ } = {}) {
494
+ const key = args.find((a) => !a.startsWith('-'));
495
+ if (!key) {
496
+ errlog('usage: bongos module submit <key> [--sign-off "Name <email>"]');
497
+ return 2;
498
+ }
499
+ const signIdx = args.indexOf('--sign-off');
500
+ const signedBy = signIdx !== -1 ? args[signIdx + 1] : null;
501
+
502
+ log(`bongos module submit — upstreaming "${key}" (ADR 0107 §1)`);
503
+
504
+ let result;
505
+ try { result = check(key); }
506
+ catch (e) { errlog(`bongos module submit: ${e.message}`); return 1; }
507
+
508
+ if (!result.ok) {
509
+ for (const f of result.findings) errlog(` ✗ ${f.kind}: ${f.detail}`);
510
+ errlog(`\nREFUSING: "${key}" is not clear to submit upstream. Fix the finding(s) above and re-run.`);
511
+ return 1;
512
+ }
513
+ log(' ✓ AGPL/publish pre-check passed.');
514
+
515
+ let signOff = readSign(key);
516
+ if (!signOff && signedBy) {
517
+ const record = sign(key, signedBy);
518
+ log(` ✓ Sign-off recorded — ${record.path}`);
519
+ signOff = readSign(key);
520
+ }
521
+ if (!signOff) {
522
+ errlog(`\nREFUSING: no DCO-style sign-off on file for "${key}". Pass --sign-off "Name <email>" (or run \`bongos module check ${key} --sign-off "Name <email>"\` first).`);
523
+ return 1;
524
+ }
525
+ log(` ✓ Sign-off on file — "${signOff.signOff.signedBy}", ${signOff.signOff.date}.`);
526
+
527
+ const doSubmit = submit || (async (k, body) => {
528
+ // Lazy require: cli-lib.js carries session/fetch plumbing that must NEVER
529
+ // load into the always-on server process — this file is also required by
530
+ // src/bongos/routes/modules.js for the pre-check it reuses. A real network
531
+ // call only happens when a human/agent actually invokes this CLI command.
532
+ const { cliClient, requireSession } = require('./cli-lib');
533
+ await requireSession();
534
+ const api = await cliClient();
535
+ return api.modules.postModulesKeySubmit({ key: k, body });
536
+ });
537
+
538
+ let res;
539
+ try { res = await doSubmit(key, { signed_by: signedBy || undefined }); }
540
+ catch (e) { errlog(`bongos module submit: request failed — ${e.message}`); return 1; }
541
+
542
+ if (!res.ok) {
543
+ const detail = (res.data && (res.data.message || res.data.error)) || JSON.stringify(res.data);
544
+ errlog(`\nREFUSING: the review queue rejected the submission (HTTP ${res.status}): ${detail}`);
545
+ if (res.status === 422) errlog('(the server re-runs the pre-check/sign-off check against its OWN deployed copy — commit + ship first if this module has local-only changes.)');
546
+ return 1;
547
+ }
548
+
549
+ const submission = res.data && res.data.submission;
550
+ log(`\n✓ Filed — submission id ${submission ? submission.id : '?'} for "${key}", status ${submission ? submission.status : 'pending'}.`);
551
+ log('Task 1771 (core-side review & accept flow) picks it up from here.');
552
+ return 0;
553
+ }
554
+
448
555
  function printHelp(out = console.log) {
449
556
  out('bongos module — scaffold + manage Cloud Bongos modules\n');
450
557
  out('Usage:');
@@ -452,14 +559,17 @@ function printHelp(out = console.log) {
452
559
  out(' bongos upgrade Pre-check enabled modules against the core version (fail-closed)');
453
560
  out(' bongos module check <key> [--sign-off "Name <email>"]');
454
561
  out(' AGPL/publish pre-check before upstreaming + record a DCO-style sign-off');
562
+ out(' bongos module submit <key> [--sign-off "Name <email>"]');
563
+ out(' Pre-check + sign-off, then file into the rank-gated upstreaming review queue');
455
564
  }
456
565
 
457
- function main(argv) {
566
+ async function main(argv) {
458
567
  const args = argv.slice(2);
459
568
  const sub = args[0];
460
569
  if (sub === 'new') return cmdNew(args.slice(1));
461
570
  if (sub === 'upgrade') return cmdUpgrade(args.slice(1));
462
571
  if (sub === 'check') return cmdCheck(args.slice(1));
572
+ if (sub === 'submit') return cmdSubmit(args.slice(1));
463
573
  if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
464
574
  printHelp();
465
575
  return sub ? 0 : 2;
@@ -470,13 +580,17 @@ function main(argv) {
470
580
  }
471
581
 
472
582
  module.exports = {
473
- main, cmdNew, cmdUpgrade, cmdCheck, printHelp,
583
+ main, cmdNew, cmdUpgrade, cmdCheck, cmdSubmit, printHelp,
474
584
  scaffoldModule, gatherUpgrade, evaluateUpgrade, buildManifest,
475
- checkModulePublishability, recordSignOff, DCO_STATEMENT,
585
+ checkModulePublishability, recordSignOff, readSignOff, DCO_STATEMENT,
586
+ listModuleFiles, packageModuleForSubmission,
476
587
  routeTemplate, claudeMdTemplate, titleize, camel, coreVersion,
477
588
  MODULES_DIR,
478
589
  };
479
590
 
480
591
  if (require.main === module) {
481
- process.exit(main(process.argv));
592
+ Promise.resolve(main(process.argv)).then(
593
+ (code) => process.exit(code),
594
+ (e) => { console.error(e); process.exit(1); }
595
+ );
482
596
  }
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ //
4
+ // scripts/gds/npmrc.js — env-fed `.npmrc` for installing the PRIVATE @bongos/* core
5
+ // from the npm registry (ADR 0134, task 2090). The single place that knows the
6
+ // registry host + scope + the `.npmrc` shape + the opt-in/token gates, shared by:
7
+ // • init.js — the greenfield `--registry-core` scaffold
8
+ // • provision.js — the standalone scaffold's registry path (opt-in, fail-soft)
9
+ // • upgrade.js — the `--registry <version>` update channel
10
+ //
11
+ // WHY private + env-fed. `@bongos/core` publishes as a SCOPED, PRIVATE package (ADR
12
+ // 0134): a private scoped package can only be installed with auth. Consumers
13
+ // authenticate with an `.npmrc` whose token EXPANDS FROM THE ENVIRONMENT at install
14
+ // time (`_authToken=${NPM_TOKEN}`) — so the committed file carries NO secret, and a
15
+ // tokenless install 404s cleanly (the expected pre-launch outcome).
16
+ //
17
+ // The vendored-tarball path (task 2053) stays the DEFAULT/fallback until this registry
18
+ // path is proven end-to-end on a real greenfield standup (ADR 0134 §Consequences).
19
+
20
+ const fs = require('node:fs');
21
+ const path = require('node:path');
22
+
23
+ const REGISTRY_HOST = 'registry.npmjs.org';
24
+ const CORE_SCOPE = '@bongos'; // the paid, canonical npm org (ADR 0134 scope correction, task 2092)
25
+ const TOKEN_ENV = 'NPM_TOKEN';
26
+ const REQUEST_ENV = 'PROVISION_CORE_FROM_REGISTRY';
27
+
28
+ // The `.npmrc` a scaffolded instance commits. The token is the LITERAL `${NPM_TOKEN}`
29
+ // (npm expands env vars in `.npmrc` values at read time), so the file is safe to
30
+ // commit — it holds no secret. Scoping @bongos to the registry keeps every unrelated
31
+ // install on the default public registry.
32
+ function npmrcContent({ scope = CORE_SCOPE, host = REGISTRY_HOST, tokenEnv = TOKEN_ENV } = {}) {
33
+ return [
34
+ `; Cloud Bongos — env-fed auth for the PRIVATE ${scope}/* packages (ADR 0134, task 2090).`,
35
+ `; The token EXPANDS FROM THE ENVIRONMENT at install time; nothing secret is committed here.`,
36
+ `; \`npm ci\` needs ${tokenEnv} set (a read-only automation token from the paid \`bongos\` org);`,
37
+ `; without it the private package 404s — that is the expected tokenless outcome.`,
38
+ `${scope}:registry=https://${host}/`,
39
+ `//${host}/:_authToken=\${${tokenEnv}}`,
40
+ '',
41
+ ].join('\n');
42
+ }
43
+
44
+ // Is a registry token present in the environment? The fail-soft gate: provisioning
45
+ // falls back to the vendored tarball when this is false, so a tokenless standup still
46
+ // works (never a broken instance).
47
+ function hasNpmToken(env = process.env) {
48
+ return !!String(env[TOKEN_ENV] || '').trim();
49
+ }
50
+
51
+ // Has the operator opted the standalone scaffold into the registry path? OFF by
52
+ // default — the vendored tarball stays the default until the registry path is proven
53
+ // end-to-end (ADR 0134). '1'/'true'/'yes'/'on' (case-insensitive) → true.
54
+ function registryCoreRequested(env = process.env) {
55
+ return /^(1|true|yes|on)$/i.test(String(env[REQUEST_ENV] || '').trim());
56
+ }
57
+
58
+ // Write the env-fed `.npmrc` into an instance dir. Default overwrites (a greenfield
59
+ // scaffold owns the file); pass overwrite:false to leave a pre-existing `.npmrc`
60
+ // untouched (an upgrade must not clobber a consumer's other registries). Returns the
61
+ // path (or null when skipped). Dry-run writes nothing.
62
+ function writeNpmrc(dir, { fsImpl = fs, dryRun = false, overwrite = true, log = () => {} } = {}) {
63
+ const p = path.join(dir, '.npmrc');
64
+ if (!overwrite && fsImpl.existsSync(p)) {
65
+ log(` • .npmrc already present — left untouched (add \`${CORE_SCOPE}:registry\` + \`_authToken=\${${TOKEN_ENV}}\` if the private core doesn't resolve).`);
66
+ return null;
67
+ }
68
+ if (dryRun) {
69
+ log(` [dry-run] would write ${p} (env-fed ${CORE_SCOPE} auth; token via $${TOKEN_ENV}, never committed)`);
70
+ return p;
71
+ }
72
+ fsImpl.writeFileSync(p, npmrcContent());
73
+ log(` ✓ wrote .npmrc (env-fed ${CORE_SCOPE} auth; token via $${TOKEN_ENV}, never committed)`);
74
+ return p;
75
+ }
76
+
77
+ module.exports = {
78
+ REGISTRY_HOST, CORE_SCOPE, TOKEN_ENV, REQUEST_ENV,
79
+ npmrcContent, hasNpmToken, registryCoreRequested, writeNpmrc,
80
+ };
81
+
82
+ // Tiny CLI: `node scripts/gds/npmrc.js [--print | --write <dir>]` — print the `.npmrc`
83
+ // body or materialize it into <dir>. Handy for a manual registry-standup rehearsal.
84
+ if (require.main === module) {
85
+ const args = process.argv.slice(2);
86
+ const writeIdx = args.indexOf('--write');
87
+ if (writeIdx !== -1 && args[writeIdx + 1]) {
88
+ const p = writeNpmrc(path.resolve(args[writeIdx + 1]), { log: console.log });
89
+ process.stdout.write(`${p}\n`);
90
+ } else {
91
+ process.stdout.write(npmrcContent());
92
+ }
93
+ }
@@ -22,28 +22,12 @@ const oauth = require(path.join(__dirname, 'oauth-secret.js')); // validateC
22
22
 
23
23
  // ── PURE core (exported for tests) ───────────────────────────────────────────
24
24
 
25
- // The canonical step sequence — the SINGLE source of truth the skill + hall wizard
26
- // mirror. `kind: 'human'` steps are the irreducibly-manual ones (UI-first prompts).
27
- // The sequence branches at exactly ONE leg — scaffold — into greenfield (`bongos init`,
28
- // a fresh empty repo) vs brownfield (`bongos init --adopt`, LAYER onto an existing repo;
29
- // ADR 0121 §Decision 1 / task 2046). Every other leg is identical, so passing `mode`
30
- // only relabels the github-repo / init / scaffold steps.
31
- function onboardPlan(mode = 'greenfield') {
32
- const adopt = mode === 'adopt';
33
- return [
34
- { key: 'name-address', kind: 'auto', label: 'Decide name + address (interview)' },
35
- { key: 'github-repo', kind: 'human', label: adopt ? 'Use your EXISTING GitHub repository' : 'Create the empty GitHub repository' },
36
- { key: 'dns-preflight', kind: 'auto', label: 'DNS token preflight — before the address step (task 1980)' },
37
- { key: 'init', kind: 'auto', label: adopt ? 'bongos init --adopt — LAYER onto the existing repo (pre-flight + accept)' : 'bongos init — scaffold the instance config' },
38
- { key: 'scaffold', kind: 'auto', label: adopt ? 'Layer config + pinned @cloudbongos/core onto the existing repo (no clobber)' : 'Scaffold the standalone repo (own repo + pinned @cloudbongos/core)' },
39
- { key: 'provision', kind: 'auto', label: 'Provision core + DB + address + service (standalone, task 1978)' },
40
- { key: 'oauth-app', kind: 'human', label: 'Wire GitHub sign-in — one-click GitHub App (task 2080), or a manual OAuth app' },
41
- { key: 'oauth-place', kind: 'auto', label: 'Place the OAuth secret — auto via the Manifest flow, or verified manual (task 1979)' },
42
- { key: 'first-sign-in', kind: 'human', label: 'First sign-in (seats the founding Archon)' },
43
- { key: 'verify', kind: 'auto', label: 'Verify /healthz auth_configured' },
44
- { key: 'brand', kind: 'auto', label: 'Brand the instance look' },
45
- ];
46
- }
25
+ // The canonical step sequence now lives in ONE place — the provisioning module — so the
26
+ // CLI (here), the branded /create page, and the hall wizard can't drift (task 2085; goal
27
+ // 35 / criterion 165: "the canonical step sequence is the source of truth; skill/CLI/hall
28
+ // are forms over it"). The web forms read it over GET /provisioning/onboard-plan; the CLI
29
+ // requires it directly. Re-exported below so `bongos onboard` + tests keep the same API.
30
+ const { onboardPlan } = require(path.join(__dirname, '..', '..', 'modules', 'provisioning', 'onboard-plan.js'));
47
31
 
48
32
  // The exact GitHub OAuth-app values for a domain. The callback is PINNED to the apex
49
33
  // /api/gds/auth/web/callback (auth.js WEB_REDIRECT_ORIGIN — a sign-in begun on a
@@ -54,6 +38,10 @@ function oauthAppInstructions(domain) {
54
38
  location: "GitHub → your ACCOUNT's Settings → Developer settings → OAuth Apps → New OAuth App",
55
39
  homepageUrl: origin,
56
40
  callbackUrl: `${origin}/api/gds/auth/web/callback`,
41
+ // GitHub disables Device Flow on a new OAuth App by DEFAULT, but the terminal
42
+ // `bongos login` uses it — leaving it off makes CLI sign-in fail with
43
+ // device_flow_disabled (the 2026-07-08 demo login-500). Must be enabled. (task 2127)
44
+ enableDeviceFlow: 'Check "Enable Device Flow" on the app page and save — off by default; the terminal `bongos login` needs it.',
57
45
  };
58
46
  }
59
47
 
@@ -149,13 +137,13 @@ async function run() {
149
137
  // Step 5 — the standalone repo (greenfield scaffolds it; adopt already layered it in ③)
150
138
  if (adopt) {
151
139
  log('\n④ Your repo is now the standalone instance (ADR 0108):');
152
- log(' `bongos init --adopt` already layered config/ + .claude/ + the pinned @cloudbongos/core dep onto it.');
140
+ log(' `bongos init --adopt` already layered config/ + .claude/ + the pinned @bongos/core dep onto it.');
153
141
  log(` Commit + push ${a.repoOwner}/${a.repoName}. That checkout is what the service runs.`);
154
142
  await ask(rl, 'Press Enter once the layered repo is committed + pushed…');
155
143
  } else {
156
144
  log('\n④ Scaffold the STANDALONE repo (ADR 0108):');
157
145
  log(` Clone ${a.repoOwner}/${a.repoName} to the control-plane standalone root, add the config/ + .claude/`);
158
- log(' from init, pin @cloudbongos/core in its package.json, commit + push. That checkout is what the service runs.');
146
+ log(' from init, pin @bongos/core in its package.json, commit + push. That checkout is what the service runs.');
159
147
  await ask(rl, 'Press Enter once the standalone repo is pushed…');
160
148
  }
161
149
 
@@ -175,11 +163,16 @@ async function run() {
175
163
  log(' RECOMMENDED — one click: in the control-plane hall, open “Start a project” →');
176
164
  log(' “Set up GitHub sign-in”. Approve the pre-filled GitHub App; the control plane');
177
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.');
178
170
  log(' — or, to create an OAuth app BY HAND —');
179
171
  log(` ${oa.location}`);
180
172
  log(' (Developer settings is under your ACCOUNT, not the repository.)');
181
173
  log(` Homepage URL: ${oa.homepageUrl}`);
182
174
  log(` Authorization callback URL: ${oa.callbackUrl}`);
175
+ log(` Enable Device Flow: ✅ ${oa.enableDeviceFlow}`);
183
176
  log(' Copy the Client ID; Generate a new client secret and copy it (shown once).');
184
177
  const clientId = await ask(rl, 'Paste the OAuth Client ID:');
185
178
  const clientSecret = await ask(rl, 'Paste the OAuth Client Secret:');