@bongos/core 1.19.1073 → 1.19.1075

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 (377) hide show
  1. package/.bongos-core.json +404 -549
  2. package/.claude/skills/feedback/SKILL.md +2 -2
  3. package/bin/bongos.js +1 -3
  4. package/clients/bongos-client/README.md +1 -1
  5. package/clients/bongos-client/bongos-client.global.js +4 -60
  6. package/clients/bongos-client/index.cjs +4 -60
  7. package/clients/bongos-client/index.d.ts +5 -90
  8. package/clients/bongos-client/index.mjs +4 -60
  9. package/config/branding.neutral.json +1 -5
  10. package/config/modules.neutral.json +2 -3
  11. package/config/scheduled-routines.json +0 -2
  12. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
  13. package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
  14. package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
  15. package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
  16. package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
  17. package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
  18. package/docs/adr/README.md +1 -0
  19. package/docs/api/openapi.json +175 -1665
  20. package/docs/api-reference.md +9 -41
  21. package/docs/architecture.md +16 -1
  22. package/docs/branding-contract.md +0 -3
  23. package/docs/copy-inventory.md +454 -479
  24. package/docs/copy-registry.json +746 -978
  25. package/docs/file-map.md +5 -34
  26. package/docs/module-api-changelog.md +4 -0
  27. package/docs/modules-contract.md +13 -1
  28. package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
  29. package/docs/onboarding/diagrams/README.md +3 -3
  30. package/docs/onboarding/diagrams/assertions.json +2 -22
  31. package/docs/onboarding/primer.md +9 -28
  32. package/docs/page-inventory.json +4 -1
  33. package/docs/page-readings.json +1032 -1077
  34. package/docs/recipes/render-pilot.md +29 -10
  35. package/migrations/core_257_module_store_registry.sql +87 -0
  36. package/migrations/core_258_drop_dev_box_tables.sql +55 -0
  37. package/modules/agents/module.json +1 -0
  38. package/modules/autonomy/module.json +1 -0
  39. package/modules/builder-settings/module.json +1 -0
  40. package/modules/builder-settings/render-prefs.js +4 -5
  41. package/modules/copy-desk/docx.js +78 -5
  42. package/modules/copy-desk/module.json +1 -0
  43. package/modules/copy-desk/routes/copy-desk.js +10 -10
  44. package/modules/copy-desk/tests/copy_docx.mjs +107 -1
  45. package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
  46. package/modules/discord/module.json +1 -0
  47. package/modules/discord/ship-broadcast.js +3 -3
  48. package/modules/economy/credits.js +2 -2
  49. package/modules/economy/module.json +1 -0
  50. package/modules/economy/routes/credits.js +1 -2
  51. package/modules/government/catalog.js +13 -17
  52. package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
  53. package/modules/government/module.json +1 -0
  54. package/modules/government/protected-surfaces.json +3 -11
  55. package/modules/government/resolver.js +3 -46
  56. package/modules/government/routes/government.js +0 -2
  57. package/modules/government/session-scopes.js +43 -83
  58. package/modules/government/session-scopes.json +8 -18
  59. package/modules/grading/module.json +1 -0
  60. package/modules/hall-ui/module.json +1 -0
  61. package/modules/hall-ui/public/board-lib.js +2 -2
  62. package/modules/hall-ui/public/builders.js +29 -40
  63. package/modules/hall-ui/public/collab.html +1 -1
  64. package/modules/hall-ui/public/collab.js +1 -1
  65. package/modules/hall-ui/public/diagrams.html +3 -3
  66. package/modules/hall-ui/public/dom-utils.js +1 -1
  67. package/modules/hall-ui/public/gate.js +2 -2
  68. package/modules/hall-ui/public/gate.states.json +1 -1
  69. package/modules/hall-ui/public/hall-render.js +43 -79
  70. package/modules/hall-ui/public/index.html +5 -1
  71. package/modules/hall-ui/public/modules.js +1 -0
  72. package/modules/hall-ui/public/oversight.css +5 -5
  73. package/modules/hall-ui/public/palette.js +0 -11
  74. package/modules/hall-ui/public/primer.js +1 -1
  75. package/modules/hall-ui/public/sessions.html +1 -1
  76. package/modules/hall-ui/public/settings-sessions.js +1 -1
  77. package/modules/hall-ui/public/settings.css +0 -1
  78. package/modules/hall-ui/public/settings.html +4 -33
  79. package/modules/hall-ui/public/settings.js +3 -33
  80. package/modules/hall-ui/public/settings.states.json +2 -2
  81. package/modules/hall-ui/public/studio.html +1 -1
  82. package/modules/hall-ui/public/style.css +10 -13
  83. package/modules/hall-ui/public/task.html +4 -1
  84. package/modules/hall-ui/public/task.js +36 -1
  85. package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
  86. package/modules/hall-ui/public/tweak-editor.css +16 -0
  87. package/modules/hall-ui/public/tweak-editor.html +26 -3
  88. package/modules/hall-ui/public/tweak-editor.js +32 -1
  89. package/modules/hall-ui/public/work.js +1 -1
  90. package/modules/hall-ui/records/tweak-editor.md +1 -0
  91. package/modules/ideas/module.json +1 -0
  92. package/modules/ideas/projection.js +1 -1
  93. package/modules/lifecycle/db-claim-reads.js +1 -58
  94. package/modules/lifecycle/db-ship.js +4 -9
  95. package/modules/lifecycle/db.js +0 -4
  96. package/modules/lifecycle/github-push.js +12 -14
  97. package/modules/lifecycle/kickoff-checklist.js +0 -4
  98. package/modules/lifecycle/lifecycle.js +0 -8
  99. package/modules/lifecycle/module.json +1 -0
  100. package/modules/lifecycle/publish-reconciler.js +10 -10
  101. package/modules/lifecycle/routes/tasks.js +13 -15
  102. package/modules/lifecycle/ship-card.js +1 -1
  103. package/modules/lifecycle/task-visuals.js +2 -2
  104. package/modules/memory/module.json +1 -0
  105. package/modules/memory/routes/memory.js +2 -2
  106. package/modules/npm-release/module.json +1 -0
  107. package/modules/onboarding/module.json +1 -0
  108. package/modules/onboarding/onboarding-state.js +17 -36
  109. package/modules/onboarding/routes/access-requests.js +3 -3
  110. package/modules/platform-identity/module.json +1 -0
  111. package/modules/platform-identity/platform-identity.js +13 -5
  112. package/modules/platform-identity/routes/sso.js +5 -4
  113. package/modules/platform-identity/tests/platform-identity.mjs +17 -3
  114. package/modules/provisioning/capacity.js +1 -1
  115. package/modules/provisioning/module.json +1 -0
  116. package/modules/provisioning/provisioning.js +6 -5
  117. package/modules/provisioning/routes/provisioning.js +4 -4
  118. package/modules/provisioning/starter-bundles.js +5 -25
  119. package/modules/public-landing/module.json +1 -0
  120. package/modules/public-landing/public/assets/cosmos.css +1 -1
  121. package/modules/public-landing/public/contact.html +1 -1
  122. package/modules/public-landing/public/privacy.html +1 -1
  123. package/modules/public-landing/public/projects.html +101 -32
  124. package/modules/public-landing/public/projects.probes.json +2 -2
  125. package/modules/public-landing/public/projects.states.json +4 -3
  126. package/modules/public-landing/public/terms.html +1 -1
  127. package/modules/security/module.json +1 -0
  128. package/modules/sessions/db.js +1 -1
  129. package/modules/sessions/module.json +1 -0
  130. package/modules/specialities/module.json +1 -0
  131. package/modules/status-ui/module.json +1 -0
  132. package/modules/ui-design/module.json +1 -0
  133. package/package-lock.json +2 -2
  134. package/package.json +1 -1
  135. package/release-notes.json +84 -0
  136. package/scripts/gds/artifact-format.js +203 -0
  137. package/scripts/gds/artifact-staleness.js +2 -2
  138. package/scripts/gds/audit-authorship.sh +1 -13
  139. package/scripts/gds/backfill-task-rewards.js +5 -5
  140. package/scripts/gds/claim.js +0 -15
  141. package/scripts/gds/claude-materialize.js +2 -2
  142. package/scripts/gds/cli-lib.js +15 -15
  143. package/scripts/gds/codemod-rename-src-gds.js +1 -1
  144. package/scripts/gds/context-pack.js +5 -7
  145. package/scripts/gds/diagram-facts.js +12 -23
  146. package/scripts/gds/do-api.js +13 -80
  147. package/scripts/gds/doc-cli-guard.js +2 -0
  148. package/scripts/gds/doctor.js +4 -4
  149. package/scripts/gds/feedback-latest.js +139 -9
  150. package/scripts/gds/fitness-checks-identity.js +2 -7
  151. package/scripts/gds/fitness-checks-packaging.js +4 -4
  152. package/scripts/gds/fitness-checks-write-validation.js +4 -0
  153. package/scripts/gds/fitness-ratchets.js +5 -5
  154. package/scripts/gds/fitness.js +10 -10
  155. package/scripts/gds/gen-api-client.js +4 -4
  156. package/scripts/gds/gen-api-docs.js +1 -1
  157. package/scripts/gds/gen-diagrams.js +49 -71
  158. package/scripts/gds/gen-repo-map.js +13 -14
  159. package/scripts/gds/gen-session-index.js +8 -8
  160. package/scripts/gds/http-api-client.js +12 -19
  161. package/scripts/gds/init.js +1 -1
  162. package/scripts/gds/leak-scan-allowlist.js +1 -1
  163. package/scripts/gds/lint-infra-exec.js +1 -1
  164. package/scripts/gds/local-preview-lib.js +8 -39
  165. package/scripts/gds/local-preview.js +2 -15
  166. package/scripts/gds/migration-namespace.js +3 -3
  167. package/scripts/gds/module-artifact.js +212 -0
  168. package/scripts/gds/module.js +95 -6
  169. package/scripts/gds/package-core.js +5 -116
  170. package/scripts/gds/plain-cards.js +1 -1
  171. package/scripts/gds/provision-config.js +2 -2
  172. package/scripts/gds/provision-net.js +2 -3
  173. package/scripts/gds/provision.js +9 -9
  174. package/scripts/gds/publish-manifest.js +1 -2
  175. package/scripts/gds/push-path-brief.js +10 -10
  176. package/scripts/gds/regen-instance-docs.js +1 -1
  177. package/scripts/gds/rename-history-check.js +1 -1
  178. package/scripts/gds/run-unit-tests.js +2 -16
  179. package/scripts/gds/sandbox-stage.js +39 -341
  180. package/scripts/gds/session-digest-build.js +2 -2
  181. package/scripts/gds/ship-deploy-target.js +13 -13
  182. package/scripts/gds/ship-flow.js +7 -7
  183. package/scripts/gds/ship-land.js +9 -10
  184. package/scripts/gds/ship-merge.js +4 -4
  185. package/scripts/gds/ship-preflight-steps.js +4 -4
  186. package/scripts/gds/ship-regen.js +15 -49
  187. package/scripts/gds/ship.js +13 -33
  188. package/scripts/gds/skill-preflight.js +9 -8
  189. package/scripts/gds/start.js +0 -15
  190. package/scripts/hall-preview/README.md +5 -5
  191. package/scripts/hall-preview/server.js +6 -6
  192. package/scripts/render-diagrams.sh +1 -1
  193. package/src/bongos/api-errors.js +1 -1
  194. package/src/bongos/api-prefix.js +4 -4
  195. package/src/bongos/auth-github.js +2 -3
  196. package/src/bongos/auth.js +51 -126
  197. package/src/bongos/db-kernel.js +2 -3
  198. package/src/bongos/db.js +5 -28
  199. package/src/bongos/module-scope-map.js +14 -29
  200. package/src/bongos/module-store.js +141 -0
  201. package/src/bongos/platform-visibility-gate.js +1 -1
  202. package/src/bongos/route-rank-check.js +3 -16
  203. package/src/bongos/routes/auth.js +0 -116
  204. package/src/bongos/routes/builders.js +5 -15
  205. package/src/bongos/routes/instance.js +8 -14
  206. package/src/bongos/routes/me.js +7 -23
  207. package/src/bongos/routes/modules.js +83 -0
  208. package/src/bongos/routes/security.js +3 -3
  209. package/src/bongos/routes.js +7 -2
  210. package/src/bongos/serve-internal.js +5 -6
  211. package/src/bongos-downloads.js +1 -6
  212. package/src/branding.js +2 -2
  213. package/src/build-info.js +4 -6
  214. package/src/instance-config.js +2 -2
  215. package/src/module-api.js +13 -6
  216. package/src/module-loader/catalog.js +3 -0
  217. package/src/module-loader/loader.js +4 -4
  218. package/src/module-loader/manifest-schema.js +16 -8
  219. package/src/module-seams.js +2 -3
  220. package/src/modules.js +42 -14
  221. package/tests/agents_spend_guard.mjs +1 -1
  222. package/tests/api_alias_caller_ratchet.mjs +1 -1
  223. package/tests/api_alias_exceptions.mjs +1 -1
  224. package/tests/api_client.mjs +1 -16
  225. package/tests/api_path_404.mjs +1 -1
  226. package/tests/auth_page_gate.mjs +10 -10
  227. package/tests/backfill_task_rewards.mjs +1 -1
  228. package/tests/canonical_profile_url.mjs +1 -3
  229. package/tests/claim_action.mjs +4 -1
  230. package/tests/claim_from_task_and_home.mjs +127 -0
  231. package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
  232. package/tests/cli_sessions.mjs +1 -1
  233. package/tests/consumer_layout_boot.mjs +2 -0
  234. package/tests/context_pack.mjs +13 -13
  235. package/tests/copy_desk_page_docx.mjs +33 -2
  236. package/tests/copy_inventory.mjs +7 -7
  237. package/tests/core_upgrade_runner.mjs +6 -6
  238. package/tests/credit_grant.mjs +2 -2
  239. package/tests/criterion_suggest.mjs +1 -1
  240. package/tests/deploy_divergence_line.mjs +11 -11
  241. package/tests/design_tokens_sync.mjs +3 -2
  242. package/tests/diagram_facts_offset.mjs +1 -1
  243. package/tests/do_api.mjs +106 -0
  244. package/tests/feedback_latest.mjs +122 -0
  245. package/tests/fitness.mjs +44 -42
  246. package/tests/fitness_ratchets.mjs +2 -2
  247. package/tests/gate_approvals.mjs +0 -1
  248. package/tests/github_push_land_proof.mjs +1 -1
  249. package/tests/go_live.mjs +3 -3
  250. package/tests/goal_advisory.mjs +3 -3
  251. package/tests/goal_suggest.mjs +3 -3
  252. package/tests/government_abuse_matrix.mjs +21 -19
  253. package/tests/government_ownership_scope.mjs +33 -35
  254. package/tests/government_parity.mjs +2 -2
  255. package/tests/government_protected_surfaces.mjs +56 -37
  256. package/tests/government_require_permission.mjs +3 -3
  257. package/tests/government_seed.mjs +37 -4
  258. package/tests/government_session_scope.mjs +92 -366
  259. package/tests/hall_audit.mjs +1 -1
  260. package/tests/hall_error_envelope.mjs +4 -0
  261. package/tests/hall_landing_boot.mjs +4 -1
  262. package/tests/hall_palette.mjs +1 -16
  263. package/tests/hall_record_world.mjs +6 -0
  264. package/tests/hall_tweak_editor.mjs +52 -1
  265. package/tests/helpers.mjs +1 -1
  266. package/tests/host_topology_skips.mjs +3 -4
  267. package/tests/html_comment_nesting.mjs +137 -0
  268. package/tests/infra_exec_paths.mjs +9 -10
  269. package/tests/init.mjs +4 -5
  270. package/tests/instance_manifest.mjs +18 -25
  271. package/tests/kickoff_checklist.mjs +1 -1
  272. package/tests/lib_sh_resolution.mjs +0 -221
  273. package/tests/local_preview.mjs +0 -12
  274. package/tests/migration_namespace.mjs +6 -6
  275. package/tests/module-scope-map.mjs +9 -9
  276. package/tests/module_api.mjs +2 -2
  277. package/tests/module_catalog.mjs +1 -1
  278. package/tests/module_cli.mjs +20 -20
  279. package/tests/module_contributions.mjs +6 -6
  280. package/tests/module_loader.mjs +6 -6
  281. package/tests/module_manifest.mjs +26 -17
  282. package/tests/module_route_rank.mjs +2 -2
  283. package/tests/module_store_publish.mjs +307 -0
  284. package/tests/module_store_publish_route.mjs +121 -0
  285. package/tests/module_store_registry_migration.mjs +70 -0
  286. package/tests/modules.mjs +56 -26
  287. package/tests/modules_route_wiring.mjs +1 -1
  288. package/tests/onboarding_route_signals.mjs +21 -31
  289. package/tests/onboarding_state.mjs +35 -49
  290. package/tests/paste_token_session_store.mjs +1 -1
  291. package/tests/permission_path.mjs +0 -3
  292. package/tests/platform_boot.mjs +1 -2
  293. package/tests/platform_visibility_gate.mjs +1 -1
  294. package/tests/profile_route.mjs +1 -6
  295. package/tests/project_modules_ui.mjs +17 -17
  296. package/tests/projects_hub.mjs +5 -5
  297. package/tests/projects_hub_app_step.mjs +138 -0
  298. package/tests/projects_hub_module_picker.mjs +77 -76
  299. package/tests/projects_hub_pre_uat.mjs +4 -5
  300. package/tests/provision.mjs +7 -7
  301. package/tests/provisioning_capacity.mjs +1 -1
  302. package/tests/provisioning_recommendations.mjs +2 -1
  303. package/tests/provisioning_starter_bundles.mjs +0 -22
  304. package/tests/publish_branch_route.mjs +11 -19
  305. package/tests/publish_manifest.mjs +2 -3
  306. package/tests/publish_reconciler.mjs +8 -8
  307. package/tests/push_path_brief.mjs +10 -9
  308. package/tests/rank_tier_single_source.mjs +1 -13
  309. package/tests/regrade_eligibility.mjs +1 -1
  310. package/tests/rename_history_restraint.mjs +1 -1
  311. package/tests/repo_map.mjs +12 -13
  312. package/tests/runner_drift.mjs +3 -21
  313. package/tests/sandbox_stage.mjs +57 -390
  314. package/tests/seam_wiring_guard.mjs +11 -11
  315. package/tests/search_isolation.mjs +3 -3
  316. package/tests/session_records.mjs +1 -1
  317. package/tests/session_rename_fallback.mjs +1 -22
  318. package/tests/session_start_freshness.mjs +3 -3
  319. package/tests/session_token_hash_db.mjs +1 -1
  320. package/tests/ship_card.mjs +1 -1
  321. package/tests/ship_ci_deploy.mjs +17 -17
  322. package/tests/ship_error_shape.mjs +1 -1
  323. package/tests/ship_premerge.mjs +1 -1
  324. package/tests/ship_resume.mjs +1 -1
  325. package/tests/skill_preflight.mjs +2 -2
  326. package/tests/skip_is_not_pass.mjs +1 -1
  327. package/tests/task_visual_slots.mjs +31 -0
  328. package/tests/terms_acceptance.mjs +2 -2
  329. package/tests/upgrade.mjs +4 -4
  330. package/tests/watch_sealed_floor.mjs +0 -1
  331. package/tests/wizard_draft_resume.mjs +2 -2
  332. package/tests/wizard_intent_resume.mjs +21 -10
  333. package/tests/wizard_preselect_why.mjs +18 -18
  334. package/docs/onboarding/browser-terminal-guide.md +0 -73
  335. package/docs/recipes/managed-settings-remote-control.md +0 -416
  336. package/modules/dev-box/CLAUDE.md +0 -15
  337. package/modules/dev-box/box-access.js +0 -623
  338. package/modules/dev-box/box-credential.js +0 -94
  339. package/modules/dev-box/box-onboard.js +0 -486
  340. package/modules/dev-box/boxes.js +0 -1038
  341. package/modules/dev-box/db.js +0 -39
  342. package/modules/dev-box/module.json +0 -19
  343. package/modules/dev-box/routes/box.js +0 -1223
  344. package/scripts/gds/box-auth-check.js +0 -160
  345. package/scripts/gds/box-infra.js +0 -321
  346. package/scripts/gds/box-sync.js +0 -216
  347. package/scripts/gds/box.js +0 -1332
  348. package/scripts/gds/cf-tunnel.js +0 -558
  349. package/scripts/gds/smoke-box.sh +0 -150
  350. package/scripts/gds/tree-preflight.js +0 -241
  351. package/src/bongos/app-pair.js +0 -172
  352. package/tests/app_pair.mjs +0 -168
  353. package/tests/box_access.mjs +0 -578
  354. package/tests/box_auth_check.mjs +0 -114
  355. package/tests/box_code_staleness.mjs +0 -129
  356. package/tests/box_connect_e2e.mjs +0 -196
  357. package/tests/box_cost.mjs +0 -70
  358. package/tests/box_credential.mjs +0 -125
  359. package/tests/box_dns_repoint.mjs +0 -153
  360. package/tests/box_env.mjs +0 -64
  361. package/tests/box_host_keys.mjs +0 -126
  362. package/tests/box_infra_resolve.mjs +0 -246
  363. package/tests/box_onboard.mjs +0 -317
  364. package/tests/box_scope_session.mjs +0 -119
  365. package/tests/box_sweep_docs.mjs +0 -119
  366. package/tests/box_sync.mjs +0 -55
  367. package/tests/box_sync_scope_report.mjs +0 -126
  368. package/tests/box_task_scope.mjs +0 -169
  369. package/tests/box_task_scope_pause.mjs +0 -66
  370. package/tests/box_terminal_ssrf.mjs +0 -96
  371. package/tests/boxes.mjs +0 -1585
  372. package/tests/cf_ruleset_preflight.mjs +0 -142
  373. package/tests/cf_tunnel.mjs +0 -496
  374. package/tests/docs_scrubber_damage.mjs +0 -111
  375. package/tests/module_scope_active_claims.mjs +0 -94
  376. package/tests/ship_api_client_pathspec.mjs +0 -100
  377. package/tests/tree_preflight.mjs +0 -243
