@bongos/core 1.18.3 → 1.18.18

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 (469) hide show
  1. package/.bongos-core.json +1575 -285
  2. package/.claude/skills/builder-box/SKILL.md +6 -0
  3. package/.claude/skills/builder-sequence/SKILL.md +97 -0
  4. package/.claude/skills/builder-ship/SKILL.md +8 -2
  5. package/.claude/skills/builder-start/SKILL.md +11 -0
  6. package/.claude/skills/builder-sync/SKILL.md +41 -0
  7. package/.claude/skills/fix-task/SKILL.md +64 -0
  8. package/.claude/skills/goal-create/SKILL.md +158 -0
  9. package/.claude/skills/goal-review/SKILL.md +3 -1
  10. package/.claude/skills/idea-triage/SKILL.md +14 -8
  11. package/.claude/skills/merge-mode/SKILL.md +2 -2
  12. package/.claude/skills/new-project/SKILL.md +1 -0
  13. package/.claude/skills/planning-session/SKILL.md +45 -18
  14. package/.env.local.example +4 -3
  15. package/README.md +4 -2
  16. package/bin/bongos.js +1 -0
  17. package/clients/bongos-client/README.md +2 -2
  18. package/clients/bongos-client/bongos-client.global.js +82 -18
  19. package/clients/bongos-client/index.cjs +82 -18
  20. package/clients/bongos-client/index.d.ts +131 -29
  21. package/clients/bongos-client/index.mjs +82 -18
  22. package/config/branding.neutral.json +6 -1
  23. package/config/governance.neutral.json +21 -0
  24. package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +24 -0
  25. package/docs/adr/0090-metic-task-authoring.md +1 -1
  26. package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
  27. package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
  28. package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
  29. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
  30. package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
  31. package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
  32. package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
  33. package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
  34. package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
  35. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
  36. package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
  37. package/docs/adr/0152-metic-task-abandonment.md +67 -0
  38. package/docs/adr/0153-sequential-chain-runner.md +51 -0
  39. package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
  40. package/docs/adr/README.md +10 -0
  41. package/docs/api/openapi.json +843 -17
  42. package/docs/api-reference.md +23 -10
  43. package/docs/architecture.md +4 -2
  44. package/docs/branding-contract.md +3 -1
  45. package/docs/canonical-permissions.md +21 -8
  46. package/docs/file-map.md +8 -0
  47. package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
  48. package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
  49. package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
  50. package/docs/onboarding/diagrams/assertions.json +4 -7
  51. package/docs/onboarding/drachmae-and-karma.md +3 -3
  52. package/docs/onboarding/primer.md +17 -16
  53. package/docs/onboarding/slash-commands.md +124 -0
  54. package/docs/recipes/builder-box-lifecycle.md +23 -0
  55. package/docs/recipes/devbox-app-release.md +14 -4
  56. package/docs/recipes/instance-first-run.md +16 -0
  57. package/docs/recipes/local-dev.md +81 -0
  58. package/docs/recipes/ops-gotchas.md +19 -1
  59. package/docs/recipes/overnight-builder.md +1 -1
  60. package/docs/recipes/parallel-goal-waves.md +174 -0
  61. package/docs/recipes/ship-and-regrade.md +2 -2
  62. package/docs/recipes/standalone-live-docs.md +48 -0
  63. package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
  64. package/migrations/003_pms.sql +6 -7
  65. package/migrations/006_pms_v2.sql +3 -31
  66. package/migrations/core_193_task_needs_rebase.sql +28 -0
  67. package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
  68. package/migrations/core_195_idea_triage_provenance.sql +57 -0
  69. package/migrations/core_196_session_reward_topup.sql +69 -0
  70. package/modules/autonomy/pollers/routine-timers.js +9 -8
  71. package/modules/builder-settings/wandering-prefs.js +61 -18
  72. package/modules/dev-box/app/README.md +73 -0
  73. package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
  74. package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
  75. package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
  76. package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
  77. package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
  78. package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
  79. package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
  80. package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
  81. package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
  82. package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
  83. package/modules/dev-box/app/assets/tray/win-off.png +0 -0
  84. package/modules/dev-box/app/assets/tray/win-on.png +0 -0
  85. package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
  86. package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
  87. package/modules/dev-box/app/build-resources/icon.png +0 -0
  88. package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
  89. package/modules/dev-box/app/electron-builder.yml +95 -0
  90. package/modules/dev-box/app/package-lock.json +5291 -0
  91. package/modules/dev-box/app/package.json +30 -0
  92. package/modules/dev-box/app/renderer/app.js +319 -0
  93. package/modules/dev-box/app/renderer/index.html +45 -0
  94. package/modules/dev-box/app/renderer/styles.css +246 -0
  95. package/modules/dev-box/app/renderer/theme.generated.css +16 -0
  96. package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
  97. package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
  98. package/modules/dev-box/app/scripts/gen-brand.js +138 -0
  99. package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
  100. package/modules/dev-box/app/scripts/gen-icons.js +286 -0
  101. package/modules/dev-box/app/scripts/vendor-client.js +23 -0
  102. package/modules/dev-box/app/src/box-controller.js +287 -0
  103. package/modules/dev-box/app/src/brand.generated.json +13 -0
  104. package/modules/dev-box/app/src/brand.js +16 -0
  105. package/modules/dev-box/app/src/config.js +19 -0
  106. package/modules/dev-box/app/src/gds-client.js +90 -0
  107. package/modules/dev-box/app/src/main.js +610 -0
  108. package/modules/dev-box/app/src/pairing.js +57 -0
  109. package/modules/dev-box/app/src/preload.js +29 -0
  110. package/modules/dev-box/app/src/session-store.js +58 -0
  111. package/modules/dev-box/app/src/ssh-setup.js +569 -0
  112. package/modules/dev-box/app/src/task-picker.js +70 -0
  113. package/modules/dev-box/app/src/tray.js +99 -0
  114. package/modules/dev-box/app/src/updater.js +55 -0
  115. package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
  116. package/modules/dev-box/app/test/unit.mjs +526 -0
  117. package/modules/dev-box/box-access.js +121 -4
  118. package/modules/dev-box/boxes.js +20 -0
  119. package/modules/dev-box/module.json +1 -1
  120. package/modules/dev-box/routes/box.js +87 -3
  121. package/modules/economy/credits.js +116 -25
  122. package/modules/economy/reward.js +8 -0
  123. package/modules/economy/streak.js +2 -1
  124. package/modules/governance/CLAUDE.md +15 -0
  125. package/modules/governance/catalog.js +419 -0
  126. package/modules/governance/config.js +174 -0
  127. package/modules/governance/db.js +125 -0
  128. package/modules/governance/migrations/governance_001_tables.sql +81 -0
  129. package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
  130. package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
  131. package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
  132. package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
  133. package/modules/governance/module.json +10 -0
  134. package/modules/governance/protected-surfaces.js +88 -0
  135. package/modules/governance/protected-surfaces.json +80 -0
  136. package/modules/governance/rank-roles.js +83 -0
  137. package/modules/governance/resolver.js +221 -0
  138. package/modules/governance/routes/governance.js +89 -0
  139. package/modules/governance/routes/role-sync.js +49 -0
  140. package/modules/governance/session-scopes.js +127 -0
  141. package/modules/governance/session-scopes.json +38 -0
  142. package/modules/hall-ui/public/atlas.css +117 -0
  143. package/modules/hall-ui/public/atlas.html +3 -114
  144. package/modules/hall-ui/public/brand-apply.js +34 -6
  145. package/modules/hall-ui/public/builders.js +187 -219
  146. package/modules/hall-ui/public/diagrams.html +2 -0
  147. package/modules/hall-ui/public/dom-utils.js +27 -0
  148. package/modules/hall-ui/public/drachmae.html +2 -0
  149. package/modules/hall-ui/public/drachmae.js +54 -1
  150. package/modules/hall-ui/public/gate.css +98 -0
  151. package/modules/hall-ui/public/gate.html +3 -95
  152. package/modules/hall-ui/public/goal-inbox.js +4 -1
  153. package/modules/hall-ui/public/goals-page.js +626 -114
  154. package/modules/hall-ui/public/goals.css +79 -1
  155. package/modules/hall-ui/public/goals.html +38 -11
  156. package/modules/hall-ui/public/hall-widgets.js +62 -52
  157. package/modules/hall-ui/public/harbor.css +123 -0
  158. package/modules/hall-ui/public/harbor.html +3 -120
  159. package/modules/hall-ui/public/index.html +11 -4
  160. package/modules/hall-ui/public/instances-panel.js +4 -1
  161. package/modules/hall-ui/public/modules.css +63 -0
  162. package/modules/hall-ui/public/modules.html +3 -60
  163. package/modules/hall-ui/public/new-project-wizard.js +7 -3
  164. package/modules/hall-ui/public/not-ready.html +2 -0
  165. package/modules/hall-ui/public/pair.css +73 -0
  166. package/modules/hall-ui/public/pair.html +3 -70
  167. package/modules/hall-ui/public/palette.js +302 -0
  168. package/modules/hall-ui/public/primer.css +13 -0
  169. package/modules/hall-ui/public/primer.html +2 -0
  170. package/modules/hall-ui/public/profile.html +2 -0
  171. package/modules/hall-ui/public/ranks.html +2 -0
  172. package/modules/hall-ui/public/roadmap.css +179 -0
  173. package/modules/hall-ui/public/roadmap.html +9 -172
  174. package/modules/hall-ui/public/roadmap.js +329 -316
  175. package/modules/hall-ui/public/sessions.css +16 -0
  176. package/modules/hall-ui/public/sessions.html +3 -13
  177. package/modules/hall-ui/public/settings.html +2 -0
  178. package/modules/hall-ui/public/settings.js +4 -4
  179. package/modules/hall-ui/public/shell.js +143 -18
  180. package/modules/hall-ui/public/style.css +231 -39
  181. package/modules/hall-ui/public/task-detail.js +347 -26
  182. package/modules/hall-ui/public/task.html +6 -1
  183. package/modules/hall-ui/public/watch.html +2 -0
  184. package/modules/hall-ui/public/work.html +8 -6
  185. package/modules/hall-ui/public/work.js +1 -1
  186. package/modules/ideas/inbox.js +61 -11
  187. package/modules/ideas/routes/inbox.js +23 -0
  188. package/modules/lifecycle/claim-eligibility.js +72 -0
  189. package/modules/lifecycle/db.js +519 -139
  190. package/modules/lifecycle/done-when.js +18 -3
  191. package/modules/lifecycle/github-push.js +19 -2
  192. package/modules/lifecycle/goal-advisory.js +114 -0
  193. package/modules/lifecycle/goal-authz.js +62 -0
  194. package/modules/lifecycle/lifecycle.js +9 -0
  195. package/modules/lifecycle/publish-reconciler.js +109 -6
  196. package/modules/lifecycle/routes/claims.js +68 -23
  197. package/modules/lifecycle/routes/done-when.js +110 -15
  198. package/modules/lifecycle/routes/goals.js +8 -38
  199. package/modules/lifecycle/routes/tasks.js +38 -6
  200. package/modules/lifecycle/ship-card.js +6 -0
  201. package/modules/onboarding/onboarding-state.js +1 -1
  202. package/modules/platform-identity/CLAUDE.md +15 -0
  203. package/modules/platform-identity/hub-device.js +147 -0
  204. package/modules/platform-identity/hub-keys.js +151 -0
  205. package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
  206. package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
  207. package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
  208. package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
  209. package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
  210. package/modules/platform-identity/module.json +18 -0
  211. package/modules/platform-identity/platform-identity.js +531 -0
  212. package/modules/platform-identity/pollers/code-sweep.js +43 -0
  213. package/modules/platform-identity/routes/my-projects.js +140 -0
  214. package/modules/platform-identity/routes/projects.js +107 -0
  215. package/modules/platform-identity/routes/scouting.js +35 -0
  216. package/modules/platform-identity/routes/sso.js +402 -0
  217. package/modules/platform-identity/tests/platform-identity.mjs +388 -0
  218. package/modules/platform-identity/ui/.gitkeep +0 -0
  219. package/modules/security/routes/reports.js +10 -0
  220. package/modules/sessions/db.js +166 -21
  221. package/modules/sessions/module.json +1 -1
  222. package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
  223. package/modules/sessions/routes/sessions.js +33 -0
  224. package/package-lock.json +2 -2
  225. package/package.json +1 -1
  226. package/public-landing/assets/anim/idle/frame_00.webp +0 -0
  227. package/public-landing/assets/anim/idle/frame_01.webp +0 -0
  228. package/public-landing/assets/anim/idle/frame_02.webp +0 -0
  229. package/public-landing/assets/anim/idle/frame_03.webp +0 -0
  230. package/public-landing/assets/anim/idle/frame_04.webp +0 -0
  231. package/public-landing/assets/anim/idle/frame_05.webp +0 -0
  232. package/public-landing/assets/anim/idle/frame_06.webp +0 -0
  233. package/public-landing/assets/anim/idle/frame_07.webp +0 -0
  234. package/public-landing/assets/anim/idle/frame_08.webp +0 -0
  235. package/public-landing/assets/anim/idle/frame_09.webp +0 -0
  236. package/public-landing/assets/anim/idle/frame_10.webp +0 -0
  237. package/public-landing/assets/anim/idle/frame_11.webp +0 -0
  238. package/public-landing/assets/anim/idle/frame_12.webp +0 -0
  239. package/public-landing/assets/anim/idle/frame_13.webp +0 -0
  240. package/public-landing/assets/anim/idle/frame_14.webp +0 -0
  241. package/public-landing/assets/anim/idle/frame_15.webp +0 -0
  242. package/public-landing/assets/anim/idle/frame_16.webp +0 -0
  243. package/public-landing/assets/anim/idle/frame_17.webp +0 -0
  244. package/public-landing/assets/anim/idle/frame_18.webp +0 -0
  245. package/public-landing/assets/anim/idle/frame_19.webp +0 -0
  246. package/public-landing/assets/anim/idle/frame_20.webp +0 -0
  247. package/public-landing/assets/anim/idle/frame_21.webp +0 -0
  248. package/public-landing/assets/anim/idle/frame_22.webp +0 -0
  249. package/public-landing/assets/anim/idle/frame_23.webp +0 -0
  250. package/public-landing/assets/anim/idle/frame_24.webp +0 -0
  251. package/public-landing/assets/anim/idle/frame_25.webp +0 -0
  252. package/public-landing/assets/anim/idle/frame_26.webp +0 -0
  253. package/public-landing/assets/anim/idle/frame_27.webp +0 -0
  254. package/public-landing/assets/anim/idle/frame_28.webp +0 -0
  255. package/public-landing/assets/anim/idle/frame_29.webp +0 -0
  256. package/public-landing/assets/anim/idle/frame_30.webp +0 -0
  257. package/public-landing/assets/anim/idle/frame_31.webp +0 -0
  258. package/public-landing/assets/anim/idle/frame_32.webp +0 -0
  259. package/public-landing/assets/anim/idle/frame_33.webp +0 -0
  260. package/public-landing/assets/anim/idle/frame_34.webp +0 -0
  261. package/public-landing/assets/anim/idle/frame_35.webp +0 -0
  262. package/public-landing/assets/anim/idle/frame_36.webp +0 -0
  263. package/public-landing/assets/anim/idle/frame_37.webp +0 -0
  264. package/public-landing/assets/anim/idle/frame_38.webp +0 -0
  265. package/public-landing/assets/anim/idle/frame_39.webp +0 -0
  266. package/public-landing/assets/anim/idle/frame_40.webp +0 -0
  267. package/public-landing/assets/anim/idle/frame_41.webp +0 -0
  268. package/public-landing/assets/anim/idle/frame_42.webp +0 -0
  269. package/public-landing/assets/anim/idle/frame_43.webp +0 -0
  270. package/public-landing/create.html +51 -58
  271. package/public-landing/gate/README.md +137 -0
  272. package/public-landing/gate/dispmaps/bar.png +0 -0
  273. package/public-landing/gate/dispmaps/flat.png +0 -0
  274. package/public-landing/gate/dispmaps/key.png +0 -0
  275. package/public-landing/gate/index.html +587 -0
  276. package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
  277. package/public-landing/gate/previews/off-the-boats.jpg +0 -0
  278. package/public-landing/gate/space.css +799 -0
  279. package/public-landing/gate/space.js +1826 -0
  280. package/public-landing/gate/sprites/enter.png +0 -0
  281. package/public-landing/gate/sprites/search.png +0 -0
  282. package/public-landing/gate/sprites/terminal-blank.png +0 -0
  283. package/public-landing/gate/sprites/tile.png +0 -0
  284. package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
  285. package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
  286. package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
  287. package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
  288. package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
  289. package/public-landing/world/assets/plates/scene-establishing.png +0 -0
  290. package/scripts/gds/abandon.js +6 -4
  291. package/scripts/gds/api.js +10 -5
  292. package/scripts/gds/api.ps1 +26 -0
  293. package/scripts/gds/api.sh +6 -0
  294. package/scripts/gds/audit-ships.js +7 -1
  295. package/scripts/gds/backfill-sessions.js +78 -18
  296. package/scripts/gds/backup.js +23 -1
  297. package/scripts/gds/box-connect-lib.js +124 -31
  298. package/scripts/gds/box-sync.js +66 -0
  299. package/scripts/gds/box.js +225 -53
  300. package/scripts/gds/cf-tunnel.js +145 -6
  301. package/scripts/gds/claim.js +34 -3
  302. package/scripts/gds/cli-lib.js +161 -27
  303. package/scripts/gds/code.js +6 -2
  304. package/scripts/gds/deploy-divergence.js +209 -0
  305. package/scripts/gds/dev-lib.js +309 -0
  306. package/scripts/gds/dev.js +322 -0
  307. package/scripts/gds/doctor.js +90 -61
  308. package/scripts/gds/fetch-art-key.js +1 -1
  309. package/scripts/gds/fetch-sound-prefs.js +2 -2
  310. package/scripts/gds/fitness.js +228 -12
  311. package/scripts/gds/gds-db-backup.service +16 -1
  312. package/scripts/gds/gds-db-offsite.service +9 -1
  313. package/scripts/gds/gen-api-client.js +1 -1
  314. package/scripts/gds/gen-api-docs.js +36 -14
  315. package/scripts/gds/gen-diagrams.js +45 -28
  316. package/scripts/gds/gen-file-map.js +2 -1
  317. package/scripts/gds/gen-freshness.js +32 -0
  318. package/scripts/gds/gen-repo-map.js +3 -2
  319. package/scripts/gds/gen-session-index.js +14 -5
  320. package/scripts/gds/hooks/pre-push.js +2 -2
  321. package/scripts/gds/init.js +60 -3
  322. package/scripts/gds/install-git-hooks.js +1 -1
  323. package/scripts/gds/key-card.js +15 -6
  324. package/scripts/gds/key-status.js +1 -1
  325. package/scripts/gds/linkify-refs.js +35 -9
  326. package/scripts/gds/login.js +16 -1
  327. package/scripts/gds/paste-token.js +7 -1
  328. package/scripts/gds/preflight.js +126 -0
  329. package/scripts/gds/provision.js +123 -12
  330. package/scripts/gds/publish-manifest.js +7 -1
  331. package/scripts/gds/reauth.js +7 -3
  332. package/scripts/gds/regen-instance-docs.js +47 -0
  333. package/scripts/gds/run-unit-tests.js +4 -2
  334. package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
  335. package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
  336. package/scripts/gds/sequence.js +425 -0
  337. package/scripts/gds/session-cost.js +10 -1
  338. package/scripts/gds/setup.js +31 -5
  339. package/scripts/gds/shell.js +5 -3
  340. package/scripts/gds/ship.js +319 -29
  341. package/scripts/gds/smoke-gds.js +139 -8
  342. package/scripts/gds/start.js +171 -22
  343. package/scripts/gds/status.js +1 -1
  344. package/scripts/gds/triage.js +10 -9
  345. package/scripts/gds/upgrade.js +124 -2
  346. package/scripts/status-mirror-sync.js +114 -84
  347. package/src/bongos/api-errors.js +1 -0
  348. package/src/bongos/auth.js +447 -102
  349. package/src/bongos/db-kernel.js +27 -0
  350. package/src/bongos/db.js +39 -0
  351. package/src/bongos/module-scope-map.js +27 -1
  352. package/src/bongos/permission-path-check.js +136 -102
  353. package/src/bongos/repo-info.js +47 -11
  354. package/src/bongos/route-rank-check.js +429 -46
  355. package/src/bongos/routes/_helpers.js +12 -1
  356. package/src/bongos/routes/auth.js +219 -2
  357. package/src/bongos/routes/backup.js +58 -2
  358. package/src/bongos/routes/me.js +35 -1
  359. package/src/bongos/serve-internal.js +84 -15
  360. package/src/branding.js +32 -0
  361. package/src/instance-config.js +30 -0
  362. package/src/module-api.js +112 -1
  363. package/src/platform-server.js +86 -9
  364. package/tests/abandon_task.mjs +59 -3
  365. package/tests/api_cli_flags.mjs +53 -0
  366. package/tests/auth_page_gate.mjs +13 -0
  367. package/tests/backfill_sessions.mjs +127 -1
  368. package/tests/backup_cli.mjs +54 -0
  369. package/tests/bongos_box_connect.mjs +77 -0
  370. package/tests/box_access.mjs +130 -1
  371. package/tests/box_credential.mjs +125 -0
  372. package/tests/box_host_keys.mjs +24 -0
  373. package/tests/box_infra_resolve.mjs +149 -0
  374. package/tests/box_scope_session.mjs +6 -2
  375. package/tests/box_sync.mjs +55 -0
  376. package/tests/box_task_scope.mjs +99 -0
  377. package/tests/branding.mjs +12 -0
  378. package/tests/cf_ruleset_preflight.mjs +142 -0
  379. package/tests/cf_tunnel.mjs +37 -0
  380. package/tests/claim_gate_rebase.mjs +98 -0
  381. package/tests/clawback_confirmed_backlog.mjs +103 -0
  382. package/tests/cli_lib_formatters.mjs +20 -0
  383. package/tests/confirm_attribution.mjs +30 -46
  384. package/tests/credit_grant.mjs +198 -10
  385. package/tests/criterion_authoring_gate.mjs +272 -0
  386. package/tests/criterion_progress.mjs +33 -1
  387. package/tests/dev_lib.mjs +253 -0
  388. package/tests/devbox_artifact_gate.mjs +75 -0
  389. package/tests/devbox_dmg_backdrop.mjs +137 -0
  390. package/tests/devbox_icons.mjs +304 -0
  391. package/tests/devbox_installer_brand.mjs +232 -0
  392. package/tests/devbox_key_register.mjs +234 -0
  393. package/tests/docs_openapi_route.mjs +110 -0
  394. package/tests/doctor_hooks.mjs +141 -0
  395. package/tests/extract_json.mjs +140 -0
  396. package/tests/fitness.mjs +89 -0
  397. package/tests/gen_freshness.mjs +96 -0
  398. package/tests/goal_advisory.mjs +251 -0
  399. package/tests/goal_criterion_confirm_ui.mjs +269 -0
  400. package/tests/goal_routes.mjs +59 -4
  401. package/tests/goal_tier.mjs +12 -3
  402. package/tests/governance_catalog.mjs +129 -0
  403. package/tests/governance_claim_eligibility.mjs +262 -0
  404. package/tests/governance_config.mjs +187 -0
  405. package/tests/governance_ownership_scope.mjs +410 -0
  406. package/tests/governance_page_gates.mjs +324 -0
  407. package/tests/governance_parity.mjs +336 -0
  408. package/tests/governance_principals.mjs +232 -0
  409. package/tests/governance_protected_surfaces.mjs +208 -0
  410. package/tests/governance_require_permission.mjs +223 -0
  411. package/tests/governance_resolver.mjs +84 -0
  412. package/tests/governance_role_sync.mjs +231 -0
  413. package/tests/governance_seed.mjs +127 -0
  414. package/tests/governance_session_scope.mjs +434 -0
  415. package/tests/governance_vanilla_boot.mjs +203 -0
  416. package/tests/grade_attribution.mjs +7 -7
  417. package/tests/hall_audit.mjs +223 -0
  418. package/tests/hall_nav.mjs +191 -0
  419. package/tests/hall_palette.mjs +206 -0
  420. package/tests/hall_signout.mjs +124 -0
  421. package/tests/hook_user_agents.mjs +10 -0
  422. package/tests/idea_triage.mjs +162 -0
  423. package/tests/idp_device_exchange.mjs +93 -0
  424. package/tests/init.mjs +51 -0
  425. package/tests/instance_config.mjs +65 -0
  426. package/tests/instance_mismatch_guard.mjs +23 -6
  427. package/tests/instance_release_config.mjs +104 -0
  428. package/tests/linkify_refs.mjs +15 -2
  429. package/tests/module_contributions.mjs +55 -0
  430. package/tests/module_loader.mjs +10 -5
  431. package/tests/module_scope_active_claims.mjs +94 -0
  432. package/tests/needs_rebase.mjs +92 -0
  433. package/tests/one_active_claim_per_session.mjs +76 -0
  434. package/tests/override_attribution.mjs +10 -8
  435. package/tests/override_request_ownership.mjs +8 -4
  436. package/tests/parse_cookie_decode.mjs +42 -0
  437. package/tests/pay_on_land.mjs +129 -0
  438. package/tests/platform_boot.mjs +100 -1
  439. package/tests/post_login_landing.mjs +42 -0
  440. package/tests/preflight_checks.mjs +72 -0
  441. package/tests/provision.mjs +132 -7
  442. package/tests/publish_branch_route.mjs +53 -0
  443. package/tests/publish_reconciler.mjs +187 -2
  444. package/tests/repo_info_binding.mjs +248 -0
  445. package/tests/reward_gate.mjs +11 -2
  446. package/tests/reward_mode_cost_plus_only.mjs +116 -0
  447. package/tests/reward_mode_surfacing.mjs +168 -0
  448. package/tests/route_permission_check.mjs +301 -0
  449. package/tests/route_rank_check.mjs +254 -12
  450. package/tests/route_validation.mjs +30 -0
  451. package/tests/sequence.mjs +267 -0
  452. package/tests/session_digest.mjs +44 -0
  453. package/tests/session_index.mjs +18 -6
  454. package/tests/session_records.mjs +39 -1
  455. package/tests/session_reward_on_land.mjs +381 -0
  456. package/tests/session_reward_reconcile.mjs +229 -0
  457. package/tests/ship_api_client_pathspec.mjs +100 -0
  458. package/tests/ship_base_flag.mjs +93 -0
  459. package/tests/ship_ci_deploy.mjs +80 -3
  460. package/tests/ship_upstream_deletion.mjs +57 -0
  461. package/tests/smoke_target.mjs +233 -0
  462. package/tests/start_env_guard.mjs +87 -0
  463. package/tests/start_rebase_warning.mjs +103 -0
  464. package/tests/status_mirror_sync.mjs +122 -0
  465. package/tests/stop_refcheck.mjs +19 -1
  466. package/tests/task_goal_link.mjs +15 -6
  467. package/tests/upgrade.mjs +139 -0
  468. package/tests/web_admission_status.mjs +162 -0
  469. package/tests/write_validation_gaps.mjs +110 -0
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+ // scripts/gds/preflight.js — the onboarding ENVIRONMENT PREFLIGHT (task 1002401,
3
+ // onboarding-audit area 4).
4
+ //
5
+ // One shared, PURE check of the three things that used to bite a brand-new
6
+ // builder one reactive crash at a time:
7
+ // 1. Node >= 22 — the very first command (`bongos start`) shells to node;
8
+ // on a bare machine it died with `node: command not found`.
9
+ // 2. deps installed — a missing node_modules is why a ship later blew up with
10
+ // `Cannot find module 'pg'`.
11
+ // 3. `claude` launchable — the grader spawns it per worker; without it every ship
12
+ // failed grading with SUBAGENT_SPAWN_FAILED.
13
+ // Each finding carries a copy-paste fix.
14
+ //
15
+ // Before this, those checks lived only inside `bongos doctor` (doctor.js) and were
16
+ // never run at setup/start time — so newcomers hit the failures reactively, deep
17
+ // in the session. This module is the front-loadable, testable source of truth:
18
+ // - `setup.js` runs it at the top of `/builder-setup` (catch a bare machine up front)
19
+ // - `doctor.js` `checkToolchain()` delegates to it (one source, no drift)
20
+ // - `node scripts/gds/preflight.js` runs it standalone (a quick `bongos`-less check)
21
+ //
22
+ // Pure by construction: every OS touch (node version, fs, child_process) is an
23
+ // injectable dep, so the unit test drives it with zero real I/O.
24
+
25
+ 'use strict';
26
+
27
+ const nodeFs = require('node:fs');
28
+ const nodePath = require('node:path');
29
+ const { spawnSync } = require('node:child_process');
30
+
31
+ const REPO_ROOT = nodePath.resolve(__dirname, '..', '..');
32
+ const MIN_NODE_MAJOR = 22;
33
+
34
+ // The node_modules-present check, factored out (task 1002403) so a HOT-PATH
35
+ // caller — /builder-start runs on every session open — can reuse THIS exact
36
+ // finding without paying for the `claude --version` spawn the full
37
+ // preflightChecks() does. Pure: injectable fs + repoRoot.
38
+ function depsFinding({ repoRoot = REPO_ROOT, fs = nodeFs } = {}) {
39
+ let hasDeps = false;
40
+ try { hasDeps = fs.existsSync(nodePath.join(repoRoot, 'node_modules')); } catch (_) { /* treat as missing */ }
41
+ if (hasDeps) {
42
+ return { level: 'ok', title: 'Dependencies', detail: 'node_modules present.' };
43
+ }
44
+ return {
45
+ level: 'warn',
46
+ title: 'Dependencies',
47
+ detail: 'node_modules is missing — scripts that need pg/express (a ship, the server) will fail until installed.',
48
+ fix: 'Run `npm ci` in the repo root.',
49
+ };
50
+ }
51
+
52
+ // preflightChecks(deps) → [{ level:'ok'|'warn'|'fail', title, detail, fix? }]
53
+ // No printing, no process.exit — callers (setup, doctor, the test) format it.
54
+ function preflightChecks({
55
+ repoRoot = REPO_ROOT,
56
+ nodeVersion = process.version,
57
+ env = process.env,
58
+ fs = nodeFs,
59
+ spawn = spawnSync,
60
+ platform = process.platform,
61
+ } = {}) {
62
+ const out = [];
63
+
64
+ // 1. Node >= 22.
65
+ const major = Number(String(nodeVersion).replace(/^v/, '').split('.')[0]) || 0;
66
+ if (major >= MIN_NODE_MAJOR) {
67
+ out.push({ level: 'ok', title: 'Node', detail: `${nodeVersion} (>= ${MIN_NODE_MAJOR}).` });
68
+ } else {
69
+ out.push({
70
+ level: 'fail',
71
+ title: 'Node',
72
+ detail: `${nodeVersion} is below the required v${MIN_NODE_MAJOR}.`,
73
+ fix: 'Install Node >= 22 — e.g. `brew install node`, `nvm install 22`, or https://nodejs.org.',
74
+ });
75
+ }
76
+
77
+ // 2. Dependencies installed (node_modules present) — shared with the hot-path
78
+ // /builder-start caller via depsFinding() (task 1002403).
79
+ out.push(depsFinding({ repoRoot, fs }));
80
+
81
+ // 3. `claude` CLI launchable — same resolution order the grader uses.
82
+ const bin = env.OTB_CLAUDE_BIN || env.CLAUDE_BIN || 'claude';
83
+ const via = env.OTB_CLAUDE_BIN ? ' (via OTB_CLAUDE_BIN)' : env.CLAUDE_BIN ? ' (via CLAUDE_BIN)' : '';
84
+ let r;
85
+ try {
86
+ r = spawn(bin, ['--version'], { encoding: 'utf8', shell: platform === 'win32', windowsHide: true, timeout: 15000 });
87
+ } catch (err) { r = { error: err }; }
88
+ if (r && !r.error && r.status === 0) {
89
+ const ver = String(r.stdout || '').trim().split('\n')[0] || 'present';
90
+ out.push({ level: 'ok', title: 'claude CLI', detail: `launches${via}: ${ver} — the grader can spawn its workers.` });
91
+ } else {
92
+ const why = r && r.error ? (r.error.code || r.error.message) : `exited ${r ? r.status : '?'}`;
93
+ out.push({
94
+ level: 'fail',
95
+ title: 'claude CLI',
96
+ detail: `not launchable (${bin}: ${why}) — the grader spawns it per worker, so ships fail grading with SUBAGENT_SPAWN_FAILED.`,
97
+ fix: 'Run `npm i -g @anthropic-ai/claude-code` (puts a real claude on PATH), or set OTB_CLAUDE_BIN to its full path.',
98
+ });
99
+ }
100
+
101
+ return out;
102
+ }
103
+
104
+ const GLYPH = { ok: '✓', warn: '!', fail: '✖' };
105
+ const ANSI = { ok: '\x1b[32m', warn: '\x1b[33m', fail: '\x1b[31m', dim: '\x1b[2m', reset: '\x1b[0m' };
106
+
107
+ // formatPreflight(results, opts) → a printable multi-line string.
108
+ function formatPreflight(results, { color = false, heading = false } = {}) {
109
+ const c = (k, s) => (color ? `${ANSI[k]}${s}${ANSI.reset}` : s);
110
+ const lines = [];
111
+ if (heading) lines.push('Checking your environment…');
112
+ for (const r of results) {
113
+ lines.push(` ${c(r.level, GLYPH[r.level])} ${r.title}: ${r.detail}`);
114
+ if (r.fix) lines.push(` ${c('dim', '→')} ${r.fix}`);
115
+ }
116
+ return lines.join('\n');
117
+ }
118
+
119
+ module.exports = { preflightChecks, depsFinding, formatPreflight, REPO_ROOT, MIN_NODE_MAJOR };
120
+
121
+ // Standalone: `node scripts/gds/preflight.js` — exit 1 if any hard fail.
122
+ if (require.main === module) {
123
+ const results = preflightChecks();
124
+ console.log(formatPreflight(results, { color: process.stdout.isTTY, heading: true }));
125
+ process.exit(results.some((r) => r.level === 'fail') ? 1 : 0);
126
+ }
@@ -296,6 +296,14 @@ function standaloneMigrateCmd(inst, { privileged = false } = {}) {
296
296
  ? `sudo -u postgres env PGDATABASE=${dbName(inst)} MEDUSA_GDS_ONLY=1 bash ${CORE_PKG_DIR}/scripts/migrate.sh`
297
297
  : `PGDATABASE=${dbName(inst)} MEDUSA_GDS_ONLY=1 bash ${CORE_PKG_DIR}/scripts/migrate.sh`;
298
298
  }
