@bongos/core 1.19.1080 → 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 (258) hide show
  1. package/.bongos-core.json +573 -188
  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 +34 -0
  5. package/clients/bongos-client/index.cjs +34 -0
  6. package/clients/bongos-client/index.d.ts +53 -4
  7. package/clients/bongos-client/index.mjs +34 -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/0343-a-module-score-is-a-security-gate-then-an-average-of-visible-parts.md +2 -0
  14. package/docs/adr/0347-every-store-module-ships-a-how-to.md +135 -0
  15. package/docs/adr/0348-the-web-tier-may-look-read-only-at-what-an-owners-render-key-can-see.md +53 -0
  16. package/docs/adr/0349-a-version-preview-is-a-sandboxed-child-the-web-tier-launches.md +106 -0
  17. package/docs/adr/0350-the-hub-holds-a-per-project-write-deploy-key-so-a-hosted-upgrade-reaches-github.md +103 -0
  18. package/docs/adr/README.md +4 -0
  19. package/docs/api/openapi.json +995 -40
  20. package/docs/api-reference.md +33 -8
  21. package/docs/architecture.md +6 -2
  22. package/docs/copy-inventory.md +620 -553
  23. package/docs/copy-registry.json +1467 -820
  24. package/docs/file-map.md +2 -0
  25. package/docs/module-api-changelog.md +8 -0
  26. package/docs/modules-contract.md +1 -0
  27. package/docs/page-inventory.json +38 -4
  28. package/docs/page-readings.json +1674 -1566
  29. package/migrations/core_260_goals_working_area.sql +63 -0
  30. package/migrations/core_261_grade_attempts_verdicts.sql +24 -0
  31. package/migrations/core_262_drop_builder_box_blocked.sql +33 -0
  32. package/modules/agents/lib/validate.js +43 -0
  33. package/modules/autonomy/gauge.js +38 -2
  34. package/modules/grading/grader-subagent.js +37 -2
  35. package/modules/grading/grader-workers/reader.js +65 -5
  36. package/modules/hall-ui/public/approval-queue.css +33 -7
  37. package/modules/hall-ui/public/approval-queue.js +9 -2
  38. package/modules/hall-ui/public/atlas.html +1 -1
  39. package/modules/hall-ui/public/blockers.html +1 -1
  40. package/modules/hall-ui/public/board-room.html +1 -1
  41. package/modules/hall-ui/public/brand-holes.js +121 -0
  42. package/modules/hall-ui/public/collab.html +1 -1
  43. package/modules/hall-ui/public/collab.js +1 -1
  44. package/modules/hall-ui/public/copy-desk.html +1 -1
  45. package/modules/hall-ui/public/deploy.html +6 -1
  46. package/modules/hall-ui/public/deploy.js +22 -8
  47. package/modules/hall-ui/public/diagrams.html +1 -1
  48. package/modules/hall-ui/public/dom-utils.js +20 -0
  49. package/modules/hall-ui/public/drachmae.html +1 -1
  50. package/modules/hall-ui/public/fleet.html +1 -1
  51. package/modules/hall-ui/public/gate.html +1 -1
  52. package/modules/hall-ui/public/goals.html +1 -1
  53. package/modules/hall-ui/public/government.html +1 -1
  54. package/modules/hall-ui/public/idea.html +1 -1
  55. package/modules/hall-ui/public/idea.js +28 -0
  56. package/modules/hall-ui/public/ideas.html +1 -1
  57. package/modules/hall-ui/public/ideas.js +19 -3
  58. package/modules/hall-ui/public/index.html +5 -1
  59. package/modules/hall-ui/public/modules.html +1 -1
  60. package/modules/hall-ui/public/primer.html +1 -1
  61. package/modules/hall-ui/public/profile-nudge.js +21 -4
  62. package/modules/hall-ui/public/profile.html +1 -1
  63. package/modules/hall-ui/public/profile.js +1 -1
  64. package/modules/hall-ui/public/project-settings.html +1 -1
  65. package/modules/hall-ui/public/ranks.html +1 -1
  66. package/modules/hall-ui/public/roadmap.html +1 -1
  67. package/modules/hall-ui/public/roster.html +1 -1
  68. package/modules/hall-ui/public/sessions.html +1 -1
  69. package/modules/hall-ui/public/settings.html +19 -30
  70. package/modules/hall-ui/public/settings.js +24 -177
  71. package/modules/hall-ui/public/settings.states.json +1 -1
  72. package/modules/hall-ui/public/shell.js +4 -2
  73. package/modules/hall-ui/public/studio.css +14 -8
  74. package/modules/hall-ui/public/studio.html +5 -4
  75. package/modules/hall-ui/public/task.html +1 -1
  76. package/modules/hall-ui/public/thinking.css +6 -3
  77. package/modules/hall-ui/public/thinking.html +1 -1
  78. package/modules/hall-ui/public/tweak-editor-lib.js +82 -1
  79. package/modules/hall-ui/public/tweak-editor.css +142 -11
  80. package/modules/hall-ui/public/tweak-editor.html +25 -9
  81. package/modules/hall-ui/public/tweak-editor.js +243 -10
  82. package/modules/hall-ui/public/watch.html +1 -1
  83. package/modules/hall-ui/public/work.html +1 -1
  84. package/modules/hall-ui/records/approval-queue.md +6 -0
  85. package/modules/hall-ui/records/tweak-editor.md +6 -0
  86. package/modules/ideas/ratify.js +36 -8
  87. package/modules/ideas/routes/ratify-goal.js +6 -0
  88. package/modules/lifecycle/db-analytics.js +79 -10
  89. package/modules/lifecycle/db-claims.js +48 -1
  90. package/modules/lifecycle/db-deps-criteria.js +94 -40
  91. package/modules/lifecycle/db-goals.js +4 -1
  92. package/modules/lifecycle/db-grade.js +9 -1
  93. package/modules/lifecycle/db-relevance-flags.js +183 -0
  94. package/modules/lifecycle/db-versions.js +4 -3
  95. package/modules/lifecycle/db.js +19 -0
  96. package/modules/lifecycle/done-when.js +5 -3
  97. package/modules/lifecycle/est-advisory.js +31 -4
  98. package/modules/lifecycle/goal-task-relevance-advisory.js +153 -0
  99. package/modules/lifecycle/goal-task-relevance-judge.js +226 -0
  100. package/modules/lifecycle/migrations/lifecycle_014_goal_task_relevance_flags.sql +82 -0
  101. package/modules/lifecycle/module.json +2 -1
  102. package/modules/lifecycle/routes/claims.js +8 -1
  103. package/modules/lifecycle/routes/goal-task-relevance.js +72 -0
  104. package/modules/lifecycle/routes/goals.js +1 -1
  105. package/modules/lifecycle/routes/tasks.js +27 -24
  106. package/modules/lifecycle/routes/visuals.js +9 -0
  107. package/modules/lifecycle/ship-preflight.js +7 -11
  108. package/modules/lifecycle/task-visuals.js +109 -5
  109. package/modules/npm-release/module.json +2 -1
  110. package/modules/npm-release/preview/commands.js +81 -0
  111. package/modules/npm-release/preview/divert.js +95 -0
  112. package/modules/npm-release/preview/env.js +78 -0
  113. package/modules/npm-release/preview/proxy.js +107 -0
  114. package/modules/npm-release/preview/runtime.js +83 -0
  115. package/modules/npm-release/preview/supervisor.js +175 -0
  116. package/modules/npm-release/public/work.js +65 -1
  117. package/modules/npm-release/routes/preview.js +133 -0
  118. package/modules/provisioning/app-status.js +83 -0
  119. package/modules/provisioning/migrations/provisioning_028_render_standup.sql +46 -0
  120. package/modules/provisioning/module.json +4 -2
  121. package/modules/provisioning/pollers/app-liveness.js +110 -0
  122. package/modules/provisioning/provisioning.js +5 -5
  123. package/modules/provisioning/render-lookup.js +68 -0
  124. package/modules/provisioning/render-standup.js +134 -0
  125. package/modules/provisioning/routes/render-standup.js +165 -0
  126. package/modules/provisioning/starter-bundles.js +4 -1
  127. package/modules/public-landing/public/account-private.states.json +14 -0
  128. package/modules/public-landing/public/account.html +513 -0
  129. package/modules/public-landing/public/account.probes.json +37 -0
  130. package/modules/public-landing/public/account.states.json +16 -0
  131. package/modules/public-landing/public/index.html +5 -2
  132. package/modules/public-landing/public/projects.html +661 -18
  133. package/modules/public-landing/public/projects.states.json +8 -1
  134. package/modules/render-deploy/deploys.js +203 -0
  135. package/modules/render-deploy/migrations/render_deploy_001_app.sql +22 -0
  136. package/modules/render-deploy/module.json +25 -0
  137. package/modules/render-deploy/public/deploy.css +10 -0
  138. package/modules/render-deploy/public/deploy.js +249 -0
  139. package/modules/render-deploy/render.js +56 -0
  140. package/modules/render-deploy/routes/act.js +88 -0
  141. package/modules/render-deploy/routes/app.js +65 -0
  142. package/modules/render-deploy/routes/history.js +38 -0
  143. package/modules/specialities/db.js +27 -8
  144. package/modules/specialities/migrations/specialities_003_skills.sql +46 -0
  145. package/modules/specialities/routes/specialities.js +51 -3
  146. package/modules/specialities/skills.js +94 -0
  147. package/modules/specialities/specialities.js +56 -9
  148. package/modules/ui-design/kit/fixtures/me-cross-project-private.json +12 -0
  149. package/modules/ui-design/kit/fixtures/me__cross-project.json +15 -0
  150. package/modules/ui-design/kit/fixtures/provisioning-instance-render.json +39 -0
  151. package/modules/ui-design/kit/lib.js +3 -1
  152. package/modules/ui-design/kit/serve.js +96 -0
  153. package/package-lock.json +2 -2
  154. package/package.json +1 -1
  155. package/release-notes.json +173 -0
  156. package/scripts/gds/agents-sync.js +13 -3
  157. package/scripts/gds/autobongos-run.js +80 -2
  158. package/scripts/gds/autobongos-service.cmd +12 -0
  159. package/scripts/gds/copy-apply.js +14 -0
  160. package/scripts/gds/dev-box-guard.js +3 -2
  161. package/scripts/gds/fitness.js +9 -0
  162. package/scripts/gds/grade-correlation-audit.js +42 -12
  163. package/scripts/gds/grade-replay.js +1 -1
  164. package/scripts/gds/provision-core-upgrade.js +38 -7
  165. package/scripts/gds/provision-pin-key.js +176 -0
  166. package/scripts/gds/provision-render.js +189 -0
  167. package/scripts/gds/provision-teardown.js +9 -2
  168. package/scripts/gds/provision-units.js +31 -0
  169. package/scripts/gds/provision.js +12 -12
  170. package/scripts/gds/publish-manifest.js +1 -0
  171. package/scripts/gds/render-api.js +197 -0
  172. package/scripts/gds/render-payload.js +84 -0
  173. package/scripts/gds/run-unit-tests.js +10 -0
  174. package/scripts/gds/ship-finish.js +19 -20
  175. package/scripts/gds/smoke-dependencies.sh +37 -8
  176. package/scripts/gds/status.js +12 -4
  177. package/scripts/gds/upgrade.js +2 -2
  178. package/scripts/public-mirror-export.js +7 -1
  179. package/src/bongos/module-scope-map.js +13 -0
  180. package/src/bongos/serve-internal.js +3 -1
  181. package/src/module-api.js +10 -1
  182. package/src/platform-server.js +39 -0
  183. package/tests/account_privacy_flags.mjs +10 -6
  184. package/tests/agents_authoring.mjs +15 -1
  185. package/tests/agents_sync.mjs +106 -5
  186. package/tests/agents_validate.mjs +45 -0
  187. package/tests/api_path_404.mjs +6 -0
  188. package/tests/autobongos_cadence.mjs +7 -0
  189. package/tests/autobongos_loop.mjs +136 -1
  190. package/tests/autonomy_gauge.mjs +62 -0
  191. package/tests/blocker_hall_live_proof.mjs +275 -0
  192. package/tests/claim_gate_ci_unblock.mjs +116 -0
  193. package/tests/claim_gate_rebase.mjs +15 -3
  194. package/tests/collab_page.mjs +10 -0
  195. package/tests/conductor_main_e2e.mjs +97 -0
  196. package/tests/core_262_drop_box_blocked_db.mjs +143 -0
  197. package/tests/core_upgrade_runner.mjs +1 -0
  198. package/tests/dependency_writes_atomic.mjs +191 -0
  199. package/tests/deploy_page_projects.mjs +2 -1
  200. package/tests/effective_visibility_predicate.mjs +15 -0
  201. package/tests/est_advisory.mjs +27 -0
  202. package/tests/goal_map_page.mjs +26 -0
  203. package/tests/goal_task_relevance.mjs +518 -0
  204. package/tests/goal_working_area.mjs +146 -0
  205. package/tests/grade_attempts.mjs +61 -0
  206. package/tests/grade_attribution.mjs +23 -0
  207. package/tests/grade_correlation_audit.mjs +54 -1
  208. package/tests/grader_reader_lens.mjs +55 -2
  209. package/tests/grader_root_outage.mjs +6 -1
  210. package/tests/grader_subagent_tools_arg.mjs +102 -0
  211. package/tests/hall_approval_queue.mjs +54 -2
  212. package/tests/hall_settings_world.mjs +3 -1
  213. package/tests/hall_tweak_editor.mjs +316 -11
  214. package/tests/hub_account_page.mjs +394 -0
  215. package/tests/idea_detail_page.mjs +64 -0
  216. package/tests/idea_goal_ratification.mjs +64 -0
  217. package/tests/idea_spark_hall.mjs +60 -9
  218. package/tests/ideator_full_idea_shapes_space_proof.mjs +1 -1
  219. package/tests/landing_page.mjs +6 -3
  220. package/tests/module-scope-map.mjs +31 -8
  221. package/tests/module_api.mjs +1 -0
  222. package/tests/module_loader.mjs +1 -1
  223. package/tests/nav_permission_atoms.mjs +1 -1
  224. package/tests/no_phantom_mirror_workflow.mjs +24 -0
  225. package/tests/npm_release_preview_commands.mjs +89 -0
  226. package/tests/npm_release_preview_divert.mjs +164 -0
  227. package/tests/npm_release_preview_env.mjs +100 -0
  228. package/tests/npm_release_preview_proxy.mjs +162 -0
  229. package/tests/npm_release_preview_routes.mjs +208 -0
  230. package/tests/npm_release_preview_supervisor.mjs +203 -0
  231. package/tests/pin_write_key.mjs +383 -0
  232. package/tests/planning_session_skill.mjs +27 -0
  233. package/tests/platform_boot.mjs +71 -0
  234. package/tests/profile_nudge_links.mjs +86 -0
  235. package/tests/profile_ui_cross_project.mjs +1 -1
  236. package/tests/projects_hub.mjs +2 -2
  237. package/tests/projects_hub_app_status.mjs +249 -0
  238. package/tests/projects_hub_app_step.mjs +282 -52
  239. package/tests/projects_hub_render_connect.mjs +295 -0
  240. package/tests/provision_render.mjs +362 -0
  241. package/tests/provision_settings_apply.mjs +62 -0
  242. package/tests/provisioning_app_status.mjs +204 -0
  243. package/tests/provisioning_render_route.mjs +336 -0
  244. package/tests/provisioning_settings_apply.mjs +1 -1
  245. package/tests/provisioning_teardown_intent.mjs +2 -2
  246. package/tests/render_api.mjs +153 -0
  247. package/tests/render_check.mjs +2 -1
  248. package/tests/render_deploy.mjs +495 -0
  249. package/tests/ship_preflight.mjs +61 -8
  250. package/tests/smoke_dependencies_witness.mjs +46 -0
  251. package/tests/speciality_skills.mjs +214 -0
  252. package/tests/studio_room_height.mjs +70 -0
  253. package/tests/task_detail_includes.mjs +10 -0
  254. package/tests/task_visuals_instance_root.mjs +150 -0
  255. package/tests/tweak_batch_apply.mjs +21 -0
  256. package/tests/ui_design_kit.mjs +36 -4
  257. package/tests/upgrade.mjs +1 -1
  258. package/tests/upgrade_persist_pin.mjs +24 -0
