@bongos/core 1.19.1081 → 1.20.2

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 (253) hide show
  1. package/.bongos-core.json +528 -193
  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 +8 -0
  24. package/docs/modules-contract.md +1 -0
  25. package/docs/page-inventory.json +38 -4
  26. package/docs/page-readings.json +1666 -1558
  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/goal-inbox.js +1 -1
  51. package/modules/hall-ui/public/goals.html +1 -1
  52. package/modules/hall-ui/public/government.html +1 -1
  53. package/modules/hall-ui/public/idea.html +1 -1
  54. package/modules/hall-ui/public/idea.js +28 -0
  55. package/modules/hall-ui/public/ideas.html +1 -1
  56. package/modules/hall-ui/public/ideas.js +19 -3
  57. package/modules/hall-ui/public/index.html +5 -1
  58. package/modules/hall-ui/public/modules.html +1 -1
  59. package/modules/hall-ui/public/primer.html +1 -1
  60. package/modules/hall-ui/public/profile-nudge.js +21 -4
  61. package/modules/hall-ui/public/profile.html +1 -1
  62. package/modules/hall-ui/public/profile.js +1 -1
  63. package/modules/hall-ui/public/project-settings.html +1 -1
  64. package/modules/hall-ui/public/ranks.html +1 -1
  65. package/modules/hall-ui/public/roadmap.html +1 -1
  66. package/modules/hall-ui/public/roster.html +1 -1
  67. package/modules/hall-ui/public/sessions.html +1 -1
  68. package/modules/hall-ui/public/settings.html +19 -30
  69. package/modules/hall-ui/public/settings.js +24 -177
  70. package/modules/hall-ui/public/settings.states.json +1 -1
  71. package/modules/hall-ui/public/shell.js +4 -2
  72. package/modules/hall-ui/public/studio.css +14 -8
  73. package/modules/hall-ui/public/studio.html +5 -4
  74. package/modules/hall-ui/public/task.html +1 -1
  75. package/modules/hall-ui/public/thinking.css +6 -3
  76. package/modules/hall-ui/public/thinking.html +1 -1
  77. package/modules/hall-ui/public/tweak-editor-lib.js +82 -1
  78. package/modules/hall-ui/public/tweak-editor.css +142 -11
  79. package/modules/hall-ui/public/tweak-editor.html +25 -9
  80. package/modules/hall-ui/public/tweak-editor.js +243 -10
  81. package/modules/hall-ui/public/watch.html +1 -1
  82. package/modules/hall-ui/public/work.html +1 -1
  83. package/modules/hall-ui/records/approval-queue.md +6 -0
  84. package/modules/hall-ui/records/tweak-editor.md +6 -0
  85. package/modules/ideas/ratify.js +36 -8
  86. package/modules/ideas/routes/ratify-goal.js +6 -0
  87. package/modules/lifecycle/db-analytics.js +79 -10
  88. package/modules/lifecycle/db-claims.js +48 -1
  89. package/modules/lifecycle/db-deps-criteria.js +94 -40
  90. package/modules/lifecycle/db-goals.js +4 -1
  91. package/modules/lifecycle/db-grade.js +9 -1
  92. package/modules/lifecycle/db-relevance-flags.js +183 -0
  93. package/modules/lifecycle/db-versions.js +4 -3
  94. package/modules/lifecycle/db.js +19 -0
  95. package/modules/lifecycle/done-when.js +5 -3
  96. package/modules/lifecycle/est-advisory.js +31 -4
  97. package/modules/lifecycle/goal-task-relevance-advisory.js +153 -0
  98. package/modules/lifecycle/goal-task-relevance-judge.js +226 -0
  99. package/modules/lifecycle/migrations/lifecycle_014_goal_task_relevance_flags.sql +82 -0
  100. package/modules/lifecycle/module.json +2 -1
  101. package/modules/lifecycle/routes/claims.js +8 -1
  102. package/modules/lifecycle/routes/goal-task-relevance.js +72 -0
  103. package/modules/lifecycle/routes/goals.js +1 -1
  104. package/modules/lifecycle/routes/tasks.js +27 -24
  105. package/modules/lifecycle/routes/visuals.js +9 -0
  106. package/modules/lifecycle/ship-preflight.js +7 -11
  107. package/modules/lifecycle/task-visuals.js +109 -5
  108. package/modules/npm-release/module.json +2 -1
  109. package/modules/npm-release/preview/commands.js +81 -0
  110. package/modules/npm-release/preview/divert.js +95 -0
  111. package/modules/npm-release/preview/env.js +78 -0
  112. package/modules/npm-release/preview/proxy.js +107 -0
  113. package/modules/npm-release/preview/runtime.js +83 -0
  114. package/modules/npm-release/preview/supervisor.js +175 -0
  115. package/modules/npm-release/public/work.js +65 -1
  116. package/modules/npm-release/routes/preview.js +133 -0
  117. package/modules/provisioning/app-status.js +83 -0
  118. package/modules/provisioning/module.json +2 -1
  119. package/modules/provisioning/pollers/app-liveness.js +110 -0
  120. package/modules/provisioning/render-lookup.js +68 -0
  121. package/modules/provisioning/render-standup.js +44 -1
  122. package/modules/provisioning/routes/render-standup.js +81 -6
  123. package/modules/provisioning/starter-bundles.js +4 -1
  124. package/modules/public-landing/public/account-private.states.json +14 -0
  125. package/modules/public-landing/public/account.html +513 -0
  126. package/modules/public-landing/public/account.probes.json +37 -0
  127. package/modules/public-landing/public/account.states.json +16 -0
  128. package/modules/public-landing/public/index.html +5 -2
  129. package/modules/public-landing/public/projects.html +661 -18
  130. package/modules/public-landing/public/projects.states.json +8 -1
  131. package/modules/render-deploy/deploys.js +203 -0
  132. package/modules/render-deploy/migrations/render_deploy_001_app.sql +22 -0
  133. package/modules/render-deploy/module.json +25 -0
  134. package/modules/render-deploy/public/deploy.css +10 -0
  135. package/modules/render-deploy/public/deploy.js +249 -0
  136. package/modules/render-deploy/render.js +56 -0
  137. package/modules/render-deploy/routes/act.js +88 -0
  138. package/modules/render-deploy/routes/app.js +65 -0
  139. package/modules/render-deploy/routes/history.js +38 -0
  140. package/modules/specialities/db.js +27 -8
  141. package/modules/specialities/migrations/specialities_003_skills.sql +46 -0
  142. package/modules/specialities/routes/specialities.js +51 -3
  143. package/modules/specialities/skills.js +94 -0
  144. package/modules/specialities/specialities.js +56 -9
  145. package/modules/ui-design/kit/fixtures/me-cross-project-private.json +12 -0
  146. package/modules/ui-design/kit/fixtures/me__cross-project.json +15 -0
  147. package/modules/ui-design/kit/fixtures/provisioning-instance-render.json +39 -0
  148. package/modules/ui-design/kit/lib.js +3 -1
  149. package/modules/ui-design/kit/serve.js +96 -0
  150. package/package-lock.json +2 -2
  151. package/package.json +1 -1
  152. package/release-notes.json +169 -0
  153. package/scripts/gds/agents-sync.js +13 -3
  154. package/scripts/gds/autobongos-run.js +80 -2
  155. package/scripts/gds/autobongos-service.cmd +12 -0
  156. package/scripts/gds/copy-apply.js +14 -0
  157. package/scripts/gds/dev-box-guard.js +3 -2
  158. package/scripts/gds/fitness.js +9 -0
  159. package/scripts/gds/grade-correlation-audit.js +42 -12
  160. package/scripts/gds/grade-replay.js +1 -1
  161. package/scripts/gds/provision-core-upgrade.js +38 -7
  162. package/scripts/gds/provision-pin-key.js +176 -0
  163. package/scripts/gds/provision-render.js +62 -31
  164. package/scripts/gds/provision-teardown.js +7 -2
  165. package/scripts/gds/provision-units.js +31 -0
  166. package/scripts/gds/provision.js +10 -10
  167. package/scripts/gds/publish-manifest.js +1 -0
  168. package/scripts/gds/render-api.js +19 -0
  169. package/scripts/gds/render-payload.js +39 -65
  170. package/scripts/gds/run-unit-tests.js +10 -0
  171. package/scripts/gds/ship-finish.js +19 -20
  172. package/scripts/gds/smoke-dependencies.sh +37 -8
  173. package/scripts/gds/status.js +12 -4
  174. package/scripts/gds/upgrade.js +2 -2
  175. package/scripts/public-mirror-export.js +7 -1
  176. package/src/bongos/module-scope-map.js +13 -0
  177. package/src/bongos/serve-internal.js +3 -1
  178. package/src/module-api.js +10 -1
  179. package/src/platform-server.js +39 -0
  180. package/tests/account_privacy_flags.mjs +10 -6
  181. package/tests/agents_authoring.mjs +15 -1
  182. package/tests/agents_sync.mjs +106 -5
  183. package/tests/agents_validate.mjs +45 -0
  184. package/tests/api_path_404.mjs +6 -0
  185. package/tests/autobongos_cadence.mjs +7 -0
  186. package/tests/autobongos_loop.mjs +136 -1
  187. package/tests/autonomy_gauge.mjs +62 -0
  188. package/tests/blocker_hall_live_proof.mjs +275 -0
  189. package/tests/claim_gate_ci_unblock.mjs +116 -0
  190. package/tests/claim_gate_rebase.mjs +15 -3
  191. package/tests/collab_page.mjs +10 -0
  192. package/tests/conductor_main_e2e.mjs +97 -0
  193. package/tests/core_262_drop_box_blocked_db.mjs +143 -0
  194. package/tests/core_upgrade_runner.mjs +1 -0
  195. package/tests/dependency_writes_atomic.mjs +191 -0
  196. package/tests/deploy_page_projects.mjs +2 -1
  197. package/tests/effective_visibility_predicate.mjs +15 -0
  198. package/tests/est_advisory.mjs +27 -0
  199. package/tests/goal_map_page.mjs +26 -0
  200. package/tests/goal_task_relevance.mjs +518 -0
  201. package/tests/goal_working_area.mjs +146 -0
  202. package/tests/grade_attempts.mjs +61 -0
  203. package/tests/grade_attribution.mjs +23 -0
  204. package/tests/grade_correlation_audit.mjs +54 -1
  205. package/tests/grader_reader_lens.mjs +55 -2
  206. package/tests/grader_root_outage.mjs +6 -1
  207. package/tests/grader_subagent_tools_arg.mjs +102 -0
  208. package/tests/hall_approval_queue.mjs +54 -2
  209. package/tests/hall_settings_world.mjs +3 -1
  210. package/tests/hall_tweak_editor.mjs +316 -11
  211. package/tests/hub_account_page.mjs +394 -0
  212. package/tests/idea_detail_page.mjs +64 -0
  213. package/tests/idea_goal_ratification.mjs +64 -0
  214. package/tests/idea_spark_hall.mjs +60 -9
  215. package/tests/ideator_full_idea_shapes_space_proof.mjs +1 -1
  216. package/tests/landing_page.mjs +6 -3
  217. package/tests/module-scope-map.mjs +31 -8
  218. package/tests/module_api.mjs +1 -0
  219. package/tests/module_loader.mjs +1 -1
  220. package/tests/nav_permission_atoms.mjs +1 -1
  221. package/tests/no_phantom_mirror_workflow.mjs +24 -0
  222. package/tests/npm_release_preview_commands.mjs +89 -0
  223. package/tests/npm_release_preview_divert.mjs +164 -0
  224. package/tests/npm_release_preview_env.mjs +100 -0
  225. package/tests/npm_release_preview_proxy.mjs +162 -0
  226. package/tests/npm_release_preview_routes.mjs +208 -0
  227. package/tests/npm_release_preview_supervisor.mjs +203 -0
  228. package/tests/pin_write_key.mjs +383 -0
  229. package/tests/planning_session_skill.mjs +27 -0
  230. package/tests/platform_boot.mjs +71 -0
  231. package/tests/profile_nudge_links.mjs +86 -0
  232. package/tests/profile_ui_cross_project.mjs +1 -1
  233. package/tests/projects_hub.mjs +2 -2
  234. package/tests/projects_hub_app_status.mjs +249 -0
  235. package/tests/projects_hub_app_step.mjs +282 -52
  236. package/tests/projects_hub_render_connect.mjs +295 -0
  237. package/tests/provision_render.mjs +121 -28
  238. package/tests/provision_settings_apply.mjs +62 -0
  239. package/tests/provisioning_app_status.mjs +204 -0
  240. package/tests/provisioning_render_route.mjs +126 -6
  241. package/tests/render_check.mjs +2 -1
  242. package/tests/render_deploy.mjs +495 -0
  243. package/tests/rot_inbox.mjs +8 -0
  244. package/tests/ship_preflight.mjs +61 -8
  245. package/tests/smoke_dependencies_witness.mjs +46 -0
  246. package/tests/speciality_skills.mjs +214 -0
  247. package/tests/studio_room_height.mjs +70 -0
  248. package/tests/task_detail_includes.mjs +10 -0
  249. package/tests/task_visuals_instance_root.mjs +150 -0
  250. package/tests/tweak_batch_apply.mjs +21 -0
  251. package/tests/ui_design_kit.mjs +36 -4
  252. package/tests/upgrade.mjs +1 -1
  253. package/tests/upgrade_persist_pin.mjs +24 -0
