@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
@@ -50,7 +50,7 @@ const crypto = require('node:crypto');
50
50
 
51
51
  const { CONFIG, MANIFEST_UA, softFailResult, MANIFEST_VERIFY_INTERVAL_MS, MANIFEST_VERIFY_TRIES, MAX_INTENT_ATTEMPTS, REPO_ROOT, coreVersionSafe, hasFlag, loadDeps, oauthSecret, provisionerBotEmail } = require('./provision-config.js');
52
52
  const { accountPreflight, appGroupExpr, appUser, alreadyScaffolded, buildCorePinRefreshCommit, coreCheckoutHasGit, dbCreateCmd, dbName, dbRoleCmd, dbRoleCmdShown, dbRoleGrantsCmd, ensureInstanceUserCmd, generateDbPassword, grantInstanceRepoReadCmd, instanceDbRole, instanceStateDir, instanceUser, deployKeyPath, deployKeyTitle, ensurePrivateRepoAccess, installedCorePackDir, installedCoreTarball, instanceInitSpec, instanceRepoRemote, migrateCmd, onboardMode, ownerLoginOf, parseTargetRef, planCorePinRefresh, refreshStandaloneCorePin, resolveOwnerGithubToken, resolveVendorableCoreTarball, safeVersionLabel, scaffoldStandaloneRepo, seedFirstVersionCmd, standaloneInstallCmd, standaloneMigrateCmd, standalonePullCmd, standaloneRegenDocsCmd, standaloneRoot } = require('./provision-repo.js');
53
- const { backupScriptPath, backupService, backupServicePath, backupTimer, backupTimerPath, backupUnitName, instanceManifestCmd, originEnvVarsFor, serviceUnit, serviceUnitPath, settingsConsumed, settingsEnvVarsFor, upsertEnvVars, webEnvBody, webEnvPath } = require('./provision-units.js');
53
+ const { backupScriptPath, backupService, backupServicePath, backupTimer, backupTimerPath, backupUnitName, instanceManifestCmd, originEnvVarsFor, serviceUnit, serviceUnitPath, settingsConsumed, settingsEnvVarsFor, renamedEnvCarry, upsertEnvVars, webEnvBody, webEnvPath } = require('./provision-units.js');
54
54
  const { caddyBlock, caddySnippetPath, checkCaddyWiring, checkDnsTokenScope, classifyZoneScope, dnsReleaseEnv, dnsUpsertEnv, federateInstance, federationHubOrigin, healthzCmd, identityCmd, identityVerdict, ingressPlaneGap, ingressPreflight, shouldFederate, zoneForDomain } = require('./provision-net.js');
55
55
  const { resolveProcessManager } = require('./provision-process.js');
56
56
  const { capacityPreflight, statusCapacityLine } = require('./provision-capacity.js');
@@ -374,10 +374,10 @@ async function provisionInstance(inst, deps) {
374
374
  // throw: the greenfield flow now has no manual clone step. Idempotent + dry-run-safe.
375
375
  if (inst.hosting_shape === 'cloud-host') {
376
376
  await scaffoldStandaloneRepo(inst, deps);
377
- // A PRIVATE target_ref gets its own read-only deploy key BEFORE step 2's
378
- // `git pull --ff-only origin main` — the pull that failed unauthenticated
379
- // on every private standup (walk finding N4; ADR 0176).
377
+ // A PRIVATE target_ref gets its own read-only deploy key BEFORE step 2's `git pull
378
+ // --ff-only origin main`, which failed unauthenticated on every private standup (N4; ADR 0176).
380
379
  await ensurePrivateRepoAccess(inst, deps);
380
+ await require('./provision-pin-key.js').ensurePinWriteKey(inst, deps); // the hub's WRITE key for later pin pushes, public repos too (task 1004291, ADR 0350)
381
381
  }
382
382
  }
383
383
  if (!inst.db_name) { patch.db_name = inst.slug; inst = { ...inst, db_name: inst.slug }; }
