@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
@@ -1,111 +0,0 @@
1
- // tests/docs_scrubber_damage.mjs — the secret-scrubber ate real prose in
2
- // docs/recipes/managed-settings-remote-control.md (task 1004027, part 4 of the
3
- // damage family after 1004021 / 1004022).
4
- //
5
- // Two distinct repairs, and the SECOND is the trap. A scrubber pass replaced
6
- // two pieces of ordinary content with the literal `<redacted>`:
7
- // - an in-page anchor target, so the link went nowhere;
8
- // - an `infra/*.sh` script name, so the doc pointed at no script at all.
9
- // But four OTHER `<redacted>` occurrences in the same file are DELIBERATE — they
10
- // stand in for real secrets (a tunnel credential, two tokens, a trust seed).
11
- // A global sweep "fixes" the two and destroys the four, and nothing in the repo
12
- // would notice, because the anchor lint only ever looks at link targets.
13
- //
14
- // So this file pins BOTH halves: the anchor and the script path resolve, AND the
15
- // placeholder count stays at exactly four. The second assertion is the one that
16
- // makes a future sweep fail loudly instead of quietly.
17
- import assert from 'node:assert/strict';
18
- import { test } from 'node:test';
19
- import { createRequire } from 'node:module';
20
- import { fileURLToPath } from 'node:url';
21
- import fs from 'node:fs';
22
- import path from 'node:path';
23
-
24
- const require = createRequire(import.meta.url);
25
- const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
26
- const { parseFile } = require(path.join(ROOT, 'scripts', 'gds', 'docs-entropy.js'));
27
-
28
- const REL = 'docs/recipes/managed-settings-remote-control.md';
29
- const ABS = path.join(ROOT, REL);
30
- const raw = () => fs.readFileSync(ABS, 'utf8');
31
-
32
- // The doc is only worth asserting against while it exists at this path.
33
- test('the recipe under repair is still tracked at its documented path', () => {
34
- assert.ok(fs.existsSync(ABS), `${REL} is missing — move this test with it`);
35
- });
36
-
37
- test('every in-page anchor in the recipe resolves to a heading it actually has', () => {
38
- // The damaged link read `[named-tunnel automation](#<redacted>)`. Resolve
39
- // through the SAME parser docs-entropy.js uses, so the slug this asserts is
40
- // the slug the lint computes — a test with its own slug rule would let the
41
- // doc pass here and still red the ratchet.
42
- const { headings, links } = parseFile(ABS);
43
- const slugs = new Set(headings.map((h) => h.slug));
44
- const inPage = links.filter((l) => l.href.startsWith('#'));
45
-
46
- assert.ok(inPage.length > 0, 'fixture sanity: the recipe does link to its own sections');
47
- const broken = inPage.filter((l) => !slugs.has(l.href.slice(1)));
48
- assert.deepEqual(
49
- broken.map((l) => `${l.line}: ${l.href}`), [],
50
- 'an in-page anchor points at no heading in this file — this is what doc_broken_anchors counts',
51
- );
52
- });
53
-
54
- test('the key-sync cron names a script the cloud-init renderer actually installs', () => {
55
- // The damaged line read `infra/<redacted>.sh`. The authority for the real name
56
- // is not prose anywhere: it is the renderer that bakes the script into the
57
- // cloud-init, so assert the doc against THAT rather than against a literal.
58
- const line = raw().split(/\r?\n/).find((l) => l.includes('GET /box/authorized-keys'))
59
- ?? raw().split(/\r?\n/).find((l) => l.includes('keeps `authorized_keys` in sync'));
60
- assert.ok(line, 'fixture sanity: the recipe still describes the authorized_keys sync cron');
61
-
62
- const named = [...raw().matchAll(/`infra\/([a-z0-9-]+\.sh)`/g)].map((m) => m[1]);
63
- assert.ok(
64
- named.includes('box-authorized-keys-fetch.sh'),
65
- `the recipe names no authorized-keys script; found ${JSON.stringify(named)}`,
66
- );
67
-
68
- // Scoped to the repaired name on purpose. Other `infra/*.sh` the recipe cites
69
- // (box-connect.sh) are builder-side scripts absent from this checkout for an
70
- // unrelated, pre-existing reason (idea 1000895) — asserting over all of them
71
- // would red this test on a gap it is not about.
72
- const renderer = fs.readFileSync(path.join(ROOT, 'scripts', 'gds', 'box-infra.js'), 'utf8');
73
- assert.ok(
74
- renderer.includes('box-authorized-keys-fetch.sh'),
75
- 'box-infra.js no longer bakes box-authorized-keys-fetch.sh into the cloud-init — '
76
- + 'the recipe now names a script nothing installs, so fix the doc to match the renderer',
77
- );
78
- });
79
-
80
- test('the four deliberate <redacted> secret placeholders survive — a global sweep must fail here', () => {
81
- // This is the anti-fix assertion. Each of these stands in for a real secret and
82
- // is correct as written; "repairing" them is the damage, not the cure.
83
- const lines = raw().split(/\r?\n/);
84
- const placeholders = lines
85
- .map((text, i) => ({ line: i + 1, text }))
86
- .filter((l) => l.text.includes('<redacted>'));
87
-
88
- assert.equal(
89
- placeholders.length, 4,
90
- 'expected exactly the four secret placeholders, got:\n'
91
- + placeholders.map((p) => ` ${p.line}: ${p.text.trim().slice(0, 110)}`).join('\n'),
92
- );
93
-
94
- // Named by CONTEXT, not by line number, so ordinary edits above them don't red this.
95
- const contexts = ['"credential"', 'BOX_TUNNEL_TOKEN', 'BOX_SOURCE_FULL_TOKEN', '/root/.claude.json'];
96
- for (const ctx of contexts) {
97
- assert.ok(
98
- placeholders.some((p) => p.text.includes(ctx)),
99
- `the deliberate placeholder for ${ctx} is gone — a <redacted> sweep took a real secret stand-in with it`,
100
- );
101
- }
102
-
103
- // And no placeholder may sit in a link target or a path again: those two shapes
104
- // are exactly what the scrubber damaged.
105
- for (const p of placeholders) {
106
- assert.ok(
107
- !/\(#<redacted>\)/.test(p.text) && !/infra\/<redacted>/.test(p.text),
108
- `line ${p.line} carries scrubber damage, not a secret placeholder: ${p.text.trim().slice(0, 110)}`,
109
- );
110
- }
111
- });
@@ -1,94 +0,0 @@
1
- // tests/module_scope_active_claims.mjs
2
- //
3
- // Unit guard for db.moduleScopeForActiveClaims (goal 1000051 / R92, ADR 0148 §2) —
4
- // the lifecycle-port resolver the dev-box source-access path (R93/R94) reads to
5
- // derive a builder's TASK-scoped sparse-checkout: the union of module KEYS across
6
- // the builder's active claims' goals (claims → tasks → goals.scope_modules).
7
- //
8
- // DB-free, same posture as goal_achievement.mjs: the function honours the #538
9
- // deps.pool injection seam (ONLY when NODE_ENV==='test'), so a SQL-aware fake
10
- // client drives it with no live Postgres. We assert the query SHAPE (joins
11
- // claims→tasks→goals, unnests scope_modules, filters ACTIVE claims), the bound
12
- // params, the rows→keys mapping, and the numeric-input guard. The actual
13
- // set-union/dedup is Postgres's DISTINCT (exercised end-to-end once box-access is
14
- // wired — R95); here we prove the port method's contract.
15
- //
16
- // Run: node tests/module_scope_active_claims.mjs
17
-
18
- import { strict as assert } from 'node:assert';
19
- import { createRequire } from 'node:module';
20
- import { makeRunner, makeSqlAwareClient } from './helpers.mjs';
21
-
22
- process.env.NODE_ENV = 'test';
23
-
24
- const require = createRequire(import.meta.url);
25
- const { moduleScopeForActiveClaims } = require('../modules/lifecycle/db.js');
26
-
27
- const { test, summary } = makeRunner();
28
-
29
- function fakePool(handler) {
30
- const c = makeSqlAwareClient(handler);
31
- return { query: (sql, params) => c._client.query(sql, params), connect: c.connect, queries: c._client.queries };
32
- }
33
- const norm = (s) => String(s).replace(/\s+/g, ' ').trim();
34
- const find = (queries, re) => queries.find((q) => re.test(norm(q.sql)));
35
-
36
- // The one read this function issues: claims → tasks → goals, unnest scope_modules,
37
- // active claims only. The fake returns whatever module-key rows we hand it (the
38
- // DISTINCT/ORDER BY is Postgres's job — here we just prove the plumbing).
39
- function scopeHandler(rows) {
40
- return (s) => {
41
- if (/FROM claims c/.test(s) && /JOIN goals g/.test(s) && /unnest\(g\.scope_modules\)/.test(s)) {
42
- return { rows };
43
- }
44
- return null;
45
- };
46
- }
47
-
48
- // =========================================================================
49
- // Happy path — active claims put N goals' scope_modules in scope
50
- // =========================================================================
51
- await test('returns the module keys the active claims scope, in DB order', async () => {
52
- const pool = fakePool(scopeHandler([{ module_key: 'dev-box' }, { module_key: 'kernel' }, { module_key: 'lifecycle' }]));
53
- const keys = await moduleScopeForActiveClaims(3, { pool });
54
- assert.deepEqual(keys, ['dev-box', 'kernel', 'lifecycle'], 'maps rows → module_key list');
55
-
56
- const q = find(pool.queries, /FROM claims c JOIN tasks t .* JOIN goals g/);
57
- assert.ok(q, 'issues the claims→tasks→goals scope read');
58
- assert.match(norm(q.sql), /c\.builder_id = \$1 AND c\.released_at IS NULL/, 'active claims only, keyed on builder');
59
- assert.match(norm(q.sql), /unnest\(g\.scope_modules\)/, 'unnests the goal scope_modules array');
60
- assert.match(norm(q.sql), /SELECT DISTINCT/, 'DISTINCT dedupes the union across claims');
61
- assert.deepEqual(q.params, [3], 'binds the builder id');
62
- });
63
-
64
- // =========================================================================
65
- // No active claim (or no scoped goal) → base-only signal ([])
66
- // =========================================================================
67
- await test('no active claim / no scoped goal → empty array (box falls back to BASE-only)', async () => {
68
- const pool = fakePool(scopeHandler([]));
69
- const keys = await moduleScopeForActiveClaims(3, { pool });
70
- assert.deepEqual(keys, [], 'zero rows → []');
71
- });
72
-
73
- // =========================================================================
74
- // Input guard — a non-numeric builder id never reaches SQL
75
- // =========================================================================
76
- await test('non-numeric builderId throws before any query', async () => {
77
- const pool = fakePool(scopeHandler([{ module_key: 'dev-box' }]));
78
- await assert.rejects(() => moduleScopeForActiveClaims('not-a-number', { pool }), /must be numeric/);
79
- await assert.rejects(() => moduleScopeForActiveClaims(NaN, { pool }), /must be numeric/);
80
- assert.equal(pool.queries.length, 0, 'guard trips before the pool is touched');
81
- });
82
-
83
- // =========================================================================
84
- // The #538 deps.pool test seam is actually wired (we ARE in NODE_ENV==='test')
85
- // =========================================================================
86
- await test('honours the deps.pool test seam', async () => {
87
- let touched = false;
88
- const pool = fakePool((s) => { touched = true; return /unnest/.test(s) ? { rows: [{ module_key: 'game' }] } : null; });
89
- const keys = await moduleScopeForActiveClaims(7, { pool });
90
- assert.ok(touched, 'the injected pool was used');
91
- assert.deepEqual(keys, ['game']);
92
- });
93
-
94
- summary();
@@ -1,100 +0,0 @@
1
- // tests/ship_api_client_pathspec.mjs
2
- //
3
- // Unit tests for task 1002417 — ship.js named a git path that could not exist,
4
- // and it broke EVERY ship.
5
- //
6
- // regenerateApiClient() stages the regenerated typed client plus the devbox
7
- // Electron app's vendored CJS copy. After the app moved from a top-level
8
- // devbox-app/ to modules/dev-box/app/ (ADR 0145) the vendored path went stale.
9
- // The failure was asymmetric and therefore silent for a long time:
10
- //
11
- // git status --porcelain <missing path> → exit 0 (tolerated, so no signal)
12
- // git add <missing path> → exit 128 (aborts the WHOLE stage)
13
- //
14
- // Because the `git add` aborts, the freshly regenerated clients/bongos-client is
15
- // never staged, the worktree stays dirty, and the ship's auto-merge then bails
16
- // with "main worktree busy" — stranding the task at `confirmed` with no PR.
17
- //
18
- // apiClientPathspec is the pure rule behind that: never name a path `git add`
19
- // cannot match. Proven here with an injected existsSync — no git, no fs.
20
- //
21
- // Run: node tests/ship_api_client_pathspec.mjs
22
-
23
- import { strict as assert } from 'node:assert';
24
- import { createRequire } from 'node:module';
25
- import path from 'node:path';
26
-
27
- const require = createRequire(import.meta.url);
28
- const ship = require('../scripts/gds/ship.js');
29
- const { apiClientPathspec, VENDORED_CLIENT_REL } = ship;
30
-
31
- let passed = 0;
32
- let failed = 0;
33
- function test(name, fn) {
34
- try {
35
- fn();
36
- passed++;
37
- console.log(` ok ${name}`);
38
- } catch (err) {
39
- failed++;
40
- console.log(` FAIL ${name}`);
41
- console.log(` ${err.message}`);
42
- }
43
- }
44
-
45
- const ROOT = path.join('/repo');
46
- const never = () => false;
47
- const always = () => true;
48
-
49
- test('the vendored copy lives inside the dev-box module, not at the old top level', () => {
50
- assert.equal(VENDORED_CLIENT_REL, 'modules/dev-box/app/src/vendor/bongos-client.cjs');
51
- assert.ok(!VENDORED_CLIENT_REL.startsWith('devbox-app/'), 'must not use the pre-ADR-0145 path');
52
- });
53
-
54
- test('the generated client is always staged', () => {
55
- assert.ok(apiClientPathspec({ worktreeRoot: ROOT, existsSync: never }).includes('clients/bongos-client'));
56
- assert.ok(apiClientPathspec({ worktreeRoot: ROOT, existsSync: always }).includes('clients/bongos-client'));
57
- });
58
-
59
- test('vendored copy present → it is staged too', () => {
60
- const p = apiClientPathspec({ worktreeRoot: ROOT, existsSync: always });
61
- assert.deepEqual(p, ['clients/bongos-client', VENDORED_CLIENT_REL]);
62
- });
63
-
64
- // The regression. Before the fix this returned the vendored path unconditionally,
65
- // so `git add` exited 128 and left the worktree dirty.
66
- test('vendored copy ABSENT → it is omitted (git add would abort on it)', () => {
67
- const p = apiClientPathspec({ worktreeRoot: ROOT, existsSync: never });
68
- assert.deepEqual(p, ['clients/bongos-client']);
69
- assert.ok(!p.some((x) => x.includes('vendor')), 'no unmatched pathspec may reach git add');
70
- });
71
-
72
- test('existence is probed at the vendored path under the given worktree root', () => {
73
- const seen = [];
74
- apiClientPathspec({
75
- worktreeRoot: ROOT,
76
- existsSync: (p) => { seen.push(p); return false; },
77
- });
78
- assert.equal(seen.length, 1);
79
- assert.equal(seen[0], path.join(ROOT, VENDORED_CLIENT_REL));
80
- });
81
-
82
- // A ship can run from a linked worktree, where the main checkout is a different
83
- // tree (the task-2060 rationale). The probe must follow the root it is handed.
84
- test('probes the worktree it was handed, not a fixed root', () => {
85
- const seen = [];
86
- apiClientPathspec({
87
- worktreeRoot: path.join('/linked-worktree'),
88
- existsSync: (p) => { seen.push(p); return false; },
89
- });
90
- assert.equal(seen[0], path.join('/linked-worktree', VENDORED_CLIENT_REL));
91
- });
92
-
93
- test('every returned entry is repo-relative (git pathspecs, not absolute paths)', () => {
94
- for (const p of apiClientPathspec({ worktreeRoot: ROOT, existsSync: always })) {
95
- assert.ok(!path.isAbsolute(p), `${p} must be repo-relative`);
96
- }
97
- });
98
-
99
- console.log(`\n${passed} passed, ${failed} failed`);
100
- process.exit(failed === 0 ? 0 : 1);
@@ -1,243 +0,0 @@
1
- // tests/tree_preflight.mjs — the partial-checkout preflight (task 1003098).
2
- //
3
- // THE BUG. On a task-scoped dev box — a supported, server-driven configuration —
4
- // `scripts/gds/ship.js` died with a raw stack trace naming ONE missing module. Fix
5
- // that one, run again, learn the next. The filer lost a session to it, and their
6
- // workaround (`git sparse-checkout disable` plus disabling the re-scope cron) is
7
- // not something a builder should have to discover. Node's loader cannot do better:
8
- // it stops at the first unresolvable specifier and never reaches the second.
9
- //
10
- // THE TWO FAILURE DIRECTIONS, and both are tested, because only one is obvious:
11
- //
12
- // under-reporting — the original bug: name one missing thing, hide the other six.
13
- // OVER-reporting — the regression a naive fix causes: several of the CLI's
14
- // module requires are deliberately try-guarded, so those
15
- // commands RUN on a scoped box. Refusing them would tell a
16
- // builder who could have shipped that they cannot, which is
17
- // worse than the trace it replaces.
18
- //
19
- // Pure: a fake file system throughout, per the task's own DONE-WHEN. The cases at
20
- // the end are the empirical anchor — they run the classifier against the REAL
21
- // scripts/gds files and pin it to behaviour observed on a simulated scoped tree:
22
- // start.js survives, claim.js dies on one module, ship.js on seven.
23
- //
24
- // Run: node tests/tree_preflight.mjs
25
-
26
- import { strict as assert } from 'node:assert';
27
- import { createRequire } from 'node:module';
28
- import { fileURLToPath } from 'node:url';
29
- import { dirname, join } from 'node:path';
30
-
31
- const require = createRequire(import.meta.url);
32
- const tp = require('../scripts/gds/tree-preflight.js');
33
-
34
- const __dirname = dirname(fileURLToPath(import.meta.url));
35
- const repoRoot = join(__dirname, '..');
36
-
37
- let passed = 0;
38
- let failed = 0;
39
- const t = (name, fn) => {
40
- try { fn(); passed += 1; console.log(` PASS ${name}`); }
41
- catch (e) { failed += 1; console.log(` FAIL ${name}\n ${e.message}`); }
42
- };
43
-
44
- // A fake scripts/gds tree. Keys are absolute-ish paths as the walker resolves them.
45
- function fakeFs(files) {
46
- return {
47
- readFile: (f) => {
48
- const key = String(f).replace(/\\/g, '/');
49
- const hit = Object.keys(files).find((k) => key.endsWith(k));
50
- if (hit === undefined) { const e = new Error('ENOENT'); e.code = 'ENOENT'; throw e; }
51
- return files[hit];
52
- },
53
- };
54
- }
55
-
56
- console.log('\nthe walk — what the command actually needs:');
57
-
58
- t('collects a direct module require', () => {
59
- const fs = fakeFs({ 'scripts/gds/a.js': "const x = require('../../modules/lifecycle/dead-deps.js');" });
60
- assert.deepEqual(
61
- tp.requiredModulePaths('/repo/scripts/gds/a.js', { ...fs, root: '/repo' }),
62
- ['modules/lifecycle/dead-deps.js'],
63
- );
64
- });
65
-
66
- t('FOLLOWS sibling requires — the whole reason a declared list would be wrong', () => {
67
- // ship.js reaches its modules through sixteen ship-*.js phase files; a hand-kept
68
- // list of "what ship.js needs" is a second copy of the require graph.
69
- const fs = fakeFs({
70
- 'scripts/gds/entry.js': "const p = require('./phase.js');",
71
- 'scripts/gds/phase.js': "const g = require('../../modules/grading/grader.js');",
72
- });
73
- assert.deepEqual(
74
- tp.requiredModulePaths('/repo/scripts/gds/entry.js', { ...fs, root: '/repo' }),
75
- ['modules/grading/grader.js'],
76
- );
77
- });
78
-
79
- t('a require cycle terminates', () => {
80
- const fs = fakeFs({
81
- 'scripts/gds/a.js': "require('./b.js'); require('../../modules/lifecycle/one.js');",
82
- 'scripts/gds/b.js': "require('./a.js'); require('../../modules/lifecycle/two.js');",
83
- });
84
- assert.deepEqual(
85
- tp.requiredModulePaths('/repo/scripts/gds/a.js', { ...fs, root: '/repo' }),
86
- ['modules/lifecycle/one.js', 'modules/lifecycle/two.js'],
87
- );
88
- });
89
-
90
- t('an unreadable file is skipped, never thrown on', () => {
91
- // This runs on a checkout that is BY DEFINITION incomplete. A preflight that
92
- // crashes while diagnosing a partial tree is worse than the trace it replaces.
93
- const fs = fakeFs({ 'scripts/gds/a.js': "require('./gone.js'); require('../../modules/lifecycle/one.js');" });
94
- assert.deepEqual(
95
- tp.requiredModulePaths('/repo/scripts/gds/a.js', { ...fs, root: '/repo' }),
96
- ['modules/lifecycle/one.js'],
97
- );
98
- });
99
-
100
- t('a bare specifier gains .js; one that names an extension is left alone', () => {
101
- const fs = fakeFs({
102
- 'scripts/gds/a.js': "require('../../modules/lifecycle/ship-preflight');require('../../modules/lifecycle/d.json');",
103
- });
104
- assert.deepEqual(
105
- tp.requiredModulePaths('/repo/scripts/gds/a.js', { ...fs, root: '/repo' }),
106
- ['modules/lifecycle/d.json', 'modules/lifecycle/ship-preflight.js'],
107
- );
108
- });
109
-
110
- console.log('\nOVER-reporting — the regression a naive fix causes:');
111
-
112
- t('a try-guarded require is NOT a requirement', () => {
113
- // context-pack.js and cli-lib.js both guard theirs and fall back honestly, which
114
- // is why /builder-start works on a scoped box today. Reporting them would refuse
115
- // a command that runs.
116
- const fs = fakeFs({
117
- 'scripts/gds/a.js': "let c=null; try { c = require('../../modules/lifecycle/task-classifier'); } catch (_) {}",
118
- });
119
- assert.deepEqual(tp.requiredModulePaths('/repo/scripts/gds/a.js', { ...fs, root: '/repo' }), []);
120
- });
121
-
122
- t('a require AFTER a closed try is still a requirement', () => {
123
- // The brace walk must pop. If it did not, one guarded require near the top would
124
- // silently excuse every require below it in the file.
125
- const fs = fakeFs({
126
- 'scripts/gds/a.js': "try { require('../../modules/lifecycle/opt.js'); } catch (_) {}\nrequire('../../modules/lifecycle/need.js');",
127
- });
128
- assert.deepEqual(
129
- tp.requiredModulePaths('/repo/scripts/gds/a.js', { ...fs, root: '/repo' }),
130
- ['modules/lifecycle/need.js'],
131
- );
132
- });
133
-
134
- t('a require quoted inside a COMMENT is not walked', () => {
135
- // tree-preflight.js's own header quotes the failing require verbatim to explain
136
- // the bug. A raw-text scan would read that prose as a live edge — the defect
137
- // filed as task 1003832 against a test that scanned raw source.
138
- const fs = fakeFs({
139
- 'scripts/gds/a.js': "// require('../../modules/grading/nope.js')\n/* require('../../modules/grading/also.js') */\nrequire('../../modules/lifecycle/real.js');",
140
- });
141
- assert.deepEqual(
142
- tp.requiredModulePaths('/repo/scripts/gds/a.js', { ...fs, root: '/repo' }),
143
- ['modules/lifecycle/real.js'],
144
- );
145
- });
146
-
147
- console.log('\nthe message — one run, every missing thing:');
148
-
149
- t('nothing missing → null, so a complete checkout is a silent no-op', () => {
150
- assert.equal(tp.preflightMessage([], { command: '/builder-ship' }), null);
151
- assert.equal(tp.preflightMessage(null, { command: '/builder-ship' }), null);
152
- });
153
-
154
- t('EVERY missing requirement is listed, not the first', () => {
155
- // The whole ask. Seven for ship.js is seven guess-read-fix cycles avoided.
156
- const missing = ['modules/a/one.js', 'modules/b/two.js', 'modules/b/three.js'];
157
- const msg = tp.preflightMessage(missing, { command: '/builder-ship' });
158
- for (const m of missing) assert.ok(msg.includes(m), `names ${m}`);
159
- assert.match(msg, /Missing \(3\)/, 'and says how many');
160
- });
161
-
162
- t('it names the CONDITION and BOTH remedies', () => {
163
- const msg = tp.preflightMessage(['modules/a/one.js'], { command: '/builder-ship' });
164
- assert.match(msg, /task-scoped/i, 'names why the code is absent');
165
- assert.match(msg, /not a broken install/i, 'and rules out the wrong diagnosis');
166
- assert.match(msg, /box-sync\.js/, 're-scope');
167
- assert.match(msg, /sparse-checkout disable/, 'or take the whole tree');
168
- assert.match(msg, /\/builder-ship/, 'and says which command was refused');
169
- });
170
-
171
- t('it rolls the missing files up to whole directories', () => {
172
- const msg = tp.preflightMessage(['modules/lifecycle/a.js', 'modules/lifecycle/b.js', 'modules/grading/c.js']);
173
- assert.match(msg, /modules\/grading, modules\/lifecycle/, 'deduped and sorted');
174
- });
175
-
176
- console.log('\ncheckTree — what the entry points call:');
177
-
178
- t('a complete tree returns null', () => {
179
- const fs = fakeFs({ 'scripts/gds/a.js': "require('../../modules/lifecycle/one.js');" });
180
- assert.equal(tp.checkTree('/repo/scripts/gds/a.js', { ...fs, root: '/repo', exists: () => true }), null);
181
- });
182
-
183
- t('a partial tree returns the message', () => {
184
- const fs = fakeFs({ 'scripts/gds/a.js': "require('../../modules/lifecycle/one.js');" });
185
- const msg = tp.checkTree('/repo/scripts/gds/a.js', { ...fs, root: '/repo', exists: () => false, command: '/builder-x' });
186
- assert.match(msg, /modules\/lifecycle\/one\.js/);
187
- });
188
-
189
- t('a preflight that THROWS returns null rather than replacing one bad trace with another', () => {
190
- const boom = { readFile: () => { throw new Error('boom'); }, exists: () => { throw new Error('boom'); } };
191
- assert.equal(tp.checkTree('/repo/scripts/gds/a.js', { ...boom, root: '/repo' }), null);
192
- });
193
-
194
- console.log('\nthe empirical anchor — the REAL CLI files:');
195
-
196
- t('start.js needs NO unguarded module code, matching that it runs on a scoped box', () => {
197
- // Observed: with modules/ hidden, start.js prints its card normally. If this
198
- // list is ever non-empty, /builder-start starts refusing on a box where it works.
199
- const req = tp.requiredModulePaths(join(repoRoot, 'scripts/gds/start.js'), { root: repoRoot });
200
- assert.deepEqual(req, [], `start.js must survive a scoped box; got ${req.join(', ')}`);
201
- });
202
-
203
- t('claim.js and ship.js DO need module code, matching the traces they died with', () => {
204
- const claim = tp.requiredModulePaths(join(repoRoot, 'scripts/gds/claim.js'), { root: repoRoot });
205
- assert.ok(claim.includes('modules/lifecycle/dead-deps.js'),
206
- 'the exact module claim.js died on');
207
- const ship = tp.requiredModulePaths(join(repoRoot, 'scripts/gds/ship.js'), { root: repoRoot });
208
- assert.ok(ship.includes('modules/lifecycle/ship-preflight.js'),
209
- 'the exact module ship.js died on');
210
- assert.ok(ship.length >= 5,
211
- `ship.js fans out across phase files; the walk found ${ship.length}, so the one-per-run cost was real`);
212
- });
213
-
214
- t('the three entry points actually CALL the preflight, above their first require', () => {
215
- // The check is worthless if it runs after the require that dies. Asserted by
216
- // position, because that is the property — not merely that the call exists.
217
- for (const f of ['start.js', 'claim.js', 'ship.js']) {
218
- const src = require('node:fs').readFileSync(join(repoRoot, 'scripts/gds', f), 'utf8');
219
- const call = src.indexOf('_checkTree(__filename');
220
- assert.ok(call > 0, `${f} calls the preflight`);
221
- const firstModuleRequire = src.search(/require\(\s*['"]\.\.\/\.\.\/modules\//);
222
- if (firstModuleRequire > 0) {
223
- assert.ok(call < firstModuleRequire, `${f}: the preflight must run BEFORE the first modules/ require`);
224
- }
225
- }
226
- });
227
-
228
- t('the preflight names the command for the READER, never a hard-coded slash command', () => {
229
- // The message this preflight prints is reader-facing, and `/builder-ship` does not exist
230
- // outside Claude Code — a person running the public `bongos` CLI would be handed a command
231
- // they cannot type. tests/cli_surface.mjs scans start.js for exactly this, but nothing
232
- // scanned claim.js or ship.js, and the first draft of this task hard-coded all three.
233
- for (const f of ['start.js', 'claim.js', 'ship.js']) {
234
- const src = require('node:fs').readFileSync(join(repoRoot, 'scripts/gds', f), 'utf8');
235
- const line = src.split('\n').find((l) => l.includes('_checkTree(__filename'));
236
- assert.ok(line, `${f} calls the preflight`);
237
- assert.ok(!/\/builder-/.test(line), `${f}: hard-coded slash command in the preflight message`);
238
- assert.match(line, /_surfaceAction\(/, `${f}: the command name must come from the surface switch`);
239
- }
240
- });
241
-
242
- console.log(`\ntree_preflight: ${passed} passed, ${failed} failed`);
243
- if (failed) process.exit(1);