@@ -0,0 +1,106 @@
1
+ # ADR 0349 — A version preview is a sandboxed child the web tier launches, reached by a cookie
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-09-29
5
+ - **Task:** [task 1004300](https://cloudbongos.com/builders#/task/1004300) (goal 1000090 — BONGOS-V2); split out of [task 1002622](https://cloudbongos.com/builders#/task/1002622)
6
+ - **Deciders:** Lars (owner, Archon) approved both decisions below on 2026-09-29, in chat with the orchestrator: "Yes to cookie routing and web tier, build 1004300." Claude wrote the record.
7
+ - **Amends:** [ADR 0293](0293-the-owners-deploy-door-is-an-intent-the-control-plane-drains.md) D1, for this one feature only.
8
+ - **Related:** [ADR 0016](0016-trust-boundary-server-enforced-permissions.md) (authority is resolved per request) · [ADR 0043](0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md) (the control plane is a separate privilege domain) · [ADR 0151](0151-governance-permissions-as-atom-ranks-as-roles.md) (`requirePermission`) · [`docs/modules-contract.md`](../modules-contract.md) (the `request.divert` seam)
9
+
10
+ ## Context
11
+
12
+ The owner asked for the version history on `/deploy` to open the hall **as it was at that
13
+ version**, with a banner saying which version it is. A release-notes page was rejected: the
14
+ point is to see, and to run a version's migrations against real data before that version goes
15
+ live. So a preview is a second Bongos process, running an installed copy of that core against
16
+ a copy of the live database.
17
+
18
+ Two questions had no safe default.
19
+
20
+ **How does a browser reach it?** The hall's pages call absolute `/api/bongos/...` URLs about
21
+ 133 times across `modules/*/public`, and the hall is routed on the Host header
22
+ (`src/bongos/serve-internal.js`, `isBuildersHost`). Serving a preview under a `/preview/<v>/`
23
+ path would break both. A second origin would need its own GitHub OAuth app, an owner step.
24
+
25
+ **Who launches it?** ADR 0293 D1 keeps the web tier shell-less: it asks, and the control-plane
26
+ runner acts. That fits a privileged act like moving a pin. A preview is unprivileged by
27
+ design, and routing every start and stop through the intent queue would add a timer's latency
28
+ to something a person is waiting on.
29
+
30
+ ## Decision
31
+
32
+ ### D1 — Route by cookie, through an optional `request.divert` seam (owner)
33
+
34
+ `GET /api/bongos/npm-release/preview/enter?v=X` sets `bongos_preview=X` (HttpOnly, SameSite=Lax,
35
+ Path=/). A kernel middleware, mounted before any surface, asks an **optional** provider of the
36
+ `request.divert` seam whether to take the request. With no provider it is one map lookup and
37
+ `next()`: an instance without the npm-release module is served exactly as before. A provider
38
+ that throws is treated as absent for that request.
39
+
40
+ The npm-release provider forwards a request to the preview only when **all** hold:
41
+
42
+ - the cookie names the version now running;
43
+ - the path is not sign-in (`/api/bongos/auth/*`) and not the module's own preview routes, so
44
+ signing in and **leaving** a preview always reach the live hall;
45
+ - there is no `Authorization` header (a CLI or API call is never a preview);
46
+ - the caller is signed in and holds `core.pin.move`, resolved by the kernel's own
47
+ `requireBuilder` and `requirePermission` gates on **every** forwarded request, uncached
48
+ (ADR 0016). A refusal clears the cookie and serves the live hall.
49
+
50
+ The proxy keeps the Host header, passes server-sent-events streams through unbuffered, strips
51
+ the steering cookie from what the preview sees, and injects the banner after `<body>` on HTML
52
+ responses, so a version older than any banner code still shows it.
53
+
54
+ The session cookie is forwarded: the preview's database is a copy, so the same session rows
55
+ exist in it and no second sign-in is needed. Anything done there stays in the copy.
56
+
57
+ **Rejected:** a `/preview/<v>/` path (breaks 133 absolute URLs and Host routing); a second
58
+ origin (an OAuth app per preview host, and cookies that no longer carry).
59
+
60
+ ### D2 — The web tier launches the preview, as a sandboxed child (owner; amends ADR 0293 D1)
61
+
62
+ The npm-release module's supervisor installs the version, copies the database, migrates the
63
+ copy and spawns the child itself. This is the exception ADR 0293 D1 refuses for the deploy
64
+ door, granted here because a preview is not the deploy door: it changes nothing live.
65
+
66
+ What keeps that exception narrow:
67
+
68
+ - **The child's environment is built from nothing.** An allow-list of `PATH`, `NODE_ENV`,
69
+ `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, plus its own `PORT`/`HOST` on 127.0.0.1. `HOME`
70
+ is an empty scratch directory, so the Discord webhook file is not found. Every `GITHUB_*`,
71
+ `DISCORD_*` and AI key is absent because nothing carries it, not because it was removed.
72
+ `CHAT_DRY_RUN=1`; nine pollers off (`PUBLISH_RECONCILER_DISABLED`, `ROUTINE_TIMERS_DISABLED`,
73
+ `BOARD_EXPIRY_DISABLED`, `SEARCH_INDEX_SWEEP_DISABLED`, `SESSION_UPLOAD_HEALTH_DISABLED`,
74
+ `SESSION_REWARD_RECONCILE_DISABLED`, `PROVISIONING_LIVENESS_DISABLED`,
75
+ `PROVISIONING_CATALOG_BACKFILL_DISABLED`, `CONFLICT_RESOLVE_DISABLED`); modules `discord`,
76
+ `agents`, `provisioning` and `npm-release` off.
77
+ - **The copy is a dump and a restore, never a template copy.** `pg_dump -Fc` then `pg_restore
78
+ --no-owner` into `bongos_preview`. A template copy needs no other connection on the source,
79
+ and the live pool holds some. `PGDATABASE` is pinned to the copy and `DATABASE_URL` blanked
80
+ for the migration, so nothing can be applied to the live database.
81
+ - **Commands run without a shell.** A version is `^\d+\.\d+\.\d+$` before it reaches any
82
+ command line.
83
+ - **One preview at a time; 20 minutes idle stops it** and drops the copy.
84
+ - **Only published versions**, no older than the running version minus 30 published versions.
85
+ - **Nothing here holds a control-plane credential.** The web tier already cannot read
86
+ `/etc/cloudbongos/box.env` (ADR 0327 §5); the child gets less than the web tier has.
87
+
88
+ ### D3 — Accepted residual risks, for now
89
+
90
+ - The child connects to Postgres as the same role as the live hall (`PGUSER`), so a hostile
91
+ published core could open the live database. It is our own published package, built from
92
+ this repository by CI, and a preview is limited to `core.pin.move` holders; a per-preview
93
+ database role is the next step, not built here.
94
+ - **No egress firewall yet** (owner default). The env allow-list means the child holds no
95
+ outward credentials; it can still make anonymous outbound requests. `systemd-run
96
+ IPAddressDeny` is the follow-up and needs the droplet.
97
+ - A preview started by the live process is stopped when that process exits, but the copy is
98
+ dropped only by the next start or stop. It is one fixed name, so it cannot pile up.
99
+
100
+ ## Consequences
101
+
102
+ - One new optional seam, `request.divert`, listed in `docs/modules-contract.md`.
103
+ - No migration. No schema. The provider is registered only where npm-release is on.
104
+ - **Part B is unverified until run on the droplet:** the `lars` role has CREATEDB, the unit's
105
+ `MemoryMax` fits a second Node process plus a restore, the dump time, one preview end to end,
106
+ and live row counts before and after. Until then the code is implemented, not verified.
@@ -0,0 +1,103 @@
1
+ # ADR 0350 — The hub holds a per-project WRITE deploy key, so a hosted project's upgraded pin always reaches its GitHub repo
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-09-29
5
+ - **Task:** [task 1004291](https://cloudbongos.com/builders#/task/1004291) (follow-up to task 1004065)
6
+ - **Deciders:** the owner chose this option on 2026-09-25 (a per-project write key, over keeping the owner's GitHub access longer, or leaving it as is); the shared-account default below was stated in the task and is the safe default, not a new ruling; Claude wrote the record.
7
+ - **Amends:** [ADR 0176](0176-private-repo-deploy-keys.md) (which said the deploy key is read-only and that pushes ride the owner's token) · **Related:** [ADR 0281](0281-an-instance-identity-is-its-own-unix-account-and-pg-role.md) (a project's own account and database) · [ADR 0155](0155-adopt-private-repo-widen-oauth-scope.md) (the owner's token)
8
+
9
+ ## Context
10
+
11
+ When a hosted project's core is upgraded, the pin is committed in the project's checkout and
12
+ the hub's runner pushes it to the owner's GitHub repo (task 1004065, `pushUpgradePin`). That
13
+ push needs a credential that can WRITE. There were two, and neither lasts:
14
+
15
+ - the owner's token is stored for one hour after they sign in with repository access
16
+ (`GITHUB_TOKEN_TTL_MS`), and the scaffold path deletes it after use, so an upgrade days later
17
+ normally finds none;
18
+ - the checkout's own `origin` is the read-only pull key of ADR 0176 (or the shared alias key),
19
+ which cannot write the owner's repo.
20
+
21
+ So most upgrades still ended "GitHub was not updated". The box and the repo split, and the
22
+ box's local commit stopped its fast-forward pull from taking the owner's new work (the
23
+ hermeslines-marketing shape).
24
+
25
+ ## Decision
26
+
27
+ **D1. A second deploy key per hosted repo, registered writable.** Minted with
28
+ `ssh-keygen -t ed25519`, registered with `read_only: false` under its own title
29
+ `cloudbongos-pin-<slug>`, distinct from the ADR 0176 pull key `cloudbongos-deploy-<slug>`. The pull key
30
+ stays exactly as it was and stays read-only. Registering a deploy key needs repository admin
31
+ for the owner, the same requirement the pull key already has. Public repos get one too (they
32
+ have no per-repo key today).
33
+
34
+ **D2. Where it lives and who can read it.** Outside every project checkout, in a directory of
35
+ the runner's own account: `PROVISION_PIN_KEY_DIR`, default `~/.config/cloudbongos/pin-keys/`,
36
+ directory mode 0700, key mode 0600, born under `umask 077`, and the modes are read back and
37
+ the key deleted if they are not exactly 700/600. It is never written into the project's
38
+ `.git`, its unix home (`/var/lib/bongos-<slug>`) or anywhere the project's account
39
+ (ADR 0281) can read.
40
+
41
+ **D3. When it is minted.** At standup (scaffold and adopt), right after the ADR 0176 wiring, and
42
+ on any later pass that holds a fresh owner token and finds no key: `pushUpgradePin` mints it
43
+ before pushing. So an existing project needs the owner to reconnect GitHub with repository
44
+ access once; the next upgrade then sets up lasting access. Minting is best effort and never
45
+ fails a standup or an upgrade.
46
+
47
+ **D4. How it is used.** `pushUpgradePin` tries, in order: the write key, then the owner's token
48
+ (when fresh), then the checkout's own `origin`. The write key is passed for that one push only
49
+ (`-c core.sshCommand="ssh -F /dev/null -i <key> -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new"`
50
+ against `git@github.com:<owner>/<repo>.git`), with hooks off, no prompt, never forced. A push the
51
+ key made that GitHub rejects as non-fast-forward is reported as "behind" and nothing else is
52
+ tried, because the credential worked and the history is what stopped it.
53
+
54
+ **D5. Revoked when the project leaves.** Teardown, and therefore disconnect (which runs the
55
+ teardown first), deletes the `cloudbongos-pin-<slug>` registration by title next to the pull
56
+ key's, and removes the local key whatever else happened. With no usable owner token the
57
+ registration cannot be removed by us; the log names it so the owner can remove it in their repo
58
+ settings.
59
+
60
+ **D6. A project with no account of its own is refused a key (the recorded default).** A project
61
+ still running as the shared app user (the `resolveRunAs` fallback, no `bongos-<slug>`
62
+ account) has the SAME uid as the runner and could read the runner's key, so no write key is
63
+ minted for it, and the deploy page reports "needs its own account first" (repo note code
64
+ `needs_account`) instead of offering a reconnect that could not help. The check is fail-closed:
65
+ only a positive `id -u bongos-<slug>` counts; an inconclusive probe refuses. If the owner wants a
66
+ shared-uid project to hold a write key anyway, that is a new decision recorded here, not a
67
+ silent one.
68
+
69
+ **D7. The deploy page.** The `no_access` note and the reconnect link now say that reconnecting
70
+ sets up lasting access, no longer "about an hour".
71
+
72
+ ## What a compromise can and cannot do
73
+
74
+ - **The runner account** (the hub's app user) already holds the database, the owner tokens and the
75
+ process manager, so it can already do everything the write key can. The key adds, for that
76
+ one account, the ability to push to each hosted owner repo without a live token — one repo per
77
+ key, revocable individually from the owner's repo settings.
78
+ - **A project's own process** (its `bongos-<slug>` account, ADR 0281) can read the read-only pull
79
+ key, exactly as before, and can NOT read the write key: wrong owner, directory 0700. It
80
+ therefore cannot push code into the owner's repo, which is the point of keeping the key
81
+ outside the checkout. A project on the shared account can, which is why D6 refuses it a key.
82
+ - **GitHub or the network** learn only the public half. A leaked write key can push to that one
83
+ repository until the owner deletes it in their settings; it cannot read other repos, and it
84
+ is a per-repo deploy key, not an account token.
85
+
86
+ ## Alternatives rejected
87
+
88
+ - **Keep the owner's token longer.** A long-lived account-wide token in the hub is a bigger
89
+ prize than a per-repo key, and it still lapses when the owner revokes the OAuth grant.
90
+ - **Leave it as is.** Most upgrades keep ending in a split that blocks the owner's own pushes.
91
+ - **Make the pull key writable.** It lives in the checkout, readable by the project's own
92
+ account, so a compromised project could push code to the owner's repo.
93
+
94
+ ## Consequences
95
+
96
+ - Existing projects get the key on the next runner pass after the owner reconnects GitHub with
97
+ repository access; until then the deploy page still reports "GitHub was not updated" with the
98
+ corrected copy.
99
+ - Reconciling an already-split repo (hermeslines-marketing) and auto-rebasing a pin onto newer
100
+ GitHub commits stay out of scope; both are still reported, not resolved.
101
+ - Code: `scripts/gds/provision-pin-key.js`, `pushUpgradePin` in
102
+ `scripts/gds/provision-core-upgrade.js`, the revoke in `scripts/gds/provision-teardown.js`,
103
+ the copy in `modules/hall-ui/public/deploy.js`; tests in `tests/pin_write_key.mjs`.
@@ -439,3 +439,6 @@ This keeps the decision history honest and traceable.
439
439
  | 0345 | [**We host every project's hall, and its app deploys where the owner chooses** ([task 1004349](https://cloudbongos.com/builders#/task/1004349), goal 1000106 — working area 1, owner Lars). Reverses ADR 0323 §2 and the hall half of ADR 0327. **D1 (owner):** every project's Builders Hall runs on our shared server; `cloud-host` now means "hosted by us". **D2 (owner):** the hall's address is ours or the owner's own domain. **D3 (owner):** the setup wizard asks where the APP deploys; Render only at launch; choosing it creates the app on the owner's own Render account with ADR 0327's borrowed key. **D4 (owner):** "decide later" is allowed. **D5 (owner):** no project is moved. **D6 (measured):** ADR 0145 Finding 1 is live — the hub cookie is `Domain=.cloudbongos.com` and a hall loads modules from its own repo — so our-address halls need task 1004357; an owner domain is safe. **D7 (measured):** at the ceiling the create route refuses `box_full` up front, and the resize is not a prerequisite. Task 1004184 re-scoped, task 1004185 on hold.](0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md) | hosting / provisioning / security |
440
440
  | 0346 | [**Dev boxes are retired; a builder builds from their own checkout** ([task 1003898](https://cloudbongos.com/builders#/task/1003898), goal 1000120 — owner Lars). Records the whole removal, link by link, the owner's three rulings of 2026-09-13 (drop the data, delete `.devcontainer/`, remove `bongos box`/`shell`/`code`), what was kept on purpose (server-mediated publish, the CLI downloads, `secret-box`, `do-api`, the declared-source rule), and the retirement guards that still name the box. Supersedes 0031, 0044, 0045, 0053, 0057, 0059, 0071, 0072 (code staleness), 0123, 0144, 0145 (desktop-app branding), 0148, 0193, 0277; supersedes in part 0035, 0046, 0052, 0055, 0072 (Bongos app), 0151 §3.](0346-dev-boxes-are-retired.md) | builder environment / removal |
441
441
  | 0347 | [**Every store module ships a how-to: a file in the module, a publish gate, and a Docs score shown on its own** ([task 1004364](https://cloudbongos.com/builders#/task/1004364), goal 1000091 — working area 5, owner Will). Amends ADR 0343 D1. **D1-D2 (owner):** the how-to is `HOWTO.md` at the module root, frozen with the version by the tarball hashes, with five required sections (what it does; install and enable; how to use it, with one worked example; configuration; limits and known issues). **D3:** an optional `howto.artifactUrl` Claude artifact shown as "also available as", never graded. **D4:** `bongos module publish` and the store refuse a version with a missing file or empty section, naming it; store versions only. **D5 (owner):** a Docs part scored 0–100 per version, day one, shown beside the parts but NOT in the D3 average, floor or ranking; "pending"/"not scored" never zero; price never an input. **D6 (owner):** one Sonnet 5 call per version, 5¢ ceiling.](0347-every-store-module-ships-a-how-to.md) | modules / store / quality |
442
+ | 0348 | [**The web tier may look, read-only, at what an owner's Render key can see** ([task 1004352](https://cloudbongos.com/builders#/task/1004352), goal 1000106 — working area 1, owner Lars). Amends ADR 0111 §2. **D1 (owner):** one route, `POST /provisioning/render/lookup`, may call Render from the web tier — GET only (workspaces; web services in one named workspace), the owner's key held for that request and never stored, logged or returned, every service checked to carry the workspace asked about, capped per builder. Every write stays in the runner. **D2 (owner):** a key sent during a Create rides the project's own `provision` run; the runner sets the app up on Render straight after the project is up and clears the key either way.](0348-the-web-tier-may-look-read-only-at-what-an-owners-render-key-can-see.md) | provisioning / security / render |
443
+ | 0349 | [**A version preview is a sandboxed child the web tier launches, reached by a cookie** ([task 1004300](https://cloudbongos.com/builders#/task/1004300), goal 1000090 — BONGOS-V2, owner Lars, approved 2026-09-29). Amends ADR 0293 D1 for this feature only. **D1 (owner):** route by a `bongos_preview` cookie through an optional `request.divert` seam, not a path; forward only with the cookie, off the sign-in and preview paths, with no Bearer, and with `core.pin.move` re-checked on every request. **D2 (owner):** the npm-release module launches the preview itself, with a deny-by-default environment, a dump-and-restore copy of the database (never a template copy), one at a time, 20-minute idle stop, published versions within 30 of the running one. **D3:** the same database role and no egress firewall are accepted for now.](0349-a-version-preview-is-a-sandboxed-child-the-web-tier-launches.md) | modules / npm-release / deploy |
444
+ | 0350 | [**The hub holds a per-project WRITE deploy key, so a hosted project's upgraded pin always reaches its GitHub repo** ([task 1004291](https://cloudbongos.com/builders#/task/1004291), follow-up to task 1004065). Amends ADR 0176. The owner's token lasts one hour and the checkout's own key is read-only, so most hosted upgrades ended "GitHub was not updated". **D1:** a second deploy key per repo, registered `read_only: false` as `cloudbongos-pin-<slug>`, the pull key untouched. **D2:** kept outside every checkout in the runner's own directory (0700 dir, 0600 key, modes verified), unreadable by the project's account. **D3:** minted at standup and whenever a fresh owner token finds none, so reconnecting GitHub once is enough. **D4:** `pushUpgradePin` tries write key, then owner token, then origin, never forced. **D5:** revoked on teardown and disconnect. **D6:** a project on the shared app user is refused a key: "needs its own account first". **D7:** /deploy says reconnecting sets up lasting access.](0350-the-hub-holds-a-per-project-write-deploy-key-so-a-hosted-upgrade-reaches-github.md) | provisioning / security |