@@ -904,7 +904,7 @@ async function settingsApplyInstance(inst, deps) {
904
904
  // exists to avoid). An unreadable or empty file is refused, never "patched":
905
905
  // writing two lines over a missing PORT/PGDATABASE would take the instance down.
906
906
  const read = boxExec(`${sudoP}cat ${webEnvPath(inst)}`, { allowFail: true });
907
- const current = String((read && read.stdout) || '');
907
+ const current = String((read && read.stdout) || ''), carry = renamedEnvCarry(current); // task 1004395: only its NAMES are ever logged
908
908
  if (!apply) {
909
909
  log(` [config] would patch ${Object.keys(vars).join(', ')} in ${webEnvPath(inst)} in place (every other line untouched)`);
910
910
  pmFor(deps).restartService(inst, { exec: boxExec, sudoP });
@@ -916,8 +916,8 @@ async function settingsApplyInstance(inst, deps) {
916
916
  return { ok: false, error: `cannot read ${webEnvPath(inst)} — nothing to patch (was this instance provisioned by the runner?)` };
917
917
  }
918
918
  try {
919
- log(' [config] patch the settings lines of web.env in place (creds + origins untouched)');
920
- boxWriteFile(webEnvPath(inst), upsertEnvVars(current, settingsEnvVarsFor(inst, current)), { mode: 0o600, sudo: true });
919
+ log(` [config] patch the settings lines of web.env in place (creds + origins untouched)${carry.carried.length ? `; carry the renamed env forward: ${carry.carried.join(', ')} (value copied on the box, never shown)` : ''}`);
920
+ boxWriteFile(webEnvPath(inst), upsertEnvVars(current, { ...settingsEnvVarsFor(inst, current), ...carry.vars }), { mode: 0o600, sudo: true });
921
921
  log(' [service] restart so the memoized branding pack re-reads them');
922
922
  pmFor(deps).restartService(inst, { exec: boxExec, sudoP });
923
923
  } catch (e) {
@@ -960,7 +960,7 @@ async function settingsApplyInstance(inst, deps) {
960
960
  if (provisioning.clearErrorNote) await provisioning.clearErrorNote(db, inst.id, 'settings-apply').catch(() => {});
961
961
  await provisioning.recordEvent(db, {
962
962
  instanceId: inst.id, ownerBuilderId: inst.owner_builder_id, event: 'settings-applied',
963
- detail: `settings pushed (${shown}) — web.env patched in place; ${outcome}`,
963
+ detail: `settings pushed (${shown}) — web.env patched in place${carry.carried.length ? `; carried ${carry.carried.join(', ')}` : ''}; ${outcome}`,
964
964
  actor: 'runner',
965
965
  }).catch(() => {});
966
966
  log(healthy && consumed !== false
@@ -1285,7 +1285,7 @@ async function cmdRunIntents(deps) {
1285
1285
  // Dispatch EXPLICITLY per action — an unrecognized action must surface as an
1286
1286
  // error, never fall through to the destructive teardown leg (task 1002692).
1287
1287
  let result;
1288
- if (intent.action === 'provision') result = await provisionInstance(inst, deps);
1288
+ if (intent.action === 'provision') result = await require('./provision-render.js').afterProvision(inst, deps, intent, await provisionInstance(inst, deps)); // then the app on Render, when the wizard lent a key (task 1004352)
1289
1289
  else if (intent.action === 'teardown') result = await teardownInstance(inst, deps);
1290
1290
  else if (intent.action === 'domain-attach') result = await domainAttachInstance(inst, deps);
1291
1291
  else if (intent.action === 'settings-apply') result = await settingsApplyInstance(inst, deps);
@@ -1489,7 +1489,7 @@ module.exports = {
1489
1489
  makeExec, makeWriteFile, allocatePort, boundLocalPorts, provisionInstance, teardownInstance,
1490
1490
  domainAttachInstance, releasableDomains, cmdRunIntents, cmdReconcile, cmdStatus,
1491
1491
  // live settings push (task 1003140) + the in-place env patch + read-back it rides on
1492
- settingsApplyInstance, upsertEnvVars, settingsEnvVarsFor, settingsConsumed, instanceManifestCmd,
1492
+ settingsApplyInstance, upsertEnvVars, renamedEnvCarry, settingsEnvVarsFor, settingsConsumed, instanceManifestCmd,
1493
1493
  // restart an unreachable ACTIVE instance (task 1003238) — the settings leg minus the env patch
1494
1494
  restartInstance,
1495
1495
  // GitHub App Manifest flow (task 2080, ADR 0133)
@@ -72,6 +72,7 @@ const PUBLISH_ALLOWLIST = [
72
72
  'modules/provisioning/', // instance-hosting control plane — CORE (its do-api toolchain is 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.
73
73
  'modules/agents/', // the declarative agent registry + spawn seam (goal 1000038) — CORE: its own manifest declares maintenance.status 'core-maintained', and it consumes the core `grade` port, so a released core without it cannot run the MAS machinery the methodology describes. default:false — shipped is not enabled (ADR 0083), exactly like provisioning + platform-identity below. Omitted at module-creation time (tasks 1002485/1002486): the omission was invisible until tests/agents_spawn.mjs became the first PUBLISHABLE file to import it and the R89 boundary guard caught it (task 1002489).
74
74
  'modules/npm-release/', // the deploy page's "where is the work" + version history (task 1002622). default:false — on only in the hall that publishes the core. MUST be here: modules/hall-ui/ ships as one subtree, so a released core without this carries deploy.js's loader and the #nr-work mounts with no routes behind them — the copy-desk asymmetry above.
75
+ 'modules/render-deploy/', // the deploy page of a project whose app deploys through Render (task 1004270). default:false. MUST be here for the npm-release reason: modules/hall-ui/ ships as one subtree, so a released core without it carries deploy.js's loader for #rd-panel with no routes behind it.
75
76
  '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.
76
77
  // --- neutral (customer-0) config starters + their schemas (the filled-in *.json twins are denied) ---
77
78
  'config/branding.neutral.json',
@@ -137,6 +137,25 @@ class RenderApi extends JsonHttpApi {
137
137
  return this._req('POST', `/services/${encodeURIComponent(serviceId)}/deploys`, {}, { dryRunResult: { id: null } });
138
138
  }
139
139
 
140
+ // A service's deploys, newest first, as the bare deploy objects (Render wraps each in
141
+ // `{ deploy, cursor }`; both shapes are read). The list carries no owner, so the
142
+ // service is read through the guard first (the deploy hall's history, task 1004270).
143
+ async listDeploys(serviceId, { limit = 30 } = {}) {
144
+ await this.getService(serviceId);
145
+ const n = Math.max(1, Math.min(100, Number.parseInt(limit, 10) || 30));
146
+ const json = await this._req('GET', `/services/${encodeURIComponent(serviceId)}/deploys?limit=${n}`, null, { dryRunResult: { items: [] } });
147
+ const items = Array.isArray(json) ? json : (json && json.items) || [];
148
+ return items.map((e) => (e && e.deploy) || e).filter((d) => d && typeof d.id === 'string');
149
+ }
150
+
151
+ // Roll the service back to one of its earlier deploys. The answer carries no owner, so
152
+ // the service is read through the guard first. This is a write on the owner's account;
153
+ // the caller (the deploy hall) asks the owner twice before it reaches here.
154
+ async rollbackTo(serviceId, deployId) {
155
+ await this.getService(serviceId);
156
+ return this._req('POST', `/services/${encodeURIComponent(serviceId)}/rollback`, { deployId }, { dryRunResult: { id: null } });
157
+ }
158
+
140
159
  // ---- Postgres --------------------------------------------------------------
141
160
 
142
161
  async createPostgres(payload) {
@@ -1,23 +1,27 @@
1
1
  // scripts/gds/render-payload.js — WHAT the Render standup leg creates, kept apart from
2
- // HOW it creates it (scripts/gds/provision-render.js) (task 1004183).
2
+ // HOW it creates it (scripts/gds/provision-render.js) (task 1004183; retargeted by
3
+ // task 1004350).
3
4
  //
4
- // Pure functions, no I/O. They are separate so the thing being stood up can change
5
- // without touching the guard or the key plumbing: this file builds the Bongos instance
6
- // the Render pilot proved (task 1004142, docs/recipes/render-pilot.md §4 and its
7
- // "Corrections" box), and task 1004350 retargets it at the project's own app, per
8
- // ADR 0345 (we host the hall; the owner's app is what goes to their Render account).
5
+ // Pure functions, no I/O. What goes to the owner's Render account is their APP, not the
6
+ // Bongos hall: we host every project's hall, and the app deploys where its owner
7
+ // chooses (ADR 0345 D1, D3). So the service is built from the project's own repository
8
+ // and started the way that repository says — the owner's runtime and commands, or the
9
+ // repository's own Dockerfile — never the Bongos core.
9
10
  //
10
- // The plans are per-instance settings (ADR 0327 §5), never constants. The defaults are
11
- // what the pilot measured as enough for a new instance at idle: a `starter` web service
12
- // (48-62 MiB of 512) and a paid `basic_256mb` Postgres, because Render's free Postgres
13
- // has no backups of any kind.
11
+ // A database is optional (some apps need none). When there is one, its internal
12
+ // connection string is handed to the app as DATABASE_URL, the one name every mainstream
13
+ // framework reads. That is the only env var the platform sets: every other variable is
14
+ // the owner's to set on Render, and Bongos keeps their NAMES only (task 1004062's
15
+ // manifest) — never a value.
16
+ //
17
+ // The plans are per-project settings (ADR 0327 §5), never constants. The defaults are
18
+ // what the Render pilot (task 1004142) found enough at idle: a `starter` web service and
19
+ // a paid `basic_256mb` Postgres, because Render's free Postgres has no backups.
14
20
 
15
21
  'use strict';
16
22
 
17
- const crypto = require('node:crypto');
18
-
19
23
  // The vocabulary lives with the web tier's validation, so the two can never disagree.
20
- const { WEB_PLANS, DB_PLANS, REGIONS } = require('../../modules/provisioning/render-standup.js');
24
+ const { WEB_PLANS, DB_PLANS, REGIONS, RUNTIMES } = require('../../modules/provisioning/render-standup.js');
21
25
  const DEFAULTS = Object.freeze({ webPlan: 'starter', dbPlan: 'basic_256mb', region: 'virginia' });
22
26
 
23
27
  // A Render resource name from the project's slug. Render allows letters, digits and
@@ -27,7 +31,7 @@ function resourceName(inst, suffix) {
27
31
  return suffix ? `${base}-${suffix}` : base;
28
32
  }
29
33
 
30
- // The plan and region an instance asked for, with the defaults filled in. A value outside
34
+ // The plan and region a project asked for, with the defaults filled in. A value outside
31
35
  // the vocabulary is refused rather than passed through for Render to reject.
32
36
  function plansFor(appHost) {
33
37
  const h = appHost || {};
@@ -42,35 +46,29 @@ function plansFor(appHost) {
42
46
  return out;
43
47
  }
44
48
 
49
+ // How the app is built and started, as the owner said. `docker` builds the repository's
50
+ // own Dockerfile and needs no commands; every other runtime needs a start command, and a
51
+ // build command if the app has a build step.
52
+ function buildFor(appHost) {
53
+ const h = appHost || {};
54
+ if (!RUNTIMES.includes(h.runtime)) throw new Error(`unknown Render runtime '${h.runtime || 'none'}'`);
55
+ if (h.runtime === 'docker') return { runtime: 'docker' };
56
+ if (!h.startCommand) throw new Error('the app needs a start command');
57
+ return { runtime: h.runtime, buildCommand: h.buildCommand || null, startCommand: h.startCommand };
58
+ }
59
+
45
60
  // POST /postgres. `plan` is never 'free' — see the header.
46
61
  function renderPostgresPayload(inst, { ownerId, dbPlan, region }) {
47
62
  return { name: resourceName(inst, 'db'), ownerId, plan: dbPlan, region, version: '16' };
48
63
  }
49
64
 
50
- // POST /services, for a Bongos instance from its repository. `dbUrl` is the database's
51
- // internal connection string; `secretKey` is a fresh BUILDER_SECRET_KEY. Both are VALUES
52
- // sent to Render and held there — the platform keeps neither.
53
- //
54
- // What each non-obvious line is for, from the pilot's corrections:
55
- // - runtime + startCommand are explicit: a hybrid repo defaults to Python on Render, and
56
- // an adopted instance has no `npm start`;
57
- // - HOST=0.0.0.0: the server binds 127.0.0.1 otherwise and Render's router never reaches it;
58
- // - PGDATABASE = the URL: migrate.sh addresses the database as `psql -d "$PGDATABASE"`;
59
- // - BUILDER_SECRET_KEY: Render's disk is wiped each deploy, so the key-file fallback
60
- // would mint a new key every time and orphan every stored per-builder secret;
61
- // - no PORT: Render injects it.
62
- // Bongos reads DATABASE_URL first (src/bongos/pool.js), so none of the PG* family the box
63
- // writes is needed — the one simplification Render buys.
64
- function renderServicePayload(inst, { ownerId, repoUrl, branch, webPlan, region, dbUrl, secretKey, envPrefix, firstAdmin }) {
65
- const envVars = [
66
- { key: 'NODE_VERSION', value: '22' },
67
- { key: 'HOST', value: '0.0.0.0' },
68
- { key: 'DATABASE_URL', value: dbUrl },
69
- { key: 'PGDATABASE', value: dbUrl },
70
- { key: 'PGPOOL_MAX', value: '5' },
71
- { key: 'BUILDER_SECRET_KEY', value: secretKey },
72
- ];
73
- if (firstAdmin) envVars.push({ key: `${envPrefix}_FIRST_ADMIN`, value: firstAdmin });
65
+ // POST /services, for the owner's app from their repository. `dbUrl` is the database's
66
+ // internal connection string, or null when the app has no database. It is a VALUE sent
67
+ // to Render and held there — the platform keeps none.
68
+ function renderServicePayload(inst, { ownerId, repoUrl, branch, webPlan, region, build, dbUrl }) {
69
+ const envSpecificDetails = build.runtime === 'docker'
70
+ ? {}
71
+ : { ...(build.buildCommand ? { buildCommand: build.buildCommand } : {}), startCommand: build.startCommand };
74
72
  return {
75
73
  type: 'web_service',
76
74
  name: resourceName(inst),
@@ -78,33 +76,9 @@ function renderServicePayload(inst, { ownerId, repoUrl, branch, webPlan, region,
78
76
  repo: repoUrl,
79
77
  branch: branch || 'main',
80
78
  autoDeploy: 'yes',
81
- serviceDetails: {
82
- runtime: 'node',
83
- plan: webPlan,
84
- region,
85
- healthCheckPath: '/healthz',
86
- envSpecificDetails: {
87
- buildCommand: 'npm ci',
88
- startCommand: 'node node_modules/@bongos/core/src/platform-server.js',
89
- preDeployCommand: 'bash node_modules/@bongos/core/scripts/migrate.sh',
90
- },
91
- },
92
- envVars,
79
+ serviceDetails: { runtime: build.runtime, plan: webPlan, region, envSpecificDetails },
80
+ envVars: dbUrl ? [{ key: 'DATABASE_URL', value: dbUrl }] : [],
93
81
  };
94
82
  }
95
83
 
96
- // The env vars that can only be set once the service exists, because they name the URL
97
- // Render gave it. The four origins must all be that one host: a single-host instance has
98
- // no separate builders./status. subdomain, and without them the hall never loads.
99
- function renderAfterCreateEnv(serviceUrl, { envPrefix }) {
100
- const origin = String(serviceUrl || '').replace(/\/+$/, '');
101
- if (!/^https:\/\//.test(origin)) throw new Error(`Render gave the service no https URL (${serviceUrl || 'none'})`);
102
- return ['PUBLIC', 'BUILDERS', 'STATUS', 'OAUTH'].map((k) => ({ key: `${envPrefix}_${k}_ORIGIN`, value: origin }));
103
- }
104
-
105
- function freshSecretKey() { return crypto.randomBytes(32).toString('hex'); }
106
-
107
- module.exports = {
108
- WEB_PLANS, DB_PLANS, REGIONS, DEFAULTS,
109
- resourceName, plansFor, renderPostgresPayload, renderServicePayload, renderAfterCreateEnv, freshSecretKey,
110
- };
84
+ module.exports = { DEFAULTS, resourceName, plansFor, buildFor, renderPostgresPayload, renderServicePayload };
@@ -102,6 +102,13 @@ const INTEGRATION = new Set([
102
102
  // never was a dedicated consumer-layout-boot workflow, whatever older notes say.
103
103
  'consumer_layout_boot',
104
104
  'claim_gate_open_blocker', 'claim_gate_rebase',
105
+ // task 1003457: proves the blocker-resolve auto-promotion trigger (migration
106
+ // 009, tightened by core_223) fires on the HALL'S OWN write path — the real
107
+ // buildGdsRouter() HTTP routes, not a static SQL-text pin (blocker_resolve_guard.mjs,
108
+ // blocker_unlink.mjs) or a pure-predicate unit test (blocker_link.mjs, which never
109
+ // touches a database). Only a real transaction engine behind a real router proves
110
+ // that; self-skips without a reachable Postgres.
111
+ 'blocker_hall_live_proof',
105
112
  // task 1002709: core_221's re-keyed backstop index against a real Postgres.
106
113
  // The DB-free sibling pins the guard and the migration's text; this proves the
107
114
  // index actually refuses the commingling INSERT and still allows the parallel
@@ -122,6 +129,9 @@ const INTEGRATION = new Set([
122
129
  // dev box session. The claims foreign key that failed the live upgrade is a
123
130
  // schema fact only a transaction engine enforces. Always rolled back.
124
131
  'core_258_box_session_claims_db',
132
+ // task 1004360: core_262 drops the four builders columns migration 104 added, against a real
133
+ // Postgres, with the foreign key live, then re-runs it. Always rolled back.
134
+ 'core_262_drop_box_blocked_db',
125
135
  'discord_autojoin', 'discord_oauth',
126
136
  // task 1003000 (IC6): the three ideator credit streams against a real Postgres.
127
137
  // The three DB-free sibling suites stub the client and SIMULATE the
@@ -14,7 +14,8 @@ const { cliClient, printOnboardingNudge, rewardPolicy, cardFlagsFromMe } = requi
14
14
  const { closeoutPlan } = require('../../modules/lifecycle/ship-preflight');
15
15
  const { syncMemoryOnShip } = require('./ship-memory-sync.js');
16
16
  const { uploadSessionOnShip } = require('./ship-session-upload.js');
17
- const { gitOk, shellOk } = require('./ship-git.js');
17
+ const { execFileSync } = require('node:child_process');
18
+ const { gitOk } = require('./ship-git.js');
18
19
  const { fetchDeployedVersion } = require('./ship-deploy-target.js');
19
20
  const { landBailed, reconciledCardStatus } = require('./ship-land.js');
20
21
  const { autoMerge, publishHeld } = require('./ship-merge.js');
@@ -142,18 +143,23 @@ async function onboardingNudgeBestEffort() {
142
143
  // close-out, folded into the delivery process (NOT a separate skill). After a
143
144
  // ship lands, scratch often lingers uncommitted — untracked files the preflight
144
145
  // lets through, generated artifacts, post-commit edits — making the session
145
- // hard to archive cleanly. This reports what's left and stashes it under a
146
- // labeled, RECOVERABLE stash so the tree is left clean + archive-ready and
147
- // nothing is lost. STRICTLY fail-open: the ship has already succeeded by the
148
- // time this runs, so nothing here may throw, prompt, or change the exit status —
149
- // a messy tree (or a stash failure) must never surface as a ship failure. The
150
- // caller gates this on a SUCCESSFUL land so a grade-failed 'completed' tree the
151
- // builder still needs to fix is never stashed out from under them.
146
+ // hard to archive cleanly. This REPORTS what's left and leaves it in place.
147
+ // task 1004254: it used to `git stash push -u` the leftover, but the stash stack
148
+ // is repo-global across every worktree (task 1003344), so one builder's scratch
149
+ // landed where a parallel agent's pop could apply it to the wrong branch — and
150
+ // it swallowed review renders an owner still needed. It never touches the stash.
151
+ // STRICTLY fail-open: the ship has already succeeded by the time this runs, so
152
+ // nothing here may throw, prompt, or change the exit status — a messy tree must
153
+ // never surface as a ship failure. The caller gates this on a SUCCESSFUL land.
152
154
  function postShipCloseout(taskId) {
153
155
  try {
154
- const out = gitOk('git status --porcelain');
155
- if (out == null) return; // git unavailable / not a repo — nothing to reconcile
156
- const plan = closeoutPlan(out, taskId);
156
+ // Untrimmed: gitOk's trim() eats the first line's leading status column, so
157
+ // " M a.txt" was listed as ".txt" (task 1004254).
158
+ let out;
159
+ try {
160
+ out = execFileSync('git', ['status', '--porcelain'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
161
+ } catch (_) { return; } // git unavailable / not a repo — nothing to reconcile
162
+ const plan = closeoutPlan(out);
157
163
  if (plan.clean) {
158
164
  console.log('Close-out: working tree clean — session is archive-ready.');
159
165
  return;
@@ -162,15 +168,7 @@ function postShipCloseout(taskId) {
162
168
  console.log(`Close-out: ${plan.leftover.length} uncommitted/untracked path(s) remain after the ship:`);
163
169
  for (const f of plan.leftover.slice(0, 15)) console.log(` ${f}`);
164
170
  if (plan.leftover.length > 15) console.log(` … and ${plan.leftover.length - 15} more`);
165
- // --include-untracked so scratch the preflight waved through is captured too.
166
- // Label is `otb-postship-<numericId>` — no shell metacharacters.
167
- const stashed = shellOk(`git stash push --include-untracked -m "${plan.stashLabel}"`);
168
- if (stashed) {
169
- console.log(`Close-out: stashed the leftover as "${plan.stashLabel}" — tree is now clean + archive-ready.`);
170
- console.log(' Recover anytime: git stash list → git stash pop');
171
- } else {
172
- console.log('Close-out: could not stash the leftover; left it in place (the ship already succeeded — tidy up manually when convenient).');
173
- }
171
+ console.log(`Close-out: left in place (task ${taskId} already shipped) — commit, move or delete them before archiving this worktree.`);
174
172
  } catch (err) {
175
173
  // Fail-open — a post-ship close-out problem must never look like a ship failure.
176
174
  console.log(`Close-out: skipped (${err && err.message ? err.message : 'error'}).`);
@@ -404,6 +402,7 @@ module.exports = {
404
402
  finishHeld,
405
403
  mergeAndDeploy,
406
404
  onboardingNudgeBestEffort,
405
+ postShipCloseout,
407
406
  postShipSync,
408
407
  printCompletionCard,
409
408
  };
@@ -10,9 +10,12 @@
10
10
  # 5. Promote A manually (the route allows it; the gate is at claim time)
11
11
  # 6. Try to claim A — still 409 DEPS_NOT_SHIPPED
12
12
  # 7. Claim B, ship B (active → completed → confirmed → shipped)
13
- # 8. Verify A auto-promoted backlog→ready (or stays ready if step 5 ran)
13
+ # 8. Verify C — a second dependent of B that NOTHING ever promoted by hand —
14
+ # flipped backlog→ready. This is the only assertion that can fail if the
15
+ # auto-promotion trigger is broken (task 1004191): A was already 'ready'
16
+ # from the manual step, so A's status proves nothing about the trigger.
14
17
  # 9. Claim A — succeeds
15
- # 10. Release A, abandon A (B is shipped and cannot be abandoned)
18
+ # 10. Release A, abandon A and C (B is shipped and cannot be abandoned)
16
19
  #
17
20
  # Run from any environment with a valid CLI session (/builder-setup).
18
21
  # Requires curl.
@@ -44,7 +47,22 @@ A=$(curl -sS -X POST "$BASE/api/bongos/tasks" \
44
47
  A_ID=$(echo "$A" | python3 -c "import json,sys; print(json.load(sys.stdin)['task']['id'])")
45
48
  echo " A id=$A_ID"
46
49
 
47
- echo "=== 3. Wire A → depends_on B ==="
50
+ echo "=== 2b. Create task C (dependent, status=backlog, NEVER promoted by hand) ==="
51
+ # task 1004191 (finding 62 of the task 1000379 audit): A is promoted manually in
52
+ # step 4, so "A is ready after B ships" held whether or not the auto-promotion
53
+ # trigger fired. C exists to be the dependent only the trigger can flip.
54
+ C=$(curl -sS -X POST "$BASE/api/bongos/tasks" \
55
+ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
56
+ --data "{\"version_id\":\"GDS-V3\",\"title\":\"__smoke__ dep-test C $SUFFIX\",\"description\":\"smoke\",\"status\":\"backlog\",\"kind\":\"feature\",\"discipline\":\"engineering\",\"priority\":5,\"touches\":[\"smoke-dep-C-$SUFFIX/\"],\"source\":\"smoke\",\"source_ref\":\"smoke-dep-C-$SUFFIX\"}")
57
+ C_ID=$(echo "$C" | python3 -c "import json,sys; print(json.load(sys.stdin)['task']['id'])")
58
+ echo " C id=$C_ID"
59
+
60
+ echo "=== 3. Wire A → depends_on B, and C → depends_on B ==="
61
+ curl -sS -X POST "$BASE/api/bongos/tasks/$C_ID/dependencies" \
62
+ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
63
+ --data "{\"depends_on_task_id\": $B_ID}" >/dev/null
64
+ C_PRE=$(curl -sS -H "Authorization: Bearer $TOKEN" "$BASE/api/bongos/tasks/$C_ID" | python3 -c "import json,sys; print(json.load(sys.stdin)['task']['status'])")
65
+ [ "$C_PRE" = "backlog" ] && echo " PASS: C is backlog before B ships" || (echo " FAIL: C is '$C_PRE' before B ships — the auto-promotion check below would prove nothing"; exit 1)
48
66
  curl -sS -X POST "$BASE/api/bongos/tasks/$A_ID/dependencies" \
49
67
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
50
68
  --data "{\"depends_on_task_id\": $B_ID}" | python3 -m json.tool
@@ -69,15 +87,18 @@ curl -sS -X POST "$BASE/api/bongos/claims/by-task/$B_ID/resolve" \
69
87
  curl -sS -X POST "$BASE/api/bongos/tasks/$B_ID/confirm" -H "Authorization: Bearer $TOKEN" >/dev/null
70
88
  curl -sS -X POST "$BASE/api/bongos/tasks/$B_ID/ship" -H "Authorization: Bearer $TOKEN" >/dev/null
71
89
 
72
- echo "=== 7. Verify A auto-promoted (status should still be 'ready' from manual step 4, or auto-flipped from backlog) ==="
73
- A_STATUS=$(curl -sS -H "Authorization: Bearer $TOKEN" "$BASE/api/bongos/tasks/$A_ID" | python3 -c "import json,sys; print(json.load(sys.stdin)['task']['status'])")
74
- echo " A status: $A_STATUS"
75
- [ "$A_STATUS" = "ready" ] && echo " PASS" || (echo " FAIL: A not ready"; exit 1)
90
+ echo "=== 7. Verify the auto-promotion trigger fired: C backlog → ready ==="
91
+ # C was never promoted by hand, so only promote_dependents_on_ship can have made
92
+ # it 'ready'. (A is 'ready' from the manual step 4 regardless, so A is not asserted
93
+ # here — that is the assertion that used to pass with the trigger broken.)
94
+ C_STATUS=$(curl -sS -H "Authorization: Bearer $TOKEN" "$BASE/api/bongos/tasks/$C_ID" | python3 -c "import json,sys; print(json.load(sys.stdin)['task']['status'])")
95
+ echo " C status: $C_STATUS"
96
+ [ "$C_STATUS" = "ready" ] && echo " PASS: C auto-promoted" || (echo " FAIL: C is '$C_STATUS' — B shipped but the auto-promotion trigger did not flip its dependent"; exit 1)
76
97
 
77
98
  echo "=== 8. Claim A — must succeed now ==="
78
99
  node scripts/gds/claim.js "$A_ID" --worktree "smoke-$SUFFIX-A"
79
100
 
80
- echo "=== 9. Cleanup — abandon A; B is shipped and stays ==="
101
+ echo "=== 9. Cleanup — abandon A and C; B is shipped and stays ==="
81
102
  # task 1004026: this step used to ANNOUNCE "release A; mark both abandoned" and then
82
103
  # only release A. Neither task reached a terminal state, so every run leaked two
83
104
  # __smoke__ rows into the live tasks table — invisibly, because anyone grepping the
@@ -108,6 +129,14 @@ case "${A_CLEANUP_CODE:-000}" in
108
129
  000) echo " SKIP cleanup — POST /tasks/$A_ID/abandon got no response; task #$A_ID left in the ledger" ;;
109
130
  *) echo " SKIP cleanup — POST /tasks/$A_ID/abandon returned HTTP $A_CLEANUP_CODE; task #$A_ID left in the ledger" ;;
110
131
  esac
132
+ C_CLEANUP_CODE=$(curl -sS -o /dev/null -w '%{http_code}' -X POST \
133
+ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
134
+ --data '{"reason":"smoke cleanup (task 1004191) - auto-promotion witness; the throwaway row has served its purpose"}' \
135
+ "$BASE/api/bongos/tasks/$C_ID/abandon") || true
136
+ case "${C_CLEANUP_CODE:-000}" in
137
+ 2*) echo " PASS cleaned up throwaway task #$C_ID (abandoned)" ;;
138
+ *) echo " SKIP cleanup — POST /tasks/$C_ID/abandon returned HTTP ${C_CLEANUP_CODE:-000}; task #$C_ID left in the ledger" ;;
139
+ esac
111
140
  echo " NOTE task #$B_ID is SHIPPED and is deliberately left in the ledger — the abandon route refuses shipped work (cannot_abandon_shipped)."
112
141
 
113
142
  echo
@@ -167,6 +167,14 @@ function shortTitle(title, max = 52) {
167
167
  return t.length > max ? t.slice(0, max - 1) + '…' : t;
168
168
  }
169
169
 
170
+ // "area 6" for a working-area goal, else nothing (task 1004057). Read from the
171
+ // goal's working_area FIELD, never parsed from its title and never taken from
172
+ // sort_order or the id, which both look like it and are not.
173
+ function areaTag(g) {
174
+ const n = g && g.working_area;
175
+ return Number.isInteger(n) && n > 0 ? 'area ' + n : '';
176
+ }
177
+
170
178
  // "1 confirmed, 2 backlog" — non-shipped tasks grouped by status, ordered.
171
179
  function remainingSummary(remaining) {
172
180
  if (remaining.length === 0) return 'none — all shipped ✓';
@@ -283,7 +291,7 @@ function renderStatusWidget(data, criterion) {
283
291
  const rows = goals
284
292
  ? goals.map((g) => {
285
293
  const ach = g.status === 'achieved';
286
- const head = `<tr><td class="st-td" colspan="5" style="background:var(--color-background-secondary);font-weight:500;"><i class="ti ti-target" aria-hidden="true"></i> ${sesc(g.title)} <span class="st-sub">· ${ach ? '<span class="st-ok">achieved</span>' : sesc(g.status)}${g.criteria.length ? '' : ' · no criteria yet'}</span></td></tr>`;
294
+ const head = `<tr><td class="st-td" colspan="5" style="background:var(--color-background-secondary);font-weight:500;"><i class="ti ti-target" aria-hidden="true"></i> ${sesc(g.title)} <span class="st-sub">· ${areaTag(g) ? sesc(areaTag(g)) + ' · ' : ''}${ach ? '<span class="st-ok">achieved</span>' : sesc(g.status)}${g.criteria.length ? '' : ' · no criteria yet'}</span></td></tr>`;
287
295
  return head + g.criteria.map(critRow).join('');
288
296
  }).join('')
289
297
  : data.criteria.map(critRow).join('');
@@ -336,7 +344,7 @@ function renderStatusMarkdown(data, criterion) {
336
344
  const tableHeader = () => { lines.push('| | Criterion | Pos | Shipped | Remaining |'); lines.push('|---|---|---|---|---|'); };
337
345
  if (goals) {
338
346
  for (const g of goals) {
339
- lines.push(`**🎯 ${g.title}** — ${g.status === 'achieved' ? 'achieved ✓' : g.status}${g.criteria.length ? '' : ' · no criteria yet'}`);
347
+ lines.push(`**🎯 ${g.title}** — ${areaTag(g) ? areaTag(g) + ' · ' : ''}${g.status === 'achieved' ? 'achieved ✓' : g.status}${g.criteria.length ? '' : ' · no criteria yet'}`);
340
348
  if (g.criteria.length) { tableHeader(); for (const c of g.criteria) lines.push(critRow(c)); }
341
349
  lines.push('');
342
350
  }
@@ -383,7 +391,7 @@ function printAll(data) {
383
391
  };
384
392
  if (goals) {
385
393
  for (const g of goals) {
386
- console.log(` 🎯 ${g.title} [${g.status === 'achieved' ? 'achieved ✓' : g.status}]${g.criteria.length ? '' : ' (no criteria yet)'}`);
394
+ console.log(` 🎯 ${g.title} [${areaTag(g) ? areaTag(g) + ' · ' : ''}${g.status === 'achieved' ? 'achieved ✓' : g.status}]${g.criteria.length ? '' : ' (no criteria yet)'}`);
387
395
  for (const c of g.criteria) printCrit(c);
388
396
  if (g.criteria.length) console.log('');
389
397
  }
@@ -523,4 +531,4 @@ if (require.main === module) {
523
531
  });
524
532
  }
525
533
 
526
- module.exports = { parseArgs, resolveVersion, defaultedVersionNote, positionalRefNote, renderStatusWidget, renderStatusMarkdown };
534
+ module.exports = { parseArgs, resolveVersion, defaultedVersionNote, positionalRefNote, renderStatusWidget, renderStatusMarkdown, areaTag };
@@ -127,9 +127,9 @@ function restorePin(instanceDir, depString, fsImpl = fs) {
127
127
 
128
128
  // ---- steps (each injectable for tests) --------------------------------------
129
129
 
130
- // `ignore` is waived (it comes from — and is argued for in — claude-materialize.js materializeWillRewrite, task 1004122); every OTHER dirty path still halts the bump.
130
+ // `ignore` is waived (it comes from — and is argued for in — claude-materialize.js materializeWillRewrite, task 1004122); every OTHER dirty path still halts the bump. -uall (task 1004188): a bare status collapses a NEW untracked dir to one '?? dir/' entry the file-path waiver never matches.
131
131
  function gitTreeClean(instanceDir, run = spawnSync, ignore = []) {
132
- const r = run('git', ['status', '--porcelain'], { cwd: instanceDir, encoding: 'utf8' });
132
+ const r = run('git', ['status', '--porcelain', '-uall'], { cwd: instanceDir, encoding: 'utf8' });
133
133
  if (!r || r.error || r.status !== 0) return { ok: false, reason: 'git status failed (not a git repo?)' };
134
134
  const skip = new Set(ignore), all = (r.stdout || '').split('\n').filter((l) => l.trim());
135
135
  const kept = all.filter((l) => !skip.has(l.slice(3).trim()));
@@ -22,7 +22,13 @@
22
22
  //
23
23
  // The 6-month delay alone does NOT scrub secrets/topology — redaction is mandatory and
24
24
  // the gate is the backstop. Reuses the status-mirror-sync transport shape (--repo-dir /
25
- // --push + a scoped deploy key, see .github/workflows/sync-public-mirror.yml).
25
+ // --push + a scoped deploy key).
26
+ //
27
+ // PUBLISHING IS A MANUAL STEP. No workflow or schedule runs this script (there is
28
+ // no sync-public-mirror workflow) and nothing publishes the public mirror automatically.
29
+ // A person runs it by hand, e.g.
30
+ // node scripts/public-mirror-export.js --repo-dir <mirror-checkout> --push
31
+ // (a scheduled public release channel needs the owner's approval first).
26
32
  //
27
33
  // USAGE:
28
34
  // node scripts/public-mirror-export.js --repo-dir <mirror-checkout> [--push]
@@ -73,6 +73,16 @@ const MODULE_GLOBS = {
73
73
  // protected - ADR 0314 in a second costume. Ownership follows what the
74
74
  // file IS, and what it is is the core read facade over ten modules.
75
75
  'src/bongos/routes/me.js',
76
+ // The rest of the core route directory (task 1004116). It is a BLANKET
77
+ // protected glob, so every file in it is walled, but until now only the two
78
+ // entries above were authorable: sixteen live routes (auth, public,
79
+ // core-update, builders, ...) sat in no module's territory and no goal could
80
+ // author a task on them. They are the core HTTP surface that stayed in core
81
+ // while domain routes carved into modules/<key>/routes/, so they are kernel's.
82
+ // Kernel was already protected, so this changes no module's protected status;
83
+ // the me.js overlap the task feared is gone (task 1004110 moved it here too).
84
+ // The two explicit entries above stay for their reasons.
85
+ 'src/bongos/routes/',
76
86
  'server.js', 'config/',
77
87
 
78
88
  // ADOPTED (task 1004111): a protected path owned by no module is authorable
@@ -340,6 +350,9 @@ const MODULE_GLOBS = {
340
350
  // (tests/npm_release_*.mjs) because a default-off module's own tests/ is skipped
341
351
  // by the unit runner. The /deploy page shell stays hall-ui's (one key per surface).
342
352
  'npm-release': ['modules/npm-release/', 'tests/npm_release_*.mjs'],
353
+ // --- the render-deploy module (task 1004270): a Render project's deploy page. Tests live at
354
+ // the repo root (tests/render_deploy_*.mjs) for the same reason as npm-release above.
355
+ 'render-deploy': ['modules/render-deploy/', 'tests/render_deploy_*.mjs'],
343
356
  // --- instance provisioning: the web-tier REQUEST + STATE surface (module home)
344
357
  // plus the control-plane runner CLI — a command travels with its domain (the
345
358
  // lifecycle precedent). Covers the projects-surface work (goal
@@ -986,7 +986,9 @@ function mountInternalSurfaces(app) {
986
986
  if (req.method !== 'GET' && req.method !== 'HEAD') return next();
987
987
  if (!DEPLOY_PAGE_RE.test(path.posix.normalize(req.path))) return next();
988
988
  let door = false;
989
- try { door = isModuleEnabled('provisioning'); } catch { door = false; }
989
+ // The page also exists where the render-deploy module is on: a Render project's own deploy
990
+ // page (task 1004270). Neither module on and the page still answers with Settings.
991
+ try { door = isModuleEnabled('provisioning') || isModuleEnabled('render-deploy'); } catch { door = false; }
990
992
  if (door) return next();
991
993
  return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/settings#software-update`);
992
994
  });
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.19.1081'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.20.1'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -255,6 +255,15 @@ module.exports = {
255
255
  // second protected list — so a module reaches it via the doorway, not a deep
256
256
  // core require (which the module-boundary fitness check would red). PERMANENT.
257
257
  get goalScopeCheck() { return require('./bongos/goal-scope-check'); },
258
+ // cascadeDispatch — the cheapest-model-first cascade router (src/bongos/
259
+ // cascade-dispatch.js, task 1001691 / ADR 0101 Part 1). KERNEL (it composes
260
+ // no domain state — a pure ladder + an injected runner), grandfathered
261
+ // dormant core since System 3 (grading/CLAUDE.md's own note: "stays in
262
+ // src/gds/... nothing calls it but its mock unit test"). The lifecycle
263
+ // module's relevance/anti-hack judge (goal-task-relevance-judge.js) is its
264
+ // FIRST real caller, injecting grade.runSubagent (resolved via the `grade`
265
+ // port, never required directly) as the runner — never a deep core require.
266
+ get cascadeDispatch() { return require('./bongos/cascade-dispatch'); },
258
267
 
259
268
  // === DOMAIN capabilities still on the doorway — SCHEDULED TO RELOCATE ======
260
269
  // NOT kernel: these leak a domain's queries through the doorway (ADR 0091