@@ -0,0 +1,135 @@
1
+ # ADR 0347 — Every store module ships a how-to: a file in the module, a publish gate, and a Docs score shown on its own
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-09-29
5
+ - **Task:** [task 1004364](https://cloudbongos.com/builders#/task/1004364) (goal 1000091 — working area 5, Module distribution & economy)
6
+ - **Deciders:** Will (owner of working area 5) decided the how-to is a file, the "gate plus score" grader, that the Docs score is shown separately rather than averaged in, and the grader's model and cost ceiling; Claude wrote the record.
7
+ - **Related:** [ADR 0343](0343-a-module-score-is-a-security-gate-then-an-average-of-visible-parts.md) (the score; D1's parts table is amended here) · [ADR 0338](0338-modules-travel-through-a-bongos-hosted-store.md) (the store, the tarball and its hashes) · [ADR 0098](0098-public-mirror-publish-manifest.md) (`isPublishable()`) · [`docs/modules-contract.md`](../modules-contract.md) §6 (publish) and §7 (install) · follow-on tasks 1004365 (the file and the publish check), 1004366 (the store page and PDF), 1004367 (the AI grader)
8
+
9
+ ## Context
10
+
11
+ A buyer browsing the store (ADR 0338) sees a module's name, category and score (ADR 0343),
12
+ but nothing tells them how to use what they are getting. Today a module carries a
13
+ `CLAUDE.md` written for AI sessions working *inside* the module, not for the person who
14
+ installs it. There is no rule that an author explains their module at all.
15
+
16
+ The owner wants every store module to come with a how-to that works for a person or for any
17
+ AI, that the buyer can read before acquiring, and that is judged so good explanations are
18
+ visible.
19
+
20
+ ## Decision
21
+
22
+ ### D1 — The how-to is a file: `HOWTO.md` at the module root (owner)
23
+
24
+ The how-to is `modules/<key>/HOWTO.md`, plain Markdown. It is a **file in the module**, not a
25
+ hosted page, because:
26
+
27
+ - it travels inside the tarball, so it is covered by the manifest hashes and **cannot change
28
+ after a version is published** — the how-to a buyer read is the one they get;
29
+ - any AI or person can read and write Markdown, so the module does not depend on one vendor;
30
+ - it goes wherever the module goes, including instances that are offline.
31
+
32
+ The packaging allowlist (`isPublishable()`, ADR 0098) must include `HOWTO.md`; task 1004365
33
+ checks it is in the tarball.
34
+
35
+ ### D2 — Five required sections, kept short
36
+
37
+ `HOWTO.md` must contain these five level-2 headings, matched case-insensitively and in any
38
+ order, each with some text under it:
39
+
40
+ | Heading | What goes in it |
41
+ |---|---|
42
+ | `## What it does` | A few sentences: what the module adds and who it is for. |
43
+ | `## Install and enable` | How to get it (`bongos module install <key>`) and switch it on (hall Modules tab). |
44
+ | `## How to use it` | Its commands, routes and hall surfaces, with **one worked example**. |
45
+ | `## Configuration` | Env vars and settings, with defaults. "None." is a valid answer. |
46
+ | `## Limits and known issues` | What it doesn't do, and anything known to go wrong. "None known." is a valid answer. |
47
+
48
+ Other headings and sections are welcome. The list is short on purpose, so an author can meet
49
+ it in minutes; quality above that bar is what the Docs score (D5) rewards.
50
+
51
+ ### D3 — An optional Claude artifact link, shown as an extra and never graded (owner)
52
+
53
+ An author may set `howto.artifactUrl` in `module.json` to a Claude artifact (an `https://`
54
+ link on `claude.ai`). The store page shows it as **"Also available as a Claude page"**, next
55
+ to the file, never instead of it.
56
+
57
+ It is **never graded and never required**, because a hosted page can change after publish;
58
+ only the file is frozen with the version. The store validates the field's shape only.
59
+
60
+ ### D4 — The gate: no how-to, no publish
61
+
62
+ `bongos module publish` **refuses** a version when `HOWTO.md` is missing, or when any required
63
+ section (D2) is missing or empty. The refusal names the file or each failing section, e.g.
64
+ `HOWTO.md: section "Configuration" is empty`.
65
+
66
+ - It is a free, deterministic check: it reads headings, it does not judge writing.
67
+ - The CLI runs it before uploading, and the store runs it again on upload (as it already
68
+ re-checks hashes and the denylist, `modules-contract.md` §6), so a hand-built upload can't
69
+ skip it.
70
+ - It applies **only to store versions**. Core-bundled modules and `bongos module submit`
71
+ (proposing a module into core, ADR 0135) are not gated by it.
72
+ - Versions published before the gate existed are not delisted or re-checked; the gate applies
73
+ from the next version.
74
+
75
+ ### D5 — The Docs part: scored, visible, and **not** in the overall score (owner)
76
+
77
+ This amends ADR 0343 D1's parts table with one row:
78
+
79
+ | Part | What it measures | Available |
80
+ |---|---|---|
81
+ | **Docs** | clarity and completeness of the version's `HOWTO.md`, scored by an AI grader (task 1004367) | day one |
82
+
83
+ - **Scored 0–100 per version**, like every other part, because each version ships its own
84
+ how-to.
85
+ - **Shown separately, not averaged in (owner).** Docs is displayed with the other parts
86
+ (ADR 0343 D4), labelled as not counting toward the overall score. It does **not** join the
87
+ plain average in ADR 0343 D3, and so does not move the floor, ranking or cap
88
+ (tasks 1003807–1003809). The overall score stays a measure of how the module *works*; Docs
89
+ tells the buyer how well it is *explained*. A superseding ADR can fold it in later if the
90
+ owner wants.
91
+ - **The grader gives a short reason** with the number (a sentence on what is clear and what is
92
+ missing, such as "no worked example"), so an author knows what to fix.
93
+ - **"No score yet" is never a zero** (ADR 0343 D5). While grading is under way the part shows
94
+ **"Docs: pending"**; if grading could not run, **"Docs: not scored"**. A version is listed
95
+ as soon as it passes the gate; it never waits for the grader.
96
+ - **Price is never an input** (ADR 0343 D6). Free and paid modules are graded by the same
97
+ call, and the Metic+ override applies to Docs like any part, with an audit entry.
98
+
99
+ ### D6 — Cost: one Sonnet 5 call per version, capped at 5¢ (owner)
100
+
101
+ - **One grader call per published version**, made by the store after the version is accepted.
102
+ A failed call is retried once; if both fail, the part stays "not scored" until Metic+
103
+ re-runs it. Nothing re-grades a version automatically after that.
104
+ - **Model: Claude Sonnet 5** (`claude-sonnet-5`), the rung the task grader already uses
105
+ (`modules/grading/grader-rubric.json`). Changing the rung needs the area owner's nod.
106
+ - **Ceiling: 5¢ per call** at list price (Sonnet 5 is $2 in / $10 out per million tokens,
107
+ `src/bongos/llm-pricing.js`). The grader caps its output and its input to fit: a how-to too
108
+ long to read in full is cut to fit, and the reason says the score covers only the part read.
109
+ A typical how-to costs about 1–2¢.
110
+ - The platform pays, and each call is recorded in the cost log like any other API spend
111
+ (CLAUDE.md §4). At 5¢ a version, a thousand published versions is $50.
112
+
113
+ ## Consequences
114
+
115
+ - **Task 1004365** adds the scaffolded `HOWTO.md` (with the five headings) to
116
+ `bongos module new`, the section check in `bongos module publish`, the same check on the
117
+ store's upload route, and `HOWTO.md` in the packaging allowlist.
118
+ - **Task 1004366** renders the file as the module's store page, with a PDF download of the
119
+ same content, and shows the D3 link when present.
120
+ - **Task 1004367** builds the grader to D5 and D6, and stores its score and reason as the
121
+ Docs part.
122
+ - **Authors of existing store modules** must add a `HOWTO.md` before their next version.
123
+ - **The overall score and ranking are unchanged** by this ADR (D5).
124
+
125
+ ## Rejected
126
+
127
+ - **A hosted Claude artifact as the how-to.** It can change after publish, isn't covered by
128
+ the hashes, and ties the module to one vendor. Kept only as the optional extra (D3).
129
+ - **Gate only, no score.** It proves the sections exist, not that they are any good.
130
+ - **AI grader only, no gate.** It costs money for modules with no how-to at all, and an
131
+ outage would leave nothing enforcing the basics.
132
+ - **Docs in the plain average.** Considered; the owner chose to keep the overall score about
133
+ how the module works and show Docs beside it.
134
+ - **Grading core-bundled modules.** They reach instances through the core, not the store; the
135
+ store page is where the how-to is read.
@@ -0,0 +1,53 @@
1
+ # ADR 0348 — The web tier may look, read-only, at what an owner's Render key can see
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-09-29
5
+ - **Task:** [task 1004352](https://cloudbongos.com/builders#/task/1004352) (goal 1000106 — working area 1, Project creation)
6
+ - **Deciders:** Lars (Archon) chose both decisions in chat on 2026-09-29, from options Claude laid out. Claude wrote the record.
7
+ - **Amends:** [ADR 0111](0111-instance-hosting-provisioning-module.md) §2 ("the internet-facing web process never calls a cloud API") — one narrow, read-only exception
8
+ - **Keeps:** [ADR 0327](0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md) §2 (the key is borrowed, never stored) and §3 (we never destroy an owner's infrastructure) · [ADR 0345](0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md) (we host the hall; the app goes where the owner chooses)
9
+
10
+ ## Context
11
+
12
+ The setup wizard's app step lets an owner put their app on their own Render account ([task 1004351](https://cloudbongos.com/builders#/task/1004351), [task 1004352](https://cloudbongos.com/builders#/task/1004352)). Two things the owner must see before Create can only come from Render itself:
13
+
14
+ - **Whether the key works.** A refused key discovered after Create is a project with a broken promise on its done screen.
15
+ - **Which workspaces the key reaches.** A Render key belongs to a *user*, and reaches every workspace that user is in — measured on 2026-09-22 at three, one of them another builder's (ADR 0327). The owner has to choose one. Picking the first would be exactly the mistake ADR 0327 exists to prevent.
16
+
17
+ ADR 0111 §2 says the web process never calls a cloud API; the runner does, from the intent queue. The runner can't answer an owner who is still on the page, and keeping the key between a check and a Create would break ADR 0327 §2.
18
+
19
+ There was a second, related problem. Create queues the project's own `provision` run, and a project has one open-intent slot, so a separate `render-standup` request made a moment later collides with it.
20
+
21
+ ## Decision
22
+
23
+ ### 1. One read-only look, on the owner's own key
24
+
25
+ `POST /provisioning/render/lookup` (`modules/provisioning/render-lookup.js`) may call Render from the web tier, and nothing else may. Its limits are the whole exception:
26
+
27
+ - **GET only, two calls:** the workspace list, and the web services in one named workspace. Nothing in it can create, change or delete anything.
28
+ - **The key is the owner's own, held for that one request.** It travels in a POST body, never a URL; it is never stored, logged or sent back. Nothing encrypts it, because nothing keeps it.
29
+ - **Every service in the answer is checked to carry the workspace asked about.** One that doesn't voids the whole answer. This is the runner client's guard (`scripts/gds/render-api.js`), carried here as two GETs because a module may not require that file (ADR 0083).
30
+ - **It is capped per builder** (20 a minute): a wizard needs a handful.
31
+
32
+ What this does *not* change: every write to an owner's Render account still happens in the runner, from the queue, on a key borrowed for one run.
33
+
34
+ ### 2. The key rides the project's own setup run
35
+
36
+ When the wizard sends the key right after Create, and the project's `provision` run holds the open slot, the key is attached to **that** run (`attachToProvision`). It goes in one statement, only onto a run that holds no key yet, and the table's CHECK still refuses a key on a resolved run. After the project is up, the runner sets the app up on Render in the same drain (`afterProvision`) and clears the key whatever happens:
37
+
38
+ - **Project up, app set up:** done.
39
+ - **Project up, app failed:** the run is still a success, since the project is live. The app's failure is recorded on `app_host` and in the run's note.
40
+ - **Project failed:** Render is never called. The key is cleared at once, so a provision retry never carries it into a later run, and the owner connects Render again once the project is live.
41
+
42
+ ## Consequences
43
+
44
+ - The web tier now makes outbound calls to one third party, on user-supplied credentials. That is a new egress path, and the reason decision 1 is drawn so narrowly: read-only, one host, capped, key never kept.
45
+ - A compromised web tier could read keys from lookup requests. It could already read them from the setup request (ADR 0327 §2 says so plainly), so this adds no new exposure of the key.
46
+ - The key waits on the provision run for as long as the project takes to stand up: minutes, and at most the leg's 30-minute limit, after which it is refused and cleared.
47
+ - A resumed wizard draft asks for the key again, because the key never enters the draft.
48
+
49
+ ## Alternatives considered
50
+
51
+ - **Keep the rule strict: no check before Create.** The owner types a workspace id, and a refused key surfaces after Create. The owner rejected this: it fails the task's done-when and leaves the workspace choice to guesswork.
52
+ - **Let the browser call Render directly.** It depends on Render allowing cross-origin calls from our page, which we can't rely on. It would also move the workspace guard somewhere we can't test on the server.
53
+ - **Ask for the key on the project page once the project is live.** Simpler, but not one wizard pass. It stays available as the fallback when an app fails to set up ([task 1004184](https://cloudbongos.com/builders#/task/1004184)).
@@ -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`.
@@ -438,3 +438,7 @@ This keeps the decision history honest and traceable.
438
438
  | 0344 | [**A tester is a project that opts in once; an untested version reaches everyone after seven days** ([task 1003801](https://cloudbongos.com/builders#/task/1003801), goal 1000091 — working area 5, owner Will). Settles "willing user" for criterion `wa5-staged-rollout`. **D1 (owner):** a project's own admin switches on "tester" once and gets tester versions of every module it has, with a per-module "general only" override; never on by default; a builder cannot opt in someone else's project. **D2:** first releases and updates alike go to testers first. **D3 (owner):** a version moves to general after seven days if it still passes the security gate and tests, tested or not; the promotion gate may promote earlier or hold on evidence, never forever. **D4:** testers pay the normal price; any tester discount is area 8's call, named not decided. **D5:** tester installs, errors and crashes feed the score (ADR 0343). Rejected: per-module-only opt-in, one all-or-nothing switch, waiting for a tester, releasing at once.](0344-a-tester-is-a-project-that-opts-in-once.md) | modules / store / rollout |
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
+ | 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 |