299
+ // Regenerate the instance's whole-file navigation docs (session-log index) from the pinned core,
300
+ // AFTER the pull+install+migrate — the standalone analogue of the monorepo's deploy-prod.sh regen
301
+ // block (ADR 0110 §2, task 1002314). Best-effort + LOUD (the script always exits 0 + prints
302
+ // ::ERROR:: on a generator failure) so a stale/absent nav doc never fails a deploy. Run with
303
+ // cwd = standaloneRoot so resolveInstanceRoot() writes into the instance repo. Without this a
304
+ // standalone instance's docs never refresh on deploy — the gap the owner hit ("docs didn't
305
+ // transfer from primea"): its bespoke pull-deploy regenerated nothing.
306
+ function standaloneRegenDocsCmd() { return `node ${CORE_PKG_DIR}/scripts/gds/regen-instance-docs.js`; }
299
307
 
300
308
  // ── STANDALONE scaffold (ADR 0108 / 0125, task 2055) ──────────────────────────
301
309
  // The MISSING first leg of the greenfield "one guided flow": provision.js used to
@@ -342,7 +350,54 @@ function instanceInitSpec(inst, { ownerLogin } = {}) {
342
350
  // dist/ + secrets OUT of the commit, while the vendored core tarball (vendor/*.tgz)
343
351
  // and package-lock.json stay IN — `npm ci` on deploy resolves the pinned core from
344
352
  // the committed tarball (ADR 0108), so they MUST be tracked.
345
- const INSTANCE_GITIGNORE = ['node_modules/', 'dist/', '.env', '.env.local', '*.log', ''].join('\n');
353
+ //
354
+ // docs/session-log-index.md is a WHOLE-FILE generated navigation doc (ADR 0110): every byte is
355
+ // machine-owned, regenerated at deploy by regen-instance-docs.js (task 1002314) into the served
356
+ // tree. Untracked so `git reset --hard origin/main` never wipes it and no branch can diff it —
357
+ // the same reasoning the core's own .gitignore applies to it. (docs/repo-map.md is listed for
358
+ // parity/forward-compat; it is core-scoped today so a standalone instance doesn't generate its
359
+ // own — see regenerateNavDocs() in upgrade.js — but ignoring it costs nothing and prevents a
360
+ // stray future write from entering the tree.)
361
+ const INSTANCE_GITIGNORE = [
362
+ 'node_modules/', 'dist/', '.env', '.env.local', '*.log',
363
+ '# Whole-file generated navigation docs — regenerated at deploy (ADR 0110, task 1002314).',
364
+ 'docs/session-log-index.md', 'docs/repo-map.md', '',
365
+ ].join('\n');
366
+
367
+ // A PRE-BUILT @bongos/core tarball this control plane can vendor into a NEW instance
368
+ // WITHOUT rebuilding from source — the consumer-control-plane fix (task 1002275).
369
+ //
370
+ // The scaffold's default core-install path rebuilds the tarball with package-core.js,
371
+ // which enumerates GIT-TRACKED files (`git -C REPO_ROOT ls-tree`). That works from a core
372
+ // SOURCE checkout, but NOT when this runner is ITSELF a consumer instance whose core is an
373
+ // installed npm dependency: provision.js then runs out of node_modules/@bongos/core, so
374
+ // REPO_ROOT points INSIDE gitignored node_modules, git sees ZERO tracked files there, and
375
+ // package-core aborts ("the core set is EMPTY") — the scaffold never pushes and the freshly
376
+ // created repo is left EMPTY (seen live: trial/test on cloudbongos.com).
377
+ //
378
+ // But the exact pinned tarball this instance installed FROM is on disk, named by its own
379
+ // package.json ("@bongos/core": "file:vendor/bongos-core-<v>.tgz"). Reuse it: byte-identical,
380
+ // already proven, and the child pins the SAME core the hub runs. Returns an absolute .tgz
381
+ // path, or null on a source checkout (no `file:` core pin) — where package-core.js is the
382
+ // right, unchanged path. `instanceRoot` defaults to $BONGOS_INSTANCE_ROOT (what
383
+ // infra/core-node.sh resolves against) else process.cwd() (the runner's WorkingDirectory).
384
+ // fs injectable for tests. PURE-ish (fs reads only, no mutation → dry-run safe).
385
+ function installedCoreTarball({ instanceRoot, fsImpl = fs } = {}) {
386
+ const root = instanceRoot || process.env.BONGOS_INSTANCE_ROOT || process.cwd();
387
+ let pkg;
388
+ try { pkg = JSON.parse(fsImpl.readFileSync(path.join(root, 'package.json'), 'utf8')); }
389
+ catch { return null; } // no/unreadable package.json → treat as a source checkout
390
+ const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
391
+ // Both scope aliases the loader accepts (infra/core-node.sh checks the same two).
392
+ for (const name of ['@bongos/core', '@cloudbongos/core']) {
393
+ const m = /^file:(.+\.tgz)$/.exec(String(deps[name] || '').trim());
394
+ if (m) {
395
+ const abs = path.resolve(root, m[1]);
396
+ if (fsImpl.existsSync(abs)) return abs; // a dangling pin (tarball pruned) → fall through to null
397
+ }
398
+ }
399
+ return null;
400
+ }
346
401
 
347
402
  // Clone the (empty) target_ref → standaloneRoot → materialize config/ + .claude/ + a
348
403
  // file:-pinned @bongos/core + lockfile (via `bongos init --vendor-core`, task 2053)
@@ -383,11 +438,19 @@ async function scaffoldStandaloneRepo(inst, deps) {
383
438
  } catch { /* fall back to the repo owner */ }
384
439
  }
