@bongos/core 1.19.1081 → 1.20.1

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 (251) hide show
  1. package/.bongos-core.json +526 -191
  2. package/.claude/skills/planning-session/SKILL.md +6 -2
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +32 -0
  5. package/clients/bongos-client/index.cjs +32 -0
  6. package/clients/bongos-client/index.d.ts +51 -6
  7. package/clients/bongos-client/index.mjs +32 -0
  8. package/docs/adr/0099-delayed-redacted-mirror-export.md +5 -1
  9. package/docs/adr/0111-instance-hosting-provisioning-module.md +1 -0
  10. package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +17 -0
  11. package/docs/adr/0176-private-repo-deploy-keys.md +2 -0
  12. package/docs/adr/0310-a-speciality-offers-skills-and-the-adopter-chooses-them.md +1 -1
  13. package/docs/adr/0348-the-web-tier-may-look-read-only-at-what-an-owners-render-key-can-see.md +53 -0
  14. package/docs/adr/0349-a-version-preview-is-a-sandboxed-child-the-web-tier-launches.md +106 -0
  15. package/docs/adr/0350-the-hub-holds-a-per-project-write-deploy-key-so-a-hosted-upgrade-reaches-github.md +103 -0
  16. package/docs/adr/README.md +3 -0
  17. package/docs/api/openapi.json +851 -19
  18. package/docs/api-reference.md +33 -9
  19. package/docs/architecture.md +6 -2
  20. package/docs/copy-inventory.md +620 -553
  21. package/docs/copy-registry.json +1467 -820
  22. package/docs/file-map.md +2 -0
  23. package/docs/module-api-changelog.md +6 -0
  24. package/docs/modules-contract.md +1 -0
  25. package/docs/page-inventory.json +38 -4
  26. package/docs/page-readings.json +1674 -1566
  27. package/migrations/core_260_goals_working_area.sql +63 -0
  28. package/migrations/core_261_grade_attempts_verdicts.sql +24 -0
  29. package/migrations/core_262_drop_builder_box_blocked.sql +33 -0
  30. package/modules/agents/lib/validate.js +43 -0
  31. package/modules/autonomy/gauge.js +38 -2
  32. package/modules/grading/grader-subagent.js +37 -2
  33. package/modules/grading/grader-workers/reader.js +65 -5
  34. package/modules/hall-ui/public/approval-queue.css +33 -7
  35. package/modules/hall-ui/public/approval-queue.js +9 -2
  36. package/modules/hall-ui/public/atlas.html +1 -1
  37. package/modules/hall-ui/public/blockers.html +1 -1
  38. package/modules/hall-ui/public/board-room.html +1 -1
  39. package/modules/hall-ui/public/brand-holes.js +121 -0
  40. package/modules/hall-ui/public/collab.html +1 -1
  41. package/modules/hall-ui/public/collab.js +1 -1
  42. package/modules/hall-ui/public/copy-desk.html +1 -1
  43. package/modules/hall-ui/public/deploy.html +6 -1
  44. package/modules/hall-ui/public/deploy.js +22 -8
  45. package/modules/hall-ui/public/diagrams.html +1 -1
  46. package/modules/hall-ui/public/dom-utils.js +20 -0
  47. package/modules/hall-ui/public/drachmae.html +1 -1
  48. package/modules/hall-ui/public/fleet.html +1 -1
  49. package/modules/hall-ui/public/gate.html +1 -1
  50. package/modules/hall-ui/public/goals.html +1 -1
  51. package/modules/hall-ui/public/government.html +1 -1
  52. package/modules/hall-ui/public/idea.html +1 -1
  53. package/modules/hall-ui/public/idea.js +28 -0
  54. package/modules/hall-ui/public/ideas.html +1 -1
  55. package/modules/hall-ui/public/ideas.js +19 -3
  56. package/modules/hall-ui/public/index.html +5 -1
  57. package/modules/hall-ui/public/modules.html +1 -1
  58. package/modules/hall-ui/public/primer.html +1 -1
  59. package/modules/hall-ui/public/profile-nudge.js +21 -4
  60. package/modules/hall-ui/public/profile.html +1 -1
  61. package/modules/hall-ui/public/profile.js +1 -1
  62. package/modules/hall-ui/public/project-settings.html +1 -1
  63. package/modules/hall-ui/public/ranks.html +1 -1
  64. package/modules/hall-ui/public/roadmap.html +1 -1
  65. package/modules/hall-ui/public/roster.html +1 -1
  66. package/modules/hall-ui/public/sessions.html +1 -1
  67. package/modules/hall-ui/public/settings.html +19 -30
  68. package/modules/hall-ui/public/settings.js +24 -177
  69. package/modules/hall-ui/public/settings.states.json +1 -1
  70. package/modules/hall-ui/public/shell.js +4 -2
  71. package/modules/hall-ui/public/studio.css +14 -8
  72. package/modules/hall-ui/public/studio.html +5 -4
  73. package/modules/hall-ui/public/task.html +1 -1
  74. package/modules/hall-ui/public/thinking.css +6 -3
  75. package/modules/hall-ui/public/thinking.html +1 -1
  76. package/modules/hall-ui/public/tweak-editor-lib.js +82 -1
  77. package/modules/hall-ui/public/tweak-editor.css +142 -11
  78. package/modules/hall-ui/public/tweak-editor.html +25 -9
  79. package/modules/hall-ui/public/tweak-editor.js +243 -10
  80. package/modules/hall-ui/public/watch.html +1 -1
  81. package/modules/hall-ui/public/work.html +1 -1
  82. package/modules/hall-ui/records/approval-queue.md +6 -0
  83. package/modules/hall-ui/records/tweak-editor.md +6 -0
  84. package/modules/ideas/ratify.js +36 -8
  85. package/modules/ideas/routes/ratify-goal.js +6 -0
  86. package/modules/lifecycle/db-analytics.js +79 -10
  87. package/modules/lifecycle/db-claims.js +48 -1
  88. package/modules/lifecycle/db-deps-criteria.js +94 -40
  89. package/modules/lifecycle/db-goals.js +4 -1
  90. package/modules/lifecycle/db-grade.js +9 -1
  91. package/modules/lifecycle/db-relevance-flags.js +183 -0
  92. package/modules/lifecycle/db-versions.js +4 -3
  93. package/modules/lifecycle/db.js +19 -0
  94. package/modules/lifecycle/done-when.js +5 -3
  95. package/modules/lifecycle/est-advisory.js +31 -4
  96. package/modules/lifecycle/goal-task-relevance-advisory.js +153 -0
  97. package/modules/lifecycle/goal-task-relevance-judge.js +226 -0
  98. package/modules/lifecycle/migrations/lifecycle_014_goal_task_relevance_flags.sql +82 -0
  99. package/modules/lifecycle/module.json +2 -1
  100. package/modules/lifecycle/routes/claims.js +8 -1
  101. package/modules/lifecycle/routes/goal-task-relevance.js +72 -0
  102. package/modules/lifecycle/routes/goals.js +1 -1
  103. package/modules/lifecycle/routes/tasks.js +27 -24
  104. package/modules/lifecycle/routes/visuals.js +9 -0
  105. package/modules/lifecycle/ship-preflight.js +7 -11
  106. package/modules/lifecycle/task-visuals.js +109 -5
  107. package/modules/npm-release/module.json +2 -1
  108. package/modules/npm-release/preview/commands.js +81 -0
  109. package/modules/npm-release/preview/divert.js +95 -0
  110. package/modules/npm-release/preview/env.js +78 -0
  111. package/modules/npm-release/preview/proxy.js +107 -0
  112. package/modules/npm-release/preview/runtime.js +83 -0
  113. package/modules/npm-release/preview/supervisor.js +175 -0
  114. package/modules/npm-release/public/work.js +65 -1
  115. package/modules/npm-release/routes/preview.js +133 -0
  116. package/modules/provisioning/app-status.js +83 -0
  117. package/modules/provisioning/module.json +2 -1
  118. package/modules/provisioning/pollers/app-liveness.js +110 -0
  119. package/modules/provisioning/render-lookup.js +68 -0
  120. package/modules/provisioning/render-standup.js +44 -1
  121. package/modules/provisioning/routes/render-standup.js +81 -6
  122. package/modules/provisioning/starter-bundles.js +4 -1
  123. package/modules/public-landing/public/account-private.states.json +14 -0
  124. package/modules/public-landing/public/account.html +513 -0
  125. package/modules/public-landing/public/account.probes.json +37 -0
  126. package/modules/public-landing/public/account.states.json +16 -0
  127. package/modules/public-landing/public/index.html +5 -2
  128. package/modules/public-landing/public/projects.html +661 -18
  129. package/modules/public-landing/public/projects.states.json +8 -1
  130. package/modules/render-deploy/deploys.js +203 -0
  131. package/modules/render-deploy/migrations/render_deploy_001_app.sql +22 -0
  132. package/modules/render-deploy/module.json +25 -0
  133. package/modules/render-deploy/public/deploy.css +10 -0
  134. package/modules/render-deploy/public/deploy.js +249 -0
  135. package/modules/render-deploy/render.js +56 -0
  136. package/modules/render-deploy/routes/act.js +88 -0
  137. package/modules/render-deploy/routes/app.js +65 -0
  138. package/modules/render-deploy/routes/history.js +38 -0
  139. package/modules/specialities/db.js +27 -8
  140. package/modules/specialities/migrations/specialities_003_skills.sql +46 -0
  141. package/modules/specialities/routes/specialities.js +51 -3
  142. package/modules/specialities/skills.js +94 -0
  143. package/modules/specialities/specialities.js +56 -9
  144. package/modules/ui-design/kit/fixtures/me-cross-project-private.json +12 -0
  145. package/modules/ui-design/kit/fixtures/me__cross-project.json +15 -0
  146. package/modules/ui-design/kit/fixtures/provisioning-instance-render.json +39 -0
  147. package/modules/ui-design/kit/lib.js +3 -1
  148. package/modules/ui-design/kit/serve.js +96 -0
  149. package/package-lock.json +2 -2
  150. package/package.json +1 -1
  151. package/release-notes.json +163 -0
  152. package/scripts/gds/agents-sync.js +13 -3
  153. package/scripts/gds/autobongos-run.js +80 -2
  154. package/scripts/gds/autobongos-service.cmd +12 -0
  155. package/scripts/gds/copy-apply.js +14 -0
  156. package/scripts/gds/dev-box-guard.js +3 -2
  157. package/scripts/gds/fitness.js +9 -0
  158. package/scripts/gds/grade-correlation-audit.js +42 -12
  159. package/scripts/gds/grade-replay.js +1 -1
  160. package/scripts/gds/provision-core-upgrade.js +38 -7
  161. package/scripts/gds/provision-pin-key.js +176 -0
  162. package/scripts/gds/provision-render.js +62 -31
  163. package/scripts/gds/provision-teardown.js +7 -2
  164. package/scripts/gds/provision-units.js +31 -0
  165. package/scripts/gds/provision.js +10 -10
  166. package/scripts/gds/publish-manifest.js +1 -0
  167. package/scripts/gds/render-api.js +19 -0
  168. package/scripts/gds/render-payload.js +39 -65
  169. package/scripts/gds/run-unit-tests.js +10 -0
  170. package/scripts/gds/ship-finish.js +19 -20
  171. package/scripts/gds/smoke-dependencies.sh +37 -8
  172. package/scripts/gds/status.js +12 -4
  173. package/scripts/gds/upgrade.js +2 -2
  174. package/scripts/public-mirror-export.js +7 -1
  175. package/src/bongos/module-scope-map.js +13 -0
  176. package/src/bongos/serve-internal.js +3 -1
  177. package/src/module-api.js +10 -1
  178. package/src/platform-server.js +39 -0
  179. package/tests/account_privacy_flags.mjs +10 -6
  180. package/tests/agents_authoring.mjs +15 -1
  181. package/tests/agents_sync.mjs +106 -5
  182. package/tests/agents_validate.mjs +45 -0
  183. package/tests/api_path_404.mjs +6 -0
  184. package/tests/autobongos_cadence.mjs +7 -0
  185. package/tests/autobongos_loop.mjs +136 -1
  186. package/tests/autonomy_gauge.mjs +62 -0
  187. package/tests/blocker_hall_live_proof.mjs +275 -0
  188. package/tests/claim_gate_ci_unblock.mjs +116 -0
  189. package/tests/claim_gate_rebase.mjs +15 -3
  190. package/tests/collab_page.mjs +10 -0
  191. package/tests/conductor_main_e2e.mjs +97 -0
  192. package/tests/core_262_drop_box_blocked_db.mjs +143 -0
  193. package/tests/core_upgrade_runner.mjs +1 -0
  194. package/tests/dependency_writes_atomic.mjs +191 -0
  195. package/tests/deploy_page_projects.mjs +2 -1
  196. package/tests/effective_visibility_predicate.mjs +15 -0
  197. package/tests/est_advisory.mjs +27 -0
  198. package/tests/goal_map_page.mjs +26 -0
  199. package/tests/goal_task_relevance.mjs +518 -0
  200. package/tests/goal_working_area.mjs +146 -0
  201. package/tests/grade_attempts.mjs +61 -0
  202. package/tests/grade_attribution.mjs +23 -0
  203. package/tests/grade_correlation_audit.mjs +54 -1
  204. package/tests/grader_reader_lens.mjs +55 -2
  205. package/tests/grader_root_outage.mjs +6 -1
  206. package/tests/grader_subagent_tools_arg.mjs +102 -0
  207. package/tests/hall_approval_queue.mjs +54 -2
  208. package/tests/hall_settings_world.mjs +3 -1
  209. package/tests/hall_tweak_editor.mjs +316 -11
  210. package/tests/hub_account_page.mjs +394 -0
  211. package/tests/idea_detail_page.mjs +64 -0
  212. package/tests/idea_goal_ratification.mjs +64 -0
  213. package/tests/idea_spark_hall.mjs +60 -9
  214. package/tests/ideator_full_idea_shapes_space_proof.mjs +1 -1
  215. package/tests/landing_page.mjs +6 -3
  216. package/tests/module-scope-map.mjs +31 -8
  217. package/tests/module_api.mjs +1 -0
  218. package/tests/module_loader.mjs +1 -1
  219. package/tests/nav_permission_atoms.mjs +1 -1
  220. package/tests/no_phantom_mirror_workflow.mjs +24 -0
  221. package/tests/npm_release_preview_commands.mjs +89 -0
  222. package/tests/npm_release_preview_divert.mjs +164 -0
  223. package/tests/npm_release_preview_env.mjs +100 -0
  224. package/tests/npm_release_preview_proxy.mjs +162 -0
  225. package/tests/npm_release_preview_routes.mjs +208 -0
  226. package/tests/npm_release_preview_supervisor.mjs +203 -0
  227. package/tests/pin_write_key.mjs +383 -0
  228. package/tests/planning_session_skill.mjs +27 -0
  229. package/tests/platform_boot.mjs +71 -0
  230. package/tests/profile_nudge_links.mjs +86 -0
  231. package/tests/profile_ui_cross_project.mjs +1 -1
  232. package/tests/projects_hub.mjs +2 -2
  233. package/tests/projects_hub_app_status.mjs +249 -0
  234. package/tests/projects_hub_app_step.mjs +282 -52
  235. package/tests/projects_hub_render_connect.mjs +295 -0
  236. package/tests/provision_render.mjs +121 -28
  237. package/tests/provision_settings_apply.mjs +62 -0
  238. package/tests/provisioning_app_status.mjs +204 -0
  239. package/tests/provisioning_render_route.mjs +126 -6
  240. package/tests/render_check.mjs +2 -1
  241. package/tests/render_deploy.mjs +495 -0
  242. package/tests/ship_preflight.mjs +61 -8
  243. package/tests/smoke_dependencies_witness.mjs +46 -0
  244. package/tests/speciality_skills.mjs +214 -0
  245. package/tests/studio_room_height.mjs +70 -0
  246. package/tests/task_detail_includes.mjs +10 -0
  247. package/tests/task_visuals_instance_root.mjs +150 -0
  248. package/tests/tweak_batch_apply.mjs +21 -0
  249. package/tests/ui_design_kit.mjs +36 -4
  250. package/tests/upgrade.mjs +1 -1
  251. package/tests/upgrade_persist_pin.mjs +24 -0