@@ -26,7 +26,6 @@ import { test } from 'node:test';
26
26
  import { createRequire } from 'node:module';
27
27
  import { spawnSync } from 'node:child_process';
28
28
  import fs from 'node:fs';
29
- import os from 'node:os';
30
29
  import path from 'node:path';
31
30
  import { fileURLToPath } from 'node:url';
32
31
  import { fitnessSource } from './helpers.mjs';
@@ -35,12 +34,6 @@ const require = createRequire(import.meta.url);
35
34
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
36
35
  const GDS = path.join(ROOT, 'scripts', 'gds');
37
36
  const fitness = require(path.join(GDS, 'fitness.js'));
38
- // The CURRENT session filename, so the expectations below follow the rename chain
39
- // (task 1003704 renamed the file; 1003706 retires the old names) instead of being
40
- // re-edited at each step. SESSION_FILENAMES[0] is what the CLI WRITES; the tail is
41
- // what it still accepts on read.
42
- const ic = require(path.join(ROOT, 'src', 'instance-config.js'));
43
- const SESSION_FILE = ic.SESSION_FILENAMES[0];
44
37
 
45
38
  // Every bash script that resolves a GDS token or API base.
46
39
  const SMOKES = [
@@ -159,217 +152,3 @@ test('the whole host-identity check is green on this tree', () => {
159
152
  const r = fitness.checkKernelPromptsNoHardcodedIdentity();
160
153
  assert.equal(r.ok, true, `violations:\n${r.violations.join('\n')}`);
161
154
  });
162
-
163
- // ---- 4. the SAME rule applies to the baked box scripts (task 1003252) ------
164
- //
165
- // THE SECOND BUG, same class, different file group. The sweep above fixed
166
- // scripts/gds/*.sh but never looked at infra/box-*.sh — the scripts baked onto a
167
- // dev box. Those kept reading a hardcoded ~/.config/otb/gds-session.json while
168
- // paste-token.js writes to ~/.config/<branding.configDir>/. On this instance
169
- // (configDir "cloudbongos") that meant every /builder-reauth silently stranded the
170
- // box: the 5-min heartbeat, the terminal/host-key/version reporters and the
171
- // GDS-backed git credential helper all kept presenting a DEAD token and failing
172
- // with a misleading "session expired — re-auth on the box", which is exactly what
173
- // the builder had just done. Observed live: four reporters 401ing every tick and a
174
- // checkout 311 commits behind origin/main.
175
- //
176
- // The fix mirrors instance-config.js's read-side fallback in bash: prefer the most
177
- // recently written gds-session.json across ~/.config/*/, since branding is not
178
- // readable from a script that must also run before any clone exists.
179
-
180
- const BOX_SCRIPTS = [
181
- 'box-heartbeat.sh',
182
- 'box-report-terminal.sh',
183
- 'box-report-host-keys.sh',
184
- 'box-report-version.sh',
185
- ];
186
-
187
- const INFRA = path.join(ROOT, 'infra');
188
- const boxCode = (f) => fitness.stripCommentsForIdentity(fs.readFileSync(path.join(INFRA, f), 'utf8'), '.sh');
189
-
190
- test('every baked box script resolves the session across config dirs, not just the legacy one', () => {
191
- for (const f of BOX_SCRIPTS) {
192
- const src = boxCode(f);
193
- for (const name of ic.SESSION_FILENAMES.slice(0, 2)) {
194
- assert.ok(src.includes(`.config/*/${name}`),
195
- `${f} must scan ~/.config/*/${name} — without the glob a /builder-reauth into a `
196
- + 'non-"otb" configDir strands the box on a dead token, and without BOTH names '
197
- + 'half the fleet is unreadable: a box provisioned before the rename has only the '
198
- + 'old name, one provisioned after has only the new (task 1003704)');
199
- }
200
- // The legacy literal may survive ONLY as the last-resort default, never as the
201
- // sole source — i.e. it must be preceded by the glob scan.
202
- // The scan widens what these scripts will read, so each candidate must be owned
203
- // by the box user -- otherwise a file dropped by another local account could win
204
- // simply by being newest.
205
- assert.match(src, /\[ -O "\$_sess" \]/,
206
- `${f} must require candidate session files to be owned by the current user`);
207
- const globAt = src.indexOf('.config/*/');
208
- const legacyAt = src.indexOf(`.config/otb/${SESSION_FILE}`);
209
- if (legacyAt !== -1) {
210
- assert.ok(globAt < legacyAt,
211
- `${f} reaches the legacy path before scanning ~/.config/*/ — the fallback must come first`);
212
- }
213
- }
214
- });
215
-
216
- test('every baked box script passes bash -n', (t) => {
217
- const probe = bash(['-n', 'infra/box-heartbeat.sh']);
218
- if (!probe) return t.skip('bash not available on this machine');
219
- for (const f of BOX_SCRIPTS) {
220
- const r = bash(['-n', `infra/${f}`]);
221
- assert.equal(r.status, 0, `bash -n infra/${f} failed:\n${r.stderr}`);
222
- }
223
- });
224
-
225
- // Pull the resolver straight out of the committed file so this exercises the real
226
- // code, not a copy that could drift away from it.
227
- function resolverBlock(f) {
228
- const src = fs.readFileSync(path.join(INFRA, f), 'utf8');
229
- const start = src.indexOf('if [ -z "${GDS_SESSION:-}" ]; then');
230
- // Matched by PREFIX, up to the closing quote — never by the full line including the
231
- // filename. The filename is the one thing a rename changes, so spelling it here made
232
- // this test fail with "could not locate the resolver block" on a perfectly good
233
- // rename, and on Windows (where it already failed for its own reason) that looked
234
- // like no new failure at all (task 1003704).
235
- const endPrefix = 'GDS_SESSION="${GDS_SESSION:-$HOME/.config/otb/';
236
- const at = src.indexOf(endPrefix, start);
237
- const close = at === -1 ? -1 : src.indexOf('"', at + endPrefix.length);
238
- assert.ok(start !== -1 && at !== -1 && close !== -1,
239
- `could not locate the resolver block in ${f}`);
240
- return src.slice(start, close + 1);
241
- }
242
-
243
- // These two assert on the PATH STRING the shell resolver prints. Everything above
244
- // checks content and syntax and runs everywhere; only these two compare spellings,
245
- // and Git Bash answers in its own mount namespace — a Win32 HOME comes back as
246
- // '/tmp/boxsess-…', which maps to a Git-install mount this side cannot reconstruct
247
- // without reading Git's mount table (task 1003529). The box scripts these cover run
248
- // on the Linux dev box, so the claim is verified where it is true rather than
249
- // left permanently red here — a red nobody can act on is how a suite stops being read.
250
- const SHELL_PATH_SPELLING_SKIP =
251
- 'shell path spellings: Git Bash resolves HOME into its own mount namespace (these scripts run on the Linux box)';
252
-
253
- test('the resolver prefers the most recently written session across config dirs', (t) => {
254
- if (process.platform === 'win32') return t.skip(SHELL_PATH_SPELLING_SKIP);
255
- const probe = bash(['-n', 'infra/box-report-version.sh']);
256
- if (!probe) return t.skip('bash not available on this machine');
257
-
258
- const home = fs.mkdtempSync(path.join(os.tmpdir(), 'boxsess-'));
259
- const legacy = path.join(home, '.config', 'otb');
260
- const configured = path.join(home, '.config', 'cloudbongos');
261
- fs.mkdirSync(legacy, { recursive: true });
262
- fs.mkdirSync(configured, { recursive: true });
263
- fs.writeFileSync(path.join(legacy, 'gds-session.json'), '{"token":"stale"}');
264
- fs.writeFileSync(path.join(configured, 'gds-session.json'), '{"token":"fresh"}');
265
- // Make the legacy copy unambiguously older than the freshly re-authed one.
266
- const old = new Date(Date.now() - 60_000);
267
- fs.utimesSync(path.join(legacy, 'gds-session.json'), old, old);
268
-
269
- const block = resolverBlock('box-report-version.sh');
270
- const run = (env) => spawnSync('bash', ['-c', `set -uo pipefail\n${block}\nprintf '%s' "$GDS_SESSION"`],
271
- { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: home, ...env } });
272
-
273
- // 1. the freshly written session wins over the stale legacy one
274
- assert.equal(run({}).stdout, path.join(configured, 'gds-session.json'));
275
-
276
- // 2. an explicit override still beats the scan
277
- assert.equal(run({ GDS_SESSION: '/explicit/path.json' }).stdout, '/explicit/path.json');
278
-
279
- // 3. with nothing on disk it falls back to the historical path, so the caller's
280
- // "no session at ..." message still names somewhere sensible
281
- const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'boxsess-empty-'));
282
- const r3 = spawnSync('bash', ['-c', `set -uo pipefail\n${block}\nprintf '%s' "$GDS_SESSION"`],
283
- { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: empty } });
284
- assert.equal(r3.stdout, path.join(empty, '.config', 'otb', SESSION_FILE));
285
-
286
- fs.rmSync(home, { recursive: true, force: true });
287
- fs.rmSync(empty, { recursive: true, force: true });
288
- });
289
-
290
- // box-heartbeat.sh is the odd one out: its override variable (OTB_GDS_SESSION) is
291
- // NOT the variable it resolves into (SF). A first cut of the fix guarded on the
292
- // override but assigned to SF, so a caller that SET the override had it silently
293
- // dropped and fell through to the legacy default — the exact bug being fixed,
294
- // reintroduced through the back door. Pin all three branches.
295
- // box-heartbeat.sh's resolver, pulled straight out of the committed file. Shared by the
296
- // two tests below so there is a single extraction to fix when the names next move.
297
- function heartbeatSfBlock() {
298
- const src = fs.readFileSync(path.join(INFRA, 'box-heartbeat.sh'), 'utf8');
299
- const start = src.indexOf('SF="${OTB_GDS_SESSION:-}"');
300
- // Prefix, not the whole line — see resolverBlock above.
301
- const endPrefix = 'SF="${SF:-$HOME/.config/otb/';
302
- const at = src.indexOf(endPrefix, start);
303
- const close = at === -1 ? -1 : src.indexOf('"', at + endPrefix.length);
304
- assert.ok(start !== -1 && at !== -1 && close !== -1,
305
- 'could not locate the SF resolver in box-heartbeat.sh');
306
- return src.slice(start, close + 1);
307
- }
308
-
309
- test('box-heartbeat.sh honors OTB_GDS_SESSION, scans, then falls back', (t) => {
310
- if (process.platform === 'win32') return t.skip(SHELL_PATH_SPELLING_SKIP);
311
- const probe = bash(['-n', 'infra/box-heartbeat.sh']);
312
- if (!probe) return t.skip('bash not available on this machine');
313
-
314
- const block = heartbeatSfBlock();
315
-
316
- const home = fs.mkdtempSync(path.join(os.tmpdir(), 'hbsess-'));
317
- const configured = path.join(home, '.config', 'cloudbongos');
318
- fs.mkdirSync(configured, { recursive: true });
319
- fs.writeFileSync(path.join(configured, 'gds-session.json'), '{"token":"fresh"}');
320
-
321
- const run = (env, h = home) => spawnSync('bash',
322
- ['-c', `set -euo pipefail\n${block}\nprintf '%s' "$SF"`],
323
- { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: h, ...env } }).stdout;
324
-
325
- assert.equal(run({ OTB_GDS_SESSION: '/explicit/override.json' }), '/explicit/override.json',
326
- 'the OTB_GDS_SESSION override must win — guarding on it but assigning elsewhere drops it');
327
- assert.equal(run({}), path.join(configured, 'gds-session.json'));
328
-
329
- const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'hbsess-empty-'));
330
- assert.equal(run({}, empty), path.join(empty, '.config', 'otb', SESSION_FILE));
331
-
332
- fs.rmSync(home, { recursive: true, force: true });
333
- fs.rmSync(empty, { recursive: true, force: true });
334
- });
335
-
336
- // The rename leaves the fleet in FOUR states at once, and the two tests above only cover
337
- // the pre-rename half (their fixtures write the old name). These are the other two, and
338
- // they are the ones a rename actually breaks (task 1003704). Same extracted resolver — one
339
- // harness for this block, deliberately, so part 4 (task 1003706) has one place to change.
340
- test('the box resolver handles a post-rename box and a migrated one', (t) => {
341
- const probe = bash(['-n', 'infra/box-heartbeat.sh']);
342
- if (!probe) return t.skip('bash not available on this machine');
343
-
344
- const block = heartbeatSfBlock();
345
- const run = (h) => spawnSync('bash', ['-c', `set -euo pipefail\n${block}\nprintf '%s' "$SF"`],
346
- { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: h } }).stdout;
347
-
348
- // A box provisioned AFTER the rename has ONLY the new name. Before the glob was widened
349
- // this fell through to the historical default, which does not exist, so every heartbeat
350
- // tick failed with a misleading "no session".
351
- const fresh = fs.mkdtempSync(path.join(os.tmpdir(), 'hbsess-new-'));
352
- const freshCfg = path.join(fresh, '.config', 'cloudbongos');
353
- fs.mkdirSync(freshCfg, { recursive: true });
354
- fs.writeFileSync(path.join(freshCfg, SESSION_FILE), '{"token":"fresh"}');
355
- const tail = (p) => p.replace(/\\/g, '/').split('/').slice(-2).join('/');
356
- assert.equal(tail(run(fresh)), `cloudbongos/${SESSION_FILE}`,
357
- 'a box provisioned after the rename must resolve the new name');
358
-
359
- // A MIGRATED box carries both, the new one written later by the CLI's migrate-on-read.
360
- // Newest-wins must take the new one; taking the older would read a stale token and report
361
- // the misleading "session expired" of task 1003252.
362
- const both = fs.mkdtempSync(path.join(os.tmpdir(), 'hbsess-both-'));
363
- const bothCfg = path.join(both, '.config', 'cloudbongos');
364
- fs.mkdirSync(bothCfg, { recursive: true });
365
- const legacyName = ic.SESSION_FILENAMES[1];
366
- fs.writeFileSync(path.join(bothCfg, legacyName), '{"token":"stale"}');
367
- fs.writeFileSync(path.join(bothCfg, SESSION_FILE), '{"token":"fresh"}');
368
- const older = new Date(Date.now() - 60_000);
369
- fs.utimesSync(path.join(bothCfg, legacyName), older, older);
370
- assert.equal(tail(run(both)), `cloudbongos/${SESSION_FILE}`,
371
- 'a migrated box must prefer the newly written file, not the superseded one');
372
-
373
- fs.rmSync(fresh, { recursive: true, force: true });
374
- fs.rmSync(both, { recursive: true, force: true });
375
- });
@@ -41,18 +41,6 @@ t('hasGameEntry: keyed on the preview entry file', () => {
41
41
  assert.equal(lib.hasGameEntry({ fs: { existsSync: () => false } }), false);
42
42
  });