385
440
 
386
- // 1. clone the (possibly empty) repo, then force the initial branch to `main` (an empty
387
- // clone leaves an unborn HEAD). A re-run after a partial clone reuses the existing dir.
441
+ // 1. Materialize the working tree + set the initial branch to `main`. A freshly-created
442
+ // instance repo is EMPTY (github-repos.createUserRepo, auto_init:false), so there is
443
+ // NOTHING to clone — and a git DEPLOY KEY is per-repo, so the shared PROVISION_GIT_SSH_ALIAS
444
+ // key (e.g. `github-instances`, a deploy key scoped to ONE repo) can't even READ a
445
+ // consumer's brand-new repo under their own account: `git clone` 500s with "Repository not
446
+ // found." This is the clone-side twin of the task-1002317 push bug. So `git init` locally
447
+ // instead of cloning, and set `origin` to the SSH remote for the no-token fallback push
448
+ // below; the OAuth-preferred push (its token CREATED the repo, so it can write) handles the
449
+ // upload for consumer repos. A re-run after a partial init reuses the existing dir.
388
450
  if (!fs.existsSync(path.join(root, '.git'))) {
389
- exec(`mkdir -p ${path.posix.dirname(root)}`);
390
- exec(`git clone ${remote} ${root}`);
451
+ exec(`mkdir -p ${root}`);
452
+ exec(`git -C ${root} init -q`);
453
+ exec(`git -C ${root} remote add origin ${remote}`);
391
454
  }