@@ -0,0 +1,15 @@
1
+ {
2
+ "hide_stats": false,
3
+ "profile_state": "active",
4
+ "account_visibility": "public",
5
+ "recruiter_discoverable": null,
6
+ "recruiter_discoverable_effective": true,
7
+ "handle": "builder",
8
+ "display_name": "A Builder",
9
+ "bio": "Builds small tools that make shipping calmer. Mostly the hall, sometimes the sky.",
10
+ "links": { "website": "https://example.com/", "x": "https://x.com/builder" },
11
+ "cross_project": {
12
+ "projects": [],
13
+ "totals": { "projects": 3, "credits": 1000, "works_shipped": 19, "karma": 10, "shipped": 19 }
14
+ }
15
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "101": {
3
+ "ok": true,
4
+ "app": {
5
+ "platform": "render", "state": "live", "stage": "live", "waiting": false, "mode": "greenfield",
6
+ "workspace": "tea-aaaaaaaa1111", "url": "https://demo.onrender.com",
7
+ "links": { "service": "https://dashboard.render.com/web/srv-aaaaaaaa1111", "env": "https://dashboard.render.com/web/srv-aaaaaaaa1111/env" },
8
+ "error": null, "retry": null
9
+ },
10
+ "env_vars": [
11
+ { "name": "DATABASE_URL", "required": true, "set_for_you": true },
12
+ { "name": "SESSION_SECRET", "required": true, "set_for_you": false },
13
+ { "name": "STRIPE_KEY", "required": false, "set_for_you": false }
14
+ ],
15
+ "values_held": false
16
+ },
17
+ "105": {
18
+ "ok": true,
19
+ "app": {
20
+ "platform": "render", "state": "failed", "stage": null, "waiting": false, "mode": "greenfield",
21
+ "workspace": "tea-aaaaaaaa1111", "url": null, "links": null,
22
+ "error": "This Render key cannot reach workspace tea-aaaaaaaa1111.",
23
+ "retry": { "owner_id": "tea-aaaaaaaa1111", "mode": "greenfield", "runtime": "node", "start_command": "npm start", "web_plan": "starter", "database": false }
24
+ },
25
+ "env_vars": [{ "name": "SESSION_SECRET", "required": true, "set_for_you": false }],
26
+ "values_held": false
27
+ },
28
+ "102": {
29
+ "ok": true,
30
+ "app": {
31
+ "platform": "render", "state": "deploying", "stage": "building", "waiting": false, "mode": "greenfield",
32
+ "workspace": "tea-aaaaaaaa1111", "url": "https://harbor.onrender.com",
33
+ "links": { "service": "https://dashboard.render.com/web/srv-bbbbbbbb2222", "env": "https://dashboard.render.com/web/srv-bbbbbbbb2222/env" },
34
+ "error": null, "retry": null
35
+ },
36
+ "env_vars": [],
37
+ "values_held": false
38
+ }
39
+ }
@@ -35,7 +35,9 @@ const FLOORS = Object.freeze([
35
35
  ]);
