@bongos/core 1.19.1073 → 1.19.1074

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 (297) hide show
  1. package/.bongos-core.json +320 -465
  2. package/.claude/skills/feedback/SKILL.md +2 -2
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +4 -60
  5. package/clients/bongos-client/index.cjs +4 -60
  6. package/clients/bongos-client/index.d.ts +5 -90
  7. package/clients/bongos-client/index.mjs +4 -60
  8. package/config/branding.neutral.json +1 -5
  9. package/config/modules.neutral.json +2 -3
  10. package/config/scheduled-routines.json +0 -2
  11. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
  12. package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
  13. package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
  14. package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
  15. package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
  16. package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
  17. package/docs/adr/README.md +1 -0
  18. package/docs/api/openapi.json +171 -1661
  19. package/docs/api-reference.md +9 -41
  20. package/docs/architecture.md +16 -1
  21. package/docs/branding-contract.md +0 -3
  22. package/docs/copy-inventory.md +454 -479
  23. package/docs/copy-registry.json +746 -978
  24. package/docs/file-map.md +5 -34
  25. package/docs/module-api-changelog.md +2 -0
  26. package/docs/modules-contract.md +13 -1
  27. package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
  28. package/docs/onboarding/diagrams/README.md +3 -3
  29. package/docs/onboarding/diagrams/assertions.json +2 -22
  30. package/docs/onboarding/primer.md +9 -28
  31. package/docs/page-inventory.json +4 -1
  32. package/docs/page-readings.json +1032 -1077
  33. package/docs/recipes/render-pilot.md +29 -10
  34. package/migrations/core_257_module_store_registry.sql +87 -0
  35. package/migrations/core_258_drop_dev_box_tables.sql +55 -0
  36. package/modules/agents/module.json +1 -0
  37. package/modules/autonomy/module.json +1 -0
  38. package/modules/builder-settings/module.json +1 -0
  39. package/modules/builder-settings/render-prefs.js +4 -5
  40. package/modules/copy-desk/docx.js +78 -5
  41. package/modules/copy-desk/module.json +1 -0
  42. package/modules/copy-desk/routes/copy-desk.js +9 -9
  43. package/modules/copy-desk/tests/copy_docx.mjs +107 -1
  44. package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
  45. package/modules/discord/module.json +1 -0
  46. package/modules/economy/module.json +1 -0
  47. package/modules/economy/routes/credits.js +1 -2
  48. package/modules/government/catalog.js +13 -17
  49. package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
  50. package/modules/government/module.json +1 -0
  51. package/modules/government/protected-surfaces.json +3 -11
  52. package/modules/government/resolver.js +3 -46
  53. package/modules/government/routes/government.js +0 -2
  54. package/modules/government/session-scopes.js +43 -83
  55. package/modules/government/session-scopes.json +8 -18
  56. package/modules/grading/module.json +1 -0
  57. package/modules/hall-ui/module.json +1 -0
  58. package/modules/hall-ui/public/board-lib.js +2 -2
  59. package/modules/hall-ui/public/builders.js +29 -40
  60. package/modules/hall-ui/public/collab.html +1 -1
  61. package/modules/hall-ui/public/collab.js +1 -1
  62. package/modules/hall-ui/public/diagrams.html +3 -3
  63. package/modules/hall-ui/public/dom-utils.js +1 -1
  64. package/modules/hall-ui/public/gate.js +2 -2
  65. package/modules/hall-ui/public/gate.states.json +1 -1
  66. package/modules/hall-ui/public/hall-render.js +43 -79
  67. package/modules/hall-ui/public/index.html +5 -1
  68. package/modules/hall-ui/public/modules.js +1 -0
  69. package/modules/hall-ui/public/oversight.css +5 -5
  70. package/modules/hall-ui/public/palette.js +0 -11
  71. package/modules/hall-ui/public/primer.js +1 -1
  72. package/modules/hall-ui/public/sessions.html +1 -1
  73. package/modules/hall-ui/public/settings-sessions.js +1 -1
  74. package/modules/hall-ui/public/settings.css +0 -1
  75. package/modules/hall-ui/public/settings.html +4 -33
  76. package/modules/hall-ui/public/settings.js +3 -33
  77. package/modules/hall-ui/public/settings.states.json +2 -2
  78. package/modules/hall-ui/public/studio.html +1 -1
  79. package/modules/hall-ui/public/style.css +10 -13
  80. package/modules/hall-ui/public/task.html +4 -1
  81. package/modules/hall-ui/public/task.js +36 -1
  82. package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
  83. package/modules/hall-ui/public/tweak-editor.css +16 -0
  84. package/modules/hall-ui/public/tweak-editor.html +26 -3
  85. package/modules/hall-ui/public/tweak-editor.js +32 -1
  86. package/modules/hall-ui/public/work.js +1 -1
  87. package/modules/hall-ui/records/tweak-editor.md +1 -0
  88. package/modules/ideas/module.json +1 -0
  89. package/modules/lifecycle/db-claim-reads.js +1 -58
  90. package/modules/lifecycle/db-ship.js +0 -5
  91. package/modules/lifecycle/db.js +0 -4
  92. package/modules/lifecycle/kickoff-checklist.js +0 -4
  93. package/modules/lifecycle/lifecycle.js +0 -8
  94. package/modules/lifecycle/module.json +1 -0
  95. package/modules/lifecycle/routes/tasks.js +5 -7
  96. package/modules/memory/module.json +1 -0
  97. package/modules/npm-release/module.json +1 -0
  98. package/modules/onboarding/module.json +1 -0
  99. package/modules/onboarding/onboarding-state.js +17 -36
  100. package/modules/onboarding/routes/access-requests.js +3 -3
  101. package/modules/platform-identity/module.json +1 -0
  102. package/modules/platform-identity/platform-identity.js +13 -5
  103. package/modules/platform-identity/routes/sso.js +2 -2
  104. package/modules/platform-identity/tests/platform-identity.mjs +17 -3
  105. package/modules/provisioning/module.json +1 -0
  106. package/modules/provisioning/provisioning.js +2 -2
  107. package/modules/provisioning/starter-bundles.js +5 -25
  108. package/modules/public-landing/module.json +1 -0
  109. package/modules/public-landing/public/assets/cosmos.css +1 -1
  110. package/modules/public-landing/public/contact.html +1 -1
  111. package/modules/public-landing/public/privacy.html +1 -1
  112. package/modules/public-landing/public/projects.html +101 -32
  113. package/modules/public-landing/public/projects.probes.json +2 -2
  114. package/modules/public-landing/public/projects.states.json +4 -3
  115. package/modules/public-landing/public/terms.html +1 -1
  116. package/modules/security/module.json +1 -0
  117. package/modules/sessions/module.json +1 -0
  118. package/modules/specialities/module.json +1 -0
  119. package/modules/status-ui/module.json +1 -0
  120. package/modules/ui-design/module.json +1 -0
  121. package/package-lock.json +2 -2
  122. package/package.json +1 -1
  123. package/release-notes.json +78 -0
  124. package/scripts/gds/artifact-format.js +203 -0
  125. package/scripts/gds/audit-authorship.sh +1 -13
  126. package/scripts/gds/claim.js +0 -15
  127. package/scripts/gds/cli-lib.js +8 -8
  128. package/scripts/gds/diagram-facts.js +12 -23
  129. package/scripts/gds/do-api.js +13 -80
  130. package/scripts/gds/doc-cli-guard.js +2 -0
  131. package/scripts/gds/doctor.js +1 -1
  132. package/scripts/gds/feedback-latest.js +139 -9
  133. package/scripts/gds/fitness-checks-identity.js +2 -7
  134. package/scripts/gds/fitness-checks-write-validation.js +4 -0
  135. package/scripts/gds/fitness-ratchets.js +4 -4
  136. package/scripts/gds/fitness.js +10 -10
  137. package/scripts/gds/gen-diagrams.js +49 -71
  138. package/scripts/gds/http-api-client.js +12 -19
  139. package/scripts/gds/init.js +1 -1
  140. package/scripts/gds/lint-infra-exec.js +1 -1
  141. package/scripts/gds/local-preview-lib.js +8 -39
  142. package/scripts/gds/local-preview.js +2 -15
  143. package/scripts/gds/migration-namespace.js +3 -3
  144. package/scripts/gds/module-artifact.js +212 -0
  145. package/scripts/gds/module.js +95 -6
  146. package/scripts/gds/package-core.js +5 -116
  147. package/scripts/gds/provision-config.js +2 -2
  148. package/scripts/gds/provision-net.js +2 -3
  149. package/scripts/gds/provision.js +9 -9
  150. package/scripts/gds/publish-manifest.js +1 -2
  151. package/scripts/gds/regen-instance-docs.js +1 -1
  152. package/scripts/gds/run-unit-tests.js +2 -16
  153. package/scripts/gds/sandbox-stage.js +39 -341
  154. package/scripts/gds/ship-flow.js +6 -6
  155. package/scripts/gds/ship-land.js +3 -4
  156. package/scripts/gds/ship-regen.js +10 -41
  157. package/scripts/gds/ship.js +4 -24
  158. package/scripts/gds/start.js +0 -15
  159. package/scripts/hall-preview/README.md +5 -5
  160. package/scripts/hall-preview/server.js +6 -6
  161. package/scripts/render-diagrams.sh +1 -1
  162. package/src/bongos/api-errors.js +1 -1
  163. package/src/bongos/auth-github.js +2 -3
  164. package/src/bongos/auth.js +51 -126
  165. package/src/bongos/db-kernel.js +2 -3
  166. package/src/bongos/db.js +5 -28
  167. package/src/bongos/module-scope-map.js +14 -29
  168. package/src/bongos/module-store.js +141 -0
  169. package/src/bongos/route-rank-check.js +3 -16
  170. package/src/bongos/routes/auth.js +0 -116
  171. package/src/bongos/routes/builders.js +5 -15
  172. package/src/bongos/routes/instance.js +8 -14
  173. package/src/bongos/routes/me.js +6 -22
  174. package/src/bongos/routes/modules.js +83 -0
  175. package/src/bongos/routes.js +5 -0
  176. package/src/module-api.js +13 -6
  177. package/src/module-loader/catalog.js +3 -0
  178. package/src/module-loader/loader.js +4 -4
  179. package/src/module-loader/manifest-schema.js +15 -7
  180. package/src/module-seams.js +2 -3
  181. package/src/modules.js +42 -14
  182. package/tests/agents_spend_guard.mjs +1 -1
  183. package/tests/api_client.mjs +0 -15
  184. package/tests/auth_page_gate.mjs +10 -10
  185. package/tests/canonical_profile_url.mjs +1 -3
  186. package/tests/claim_action.mjs +4 -1
  187. package/tests/claim_from_task_and_home.mjs +127 -0
  188. package/tests/consumer_layout_boot.mjs +2 -0
  189. package/tests/copy_desk_page_docx.mjs +33 -2
  190. package/tests/core_upgrade_runner.mjs +6 -6
  191. package/tests/deploy_divergence_line.mjs +11 -11
  192. package/tests/diagram_facts_offset.mjs +1 -1
  193. package/tests/do_api.mjs +106 -0
  194. package/tests/feedback_latest.mjs +122 -0
  195. package/tests/fitness.mjs +44 -42
  196. package/tests/gate_approvals.mjs +0 -1
  197. package/tests/go_live.mjs +3 -3
  198. package/tests/government_abuse_matrix.mjs +20 -19
  199. package/tests/government_ownership_scope.mjs +33 -35
  200. package/tests/government_parity.mjs +2 -2
  201. package/tests/government_protected_surfaces.mjs +56 -37
  202. package/tests/government_require_permission.mjs +3 -3
  203. package/tests/government_seed.mjs +37 -4
  204. package/tests/government_session_scope.mjs +92 -366
  205. package/tests/hall_error_envelope.mjs +4 -0
  206. package/tests/hall_landing_boot.mjs +4 -1
  207. package/tests/hall_palette.mjs +1 -16
  208. package/tests/hall_record_world.mjs +6 -0
  209. package/tests/hall_tweak_editor.mjs +52 -1
  210. package/tests/host_topology_skips.mjs +1 -2
  211. package/tests/html_comment_nesting.mjs +137 -0
  212. package/tests/infra_exec_paths.mjs +9 -10
  213. package/tests/init.mjs +4 -5
  214. package/tests/instance_manifest.mjs +18 -25
  215. package/tests/kickoff_checklist.mjs +1 -1
  216. package/tests/lib_sh_resolution.mjs +0 -221
  217. package/tests/local_preview.mjs +0 -12
  218. package/tests/module-scope-map.mjs +9 -9
  219. package/tests/module_catalog.mjs +1 -1
  220. package/tests/module_cli.mjs +15 -15
  221. package/tests/module_contributions.mjs +6 -6
  222. package/tests/module_loader.mjs +6 -6
  223. package/tests/module_manifest.mjs +13 -4
  224. package/tests/module_store_publish.mjs +307 -0
  225. package/tests/module_store_publish_route.mjs +121 -0
  226. package/tests/module_store_registry_migration.mjs +70 -0
  227. package/tests/modules.mjs +56 -26
  228. package/tests/onboarding_route_signals.mjs +21 -31
  229. package/tests/onboarding_state.mjs +35 -49
  230. package/tests/permission_path.mjs +0 -3
  231. package/tests/platform_boot.mjs +1 -2
  232. package/tests/profile_route.mjs +1 -6
  233. package/tests/projects_hub.mjs +5 -5
  234. package/tests/projects_hub_app_step.mjs +138 -0
  235. package/tests/projects_hub_module_picker.mjs +2 -2
  236. package/tests/projects_hub_pre_uat.mjs +4 -5
  237. package/tests/provision.mjs +7 -7
  238. package/tests/provisioning_starter_bundles.mjs +0 -22
  239. package/tests/publish_branch_route.mjs +10 -18
  240. package/tests/publish_manifest.mjs +1 -2
  241. package/tests/rank_tier_single_source.mjs +1 -13
  242. package/tests/repo_map.mjs +3 -4
  243. package/tests/runner_drift.mjs +3 -21
  244. package/tests/sandbox_stage.mjs +57 -390
  245. package/tests/session_rename_fallback.mjs +0 -21
  246. package/tests/ship_error_shape.mjs +1 -1
  247. package/tests/task_visual_slots.mjs +31 -0
  248. package/tests/terms_acceptance.mjs +1 -1
  249. package/tests/upgrade.mjs +4 -4
  250. package/tests/watch_sealed_floor.mjs +0 -1
  251. package/tests/wizard_draft_resume.mjs +2 -2
  252. package/tests/wizard_intent_resume.mjs +21 -10
  253. package/tests/wizard_preselect_why.mjs +18 -18
  254. package/docs/onboarding/browser-terminal-guide.md +0 -73
  255. package/docs/recipes/managed-settings-remote-control.md +0 -416
  256. package/modules/dev-box/CLAUDE.md +0 -15
  257. package/modules/dev-box/box-access.js +0 -623
  258. package/modules/dev-box/box-credential.js +0 -94
  259. package/modules/dev-box/box-onboard.js +0 -486
  260. package/modules/dev-box/boxes.js +0 -1038
  261. package/modules/dev-box/db.js +0 -39
  262. package/modules/dev-box/module.json +0 -19
  263. package/modules/dev-box/routes/box.js +0 -1223
  264. package/scripts/gds/box-auth-check.js +0 -160
  265. package/scripts/gds/box-infra.js +0 -321
  266. package/scripts/gds/box-sync.js +0 -216
  267. package/scripts/gds/box.js +0 -1332
  268. package/scripts/gds/cf-tunnel.js +0 -558
  269. package/scripts/gds/smoke-box.sh +0 -150
  270. package/scripts/gds/tree-preflight.js +0 -241
  271. package/src/bongos/app-pair.js +0 -172
  272. package/tests/app_pair.mjs +0 -168
  273. package/tests/box_access.mjs +0 -578
  274. package/tests/box_auth_check.mjs +0 -114
  275. package/tests/box_code_staleness.mjs +0 -129
  276. package/tests/box_connect_e2e.mjs +0 -196
  277. package/tests/box_cost.mjs +0 -70
  278. package/tests/box_credential.mjs +0 -125
  279. package/tests/box_dns_repoint.mjs +0 -153
  280. package/tests/box_env.mjs +0 -64
  281. package/tests/box_host_keys.mjs +0 -126
  282. package/tests/box_infra_resolve.mjs +0 -246
  283. package/tests/box_onboard.mjs +0 -317
  284. package/tests/box_scope_session.mjs +0 -119
  285. package/tests/box_sweep_docs.mjs +0 -119
  286. package/tests/box_sync.mjs +0 -55
  287. package/tests/box_sync_scope_report.mjs +0 -126
  288. package/tests/box_task_scope.mjs +0 -169
  289. package/tests/box_task_scope_pause.mjs +0 -66
  290. package/tests/box_terminal_ssrf.mjs +0 -96
  291. package/tests/boxes.mjs +0 -1585
  292. package/tests/cf_ruleset_preflight.mjs +0 -142
  293. package/tests/cf_tunnel.mjs +0 -496
  294. package/tests/docs_scrubber_damage.mjs +0 -111
  295. package/tests/module_scope_active_claims.mjs +0 -94
  296. package/tests/ship_api_client_pathspec.mjs +0 -100
  297. package/tests/tree_preflight.mjs +0 -243