392
455
  exec(`git -C ${root} checkout -B main`);
393
456
 
@@ -417,9 +480,19 @@ async function scaffoldStandaloneRepo(inst, deps) {
417
480
  log(` [scaffold] core install: registry — PRIVATE @bongos/core@${coreV} via env-fed NPM_TOKEN (ADR 0134)`);
418
481
  exec(`node scripts/gds/init.js --from ${specPath} --dir ${root} --no-seed --force --registry-core`);
419
482
  } else {
420
- log(` [scaffold] core install: vendored tarball (offline default)`);
421
- exec(`node scripts/gds/package-core.js`); // cwd = REPO_ROOT → dist/bongos-core-<v>.tgz
422
- const tgz = path.join(REPO_ROOT, 'dist', `bongos-core-${coreV}.tgz`);
483
+ // Vendored tarball. PREFER a pre-built pinned tarball this control plane already has
484
+ // (a consumer instance's own `file:` core pin) — rebuilding via package-core.js aborts
485
+ // with an EMPTY core set when this runner IS a consumer instance (REPO_ROOT is inside
486
+ // gitignored node_modules), which left new repos empty (task 1002275). Only rebuild
487
+ // from source when there is no such pin (a genuine core source checkout).
488
+ let tgz = installedCoreTarball({ instanceRoot: deps.instanceRoot });
489
+ if (tgz) {
490
+ log(` [scaffold] core install: reusing the pinned core tarball this control plane runs (${path.basename(tgz)}) — no source rebuild (task 1002275)`);
491
+ } else {
492
+ log(` [scaffold] core install: building a fresh vendored tarball from source (package-core.js)`);
493
+ exec(`node scripts/gds/package-core.js`); // cwd = REPO_ROOT → dist/bongos-core-<v>.tgz
494
+ tgz = path.join(REPO_ROOT, 'dist', `bongos-core-${coreV}.tgz`);
495
+ }
423
496
  exec(`node scripts/gds/init.js --from ${specPath} --dir ${root} --no-seed --force --vendor-core ${tgz}`);
424
497
  }