36
36
 
37
37
  // 1280 is the review sheet's desktop width; render.js renders DEFAULT_SIZES unless --sizes names it.
38
- const SIZES = Object.freeze({ 1440: [1440, 900], 1280: [1280, 800], 390: [390, 844], 320: [320, 700] });
38
+ // 1024 is a laptop-window width the owner reads panels at (task 1004381: the
39
+ // Approval queue ran off-screen there). Opt-in with --sizes; not a default.
40
+ const SIZES = Object.freeze({ 1440: [1440, 900], 1280: [1280, 800], 1024: [1024, 768], 390: [390, 844], 320: [320, 700] });
39
41
  const DEFAULT_SIZES = Object.freeze([1440, 390, 320]);
40
42
  const MODES = Object.freeze(['dark', 'light']);
41
43
  const CHANNELS = Object.freeze(['msedge', 'chrome']);
@@ -280,6 +280,89 @@ function me(origin) {
280
280
  return m;
281
281
  }
282
282
 
283
+ // The hub account page (task 1004259): GET/PATCH /me/cross-project, PATCH /me/profile and
284
+ // PATCH /me/handle, answered from ONE account held for the run (me__cross-project.json, or
285
+ // the file a states file's stub.fixtures names) so a save the page re-reads comes back
286
+ // saved. The routes' refusals are mirrored — the unknown key, the off-vocabulary value, the
287
+ // bio over its cap, a typed link off its pinned host, the malformed or taken handle — because
288
+ // a stub that takes any body is how task 1003352 shipped a key the real route 400s on. The
289
+ // caps and pins are copied from platform-identity.js (a kit module may not import one), and
290
+ // "taken" is the one handle already in use, so a probe can drive that refusal too.
291
+ let ACCOUNT = null;
292
+ function account() {
293
+ if (!ACCOUNT) {
294
+ const name = 'me__cross-project';
295
+ ACCOUNT = JSON.parse(fs.readFileSync(path.join(FIXTURES, (ALIAS[name] || name) + '.json'), 'utf8'));
296
+ }
297
+ return ACCOUNT;
298
+ }
299
+ const STUB_BIO_MAX = 280;
300
+ const STUB_LINK_HOSTS = { website: null, x: ['x.com', 'twitter.com'], linkedin: ['linkedin.com'], discord: ['discord.gg', 'discord.com'] };
301
+ const STUB_HANDLE = /^(?=.{1,39}$)[a-z0-9]+(?:-[a-z0-9]+)*$/;
302
+ function refuse(res, status, code, message) { return json(res, status, { error: { code, message } }); }
303
+ function effectiveReach(a) { return a.recruiter_discoverable == null ? a.account_visibility !== 'private' : !!a.recruiter_discoverable; }
304
+ function crossProjectPatch(res, body) {
305
+ const a = account();
306
+ const keys = Object.keys(body);
307
+ const bad = keys.find((k) => !['hide_stats', 'account_visibility', 'recruiter_discoverable'].includes(k));
308
+ if (bad) return refuse(res, 400, 'validation_failed', `unknown field: ${bad}`);
309
+ if (!keys.length) return refuse(res, 400, 'no_fields', 'send at least one of hide_stats, account_visibility, recruiter_discoverable');
310
+ if ('hide_stats' in body && typeof body.hide_stats !== 'boolean') return refuse(res, 400, 'validation_failed', 'hide_stats must be a boolean');
311
+ if ('account_visibility' in body && !['public', 'private'].includes(body.account_visibility)) return refuse(res, 400, 'validation_failed', 'account_visibility is one of public, private');
312
+ if ('recruiter_discoverable' in body && typeof body.recruiter_discoverable !== 'boolean') return refuse(res, 400, 'validation_failed', 'recruiter_discoverable must be a boolean');
313
+ if ('hide_stats' in body) a.hide_stats = body.hide_stats;
314
+ if ('account_visibility' in body) {
315
+ // setAccountPrivacy's consent-timing rule: turning private without naming reach resets it
316
+ if (body.account_visibility === 'private' && a.account_visibility !== 'private' && !('recruiter_discoverable' in body)) a.recruiter_discoverable = null;
317
+ a.account_visibility = body.account_visibility;
318
+ }
319
+ if ('recruiter_discoverable' in body) a.recruiter_discoverable = body.recruiter_discoverable;
320
+ a.recruiter_discoverable_effective = effectiveReach(a);
321
+ return json(res, 200, { hide_stats: !!a.hide_stats, account_visibility: a.account_visibility, recruiter_discoverable: a.recruiter_discoverable, recruiter_discoverable_effective: a.recruiter_discoverable_effective });
322
+ }
323
+ function profilePatch(res, body) {
324
+ const a = account();
325
+ const bad = Object.keys(body).find((k) => !['display_name', 'bio', 'links'].includes(k));
326
+ if (bad) return refuse(res, 400, 'validation_failed', `unknown field: ${bad}`);
327
+ if (!Object.keys(body).length) return refuse(res, 400, 'no_fields', 'send at least one of display_name, bio, links');
328
+ if ('display_name' in body) {
329
+ const n = typeof body.display_name === 'string' ? body.display_name.trim() : '';
330
+ if (!n) return refuse(res, 400, 'invalid_display_name', 'a display name must be non-empty plain text');
331
+ if (Array.from(n).length > 60) return refuse(res, 400, 'display_name_too_long', 'a display name is at most 60 characters');
332
+ }
333
+ if (body.bio != null && Array.from(String(body.bio).trim()).length > STUB_BIO_MAX) return refuse(res, 400, 'bio_too_long', `a bio is at most ${STUB_BIO_MAX} characters of plain text`);
334
+ let links = a.links;
335
+ if ('links' in body) {
336
+ links = null;
337
+ for (const [type, raw] of Object.entries(body.links || {})) {
338
+ if (!(type in STUB_LINK_HOSTS)) return refuse(res, 400, 'invalid_links', `links are a typed set (${Object.keys(STUB_LINK_HOSTS).join(', ')}), https URLs only — problem with '${type}'`);
339
+ if (raw == null || raw === '') continue;
340
+ let u = null;
341
+ try { u = new URL(String(raw).trim()); } catch { /* refused below */ }
342
+ const pins = STUB_LINK_HOSTS[type];
343
+ if (!u || u.protocol !== 'https:' || (pins && !pins.some((h) => u.hostname === h || u.hostname.endsWith('.' + h)))) {
344
+ return refuse(res, 400, 'invalid_link', `links are a typed set (${Object.keys(STUB_LINK_HOSTS).join(', ')}), https URLs only — problem with '${type}'`);
345
+ }
346
+ links = { ...(links || {}), [type]: u.href };
347
+ }
348
+ }
349
+ if ('display_name' in body) a.display_name = body.display_name.trim();
350
+ if ('bio' in body) a.bio = body.bio == null || !String(body.bio).trim() ? null : String(body.bio).trim();
351
+ a.links = links;
352
+ return json(res, 200, { display_name: a.display_name, bio: a.bio, links: a.links });
353
+ }
354
+ function handlePatch(res, body) {
355
+ const bad = Object.keys(body).find((k) => k !== 'handle');
356
+ if (bad) return refuse(res, 400, 'validation_failed', `unknown field: ${bad}`);
357
+ const h = typeof body.handle === 'string' ? body.handle.trim().toLowerCase() : '';
358
+ if (!STUB_HANDLE.test(h)) return refuse(res, 400, 'invalid_handle', 'a handle is 1–39 characters of a–z, 0–9, and hyphens (not at the ends)');
359
+ if (h === 'taken') return refuse(res, 409, 'handle_taken', 'that handle is already in use');
360
+ const a = account();
361
+ const previous = a.handle;
362
+ a.handle = h;
363
+ return json(res, 200, { handle: h, previous_handle: previous || null });
364
+ }
365
+
283
366
  const server = http.createServer((req, res) => {
284
367
  const u = new URL(req.url, 'http://x');
285
368
  const p = u.pathname;
@@ -292,7 +375,20 @@ const server = http.createServer((req, res) => {
292
375
  if (FEEDS[rest]) return LIVE ? proxy(req, res) : fixture(FEEDS[rest], res, origin);
293
376
  if (rest === '/me') return AUTH ? json(res, 200, me(origin)) : json(res, 401, UNAUTH);
294
377
  if (AUTHED[rest]) return AUTH ? fixture(AUTHED[rest], res) : json(res, 401, UNAUTH);
378
+ // the account page (task 1004259): one account for the run, reads and the three writes
379
+ if (rest === '/me/cross-project' && req.method === 'GET') return AUTH ? json(res, 200, account()) : json(res, 401, UNAUTH);
380
+ if (rest === '/me/cross-project' && req.method === 'PATCH') return AUTH ? readBody(req, (body) => crossProjectPatch(res, body)) : json(res, 401, UNAUTH);
381
+ if (rest === '/me/profile' && req.method === 'PATCH') return AUTH ? readBody(req, (body) => profilePatch(res, body)) : json(res, 401, UNAUTH);
382
+ if (rest === '/me/handle' && req.method === 'PATCH') return AUTH ? readBody(req, (body) => handlePatch(res, body)) : json(res, 401, UNAUTH);
295
383
  if (rest === '/provisioning/instances' && req.method === 'GET') return AUTH ? instances(res, null, false) : json(res, 401, UNAUTH);
384
+ // where a project's app runs (task 1004353): the status read, from a fixture keyed by
385
+ // project id — a project the fixture does not name has no platform chosen
386
+ const appRead = rest.match(/^\/provisioning\/instances\/(\d+)\/render$/);
387
+ if (appRead && req.method === 'GET') {
388
+ if (!AUTH) return json(res, 401, UNAUTH);
389
+ const all = JSON.parse(fs.readFileSync(path.join(FIXTURES, 'provisioning-instance-render.json'), 'utf8'));
390
+ return json(res, 200, all[appRead[1]] || { ok: true, app: null, env_vars: [], values_held: false });
391
+ }
296
392
  const one = rest.match(/^\/provisioning\/instances\/(\d+)(\/settings)?$/);
297
393
  if (one && req.method === 'GET') return AUTH ? instances(res, one[1], !!one[2]) : json(res, 401, UNAUTH);
298
394
  // the hub's own cross-project surface (task 1003774): the requested/leave/
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.1081",
3
+ "version": "1.20.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.1081",
9
+ "version": "1.20.1",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.1081",
3
+ "version": "1.20.1",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -7883,5 +7883,168 @@
7883
7883
  "id": "1004364",
7884
7884
  "text": "Every module in the store will now come with a how-to guide. Publishing is refused without one, and an AI gives each guide a Docs score that buyers can see next to the module's other scores."
7885
7885
  }
7886
+ ],
7887
+ "1.20.0": [],
7888
+ "1.20.1": [
7889
+ {
7890
+ "id": "1004401",
7891
+ "text": "New Cloud Bongos releases were stuck: since late yesterday, every release was held back because a set of automatic database checks kept failing on the main line of work. Three recent changes each added a check that could never"
7892
+ },
7893
+ {
7894
+ "id": "1004370",
7895
+ "text": "When the project's shared tests break for a reason nobody's work caused, builders could no longer pick up the fix, because their approved tasks could not land and that paused their queue. Now a top-priority infrastructure task"
7896
+ },
7897
+ {
7898
+ "id": "1004184",
7899
+ "text": "A project that was created without choosing where its app runs can now connect Render straight from its own page. The owner gets the same steps as in the setup wizard: check the key, choose the workspace, a new app or one they"
7900
+ },
7901
+ {
7902
+ "id": "1004072",
7903
+ "text": "Specialities can now come with a set of skills, and each person chooses which of them to switch on. A speciality lists the skills it offers; when someone adopts it, every skill starts switched off, as you decided, and they tur"
7904
+ },
7905
+ {
7906
+ "id": "1003485",
7907
+ "text": "The hall's \"add a short bio\" prompt now opens your account page, where your bio can actually be written."
7908
+ },
7909
+ {
7910
+ "id": "1004134",
7911
+ "text": "The idea page now shows what a Full Idea's author thinks it is: a goal of its own, or a smaller piece inside an existing goal. The author answers that question when filing, and the board reads it when deciding, but the idea's"
7912
+ },
7913
+ {
7914
+ "id": "1004254",
7915
+ "text": "After a ship, any files left behind now stay where they are and get listed, instead of being tucked into a shared hiding place that other sessions could accidentally grab."
7916
+ },
7917
+ {
7918
+ "id": "1004259",
7919
+ "text": "You can now change your handle, bio, links and privacy in one place on your Cloud Bongos account page, and every project's hall links to it."
7920
+ },
7921
+ {
7922
+ "id": "1004234",
7923
+ "text": "The reply box on Collab threads no longer shows an old message about a removed metrics stack as its hint. It now just says \"Write a reply…\", so nobody mistakes the hint for a real reply."
7924
+ },
7925
+ {
7926
+ "id": "1003405",
7927
+ "text": "Correct the public-mirror export docs: publishing is manual, the workflow it named never existed."
7928
+ },
7929
+ {
7930
+ "id": "1003656",
7931
+ "text": "Creating a task no longer re-runs a full historical estimate aggregate every time; it is reused for a minute."
7932
+ },
7933
+ {
7934
+ "id": "1004381",
7935
+ "text": "The Approval queue now works on the live site. Page pictures attached to a round are kept in the site's own data folder instead of inside the Cloud Bongos package, so updating Cloud Bongos no longer deletes them, and any pictu"
7936
+ },
7937
+ {
7938
+ "id": "1004398",
7939
+ "text": "The session helper that tells each work session how to talk to you and what to watch out for had been silently broken since 16 September. A single mistake made it crash every time, and the crash was hidden, so for about two we"
7940
+ },
7941
+ {
7942
+ "id": "1004270",
7943
+ "text": "A project whose app runs on Render can now turn on a deploy page in its hall that lists its Render deploys with the tasks in each, marks what is live, and deploys or rolls back after two confirmations, though it has not yet be"
7944
+ },
7945
+ {
7946
+ "id": "1002865",
7947
+ "text": "A planning session no longer throws away its interview when your sign-in runs out partway through. Planning sessions can run long, and if the login expired before the final step, every task write failed and the answers from th"
7948
+ },
7949
+ {
7950
+ "id": "1004395",
7951
+ "text": "The platform can now finish renaming the site's pre-launch password setting by itself. Before, someone had to log into the server by hand. The next time a settings change is pushed to the live site, it copies the password over"
7952
+ },
7953
+ {
7954
+ "id": "1004075",
7955
+ "text": "The house style guide for the product's wording can now grow. The automatic reviewer that checks wording against the guide had the guide's rule numbers typed into its own instructions, so adding a twelfth rule, or splitting on"
7956
+ },
7957
+ {
7958
+ "id": "1004353",
7959
+ "text": "After setting a project's app up on Render, the owner can now see how it's going, in the setup wizard and on the project's page: creating, building, then live with its address and a link to Render. If it fails they get one pla"
7960
+ },
7961
+ {
7962
+ "id": "1004344",
7963
+ "text": "The studio's night scene now fills tall screens properly. The picture (the window with the moon, the plant, the lamp) was drawn for a screen about 900 pixels tall, so on a taller screen it stayed at the top and left a wide emp"
7964
+ },
7965
+ {
7966
+ "id": "1004291",
7967
+ "text": "Hosted projects now get their own lasting key to their GitHub repo, so a core upgrade always saves its new version there instead of leaving GitHub behind."
7968
+ },
7969
+ {
7970
+ "id": "1004386",
7971
+ "text": "The tweak editor drops its title header, and artists can open the live page in a new tab or a resizable split view below their words; the editor remembers the choice for the next page."
7972
+ },
7973
+ {
7974
+ "id": "1004342",
7975
+ "text": "Your sky no longer slides to the left. The page's frame could be scrolled sideways by the browser even though it showed no scrollbar, and a line of hint text at the bottom was wider than the screen, so moving the cursor into t"
7976
+ },
7977
+ {
7978
+ "id": "1004191",
7979
+ "text": "Checked the nine old, never-verified problems with how tasks are linked as \"do this first\". Fixed three real ones: adding several links at once can no longer half-succeed, two people adding links at the same moment can no long"
7980
+ },
7981
+ {
7982
+ "id": "1004360",
7983
+ "text": "The last leftover of the old dev boxes is gone from the database: four unused columns on the builders table are removed. Nothing users see changes."
7984
+ },
7985
+ {
7986
+ "id": "1004300",
7987
+ "text": "You can now open any recent Bongos version from the deploy page to see the hall as it was, on a copy of the data, with a banner on every page."
7988
+ },
7989
+ {
7990
+ "id": "1004374",
7991
+ "text": "Fixes to the overnight builder now actually reach it — until now it kept running whatever code it started with, so four days of fixes did nothing."
7992
+ },
7993
+ {
7994
+ "id": "1004385",
7995
+ "text": "Artists can now check off each line in the tweak editor with a green check, restore a line to the page's original words, and press Shift+Enter to check a line and jump to the next."
7996
+ },
7997
+ {
7998
+ "id": "1004226",
7999
+ "text": "The ten Full Ideas the board has passed can now become goals. They were all written on the older question form, which never asked \"what does success look like\", and a goal needs that as its finish line, so every one was being"
8000
+ },
8001
+ {
8002
+ "id": "1004187",
8003
+ "text": "Typing fast in the idea composer no longer makes the goal picker flicker back to suggestions for words you already replaced: a slow answer that arrives late is now ignored, the same way the landing line already ignored one."
8004
+ },
8005
+ {
8006
+ "id": "1004007",
8007
+ "text": "Automatic agents written as files can now say which kinds of change should wake them up, and where to send what they find. Before, that part of the file was silently thrown away, so an agent meant to look only at, say, documen"
8008
+ },
8009
+ {
8010
+ "id": "1004188",
8011
+ "text": "The core upgrade no longer refuses to run just because the new version adds a brand-new skill folder. It used to mistake that folder for someone's unsaved work and stop."
8012
+ },
8013
+ {
8014
+ "id": "1001691",
8015
+ "text": "Adds the ADR 0101 Part 1 advisory relevance/anti-hack judge to goal-task creation: reuses the ADR 0024 grader harness via cascade-dispatch, flags likely scope-gaming to a Metic review queue, and can raise claim-time friction o"
8016
+ },
8017
+ {
8018
+ "id": "1004292",
8019
+ "text": "Grade attempts now keep each worker's verdict per round, so the correlation audit can see objections from rounds a re-grade overwrote."
8020
+ },
8021
+ {
8022
+ "id": "1004116",
8023
+ "text": "Sixteen of the core's web request handlers (sign-in, the public status data, software updates, the builder roster and others) could not be worked on through any goal, because they belonged to no area of the codebase. They were"
8024
+ },
8025
+ {
8026
+ "id": "1004352",
8027
+ "text": "The setup wizard can now put a new project's app on the owner's own Render account. The owner pastes their Render key, the wizard checks it with Render straight away, and they choose which Render workspace to use, whether to c"
8028
+ },
8029
+ {
8030
+ "id": "1004376",
8031
+ "text": "The overnight builder was refusing to work because it thought it had already used up today's allowance — on work it did days ago."
8032
+ },
8033
+ {
8034
+ "id": "1004057",
8035
+ "text": "Each of the eleven working areas now carries its number as a proper piece of data, instead of the number only existing inside the goal's name. Before, anything that wanted to sort or label goals by area had to read it out of t"
8036
+ },
8037
+ {
8038
+ "id": "1003457",
8039
+ "text": "Added a live-DB proof that resolving a blocker through the hall's own HTTP routes actually auto-promotes the tasks it was blocking — closing the last gap in blocker test coverage, which had only ever exercised the trigger via"
8040
+ },
8041
+ {
8042
+ "id": "1004350",
8043
+ "text": "The Render setup now puts the project owner's own app on their Render account, built from their repository the way they say, with a database only if they ask for one. The Builders' hall stays hosted by us. Linking an existing"
8044
+ },
8045
+ {
8046
+ "id": "1004368",
8047
+ "text": "CI checks were failing on every change because of a newly published security flaw in a networking library; updating it made the checks pass again so approved work can land."
8048
+ }
7886
8049
  ]
7887
8050
  }
@@ -202,9 +202,19 @@ function parseAgentFile(text, { name } = {}) {
202
202
  candidate.trigger_type = candidate.trigger;
203
203
  delete candidate.trigger;
204
204
  }
205
- if (candidate.event && !candidate.trigger_spec) {
206
- candidate.trigger_spec = { event: candidate.event };
207
- delete candidate.event;
205
+ // Every trigger PARAMETER, not just `event` (task 1004007). The frontmatter
206
+ // is flat on purpose (no YAML parser in a deploy script), so the file spells
207
+ // trigger_spec's keys at the top level and they are folded in here. Only
208
+ // `event` used to be, so a file's `touches:` filter reached the validator as
209
+ // a stray top-level key and was dropped, and every file-authored event agent
210
+ // fired on EVERY ship whatever its author wrote. `touches_globs` is the
211
+ // router's second spelling of the same filter. Values are moved VERBATIM: a
212
+ // malformed one is the validator's to refuse, not this parser's to coerce.
213
+ const SPEC_KEYS = ['event', 'touches', 'touches_globs', 'sink', 'cadence'];
214
+ const present = SPEC_KEYS.filter((k) => Object.prototype.hasOwnProperty.call(candidate, k));
215
+ if (present.length > 0 && candidate.trigger_spec === undefined) {
216
+ candidate.trigger_spec = {};
217
+ for (const k of present) { candidate.trigger_spec[k] = candidate[k]; delete candidate[k]; }
208
218
  }
209
219
  return candidate;
210
220
  }
@@ -611,7 +611,9 @@ function writeHeartbeat(now = Date.now()) {
611
611
  try {
612
612
  const p = heartbeatPath();
613
613
  fs.mkdirSync(path.dirname(p), { recursive: true });
614
- fs.writeFileSync(p, JSON.stringify({ pid: process.pid, at: now }));
614
+ // `head` is what this process is RUNNING, not what is on disk (task 1004374).
615
+ // Alive and alive-on-stale-code look identical from a check-in age alone.
616
+ fs.writeFileSync(p, JSON.stringify({ pid: process.pid, at: now, head: LOADED_HEAD }));
615
617
  return true;
616
618
  } catch (_) { return false; }
617
619
  }
@@ -735,6 +737,67 @@ async function pollSignals(deps = {}) {
735
737
  return cadence.signalFingerprint({ head, fenceUpdatedAt, claimableCount });
736
738
  }
737
739
 
740
+ // UPGRADE_EXIT_CODE — the one exit `--forever` is allowed to make (task 1004374).
741
+ //
742
+ // THE PROBLEM. autobongos-service.cmd refreshes the checkout with git fetch +
743
+ // reset --hard origin/main BEFORE launching node, and only then. `--forever`
744
+ // never returns, by design (task 1003904): a stopped daemon on an always-on
745
+ // machine is indistinguishable from a working one. Together those mean a
746
+ // process never re-reads its code, so a shipped fix reaches the disk and never
747
+ // reaches the runner, and nothing restarts it — not stopping IS the design. On
748
+ // the host this ran week-old code for four days across three landed fixes,
749
+ // while the heartbeat said alive and the hall showed a healthy runner.
750
+ //
751
+ // WHY AN EXIT IS SAFE HERE, when task 1003904 forbids exiting. That rule exists
752
+ // so a machine nobody watches cannot look idle while it is dead. This exit is
753
+ // the opposite: it is deliberate, it is logged as `upgrade_exit`, and the
754
+ // WRAPPER loops on this specific code — re-running the fetch + reset and
755
+ // relaunching — so the runner is not depending on Task Scheduler noticing.
756
+ // Every other exit still falls through to the scheduler exactly as before.
757
+ const UPGRADE_EXIT_CODE = 75;
758
+
759
+ // MIN_UPTIME_BEFORE_UPGRADE_MS — the guard against a restart storm.
760
+ //
761
+ // The wrapper's `git reset --hard origin/main` is BEST EFFORT: a fetch that
762
+ // fails leaves the checkout behind on purpose, because being a commit behind
763
+ // beats not running at all. Without this guard that becomes a spin — the runner
764
+ // starts behind, sees drift on its first loop, exits, and the wrapper relaunches
765
+ // it into the same state as fast as the machine allows. One minute of required
766
+ // uptime turns a failing fetch into a slow retry instead.
767
+ const MIN_UPTIME_BEFORE_UPGRADE_MS = 60_000;
768
+
769
+ // loadedHead — the commit this PROCESS is running, read once. Not re-read: the
770
+ // point is to compare what is executing against what has landed, and re-reading
771
+ // would just track the disk, which the wrapper already moves.
772
+ let LOADED_HEAD = null;
773
+ async function loadedHead(deps = {}) {
774
+ if (LOADED_HEAD !== null) return LOADED_HEAD;
775
+ const runCmd = deps.run || run;
776
+ try {
777
+ const r = await runCmd('git', ['rev-parse', 'HEAD'], { cwd: REPO_ROOT });
778
+ LOADED_HEAD = r && r.ok ? String(r.stdout || '').trim() || null : null;
779
+ } catch (_) { LOADED_HEAD = null; }
780
+ return LOADED_HEAD;
781
+ }
782
+
783
+ // codeDrifted — has main moved past the code this process loaded?
784
+ //
785
+ // Fails CLOSED in the "keep running" direction: an unreadable local head or an
786
+ // unreachable origin returns false, so a network blip never cycles the runner.
787
+ // The cost of a missed upgrade is one more loop; the cost of a false positive is
788
+ // a restart storm.
789
+ async function codeDrifted(deps = {}) {
790
+ // Injectable like every other collaborator here: the module-level cache is
791
+ // right for production (read the running commit ONCE) and would otherwise make
792
+ // the comparison untestable in isolation.
793
+ const mine = deps.loadedHead ? await deps.loadedHead(deps) : await loadedHead(deps);
794
+ if (!mine) return null;
795
+ let tip = null;
796
+ try { tip = await runFetchHead(deps); } catch (_) { return null; }
797
+ if (!tip) return null;
798
+ return tip !== mine ? { from: mine.slice(0, 8), to: String(tip).slice(0, 8) } : false;
799
+ }
800
+
738
801
  // The head of origin/main, or null. `git ls-remote` rather than `fetch`: it moves
739
802
  // no refs and writes nothing, so polling it every few minutes cannot interfere
740
803
  // with a worker building in a tree beside it.
@@ -849,6 +912,21 @@ async function forever(opts, deps = {}) {
849
912
  if (seen.class !== 'progress') break;
850
913
  }
851
914
 
915
+ // AFTER the burst, never during one: a worker mid-task must not be abandoned
916
+ // to pick up a fix. Before the wait, because waiting is where the runner
917
+ // spends nearly all of its time and an upgrade should not sit behind it.
918
+ const upMs = (deps.now ? deps.now() : Date.now()) - Date.parse(RUNNER_STARTED_AT);
919
+ const mayUpgrade = upMs >= MIN_UPTIME_BEFORE_UPGRADE_MS;
920
+ const drift = mayUpgrade
921
+ ? (deps.codeDrifted ? await deps.codeDrifted(deps) : await codeDrifted(deps))
922
+ : false;
923
+ if (drift) {
924
+ log({ event: 'upgrade_exit', from: drift.from, to: drift.to,
925
+ reason: `main moved from ${drift.from} to ${drift.to} — exiting so the wrapper can relaunch on the new code` });
926
+ await beat({ mode: state.mode, last_event: 'upgrade_exit' });
927
+ return UPGRADE_EXIT_CODE;
928
+ }
929
+
852
930
  const { waited, jumped } = await wait(state.waitS, { ...deps, beat }, opts.cadence);
853
931
  if (jumped) log({ event: 'cadence', mode: state.mode, jumped: true, waited_s: waited, why: 'a new signal arrived — main moved, the fence changed, or the queue did' });
854
932
  }