@@ -263,23 +263,6 @@ test('the browser logout clears every session-cookie name', () => {
263
263
  }
264
264
  });
265
265
 
266
- // ── 6. the box scripts read both names ──────────────────────────────────────────────────────
267
-
268
- test('the baked box scripts scan BOTH session filenames', () => {
269
- // These run on live dev boxes and are baked at provision time, so they cannot import anything.
270
- // A box provisioned AFTER the rename writes only the new name; one provisioned before has only
271
- // the old. Missing either silently breaks heartbeat/reporting on half the fleet.
272
- for (const rel of ['infra/box-heartbeat.sh', 'infra/box-report-host-keys.sh',
273
- 'infra/box-report-terminal.sh', 'infra/box-report-version.sh']) {
274
- const src = read(rel);
275
- const glob = src.split('\n').find((l) => l.includes('for _sess in'));
276
- assert.ok(glob, `${rel}: session glob not found`);
277
- for (const name of [NEW_FILE, 'gds-session.json']) {
278
- assert.ok(glob.includes(name), `${rel}: glob must scan ${name} — got: ${glob.trim()}`);
279
- }
280
- }
281
- });
282
-
283
266
  // ── 8. the credential detector follows the rename ───────────────────────────────────────────
284
267
 
285
268
  test('the artifact-scan credential detector flags a read of EVERY session filename', () => {
@@ -295,7 +278,3 @@ test('the artifact-scan credential detector flags a read of EVERY session filena
295
278
  assert.ok(rule.includes(stem), `the detector must still match ${name} — got: ${rule.trim()}`);
296
279
  }
297
280
  });