425
498
 
@@ -429,8 +502,38 @@ async function scaffoldStandaloneRepo(inst, deps) {
429
502
  exec(`git -C ${root} config user.email "bot@cloudbongos.com"`);
430
503
  exec(`git -C ${root} add -A`);
431
504
  exec(`git -C ${root} commit -m "chore: scaffold ${inst.slug} on a pinned @bongos/core ${coreV} (provision.js, ADR 0108)"`, { allowFail: true });
432
- exec(`git -C ${root} push -u origin main`);
433
- log(` [scaffold] pushed initial scaffold to ${repo.owner}/${repo.name}@main`);
505
+ // Push. A GitHub DEPLOY KEY is per-repo, so the box's shared key can't write to a NEW
506
+ // project repo — the second empty-repo cause (task 1002317). PREFER the instance owner's
507
+ // OAuth token (it CREATED the repo via github-repos.createUserRepo, so `public_repo`
508
+ // already grants write). The token rides a chmod-600 git credential-store file — never
509
+ // the logged command or makeExec's env (both are surfaced) — and is removed right after
510
+ // (finally, so a failed push can't leave it on disk). No token (org-owned repo, or
511
+ // secret-box off) → fall back to the SSH `origin` push (unchanged; e.g. an instance whose
512
+ // matching deploy key was cloned into origin). `deps.getGithubToken` is injectable for
513
+ // tests; production resolves it lazily against the core db slice with the runner's pool.
514
+ let ownerToken = null;
515
+ if (inst.owner_builder_id) {
516
+ try {
517
+ const fetchTok = deps.getGithubToken
518
+ || ((bid) => require(path.join(REPO_ROOT, 'src/bongos/db')).getGithubToken({ builderId: bid, p: deps.db }));
519
+ const r = await fetchTok(inst.owner_builder_id);
520
+ ownerToken = r && r.token ? r.token : null;
521
+ } catch (e) { log(` [scaffold] owner OAuth token lookup failed (${e && e.message}) — using the SSH remote`); }
522
+ }
523
+ if (ownerToken) {
524
+ const credFile = path.join(os.tmpdir(), `bongos-gitcred-${inst.slug}-${process.pid}`);
525
+ writeFile(credFile, `https://x-access-token:${ownerToken}@github.com\n`, { mode: 0o600 });
526
+ const httpsUrl = `https://x-access-token@github.com/${repo.owner}/${repo.name}.git`;
527
+ try {
528
+ exec(`git -C ${root} -c credential.helper='store --file=${credFile}' push ${httpsUrl} HEAD:main`);
529
+ log(` [scaffold] pushed to ${repo.owner}/${repo.name}@main via the owner's GitHub token (task 1002317)`);
530
+ } finally {
531
+ exec(`rm -f ${credFile}`, { allowFail: true });
532
+ }
533
+ } else {
534
+ exec(`git -C ${root} push -u origin main`);
535
+ log(` [scaffold] pushed initial scaffold to ${repo.owner}/${repo.name}@main (SSH origin)`);
536
+ }
434
537
  return { ok: true, scaffolded: true };
435
538
  }