@@ -886,7 +964,7 @@ if (require.main === module) {
886
964
 
887
965
  module.exports = {
888
966
  readArgv, pickTask, iteration, record, logPath, spawnWorker, workerEnv, WORKER_ENV_ALLOW, resolveWorkerBin,
889
- worktreeName, readFence, reconcileLeftoverClaims, pollSignals, waitOrJump, forever, CLAIM_PREFIX, countWorkedSince,
967
+ worktreeName, readFence, reconcileLeftoverClaims, pollSignals, waitOrJump, forever, CLAIM_PREFIX, countWorkedSince, codeDrifted, loadedHead, UPGRADE_EXIT_CODE, MIN_UPTIME_BEFORE_UPGRADE_MS,
890
968
  heartbeatPath, writeHeartbeat, readHeartbeat, pidAlive, anotherRunnerIsAlive, HEARTBEAT_STALE_MS,
891
969
  isRunnerTree, RUNNER_TREE_RE, postHeartbeat, RUNNER_STARTED_AT,
892
970
  };
@@ -24,6 +24,14 @@ if not exist "%LOGDIR%" mkdir "%LOGDIR%"
24
24
 
25
25
  cd /d "%REPO%" || exit /b 1
26
26
 
27
+ REM THE UPGRADE LOOP (task 1004374). The runner exits 75 when main has moved
28
+ REM past the code it loaded; we refresh and relaunch. Without this --forever
29
+ REM never returns and so never picks up a fix: on the host this ran week-old
30
+ REM code for four days across three landed fixes while the heartbeat reported
31
+ REM it alive. Looping HERE rather than leaning on the scheduler matters,
32
+ REM because MultipleInstances IgnoreNew makes a restart a silent no-op while
33
+ REM the old process is still up.
34
+ :run
27
35
  echo. >> "%LOG%"
28
36
  echo ==== autobongos starting %DATE% %TIME% ==== >> "%LOG%"
29
37
 
@@ -34,5 +42,9 @@ git reset --quiet --hard origin/main >> "%LOG%" 2>&1
34
42
 
35
43
  node scripts\gds\autobongos-run.js --forever --json >> "%LOG%" 2>&1
36
44
  set EXITCODE=%ERRORLEVEL%
45
+ if "%EXITCODE%"=="75" (
46
+ echo ==== autobongos relaunching for an upgrade at %DATE% %TIME% ==== >> "%LOG%"
47
+ goto run
48
+ )
37
49
  echo ==== autobongos exited %EXITCODE% at %DATE% %TIME% ==== >> "%LOG%"
38
50
  exit /b %EXITCODE%
@@ -368,6 +368,11 @@ function registryRowFor(line, registryById) {
368
368
 
369
369
  const rowKey = (e) => `${e.id}|${e.file}|${e.line}`;
370
370
 
371
+ // The {{token}} brand holes in a line, sorted (a hole may move within the line).
372
+ function brandHoles(text) {
373
+ return (String(text == null ? '' : text).match(/\{\{[A-Za-z][A-Za-z0-9_]*\}\}/g) || []).sort();
374
+ }
375
+
371
376
  // One batch line -> the single-proposal patch shape (proposals.js), resolved
372
377
  // against the registry row its string_id names. `texts` carries a row's text as
373
378
  // earlier lines of this batch left it. Pure.
@@ -381,6 +386,15 @@ function patchForBatchLine(line, registryById, texts = new Map()) {
381
386
  if (want !== got) {
382
387
  return { ok: false, code: 'placeholder_mismatch', detail: { expected: want, got, what_this_means: 'The rewrite does not keep the values the page drops into this line.' } };
383
388
  }
389
+ // A {{token}} is a brand hole the server fills at serve time (ADR 0233's hole
390
+ // rule, for the instance's names; task 1004381). It is written back VERBATIM,
391
+ // so the same tokens must survive the rewrite: a dropped one would bake a
392
+ // brand value (or nothing) into brand-free source.
393
+ const wantHoles = brandHoles(line.before);
394
+ const gotHoles = brandHoles(line.after);
395
+ if (wantHoles.join('|') !== gotHoles.join('|')) {
396
+ return { ok: false, code: 'placeholder_mismatch', detail: { expected: wantHoles, got: gotHoles, what_this_means: 'The rewrite drops or adds a name the page fills in from the instance\'s branding ({{productName}} and the like).' } };
397
+ }
384
398
  const current = texts.has(rowKey(entry)) ? texts.get(rowKey(entry)) : entry.text;
385
399
  let proposed;
386
400
  if (current === line.before) proposed = line.after;
@@ -47,8 +47,7 @@ const DEV_BOX_EXEMPT = new Map([
47
47
  ['limitations/', 'historical record: shipped-version scope archives'],
48
48
  ['docs/module-api-changelog.md', 'history ledger of doorway releases; entries are appended, never rewritten'],
49
49
  // Applied migrations: they created the box objects and, last, dropped them. Named one
50
- // by one so a NEW migration is still checked. The column drop of task 1004360 joins
51
- // this list when it lands.
50
+ // by one so a NEW migration is still checked.
52
51
  ['migrations/067_builder_boxes.sql', 'applied migration (created the box tables)'],
53
52
  ['migrations/069_builder_ssh_keys.sql', 'applied migration'],
54
53
  ['migrations/070_box_intents.sql', 'applied migration'],
@@ -66,6 +65,7 @@ const DEV_BOX_EXEMPT = new Map([
66
65
  ['migrations/core_238_box_unattended_cap.sql', 'applied migration'],
67
66
  ['migrations/core_248_module_enablement_overrides.sql', 'applied migration (names the key in an example)'],
68
67
  ['migrations/core_258_drop_dev_box_tables.sql', 'the migration that drops the box tables and session source by name (task 1003895)'],
68
+ ['migrations/core_262_drop_builder_box_blocked.sql', 'the migration that drops the box_blocked columns by name (task 1004360)'],
69
69
  ['modules/government/migrations/governance_002_seed_and_backfill.sql', 'applied migration (seeded the box.* keys)'],
70
70
  ['modules/government/migrations/governance_007_delegate_archon_ops_to_metic.sql', 'applied migration'],
71
71
  ['modules/government/migrations/government_019_retire_box_permissions.sql', 'the migration that revokes the box.* keys by name (task 1003894)'],
@@ -78,6 +78,7 @@ const DEV_BOX_EXEMPT = new Map([
78
78
  ['tests/auth_page_gate.mjs', 'proves a leftover source=box session is refused a page'],
79
79
  ['tests/terms_acceptance.mjs', 'proves a leftover source=box session cannot mint an acceptance'],
80
80
  ['tests/government_seed.mjs', 'proves government_019 revoked the box.* keys from every rank'],
81
+ ['tests/core_262_drop_box_blocked_db.mjs', 'proves core_262 leaves builders with no box_blocked column (task 1004360)'],
81
82
  ['tests/core_258_box_session_claims_db.mjs', 'proves core_258 applies over a claim created from a box session (task 1004362)'],
82
83
  ['tests/government_protected_surfaces.mjs', 'RETIRED_SINCE_R101: retired protected paths may leave the registry only once gone'],
83
84
  ['scripts/gds/dev-box-guard.js', 'this guard (it defines the rules)'],
@@ -754,6 +754,15 @@ const KERNEL_IMPORT_ALLOWLIST = new Set([
754
754
  // them; they never flip the kernel→domain leak.
755
755
  'src/bongos/llm-cache.js',
756
756
  'src/bongos/llm-pricing.js',
757
+ // The cheapest-model-first cascade router (task 1001691 / ADR 0101 Part 1):
758
+ // a pure ladder + an INJECTED runner (never a domain call itself — its own
759
+ // header says so: "nothing calls it but its mock unit test" until this
760
+ // task), holding no domain state of its own. Its first real caller is the
761
+ // lifecycle module's relevance judge, which reaches it through the doorway
762
+ // exactly as grading reaches llm-cache — same category as the pg pool /
763
+ // path-match / llm-cache above, not a domain module core would otherwise
764
+ // depend on.
765
+ 'src/bongos/cascade-dispatch.js',
757
766
  ]);
758
767
 
759
768
  // Pure: for relative require `spec` from kernel file `fromRel` (repo-relative),