298
- // The box resolver is EXECUTED — a post-rename box and a migrated one — in
299
- // tests/lib_sh_resolution.mjs, which already owns the extraction harness for that shell
300
- // block. Deliberately not duplicated here: two harnesses on one block is two things to
301
- // fix when the names next move (task 1003704).
@@ -11,7 +11,7 @@
11
11
  // * interpolating `data.error` into a template string stringifies the
12
12
  // envelope OBJECT — that is the literal "[object Object]";
13
13
  // * `data.error === 'some_code'` never matches, so the graceful branch
14
- // behind it (box_scope re-auth, verify-attestation remedy, the
14
+ // behind it (the re-auth hint, verify-attestation remedy, the
15
15
  // CLAIM_SESSION_MISMATCH explanation) is DEAD CODE against a live server;
16
16
  // * `data.<field>` reads a top-level field that MOVED into `error.details`.
17
17
  //
@@ -157,5 +157,36 @@ await test('attach uploads every slot file in the directory to its NAMED slot, a
157
157
  assert.equal(code, 0, 'all eight attached');
158
158
  });
159
159
 
160
+ // task 1004358: every test above drives the bare module router, which is why a
161
+ // real render 413'd live — buildGdsRouter's global 64 KB express.json runs FIRST
162
+ // and rejected any slot body over 64 KB before the route's own 8 MB parser saw
163
+ // it. So this case puts the REAL gate from buildGdsRouter (its first layer)
164
+ // in front of the same router, and sends a body the size of a real render.
165
+ await test('a slot render over 64 KB gets past buildGdsRouter\'s global body cap and attaches (task 1004358)', async () => {
166
+ const { buildGdsRouter } = require('../src/bongos/routes.js');
167
+ const gate = buildGdsRouter().stack[0].handle;
168
+ const whole = express();
169
+ whole.use(gate);
170
+ whole.use((req, res, next) => { res.fail = (code, status, extra) => res.status(typeof status === 'number' ? status : (status && status.status) || 500).json({ error: code, ...(extra || {}) }); next(); });
171
+ whole.use(require('../modules/lifecycle/routes/visuals.js')());
172
+ whole.use((err, req, res, next) => res.status(err.status || 500).json({ error: err.type || 'error' }));
173
+ const srv = http.createServer(whole);
174
+ await new Promise((r) => srv.listen(0, '127.0.0.1', r));
175
+ const at = `http://127.0.0.1:${srv.address().port}`;
176
+ // A real PNG padded past the cap: ~120 KB, the size of the renders that failed live.
177
+ const big = Buffer.concat([PNG, Buffer.alloc(120 * 1024)]);
178
+ const body = JSON.stringify({ image_b64: big.toString('base64'), content_type: 'image/png' });
179
+ assert.ok(body.length > 64 * 1024, 'the body is over the global cap');
180
+ try {
181
+ const post = (url) => fetch(at + url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body });
182
+ const control = await post('/tasks/5002/not-a-bypassed-path');
183
+ assert.equal(control.status, 413, 'the gate under test really is the 64 KB cap');
184
+ const r = await post('/tasks/5002/visuals/before-desktop-light');
185
+ const j = await r.json();
186
+ assert.equal(r.status, 201, `the render attaches, not 413: ${JSON.stringify(j)}`);
187
+ assert.equal(j.slot, 'before-desktop-light');
188
+ } finally { await new Promise((r) => srv.close(r)); }
189
+ });
190
+
160
191
  fs.rmSync(TMP, { recursive: true, force: true });