43
43
 
44
- t('onBox: any box marker ⇒ true; none ⇒ false (keeps the localhost path off a box, task 1443)', () => {
45
- const only = (target) => ({ fs: { existsSync: (p) => p === target } });
46
- assert.equal(lib.onBox(only('/etc/otb/box.env')), true, 'box.env');
47
- assert.equal(lib.onBox(only('/usr/local/bin/box-preview')), true, 'box-preview wrapper');
48
- assert.equal(lib.onBox(only('/etc/systemd/system/box-game-preview.service')), true, 'box-game-preview unit');
49
- assert.equal(lib.onBox({ fs: { existsSync: () => false } }), false, 'a laptop has none of them');
50
- // honors the dep names sandbox-stage.js injects, and a throwing existsSync is swallowed → false
51
- assert.equal(lib.onBox({ boxEnvPath: '/custom/box.env', fs: { existsSync: (p) => p === '/custom/box.env' } }), true);
52
- assert.equal(lib.onBox({ previewWrapper: '/custom/box-preview', fs: { existsSync: (p) => p === '/custom/box-preview' } }), true);
53
- assert.equal(lib.onBox({ fs: { existsSync: () => { throw new Error('EACCES'); } } }), false, 'throwing existsSync → false, not a crash');
54
- });
55
-
56
44
  t('readPid: parses a pidfile; null on garbage/missing', () => {
57
45
  assert.equal(lib.readPid({ fs: { readFileSync: () => '12345\n' } }), 12345);
58
46
  assert.equal(lib.readPid({ fs: { readFileSync: () => 'garbage' } }), null);
@@ -46,8 +46,8 @@ t('parseMigrationName: core_NNN_slug.sql', () => {
46
46
  });
47
47
 
48
48
  t('parseMigrationName: multi-word module prefix splits at the number, not greedily', () => {
49
- const p = ns.parseMigrationName('dev_box_003_add_col.sql');
50
- assert.equal(p.prefix, 'dev_box', 'dev_box_003 → prefix dev_box, not dev');
49
+ const p = ns.parseMigrationName('platform_identity_003_add_col.sql');
50
+ assert.equal(p.prefix, 'platform_identity', 'platform_identity_003 → prefix platform_identity, not platform');
51
51
  assert.equal(p.number, 3);
52
52
  assert.equal(p.slug, 'add_col');
53
53
  });
@@ -114,10 +114,10 @@ t('filenameViolation: NEW module migration must be <key>_-prefixed', () => {
114
114
  });
115
115
 
116
116
  t('filenameViolation: hyphenated module key maps to underscore prefix', () => {
117
- assert.equal(ns.sqlNamespace('dev-box'), 'dev_box');
118
- assert.equal(ns.filenameViolation('module', 'dev_box_003_x.sql', { moduleKey: 'dev-box' }), null);
119
- const v = ns.filenameViolation('module', '003_x.sql', { moduleKey: 'dev-box' });
120
- assert.ok(v && /dev_box_-prefixed|dev_box_003_x/.test(v));
117
+ assert.equal(ns.sqlNamespace('platform-identity'), 'platform_identity');
118
+ assert.equal(ns.filenameViolation('module', 'platform_identity_003_x.sql', { moduleKey: 'platform-identity' }), null);
119
+ const v = ns.filenameViolation('module', '003_x.sql', { moduleKey: 'platform-identity' });
120
+ assert.ok(v && /platform_identity_-prefixed|platform_identity_003_x/.test(v));
121
121
  });
122
122
 
123
123
  // ---- stemCollisions --------------------------------------------------------
@@ -22,7 +22,7 @@ test('roster covers the decided ~16 modules + kernel, each with non-empty globs'
22
22
  // the decided roster from docs/design/modular-architecture/02-module-map.md §6 + kernel
23
23
  for (const k of ['kernel', 'lifecycle', 'economy', 'grading', 'memory', 'onboarding',
24
24
  'ideas', 'builder-settings', 'sessions', 'autonomy', 'security', 'hall-ui',
25
- 'status-ui', 'game', 'art-pipeline', 'discord', 'dev-box']) {
25
+ 'status-ui', 'game', 'art-pipeline', 'discord', 'control-plane']) {
26
26
  assert.ok(keys.includes(k), `roster missing module "${k}"`);
27
27
  const e = scope.scopeForModule(k);
28
28
  assert.ok(e && Array.isArray(e.globs) && e.globs.length > 0, `module "${k}" has no globs`);
@@ -97,7 +97,7 @@ test('`protected` is DERIVED from the imported PROTECTED_GLOBS, never hand-set',
97
97
  // inside a module, which is the core->module edge ADR 0083 rule 1 forbids.
98
98
  const PROTECTED_MODULES = [
99
99
  'agents',
100
- 'dev-box',
100
+ 'control-plane',
101
101
  'devsecops',
102
102
  'government',
103
103
  'grading',
@@ -119,7 +119,7 @@ test('the seven trust-core modules can never leave the protected set', () => {
119
119
  // with the rest: their territory IS the trust / ship / authz core, so an edit
120
120
  // that unprotects one is a security regression rather than a roster change.
121
121
  // ADR 0314 narrows which MODULES derive protected; it must never reach these.
122
- for (const k of ['kernel', 'lifecycle', 'government', 'grading', 'dev-box', 'devsecops', 'methodology']) {
122
+ for (const k of ['kernel', 'lifecycle', 'government', 'grading', 'control-plane', 'devsecops', 'methodology']) {
123
123
  assert.equal(scope.scopeForModule(k).protected, true, `${k} must stay protected`);
124
124
  }
125
125
  });
@@ -524,10 +524,10 @@ test('a cross-cutting module owns its paths everywhere EXCEPT the drift advisory
524
524
  });
525
525
 
526
526
  test('real cross-module contact is still flagged — the signal the advisory exists for', () => {
527
- // "You said lifecycle, you touched dev-box."
527
+ // "You said lifecycle, you touched discord."
528
528
  assert.deepEqual(
529
- scope.moduleDriftFiles('lifecycle', ['modules/dev-box/app/src/box-controller.js']),
530
- ['modules/dev-box/app/src/box-controller.js'],
529
+ scope.moduleDriftFiles('lifecycle', ['modules/discord/discord-bot.js']),
530
+ ['modules/discord/discord-bot.js'],
531
531
  );
532
532
  // Its own territory, and territory no module owns, both stay quiet.
533
533
  assert.deepEqual(scope.moduleDriftFiles('lifecycle', ['modules/lifecycle/rot.js']), []);
@@ -557,11 +557,11 @@ test('devsecops is deliberately NOT cross-cutting (task 1003770)', () => {
557
557
 
558
558
  test('moduleDriftFiles degrades safely on junk input', () => {
559
559
  // ship-flow calls this with whatever the claim carries; it must never throw.
560
- assert.deepEqual(scope.moduleDriftFiles('not-a-module', ['modules/dev-box/app.js']), [],
560
+ assert.deepEqual(scope.moduleDriftFiles('not-a-module', ['modules/discord/app.js']), [],
561
561
  'an unknown key has no declared scope to stray from');
562
562
  assert.deepEqual(scope.moduleDriftFiles('lifecycle', []), []);
563
563
  assert.deepEqual(scope.moduleDriftFiles('lifecycle', null), []);
564
- assert.deepEqual(scope.moduleDriftFiles(null, ['modules/dev-box/app.js']), []);
564
+ assert.deepEqual(scope.moduleDriftFiles(null, ['modules/discord/app.js']), []);
565
565
  });
566
566
 
567
567
  // EVERY PROTECTED PATH HAS AN OWNING MODULE (task 1004111).
@@ -642,7 +642,7 @@ test('adopting the unowned paths did NOT change which modules are protected', ()
642
642
  // The property that made assignment safe rather than a re-coupling: every
643
643
  // receiving module was ALREADY protected, so no goal became Archon-only that
644
644
  // was not before. If this fails, an adoption widened the trust boundary.
645
- for (const k of ['kernel', 'lifecycle', 'dev-box', 'devsecops', 'methodology']) {
645
+ for (const k of ['kernel', 'lifecycle', 'control-plane', 'devsecops', 'methodology']) {
646
646
  assert.equal(scope.scopeForModule(k).protected, true, `${k} was already protected and must stay so`);
647
647
  }
648
648
  // and the adoption changed no COUNT either. Pinned against PROTECTED_MODULES
@@ -16,8 +16,8 @@ const seams = require(path.join(ROOT, 'src', 'module-seams.js'));
16
16
  // removing/renaming a key is normally a MAJOR bump (a breaking change to every
17
17
  // module). Exception (BV1.R68 / ADR 0091 §3): relocating a DOMAIN query whose
18
18
  // sole in-repo consumer moves in lockstep is treated as MINOR — e.g.
19
- // `setBuilderBoxBlocked` left for modules/dev-box/db.js at 1.5.0, when `withTx`
20
- // was added; no enabled module at its pinned coreVersion lost a capability.
19
+ // a per-builder setter left the kernel for its own module's db.js at 1.5.0, when
20
+ // `withTx` was added; no enabled module at its pinned coreVersion lost a capability.
21
21
  const PUBLISHED_SURFACE = [
22
22
  'CORE_VERSION',
23
23
  'requireBuilder', 'requireRank', 'rankMeetsThreshold', 'requireBfgPrincipal', 'allowBoxScope', 'resolveSession',
@@ -25,7 +25,7 @@ const CORE = path.join(path.sep, 'srv', 'app', 'node_modules', '@bongos', 'core'
25
25
  const INST = path.join(path.sep, 'srv', 'app');
26
26
 
27
27
  const manifest = (over = {}) => ({
28
- key: 'x', title: 'X', description: 'a module', coreVersion: '^1.0.0', contributes: {}, ...over,
28
+ key: 'x', title: 'X', description: 'a module', version: '1.0.0', coreVersion: '^1.0.0', contributes: {}, ...over,
29
29
  });
30
30
  const loadedIn = (root, key, over = {}) => ({
31
31
  key, dir: path.join(root, 'modules', key), manifest: manifest({ key, title: key, ...over }),
@@ -63,11 +63,11 @@ test('scaffoldModule: writes a valid, loader-discoverable module with no core ed
63
63
 
64
64
  test('scaffoldModule: a kebab-case key produces matching dir, route file, and title', () => {
65
65
  const root = tmpModulesRoot();
66
- const { dir, manifest } = mod.scaffoldModule({ key: 'dev-box', modulesDir: root, core: '1.0.0' });
67
- assert.equal(manifest.title, 'Dev Box');
68
- assert.deepEqual(manifest.contributes.routes, ['dev-box']);
69
- assert.ok(fs.existsSync(path.join(dir, 'routes', 'dev-box.js')));
70
- assert.match(fs.readFileSync(path.join(dir, 'routes', 'dev-box.js'), 'utf8'), /devBoxRoutes/);
66
+ const { dir, manifest } = mod.scaffoldModule({ key: 'copy-desk', modulesDir: root, core: '1.0.0' });
67
+ assert.equal(manifest.title, 'Copy Desk');
68
+ assert.deepEqual(manifest.contributes.routes, ['copy-desk']);
69
+ assert.ok(fs.existsSync(path.join(dir, 'routes', 'copy-desk.js')));
70
+ assert.match(fs.readFileSync(path.join(dir, 'routes', 'copy-desk.js'), 'utf8'), /copyDeskRoutes/);
71
71
  });
72
72
 
73
73
  test('scaffoldModule: rejects an invalid key', () => {
@@ -102,8 +102,8 @@ test('evaluateUpgrade: all enabled modules satisfied → ok', () => {
102
102
  const v = mod.evaluateUpgrade({
103
103
  core: '1.0.0',
104
104
  rows: [
105
- { key: 'a', coreVersion: '^1.0.0', enabled: true, satisfied: true },
106
- { key: 'b', coreVersion: '^1.0.0', enabled: false, satisfied: true },
105
+ { key: 'a', version: '1.0.0', coreVersion: '^1.0.0', enabled: true, satisfied: true },
106
+ { key: 'b', version: '1.0.0', coreVersion: '^1.0.0', enabled: false, satisfied: true },
107
107
  ],
108
108
  });
109
109
  assert.equal(v.ok, true);
@@ -114,8 +114,8 @@ test('evaluateUpgrade: an ENABLED mismatch refuses; a DISABLED mismatch only war
114
114
  const v = mod.evaluateUpgrade({
115
115
  core: '1.0.0',
116
116
  rows: [
117
- { key: 'future', coreVersion: '^2.0.0', enabled: true, satisfied: false },
118
- { key: 'old-wip', coreVersion: '^2.0.0', enabled: false, satisfied: false },
117
+ { key: 'future', version: '1.0.0', coreVersion: '^2.0.0', enabled: true, satisfied: false },
118
+ { key: 'old-wip', version: '1.0.0', coreVersion: '^2.0.0', enabled: false, satisfied: false },
119
119
  ],
120
120
  });
121
121
  assert.equal(v.ok, false, 'an enabled mismatch must block (fail-closed)');
@@ -165,7 +165,7 @@ test('cmdUpgrade: exit 0 with no modules, exit 1 when an enabled module is incom
165
165
 
166
166
  const bad = mod.cmdUpgrade([], {
167
167
  ...sink,
168
- gather: () => ({ core: '1.0.0', errors: [], rows: [{ key: 'future', coreVersion: '^2.0.0', enabled: true, satisfied: false }] }),
168
+ gather: () => ({ core: '1.0.0', errors: [], rows: [{ key: 'future', version: '1.0.0', coreVersion: '^2.0.0', enabled: true, satisfied: false }] }),
169
169
  });
170
170
  assert.equal(bad, 1);
171
171
  assert.ok(lines.some((l) => /REFUSING/.test(l)), 'refusal is reported');
@@ -204,7 +204,7 @@ test('checkModulePublishability: fails a module containing denied content (game
204
204
  const dir = path.join(root, 'game');
205
205
  fs.mkdirSync(path.join(dir, 'public', 'assets'), { recursive: true });
206
206
  fs.writeFileSync(path.join(dir, 'module.json'), JSON.stringify({
207
- key: 'game', title: 'Game', description: 'x', coreVersion: '^1.0.0', license: 'AGPL-3.0', contributes: {},
207
+ key: 'game', title: 'Game', description: 'x', version: '1.0.0', coreVersion: '^1.0.0', license: 'AGPL-3.0', contributes: {},
208
208
  }, null, 2));
209
209
  fs.writeFileSync(path.join(dir, 'public', 'assets', 'reference-map.png'), 'not-a-real-png');
210
210
  const r = mod.checkModulePublishability('game', { modulesDir: root });
@@ -427,10 +427,10 @@ test('evaluateUpgrade: sorts self-declared deprecations, and flags the ones the
427
427
  const v = mod.evaluateUpgrade({
428
428
  core: '1.13.0',
429
429
  rows: [
430
- { key: 'healthy', coreVersion: '^1.0.0', enabled: true, satisfied: true, maintenance: { status: 'core-maintained' } },
431
- { key: 'sunset', coreVersion: '^1.0.0', enabled: false, satisfied: true, maintenance: { status: 'deprecated', successor: 'newer', removeAfter: '1.12.0' } },
432
- { key: 'sunset-later', coreVersion: '^1.0.0', enabled: false, satisfied: true, maintenance: { status: 'deprecated', note: 'going', removeAfter: '1.20.0' } },
433
- { key: 'nobody', coreVersion: '^1.0.0', enabled: false, satisfied: true, maintenance: { status: 'orphaned', note: 'unowned' } },
430
+ { key: 'healthy', version: '1.0.0', coreVersion: '^1.0.0', enabled: true, satisfied: true, maintenance: { status: 'core-maintained' } },
431
+ { key: 'sunset', version: '1.0.0', coreVersion: '^1.0.0', enabled: false, satisfied: true, maintenance: { status: 'deprecated', successor: 'newer', removeAfter: '1.12.0' } },
432
+ { key: 'sunset-later', version: '1.0.0', coreVersion: '^1.0.0', enabled: false, satisfied: true, maintenance: { status: 'deprecated', note: 'going', removeAfter: '1.20.0' } },
433
+ { key: 'nobody', version: '1.0.0', coreVersion: '^1.0.0', enabled: false, satisfied: true, maintenance: { status: 'orphaned', note: 'unowned' } },
434
434
  ],
435
435
  });
436
436
  assert.equal(v.ok, true, 'a deprecation is not a compatibility failure — it must not block a bump');
@@ -443,8 +443,8 @@ test('evaluateUpgrade: a module with no maintenance block is reported as undecla
443
443
  const v = mod.evaluateUpgrade({
444
444
  core: '1.0.0',
445
445
  rows: [
446
- { key: 'owned', coreVersion: '^1.0.0', enabled: true, satisfied: true, maintenance: { status: 'core-maintained' } },
447
- { key: 'orphan-risk', coreVersion: '^1.0.0', enabled: false, satisfied: true },
446
+ { key: 'owned', version: '1.0.0', coreVersion: '^1.0.0', enabled: true, satisfied: true, maintenance: { status: 'core-maintained' } },
447
+ { key: 'orphan-risk', version: '1.0.0', coreVersion: '^1.0.0', enabled: false, satisfied: true },
448
448
  ],
449
449
  });
450
450
  assert.deepEqual(v.undeclared.map((r) => r.key), ['orphan-risk']);
@@ -486,7 +486,7 @@ test('cmdUpgrade: a DISABLED incompatible module is reported with its owner, not
486
486
  core: '2.0.0',
487
487
  errors: [],
488
488
  rows: [{
489
- key: 'stale-catalog-entry', coreVersion: '^1.0.0', enabled: false, satisfied: false,
489
+ key: 'stale-catalog-entry', version: '1.0.0', coreVersion: '^1.0.0', enabled: false, satisfied: false,
490
490
  maintainer: 'Ada <ada@example.com>', maintenance: { status: 'maintained' },
491
491
  }],
492
492
  }),
@@ -507,8 +507,8 @@ test('cmdUpgrade: reports an expired deprecation and modules with no declared up
507
507
  core: '1.13.0',
508
508
  errors: [],
509
509
  rows: [
510
- { key: 'sunset', coreVersion: '^1.0.0', enabled: false, satisfied: true, maintenance: { status: 'deprecated', successor: 'newer', removeAfter: '1.12.0' } },
511
- { key: 'unowned', coreVersion: '^1.0.0', enabled: false, satisfied: true },
510
+ { key: 'sunset', version: '1.0.0', coreVersion: '^1.0.0', enabled: false, satisfied: true, maintenance: { status: 'deprecated', successor: 'newer', removeAfter: '1.12.0' } },
511
+ { key: 'unowned', version: '1.0.0', coreVersion: '^1.0.0', enabled: false, satisfied: true },
512
512
  ],
513
513
  }),
514
514
  });
@@ -54,10 +54,10 @@ test('collectContributions: ABSENT → empty; REGISTERED → each invoked with c
54
54
  assert.deepEqual(await seams.collectContributions('profile.fields', { builderId: 7 }), []);
55
55
  // registered — both function-contributors get the ctx and return their slice
56
56
  const seen = [];
57
- seams.contribute('profile.fields', (ctx) => { seen.push(ctx.builderId); return { box: { x: 1 } }; });
57
+ seams.contribute('profile.fields', (ctx) => { seen.push(ctx.builderId); return { gallery: { x: 1 } }; });
58
58
  seams.contribute('profile.fields', async (ctx) => { seen.push(ctx.builderId); return { discord: { linked: true } }; });
59
59
  const out = await seams.collectContributions('profile.fields', { builderId: 7 });
60
- assert.deepEqual(out, [{ box: { x: 1 } }, { discord: { linked: true } }]);
60
+ assert.deepEqual(out, [{ gallery: { x: 1 } }, { discord: { linked: true } }]);
61
61
  assert.deepEqual(seen, [7, 7]);
62
62
  seams._reset();
63
63
  });
@@ -86,13 +86,13 @@ test('collectContributions: a non-function contribution is returned as-is', asyn
86
86
  test('mergeContributions: adds new keys, refuses to overwrite a core key', () => {
87
87
  const base = { builder: { id: 1 }, discord: { linked: false } };
88
88
  const { base: merged, collisions } = seams.mergeContributions(base, [
89
- { box: { connected: true } }, // new key → added
89
+ { gallery: { connected: true } }, // new key → added
90
90
  { discord: { linked: true } }, // collides with a core key → ignored
91
91
  null, // ignored
92
92
  { art: { keySet: false } }, // new key → added
93
93
  ]);
94
94
  assert.equal(merged, base); // mutates + returns the same object
95
- assert.deepEqual(merged.box, { connected: true });
95
+ assert.deepEqual(merged.gallery, { connected: true });
96
96
  assert.deepEqual(merged.art, { keySet: false });
97
97
  assert.deepEqual(merged.discord, { linked: false }); // core value preserved
98
98
  assert.deepEqual(collisions, ['discord']);
@@ -139,9 +139,9 @@ test('settings-panel seam: ABSENT → empty; REGISTERED → present, sorted by o
139
139
  assert.deepEqual(OTB.settingsPanels(), []);
140
140
  // registered — registration order is by `order`, not insertion order
141
141
  OTB.contributeSettingsPanel({ id: 'discord', title: 'Connections', order: 30, mount() {} });
142
- OTB.contributeSettingsPanel({ id: 'dev-box', title: 'Dev box', order: 10, mount() {} });
142
+ OTB.contributeSettingsPanel({ id: 'art', title: 'Art keys', order: 10, mount() {} });
143
143
  const ids = OTB.settingsPanels().map((p) => p.id);
144
- assert.deepEqual(ids, ['dev-box', 'discord']);
144
+ assert.deepEqual(ids, ['art', 'discord']);
145
145
  // a panel without id or mount() is rejected loudly
146
146
  assert.throws(() => OTB.contributeSettingsPanel({ title: 'no id' }), /requires id \+ mount/);
147
147
  assert.throws(() => OTB.contributeSettingsPanel({ id: 'x' }), /requires id \+ mount/);
@@ -33,17 +33,17 @@ function fixtureRoot(spec) {
33
33
  }
34
34
 
35
35
  const validManifest = (key, extra = {}) => ({
36
- key, title: key, description: 'd', coreVersion: '^1.0.0', contributes: {}, ...extra,
36
+ key, title: key, description: 'd', version: '1.0.0', coreVersion: '^1.0.0', contributes: {}, ...extra,
37
37
  });
38
38
 
39
39
  test('discoverModules: loads a valid module, ignores a dir without module.json', () => {
40
40
  const root = fixtureRoot({
41
- 'dev-box': { manifest: validManifest('dev-box', { contributes: { routes: ['box'] } }) },
41
+ 'art-pipeline': { manifest: validManifest('art-pipeline', { contributes: { routes: ['stage'] } }) },
42
42
  'not-a-module': { files: { 'readme.txt': 'hi' } },
43
43
  });
44
44
  const { loaded, errors, skipped } = loader.discoverModules({ roots: [root], core: '1.0.0' });
45
45
  assert.equal(loaded.length, 1);
46
- assert.equal(loaded[0].key, 'dev-box');
46
+ assert.equal(loaded[0].key, 'art-pipeline');
47
47
  assert.deepEqual(errors, []);
48
48
  assert.deepEqual(skipped, []);
49
49
  });
@@ -71,7 +71,7 @@ test('discoverModules: key must match the directory name', () => {
71
71
  });
72
72
 
73
73
  test('discoverModules: a coreVersion mismatch is SKIPPED (not loaded, not an error)', () => {
74
- const root = fixtureRoot({ future: { manifest: validManifest('future', { coreVersion: '^2.0.0' }) } });
74
+ const root = fixtureRoot({ future: { manifest: validManifest('future', { version: '1.0.0', coreVersion: '^2.0.0' }) } });
75
75
  const { loaded, skipped, errors } = loader.discoverModules({ roots: [root], core: '1.0.0' });
76
76
  assert.equal(loaded.length, 0);
77
77
  assert.equal(errors.length, 0);
@@ -226,7 +226,7 @@ test('task 1003820: mountModuleRoutes refuses two enabled providers of one port'
226
226
  });
227
227
 
228
228
  test('the live tree discovers the extracted modules cleanly (BV1.R43/R48/R47/R49/R72)', () => {
229
- // dev-box graduated first (modules/dev-box/, BV1.R43), art-pipeline next
229
+ // the first module graduated in BV1.R43 (since retired), art-pipeline next
230
230
  // (BV1.R48 / task 1417), discord next (BV1.R47 / task 1416), game next
231
231
  // (BV1.R49 / c9cb5ea1 — public/ + src/world|rooms → modules/game/), then the
232
232
  // declaration-only character-anim module (ADR 0088 / task 1558), then memory —
@@ -276,7 +276,7 @@ test('the live tree discovers the extracted modules cleanly (BV1.R43/R48/R47/R49
276
276
  // That is precisely how the agents regression reached an open PR (task 1002485).
277
277
  loader._reset();
278
278
  const snap = loader.loaderSnapshot();
279
- assert.deepEqual(snap.loaded, ['agents', 'autonomy', 'builder-settings', 'copy-desk', 'dev-box', 'discord', 'economy', 'government', 'grading', 'hall-ui', 'ideas', 'lifecycle', 'memory', 'npm-release', 'onboarding', 'platform-identity', 'provisioning', 'public-landing', 'security', 'sessions', 'specialities', 'status-ui', 'ui-design']);
279
+ assert.deepEqual(snap.loaded, ['agents', 'autonomy', 'builder-settings', 'copy-desk', 'discord', 'economy', 'government', 'grading', 'hall-ui', 'ideas', 'lifecycle', 'memory', 'npm-release', 'onboarding', 'platform-identity', 'provisioning', 'public-landing', 'security', 'sessions', 'specialities', 'status-ui', 'ui-design']);
280
280
  assert.deepEqual(snap.errors, []);
281
281
  assert.deepEqual(snap.skipped, []);
282
282
  loader._reset();