436
539
 
@@ -1151,6 +1254,14 @@ async function provisionInstance(inst, deps) {
1151
1254
  // here should surface. Dedicated bakes the same step into cloud-init (dedicatedUserData).
1152
1255
  log(' [seed] first version (only if versions is empty)');
1153
1256
  boxExec(seedFirstVersionCmd(inst, { privileged }));
1257
+ // Regenerate the whole-file navigation docs from the pinned core so a fresh/redeployed
1258
+ // standalone instance's docs/ stays live (ADR 0110 §2, task 1002314) — the deploy-regen the
1259
+ // bespoke pull scripts were missing. Best-effort: regen-instance-docs.js exits 0 even on a
1260
+ // generator hiccup (LOUD ::ERROR::), so a stale nav doc never fails a provision.
1261
+ if (standalone) {
1262
+ log(' [docs] regenerate whole-file navigation docs (session-log index)');
1263
+ boxExec(standaloneRegenDocsCmd(), { cwd: standaloneRoot(inst), allowFail: true });
1264
+ }
1154
1265
  } else { log(' (db create + migrate + first-version seed run via cloud-init on the droplet)'); }
1155
1266
 
1156
1267
  // ── step 3: config + per-instance secrets ─────────────────────────────────
@@ -1654,8 +1765,8 @@ module.exports = {
1654
1765
  dbName, dbCreateCmd, migrateCmd, seedFirstVersionCmd, serviceUnit, serviceUnitPath, webEnvBody, webEnvPath,
1655
1766
  backupUnitName, backupServicePath, backupTimerPath, backupScriptPath, backupService, backupTimer,
1656
1767
  standaloneRoot, standalonePullCmd, standaloneInstallCmd, standaloneMigrateCmd,
1657
- // standalone scaffold (ADR 0108 / 0125, task 2055)
1658
- parseTargetRef, instanceRepoRemote, instanceInitSpec, scaffoldStandaloneRepo, coreVersionSafe,
1768
+ // standalone scaffold (ADR 0108 / 0125, task 2055; consumer-core fix task 1002275)
1769
+ parseTargetRef, instanceRepoRemote, instanceInitSpec, installedCoreTarball, scaffoldStandaloneRepo, coreVersionSafe,
1659
1770
  zoneForDomain, dnsUpsertEnv, classifyZoneScope, checkDnsTokenScope,
1660
1771
  caddyBlock, caddySnippetPath, healthzCmd, dedicatedUserData, nextFreePort,
1661
1772
  // executors + orchestration (injectable deps for tests)
@@ -61,17 +61,20 @@ const PUBLISH_ALLOWLIST = [
61
61
  'modules/sessions/',
62
62
  'modules/autonomy/',
63
63
  'modules/security/',
64
+ 'modules/governance/', // configurable roles & permissions (BV1 goal 1000030, ADR 0151) — the rank/permission machinery publishes AS the open-source methodology (ADR 0065 §4 / ADR 0098); it is in PROTECTED_GLOBS for write-protection, NOT publish-exclusion. default:true.
64
65
  'modules/hall-ui/',
65
66
  'modules/status-ui/',
66
67
  'modules/dev-box/',
67
68
  'modules/discord/',
68
69
  'modules/provisioning/', // instance-hosting control plane — CORE like its sibling dev-box (same do-api/cf-tunnel toolchain, all core). Reclassified IN so the core CLI scripts/gds/provision.js resolves it from the core package post-split, not a MODULE_NOT_FOUND (task 2135). default:false — shipped ≠ enabled.
70
+ 'modules/platform-identity/', // hub identity provider (ADR 0141) — ships in core, enabled ONLY on the hub (cloudbongos.com). MUST be here or the hub's enabledModules() throws "unknown module" and provision.js's federation load MODULE_NOT_FOUNDs (task 1002235, same rationale as provisioning above). default:false — shipped ≠ enabled; a vanilla/self-hosted instance never turns it on.
69
71
  // --- neutral (customer-0) config starters + their schemas (the filled-in *.json twins are denied) ---
70
72
  'config/branding.neutral.json',
71
73
  'config/modules.neutral.json',
72
74
  'config/design-tokens.neutral.json',
73
75
  'config/design-tokens.schema.json', // the design-token contract (generic schema, no instance values)
74
76
  'config/hierarchy.neutral.json',
77
+ 'config/governance.neutral.json', // the starting role templates a fresh instance inherits (BV1.R96)
75
78
  // --- the portable methodology core, governance, and sanitized docs ---
76
79
  'CLAUDE.md',
77
80
  'README.md',
@@ -86,7 +89,9 @@ const PUBLISH_ALLOWLIST = [
86
89
  'docs/architecture.md',
87
90
  // Served to the hall / advertised by /api/bongos — a standalone that omits these
88
91
  // renders broken onboarding + API-reference pages (task 2078). Redacted like the
89
- // rest of this docs section; the diagrams' .png/.svg regenerate on the box.
92
+ // rest of this docs section. Only the diagram .mmd SOURCES ship: the .png/.svg are
93
+ // gitignored (ADR 0122) so the tarball cannot carry them, and `bongos upgrade`
94
+ // regenerates them on the box via healDocAssets() (upgrade.js, task 1002441).
90
95
  'docs/onboarding/', // primer.md + drachmae-and-karma.md + diagram sources (/builders/primer · /drachmae · /diagrams)
91
96
  // API artifacts (classification settled, task 2141): CORE-shipped STARTERS so a fresh
92
97
  // hall/docs render before first regen — but per-instance GENERATED. init + `bongos
@@ -206,6 +211,7 @@ const INSTANCE_EXCLUDE = [
206
211
  'config/modules.json',
207
212
  'config/devbox-app.json',
208
213
  'config/hierarchy.json',
214
+ 'config/governance.json',
209
215
  'CODEOWNERS', // names the owner (a protected surface, excluded as identity)
210
216
  // instance SEED migrations — this host's scope-truth SQL (planning seeds, world data).
211
217
  // ADR 0105: a consumer's core must not carry them; migrate.sh reads instance seeds from
@@ -39,6 +39,9 @@
39
39
  const { spawn } = require('node:child_process');
40
40
  const lib = require('./cli-lib');
41
41
 
42
+ // lib.API_BASE is session-aware (task 1002427), so both URLs name the instance the
43
+ // builder is actually signed into — not this checkout's branding default, which is
44
+ // what sent a cloudbongos.com builder to a localhost:3000 click-path.
42
45
  const HALL_URL = `${lib.API_BASE}/builders`;
43
46
  // The "Re-issue CLI token" button lives on the Settings page (under "CLI
44
47
  // Access"), not the hall profile — open the builder right where the button is.
@@ -56,9 +59,10 @@ function openInBrowser(url) {
56
59
  case 'win32':
57
60
  // rundll32's FileProtocolHandler opens a URL without going through a
58
61
  // shell, so (unlike `cmd /c start`) shell metacharacters in the URL are
59
- // never interpreted. Defense-in-depth: today the URL is a constant
60
- // (GDS_API_BASE + '/builders'), but this keeps it safe if that ever
61
- // becomes influenced by less-trusted input.
62
+ // never interpreted. This is now load-bearing, not hypothetical: since
63
+ // task 1002427 the URL derives from the session file's api_base (via the
64
+ // session-aware lib.API_BASE), not from a bare env/branding constant. That
65
+ // file is the builder's own 0600 config, but it IS file-derived input.
62
66
  cmd = 'rundll32';
63
67
  args = ['url.dll,FileProtocolHandler', url];
64
68
  break;
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // regen-instance-docs.js — regenerate a STANDALONE instance's live navigation docs (task 1002314).
4
+ //
5
+ // THE GAP. On the monorepo, the gitignored whole-file navigation docs are regenerated at deploy by
6
+ // scripts/deploy/deploy-prod.sh + infra/box-source-fetch.sh (ADR 0110 §2/§3). STANDALONE instances
7
+ // (ADR 0108 — their own lean repo with @bongos/core vendored) never got that step: their bespoke
8
+ // pull-deploy regenerated NOTHING, so a fresh instance shipped with an empty docs/ and nothing kept
9
+ // the session-log index live. This script is the STANDALONE analogue of deploy-prod.sh's regen block
10
+ // — one line for an instance's deploy.sh to call after `git reset --hard` + migrate.
11
+ //
12
+ // USAGE (from the instance repo, core vendored in node_modules):
13
+ // node node_modules/@bongos/core/scripts/gds/regen-instance-docs.js
14
+ // It resolves the INSTANCE root (bongos.config.json instanceRoot / <PREFIX>_INSTANCE_ROOT / cwd) and
15
+ // writes docs/session-log-index.md THERE (gitignored on the instance, so the next
16
+ // `git reset --hard origin/main` never wipes it and no branch can ever diff it).
17
+ //
18
+ // SCOPE + CONTRACT: see regenerateNavDocs() in upgrade.js — it owns the generator list and the
19
+ // rationale for what is and isn't deploy-regenerated (repo-map is core-scoped; file-map + API docs
20
+ // are tracked/committed; diagrams are the headless-browser exception). Best-effort + LOUD per
21
+ // ADR 0110 §2 "accepted staleness": a broken generator prints `::ERROR::` (journald greps it) but
22
+ // this script ALWAYS exits 0 — a stale nav doc is tolerable; a failed deploy is not.
23
+
24
+ const path = require('node:path');
25
+ const { resolveInstanceRoot } = require('../../src/instance-config');
26
+ const { regenerateNavDocs } = require('./upgrade');
27
+
28
+ function main() {
29
+ const coreRoot = path.resolve(__dirname, '..', '..'); // the @bongos/core package this script ships in
30
+ const instanceDir = resolveInstanceRoot();
31
+ const res = regenerateNavDocs({ instanceDir, coreRoot });
32
+ if (res.ran.length) {
33
+ console.log(`regen-instance-docs: regenerated ${res.ran.join(', ')} → ${instanceDir}/docs/`);
34
+ }
35
+ if (!res.ok) {
36
+ for (const f of res.failed) {
37
+ // LOUD (ADR 0110 §2): journald greps ::ERROR:: so a broken generator surfaces.
38
+ console.error(`::ERROR:: regen-instance-docs: ${f.gen} failed — ${f.reason}`);
39
+ }
40
+ }
41
+ // Best-effort: NEVER fail the deploy. A stale/absent nav doc is tolerable; the ::ERROR:: is the alarm.
42
+ process.exit(0);
43
+ }
44
+
45
+ if (require.main === module) main();
46
+
47
+ module.exports = { main };
@@ -73,8 +73,10 @@ const INTEGRATION = new Set([
73
73
  // platform-server — network + package build + a spawned server, run by its own
74
74
  // .github/workflows/consumer-layout-boot.yml, not this DB-free gate (task 2142).
75
75
  'consumer_layout_boot',
76
- 'discord_autojoin', 'discord_oauth', 'linkify_refs', 'memory_isolation',
77
- 'memory_pull', 'parallel-load', 'session_records', 'setup_smoke',
76
+ 'claim_gate_rebase', 'clawback_confirmed_backlog', 'discord_autojoin', 'discord_oauth',
77
+ 'linkify_refs', 'memory_isolation',
78
+ 'memory_pull', 'needs_rebase', 'parallel-load', 'reward_mode_cost_plus_only',
79
+ 'session_records', 'setup_smoke',
78
80
  'status-probe', 'subdomain_auth',
79
81
  ]);
80
82
 
@@ -0,0 +1,104 @@
1
+ // Seed the tasks for goal 1000048 "SEO & public discoverability — rankable per-project
2
+ // profiles" (BONGOS-V1, internal). Source of truth:
3
+ // docs/specs/bongos-v1-seo-public-discoverability.md (status: final).
4
+ // Idempotent on (source, source_ref). Re-running resolves to the existing rows.
5
+ //
6
+ // Run: node scripts/gds/seed-bongos-v1-seo-tasks.js
7
+ const os = require('os');
8
+ const sess = require(os.homedir() + '/.config/cloudbongos/gds-session.json');
9
+ const BASE = (sess.api_base || 'https://cloudbongos.com').replace(/\/$/, '');
10
+ const TOKEN = sess.token;
11
+ const VER = 'BONGOS-V1';
12
+ const GOAL = 1000048;
13
+ const SRC = 'planning-session';
14
+ const H = { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' };
15
+
16
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
17
+ async function api(method, path, body, _tries = 0) {
18
+ await sleep(400); // base throttle to stay under the per-IP rate limit
19
+ const res = await fetch(`${BASE}/api/gds${path}`, {
20
+ method, headers: H, body: body ? JSON.stringify(body) : undefined,
21
+ });
22
+ const text = await res.text();
23
+ let json; try { json = JSON.parse(text); } catch { json = { _raw: text.slice(0, 300) }; }
24
+ if (res.status === 429 && _tries < 8) {
25
+ const wait = ((json.error && json.error.details && json.error.details.retry_after_seconds) || 30) + 1;
26
+ console.log(` …rate-limited, waiting ${wait}s`);
27
+ await sleep(wait * 1000);
28
+ return api(method, path, body, _tries + 1);
29
+ }
30
+ return { status: res.status, ok: res.ok, json };
31
+ }
32
+
33
+ // criterion slugs (position-independent — the goal's done_when_criteria.criterion_id)
34
+ const PP = 'seo-project-profile-page', NS = 'seo-toplevel-namespace', PC = 'seo-project-content',
35
+ PM = 'seo-page-metadata', CC = 'seo-canonical-consolidation', SR = 'seo-sitemap-robots',
36
+ VG = 'seo-visibility-gated', SB = 'seo-site-baseline';
37
+
38
+ // disc: engineer (backend/API/spike) | ui (page/markup/meta). kind: spike | feature | refactor | infra | verify (proofs).
39
+ // deps: 'R##' resolve to the just-created id; a NUMBER is a real cross-goal task id, passed through.
40
+ const TASKS = [
41
+ { ref:'R01', kind:'spike', disc:'engineer', prio:5, est:180, crit:[PP,NS,PM,CC], deps:[], title:'R01 · SEO + namespace architecture ADR', desc:'Needs-exploration: profile-page rendering (server-rendered HTML from the Node/Express hub), routing precedence (/<slug> vs /bongos/* vs /api/*), the owner-picked slug model + reserved-word list, the cross-instance canonical mechanism, and how the profile reads goal-1000046 setup content. Output an ADR + confirmed sub-tasks. See docs/specs/bongos-v1-seo-public-discoverability.md.' },
42
+ { ref:'R02', kind:'feature', disc:'engineer', prio:5, est:240, crit:[PP,NS], deps:['R01'], title:'R02 · Owner-picked slug at creation + reserved-name validation + backfill', desc:'Projects get a unique owner-chosen vanity slug at creation (username-style), validated vs a reserved list (bongos, api, sso, gate, create, explore, builders, status, assets, sitemap.xml, robots.txt, ...). Auto-derive + backfill a default slug for existing projects (owner can change). Adds a step to the creation flow — coordinate with goals 1000035/1000046. Deps R01.' },
43
+ { ref:'R03', kind:'refactor',disc:'engineer', prio:5, est:240, crit:[NS], deps:['R01'], title:'R03 · Move platform pages under /bongos/… ; top-level /<slug> resolves to a project', desc:'Serve hall/status/create/explore/gate under /bongos/… with 301 redirects from old paths; never move /api, /sso, /auth, /.well-known. Top-level /<slug> resolves to a project profile after the reserved-word check. Deps R01.' },
44
+ { ref:'R04', kind:'feature', disc:'ui', prio:5, est:240, crit:[PP,PM], deps:['R02','R03'],title:'R04 · Server-rendered project profile page at /<slug>', desc:'Crawlable server-rendered HTML: project name, description, cover art, builders, tech/modules, and a prominent "Open the live app" CTA. This page is the primary shareable destination. Deps R02,R03.' },
45
+ { ref:'R05', kind:'feature', disc:'ui', prio:4, est:180, crit:[PC], deps:['R04'], title:'R05 · Render goal-1000046 setup content on the profile + auto-derive SEO copy', desc:'Read the project\'s setup content (goal 1000046) and render it on the profile; auto-derive SEO title/description from it. No separate editor (per interview). Coordinate with 1000046 — needs a real description + tags; render current fields (name/tagline) meanwhile and enrich as 1000046 lands. Deps R04.' },
46
+ { ref:'R06', kind:'feature', disc:'ui', prio:4, est:150, crit:[PM,CC], deps:['R04'], title:'R06 · Per-page SEO metadata (title/description/OG/Twitter/self-canonical)', desc:'Dynamic <title>, meta description, Open Graph + Twitter card tags, and a self-referencing canonical on each profile. Deps R04.' },
47
+ { ref:'R07', kind:'feature', disc:'ui', prio:4, est:120, crit:[PM], deps:['R04'], title:'R07 · JSON-LD structured data on profiles', desc:'Emit SoftwareApplication/CreativeWork JSON-LD on each profile page. Deps R04.' },
48
+ { ref:'R08', kind:'feature', disc:'ui', prio:4, est:150, crit:[PP], deps:['R04'], title:'R08 · Route discovery through profiles (orbs + links → /<slug>)', desc:'Make /<slug> the shareable destination: orbs cards and "view project" links point to the profile, which carries the "Open the live app" CTA — maximizing internal links to the ranking page. Deps R04. NOTE: the orbs front door is a separate static site — the orbs-side link change likely lands instance-side.' },
49
+ { ref:'R09', kind:'feature', disc:'ui', prio:4, est:180, crit:[PP,SR], deps:['R04'], title:'R09 · Crawlable projects directory via the orbs table-view', desc:'Make the orbs page\'s table view a server-rendered, crawlable, indexable HTML index of all Public projects (a plain-link list to each /<slug>), linked from the landing. This IS the directory (no separate directory page, per interview). Deps R04.' },
50
+ { ref:'R10', kind:'feature', disc:'engineer', prio:4, est:180, crit:[SR], deps:['R04','R11'],title:'R10 · sitemap.xml (public projects + core pages)', desc:'Auto-generated sitemap.xml at /sitemap.xml listing all Public project profiles + core pages; regenerates on project create/update/visibility-change (cached). Deps R04,R11.' },
51
+ { ref:'R11', kind:'feature', disc:'engineer', prio:4, est:210, crit:[VG], deps:['R04'], title:'R11 · Visibility-gated indexing (visibility metaphor)', desc:'Public→full indexable profile; Private (star)→minimal page + join/request CTA, noindex; Stealth (black hole)→404; Unlisted (dark matter)→no public page until published. HARD conceptual dependency on goal 1000046\'s visibility states (not yet seeded) — coordinate; gate on the state field once it exists. Deps R04.' },
52
+ { ref:'R12', kind:'feature', disc:'engineer', prio:3, est:120, crit:[SR], deps:['R10'], title:'R12 · robots.txt', desc:'Serve robots.txt: allow public pages, disallow /api + internal, reference /sitemap.xml. Deps R10.' },
53
+ { ref:'R13', kind:'feature', disc:'engineer', prio:3, est:240, crit:[CC], deps:['R04'], title:'R13 · Cross-instance canonical (full scope)', desc:'rel=canonical from each live instance back to its hub profile: federated/co-tenant instances emit it; hub-hosted subdomains when that tier lands; publish a documented canonical snippet for self-hosted owners. Touches instance-side core rendering → Metic+ auto-floor. Coordinate with provisioning/federation (goals 1000032/1000041). Deps R04.' },
54
+ { ref:'R14', kind:'feature', disc:'ui', prio:3, est:210, crit:[PP,PM], deps:['R04',1002278], title:'R14 · Public builder-profile SEO page (handle-based path)', desc:'Server-rendered, SEO-optimized public builder profile at a handle path (e.g. /u/<handle>): metadata + Person/ProfilePage JSON-LD, crawlable HTML. Extends goal 1000045\'s builder profiles with a public, rankable rendering. Deps R04 + goal-1000045 profile API (task 1002278).' },
55
+ { ref:'R15', kind:'feature', disc:'engineer', prio:3, est:150, crit:[SR,VG], deps:['R14','R10'],title:'R15 · Builder profiles in sitemap, consent-gated', desc:'Add consented/public builder profiles to sitemap.xml; honor goal-1000045\'s hide/opt-out flag (coordinate with 1000045 R03). Non-consented builders excluded/noindex. Deps R14,R10.' },
56
+ { ref:'R16', kind:'feature', disc:'ui', prio:2, est:120, crit:[PC], deps:['R05'], title:'R16 · SEO content nudges in the setup/edit flow', desc:'Rule-based nudges where owners author content (goal 1000046 setup/edit): description length, tags, cover image — so profiles have enough real text to rank. Coordinate with 1000046. Deps R05.' },
57
+ { ref:'R17', kind:'feature', disc:'ui', prio:3, est:150, crit:[SB], deps:['R01'], title:'R17 · Landing + Explore SEO metadata', desc:'Give the landing page (goal 1000031) and the Explore page proper title/description/Open Graph/structured data and ensure they are indexable. Coordinate with 1000031. Deps R01.' },
58
+ { ref:'R18', kind:'infra', disc:'engineer', prio:2, est:180, crit:[SB], deps:['R04'], title:'R18 · Technical-SEO + Core Web Vitals pass', desc:'Audit + fix technical SEO across public pages: mobile-friendly, load speed / Core Web Vitals, indexability. Deps R04.' },
59
+ { ref:'R19', kind:'verify', disc:'engineer', prio:2, est:120, crit:[PP,PM,CC,SR,VG,PC], deps:['R04','R06','R07','R10','R11','R14','R15'], title:'R19 · PROOF: a public project + builder profile rank-ready; non-public hidden', desc:'End-to-end proof: a real Public project profile is live at cloudbongos.com/<slug> with correct metadata + JSON-LD, appears in sitemap.xml, passes Google Rich Results + mobile-friendly tests; a public builder profile is likewise rank-ready; and a Private project is noindex + absent from the sitemap (Stealth 404s). Deps R04,R06,R07,R10,R11,R14,R15.' },
60
+ ];
61
+
62
+ const ROOTS = ['R01']; // zero-dep entry task to open for claiming
63
+
64
+ (async () => {
65
+ const idByRef = {};
66
+ let created = 0, existing = 0;
67
+ console.log(`Seeding ${TASKS.length} tasks under goal ${GOAL} (${VER})…`);
68
+ for (const t of TASKS) {
69
+ const r = await api('POST', '/tasks', {
70
+ version_id: VER, goal_id: GOAL, title: t.title, description: t.desc,
71
+ kind: t.kind, discipline: t.disc, priority: t.prio, est_minutes: t.est,
72
+ criterion_ids: t.crit, source: SRC, source_ref: `bv1-seo-${t.ref}`,
73
+ });
74
+ if (!r.ok) { console.log(` ${t.ref}: FAILED ${r.status} ${JSON.stringify(r.json)}`); continue; }
75
+ const task = r.json.task || r.json;
76
+ idByRef[t.ref] = task.id;
77
+ if (r.json.idempotent || r.json.existing) existing++; else created++;
78
+ console.log(` ${t.ref} → task ${task.id} (${t.kind}/${t.disc}, P${t.prio}, [${t.crit.join(',')}])`);
79
+ }
80
+
81
+ console.log('Wiring dependencies…');
82
+ let edges = 0;
83
+ for (const t of TASKS) {
84
+ if (!t.deps.length) continue;
85
+ const id = idByRef[t.ref];
86
+ const depIds = t.deps.map((d) => (typeof d === 'number' ? d : idByRef[d])).filter(Boolean);
87
+ if (!id || depIds.length !== t.deps.length) { console.log(` ${t.ref}: dep resolve gap, skipping`); continue; }
88
+ const r = await api('POST', `/tasks/${id}/dependencies`, { depends_on_task_ids: depIds });
89
+ if (!r.ok) { console.log(` ${t.ref}: dep FAILED ${r.status} ${JSON.stringify(r.json)}`); continue; }
90
+ edges += (r.json.added || depIds.length);
91
+ console.log(` ${t.ref} depends on ${t.deps.join(',')}`);
92
+ }
93
+
94
+ console.log('Promoting root(s) to ready…');
95
+ for (const ref of ROOTS) {
96
+ const id = idByRef[ref];
97
+ if (!id) continue;
98
+ const r = await api('POST', `/tasks/${id}/promote`, {});
99
+ console.log(` ${ref} (task ${id}): ${r.ok ? 'ready' : 'promote note ' + JSON.stringify(r.json).slice(0, 120)}`);
100
+ }
101
+
102
+ console.log(`\nDONE. created=${created} existing=${existing} depEdges=${edges}. Roots opened: ${ROOTS.join(', ')}.`);
103
+ console.log('ref→id:', JSON.stringify(idByRef));
104
+ })();