161
192
  summary();
@@ -495,7 +495,7 @@ await ta('no hub session, no acceptance — the unauthenticated caller is bounce
495
495
  assert.equal(pool.accounts.size, 0, 'an account (and an acceptance) was created for a caller with no session');
496
496
  });
497
497
 
498
- await ta('a dev-box session cannot mint an acceptance either (ADR 0016 trust boundary)', async () => {
498
+ await ta('a session of an undeclared source (a leftover dev-box token) cannot mint an acceptance either', async () => {
499
499
  SESSION = { ...HUB_SESSION, source: 'box' };
500
500
  pool.accounts.clear();
501
501
  const res = await authorize();
package/tests/upgrade.mjs CHANGED
@@ -1055,7 +1055,7 @@ function moduleLibStub(verdict) {
1055
1055
  t('preflightModules: a DISABLED incompatible module is returned as a warning, not dropped', () => {
1056
1056
  const dir = scratchConsumer();
1057
1057
  mkdirSync(join(dir, 'modules'), { recursive: true });
1058
- const warned = { key: 'stale-catalog-entry', coreVersion: '^1.0.0', enabled: false, satisfied: false, maintainer: 'Ada <ada@example.com>', maintenance: { status: 'maintained' } };
1058
+ const warned = { key: 'stale-catalog-entry', version: '1.0.0', coreVersion: '^1.0.0', enabled: false, satisfied: false, maintainer: 'Ada <ada@example.com>', maintenance: { status: 'maintained' } };
1059
1059
  const res = u.preflightModules({ instanceDir: dir, targetVersion: '2.0.0' }, {
1060
1060
  moduleLib: moduleLibStub({ ok: true, blocking: [], warnings: [warned], deprecations: [], expired: [], core: '2.0.0' }),
1061
1061
  });
@@ -1084,7 +1084,7 @@ t('preflightModules: no modules/ dir returns every collection empty (a vanilla c
1084
1084
  t('reportModulePreflight: prints the rotted-but-disabled module WITH its maintainer named', () => {
1085
1085
  const out = [];
1086
1086
  const mods = {
1087
- warnings: [{ key: 'stale-catalog-entry', coreVersion: '^1.0.0', maintainer: 'Ada <ada@example.com>', maintenance: { status: 'maintained' } }],
1087
+ warnings: [{ key: 'stale-catalog-entry', version: '1.0.0', coreVersion: '^1.0.0', maintainer: 'Ada <ada@example.com>', maintenance: { status: 'maintained' } }],
1088
1088
  deprecations: [], expired: [],
1089
1089
  };
1090
1090
  u.reportModulePreflight(mods, '2.0.0', { log: (s) => out.push(s), err: (s) => out.push(s) });
@@ -1122,8 +1122,8 @@ t('runUpgrade: a REFUSED bump still reports the non-blocking module findings', a
1122
1122
  err: (s) => out.push(String(s)),
1123
1123
  moduleLib: moduleLibStub({
1124
1124
  ok: false,
1125
- blocking: [{ key: 'enabled-breaker', coreVersion: '^1.0.0', enabled: true, satisfied: false }],
1126
- warnings: [{ key: 'stale-catalog-entry', coreVersion: '^1.0.0', maintenance: { status: 'orphaned', note: 'author unreachable' } }],
1125
+ blocking: [{ key: 'enabled-breaker', version: '1.0.0', coreVersion: '^1.0.0', enabled: true, satisfied: false }],
1126
+ warnings: [{ key: 'stale-catalog-entry', version: '1.0.0', coreVersion: '^1.0.0', maintenance: { status: 'orphaned', note: 'author unreachable' } }],
1127
1127
  deprecations: [], expired: [], core: '2.0.0',
1128
1128
  }),
1129
1129
  });
@@ -19,7 +19,6 @@
19
19
  // builder.roster.read → metic (ADR 0157, was archon)
20
20
  // session.search → metic (ADR 0157, was archon)
21
21
  // audit_log.read → metic (ADR 0157, was archon)
22
- // box.fleet.manage → metic (ADR 0157, was archon)
23
22
  //
24
23
  // A hardcoded 'metic' is the same defect one ADR later, which is why the fix reads
25
24
  // the floor off the DENIAL: requirePermission already rides `details.floors` on
@@ -90,9 +90,9 @@ test('post-auth: the real step is rendered', () => {
90
90
 
91
91
  test('a draft already at step 1 is unchanged, and an out-of-range step is not resurrected', () => {
92
92
  assert.equal(runStep({ authKnown: false, draftStep: 1 }).persisted.step, 1);
93
- // 8 is the done panel, reached by its own branch above this slice; 0 is the gate. Neither
93
+ // 9 is the done panel (8 before task 1004351), reached by its own branch above this slice; 0 is the gate. Neither
94
94
  // is a resumable wizard position, so the placeholder is allowed to stand.
95
- for (const bad of [0, 8, 99]) {
95
+ for (const bad of [0, 9, 99]) {
96
96
  assert.equal(runStep({ authKnown: false, draftStep: bad }).persisted.step, 1, `step ${bad} is not resumed`);
97
97
  }
98
98
  });
@@ -15,12 +15,12 @@
15
15
  // ruled out the task's other option (drop the record at a terminal state).
16
16
  //
17
17
  // The reset fires only when there is a SECOND DRAFT to lose, and the draft's own step is
18
- // what says so. A landed create clears DRAFT_KEY — but show(8) then persists the done
18
+ // what says so. A landed create clears DRAFT_KEY — but show(9) then persists the done
19
19
  // panel's position straight back into it, so a plain "is there a draft?" test is always
20
20
  // yes and would skip the reset forever, leaving the finished project's name sitting in
21
21
  // the fresh form. (That is exactly what the first cut of this change shipped into a
22
22
  // render: "still watching Mercury" above a name field still reading "Mercury".) Only
23
- // steps 1-7 are wizard steps; anything else is the done panel's own bookkeeping.
23
+ // steps 1-8 are wizard steps; anything else is the done panel's own bookkeeping.
24
24
  //
25
25
  // Harness: there is no jsdom in this repo, so this follows tests/wizard_draft_resume.mjs
26
26
  // and tests/project_door_ui.mjs — slice the real region out of the page's INLINE script
@@ -57,8 +57,8 @@ test('both slices are still findable (a rename must fail loudly, not silently pa
57
57
  assert.ok(offerSlice, 'paintResumeOffer() was not found in projects.html');
58
58
  assert.match(enterSlice, /if \(focus && !resumeIntent\) \{/, 'the intent branch is part of the slice');
59
59
  assert.match(enterSlice, /startProgress\(created\.id\);/, 'so is the resume branch it guards');
60
- assert.match(enterSlice, /draft\.step >= 1 && draft\.step <= 7/,
61
- 'the STORED draft\'s step decides, not state.step — state.step is 8 for the whole life of a finished standup');
60
+ assert.match(enterSlice, /draft\.step >= 1 && draft\.step <= 8/,
61
+ 'the STORED draft\'s step decides, not state.step — state.step is 9 for the whole life of a finished standup');
62
62
  assert.match(SCRIPT, /if \(created && created\.id\) \{ enterCreated\(created, !!focus\); return; \}/,
63
63
  'enter() still hands the created record straight to it');
64
64
  });
@@ -72,7 +72,7 @@ function runEnter({ focus, resumeIntent = false, draft = null, record }) {
72
72
  const calls = { show: [], resets: 0, offer: [], done: 0, progress: [], polls: 0, focused: 0 };
73
73
  // state.step is the DONE step for the whole life of a finished standup — the value
74
74
  // a draft-blind reading would have trusted
75
- const state = { step: draft ? draft.step : 8, name: 'Mercury', slug: 'mercury', domain: '', mode: 'greenfield', noAddress: false, modules: null };
75
+ const state = { step: draft ? draft.step : 9, name: 'Mercury', slug: 'mercury', domain: '', mode: 'greenfield', noAddress: false, modules: null };
76
76
  const sandbox = {
77
77
  record, focus, resumeIntent, state,
78
78
  DRAFT_KEY: 'draft',
@@ -98,7 +98,7 @@ const REC = { id: 'inst-1', name: 'Acme', slug: 'acme', domain: 'acme.example',
98
98
 
99
99
  test('a reload / Back / identity settle (focus false) still resumes the done panel, watch and all', () => {
100
100
  const { calls, state } = runEnter({ focus: false, record: REC });
101
- assert.deepEqual(calls.show, [[8, false]], 'the done panel, and no focus steal on a non-deliberate arrival');
101
+ assert.deepEqual(calls.show, [[9, false]], 'the done panel, and no focus steal on a non-deliberate arrival');
102
102
  assert.equal(calls.resets, 0, 'nothing is reset — this is the same standup continuing');
103
103
  assert.equal(calls.done, 1, 'the checklist is re-rendered');
104
104
  assert.deepEqual(calls.progress, ['inst-1'], 'the progress watch is restarted from the record');
@@ -126,7 +126,7 @@ test('…and the standup stays one click away, named, on the resume link', () =>
126
126
 
127
127
  test('the resume link gets the done panel back — the record was never cleared', () => {
128
128
  const { calls, sandbox } = runEnter({ focus: true, resumeIntent: true, record: REC });
129
- assert.deepEqual(calls.show, [[8, true]], 'a deliberate resume moves focus to the done panel');
129
+ assert.deepEqual(calls.show, [[9, true]], 'a deliberate resume moves focus to the done panel');
130
130
  assert.deepEqual(calls.progress, ['inst-1'], 'the watch comes back with it');
131
131
  assert.equal(sandbox.resumeIntent, false, 'the flag is ONE-SHOT — the next plain nav click must mean "new" again');
132
132
  assert.deepEqual(calls.offer, [null], 'and the offer comes down behind it');
@@ -144,17 +144,28 @@ test('a second draft already under way is NOT wiped by navigating back to the wi
144
144
  });
145
145
 
146
146
  test('the done panel\'s own saved step is NOT a second draft — it must not block the reset', () => {
147
- // Caught in a render, not in a unit test: show(8) calls saveDraft(), so DRAFT_KEY is
147
+ // Caught in a render, not in a unit test: show(9) calls saveDraft(), so DRAFT_KEY is
148
148
  // non-empty again the moment a create lands. Reading "is there a draft?" left the
149
149
  // finished project's name in the fresh form, under a line offering to resume it.
150
- const { calls, state } = runEnter({ focus: true, record: REC, draft: { step: 8, name: 'Mercury' } });
150
+ const { calls, state } = runEnter({ focus: true, record: REC, draft: { step: 9, name: 'Mercury' } });
151
151
  assert.equal(calls.resets, 1, 'the done step is bookkeeping, not a project being typed');
152
152
  assert.deepEqual(calls.show, [[1, false]], 'out-of-range steps land on the name step');
153
153
  assert.equal(state.name, '', 'and the finished project\'s name does not survive into it');
154
154
  assert.equal(calls.done, 0, 'never back onto the done panel');
155
155
  });
156
156
 
157
- test('a draft whose step is missing or junk resets too — only 1-7 preserves anything', () => {
157
+ test('a draft on the app step or the review is live too — the last two numbered steps (task 1004351)', () => {
158
+ // The app step moved the review from 7 to 8. A guard left at 7 would reset a second
159
+ // project sitting on its review, silently wiping it.
160
+ for (const step of [7, 8]) {
161
+ const { calls, state } = runEnter({ focus: true, record: REC, draft: { step } });
162
+ assert.equal(calls.resets, 0, `step ${step} is a live draft`);
163
+ assert.deepEqual(calls.show, [[step, true]]);
164
+ assert.equal(state.step, step);
165
+ }
166
+ });
167
+
168
+ test('a draft whose step is missing or junk resets too — only 1-8 preserves anything', () => {
158
169
  for (const draft of [{ step: 0 }, { step: 9 }, { name: 'half-typed' }]) {
159
170
  const { calls } = runEnter({ focus: true, record: REC, draft });
160
171
  assert.equal(calls.resets, 1, `step ${JSON.stringify(draft.step)} is not a wizard step`);
@@ -1,12 +1,12 @@
1
1
  // tests/wizard_preselect_why.mjs — the module picker always says WHY the preselect is on
2
2
  // (task 1003684).
3
3
  //
4
- // The bug as filed said the starter-bundle "why" sentence was being dropped for
5
- // Game + small-team. It was not: `adjustments` is a DELTA, and a rule that fires without
6
- // changing anything claims nothing (ADR 0243). The game bundle is already
7
- // ['dev-box','discord'] and the small-team rule ADDS dev-box, so the rule fires, moves
8
- // nothing, and correctly reports []. Verified against live core 1.19.580:
9
- // byTeamShape['small-team'] => {"bundle":["dev-box","discord"],"adjustments":[]}
4
+ // The bug as filed said the starter-bundle "why" sentence was being dropped for one
5
+ // type + team-shape pair. It was not: `adjustments` is a DELTA, and a rule that fires
6
+ // without changing anything claims nothing (ADR 0243). When a type's bundle already holds
7
+ // the module a rule adds — today the game bundle is ['discord'] and the community rule
8
+ // ADDS discord — the rule fires, moves nothing, and correctly reports []:
9
+ // byTeamShape.community => {"bundle":["discord"],"adjustments":[]}
10
10
  //
11
11
  // The real defect is what the owner then saw: two extras pre-switched-on with no reason
12
12
  // beside them — precisely the state the paint site's own comment calls out, "a preselect an
@@ -46,13 +46,13 @@ const BUNDLES = {
46
46
  type: 'game',
47
47
  label: 'Game starter',
48
48
  summary: 'Somewhere for players to gather, and a place to walk a change through before anyone plays it.',
49
- bundle: ['dev-box', 'discord'],
49
+ bundle: ['discord'],
50
50
  byTeamShape: {
51
51
  // the live case: the rule fires, moves nothing, reports nothing
52
- 'small-team': { bundle: ['dev-box', 'discord'], adjustments: [] },
52
+ community: { bundle: ['discord'], adjustments: [] },
53
53
  solo: {
54
- bundle: ['discord'],
55
- adjustments: [{ reason: 'You are building this alone, so the shared cloud environment is off — your own machine is the setup.' }],
54
+ bundle: [],
55
+ adjustments: [{ reason: 'You are building this alone, so the community space is off.' }],
56
56
  },
57
57
  },
58
58
  },
@@ -62,9 +62,9 @@ const BUNDLES = {
62
62
  summary: 'Extra agents to run the reading and the experiments the question needs.',
63
63
  bundle: ['agents'],
64
64
  byTeamShape: {
65
- 'small-team': {
66
- bundle: ['agents', 'dev-box'],
67
- adjustments: [{ reason: 'A small team gets one shared environment to build in, so nobody is debugging their own laptop.' }],
65
+ community: {
66
+ bundle: ['agents', 'discord'],
67
+ adjustments: [{ reason: 'You said an open community is building this, so somewhere for it to gather is switched on.' }],
68
68
  },
69
69
  },
70
70
  },
@@ -83,16 +83,16 @@ function why({ projType, teamShape, modules = null, bundles = BUNDLES }) {
83
83
  return Array.from(sandbox.__out);
84
84
  }
85
85
 
86
- test('game + small-team: no delta, so the type\'s own summary is the reason', () => {
86
+ test('game + community: no delta, so the type\'s own summary is the reason', () => {
87
87
  // This is the exact combination from the live walk that surfaced the bug.
88
- const out = why({ projType: 'game', teamShape: 'small-team' });
88
+ const out = why({ projType: 'game', teamShape: 'community' });
89
89
  assert.deepEqual(out, ['Somewhere for players to gather, and a place to walk a change through before anyone plays it.']);
90
90
  });
91
91
 
92
92
  test('a REAL adjustment still speaks for itself — the summary does not displace it', () => {
93
- const out = why({ projType: 'research', teamShape: 'small-team' });
93
+ const out = why({ projType: 'research', teamShape: 'community' });
94
94
  assert.equal(out.length, 1);
95
- assert.match(out[0], /shared environment/);
95
+ assert.match(out[0], /somewhere for it to gather/);
96
96
  assert.doesNotMatch(out[0], /reading and the experiments/, 'the rule\'s sentence wins, not the summary');
97
97
  });
98
98
 
@@ -128,7 +128,7 @@ test('before the bundles read lands, nothing is claimed', () => {
128
128
 
129
129
  test('every sentence returned is a plain string, so the paint site can escape it directly', () => {
130
130
  for (const t of ['game', 'research']) {
131
- for (const s of ['small-team', 'solo', null]) {
131
+ for (const s of ['community', 'solo', null]) {
132
132
  for (const line of why({ projType: t, teamShape: s })) {
133
133
  assert.equal(typeof line, 'string', `${t}/${s} yielded a non-string`);
134
134
  }
@@ -1,73 +0,0 @@
1
- # Building from a Chromebook, iPad, or phone (the browser terminal)
2
-
3
- *Plain-language how-to for builders on a device that can't run the Claude Code desktop app.*
4
-
5
- Most builders run Claude Code in the **desktop app** (Mac or Windows). But you don't need it. If all you have is a **Chromebook, iPad, or phone** — anything with a web browser — you can still build, because your dev box serves its own **terminal in a browser tab**, and from there you switch to driving the box from the Claude app. This is the **Remote Control** path.
6
-
7
- You never install anything and you never touch SSH. It's two things: open a link, then sign in.
8
-
9
- > **On a Mac or Windows laptop?** You don't need this page — use the desktop app instead ([claude.com/download](https://claude.com/download)); it's the smoother path. This guide is only for browser-only devices.
10
-
11
- ## What you'll do, at a glance
12
-
13
- 1. Ask for your box and its terminal link (**`/builder-box`**).
14
- 2. Open the link and sign in.
15
- 3. Start Claude in the terminal and sign in once.
16
- 4. Switch to **Remote Control** so you can work from the Claude app.
17
-
18
- That's it. Steps 1–3 are a one-time launchpad; after that you live in the Claude app.
19
-
20
- ## Step 1 — Get your box and the terminal link
21
-
22
- In a Claude session, run **`/builder-box`**. It's your box's control panel — it will:
23
-
24
- - **wake or create your box** hands-off if it isn't already running (you never wait on an operator), and
25
- - give you your **terminal link** and a **one-time password** once the box is up.
26
-
27
- If the box was asleep, give it about a minute to come up, then run `/builder-box` again to read the link. (Under the hood `/builder-box` uses the box's own web terminal, published to the hall — you don't need any of that detail; just run the command.)
28
-
29
- ## Step 2 — Open the link and sign in
30
-
31
- 1. Open the **terminal link** in any browser tab.
32
- 2. A small sign-in box appears. Enter:
33
- - **Username:** `otb`
34
- - **Password:** the one-time password `/builder-box` gave you.
35
- 3. You're now looking at a terminal running on your box, already in your project folder.
36
-
37
- ## Step 3 — Start Claude in the terminal
38
-
39
- In that terminal, type:
40
-
41
- ```
42
- claude
43
- ```
44
-
45
- and press Enter. The first time, it walks you through a normal browser sign-in (and may ask you to trust the folder once — say yes). When it finishes, you're in a real, interactive Claude Code session on your box. This first sign-in is the whole reason the browser terminal exists: a real person at a real browser can complete the login that a headless setup never could.
46
-
47
- ## Step 4 — Switch to Remote Control (the daily surface)
48
-
49
- The terminal is a **launchpad, not where you'll live.** Once Claude is running in the terminal, run this **inside that session**:
50
-
51
- ```
52
- /remote-control
53
- ```
54
-
55
- It prints a **pairing link**. Open that link and you can now drive your box straight from the **Claude web or mobile app** — a much nicer surface than the terminal tab. From here on, that's where you work; the terminal was just where you logged in.
56
-
57
- > ⚠️ **Use the `/remote-control` slash command from inside a running Claude session — not a standalone `claude rc` command in the shell.** The standalone version mis-reads folder trust and fails; starting Remote Control from within an already-signed-in session is the path that works.
58
-
59
- ## Good to know
60
-
61
- - **Closing the terminal tab ends the session and closes the box.** That's expected — it's how the session cleans up. Your work that's committed/shipped is safe; the box itself just shuts down.
62
- - **The box sleeps when idle and wakes on demand.** Next time, start again at Step 1 — `/builder-box` wakes it and hands you a fresh link.
63
- - **You also get a live game preview.** If your box is on the stable-hostname setup, your terminal link looks like `https://term-<you>.example.com`; your private game preview (the **sandbox**) is the same address with `term-` swapped for `sandbox-` — `https://sandbox-<you>.example.com`. Run **`/builder-stage`** to push your current edits there and review a change in a browser before you ship it. (See "Your own dev box" in the [primer](primer.md).)
64
-
65
- ## If something isn't working
66
-
67
- - **No link yet?** The box may still be waking. Wait ~1 minute after `/builder-box` says it's starting, then run `/builder-box` again.
68
- - **Password rejected?** Re-run `/builder-box` to get a fresh link + password — the box republishes them when it restarts.
69
- - **Brand-new to the project on a browser-only device?** Getting the very first box onto a pure browser-only device can need a one-time assist (there's a known first-boot gap for a device with no terminal at all). If `/builder-box` can't hand you a link, ask an Archon to help seed your box once; after that the browser flow works on its own.
70
-
71
- ---
72
-
73
- *Deeper detail (the systemd units, the Cloudflare tunnel, operator setup, and end-to-end verification) lives in the operator recipe [`docs/recipes/managed-settings-remote-control.md`](../recipes/managed-settings-remote-control.md) (Part 2) and [ADR 0038](../adr/0038-chromebook-ttyd-cloudflare-tunnel.md) / [ADR 0040](../adr/0040-remote-control-default-browser-onramp.md). This page is the builder-facing summary of that flow.*