@bongos/core 1.19.1072 → 1.19.1074

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 (297) hide show
  1. package/.bongos-core.json +320 -465
  2. package/.claude/skills/feedback/SKILL.md +2 -2
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +4 -60
  5. package/clients/bongos-client/index.cjs +4 -60
  6. package/clients/bongos-client/index.d.ts +5 -90
  7. package/clients/bongos-client/index.mjs +4 -60
  8. package/config/branding.neutral.json +1 -5
  9. package/config/modules.neutral.json +2 -3
  10. package/config/scheduled-routines.json +0 -2
  11. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
  12. package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
  13. package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
  14. package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
  15. package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +6 -2
  16. package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
  17. package/docs/adr/README.md +2 -1
  18. package/docs/api/openapi.json +171 -1661
  19. package/docs/api-reference.md +9 -41
  20. package/docs/architecture.md +16 -1
  21. package/docs/branding-contract.md +0 -3
  22. package/docs/copy-inventory.md +454 -479
  23. package/docs/copy-registry.json +746 -978
  24. package/docs/file-map.md +5 -34
  25. package/docs/module-api-changelog.md +4 -0
  26. package/docs/modules-contract.md +13 -1
  27. package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
  28. package/docs/onboarding/diagrams/README.md +3 -3
  29. package/docs/onboarding/diagrams/assertions.json +2 -22
  30. package/docs/onboarding/primer.md +9 -28
  31. package/docs/page-inventory.json +4 -1
  32. package/docs/page-readings.json +1032 -1077
  33. package/docs/recipes/render-pilot.md +29 -10
  34. package/migrations/core_257_module_store_registry.sql +87 -0
  35. package/migrations/core_258_drop_dev_box_tables.sql +55 -0
  36. package/modules/agents/module.json +1 -0
  37. package/modules/autonomy/module.json +1 -0
  38. package/modules/builder-settings/module.json +1 -0
  39. package/modules/builder-settings/render-prefs.js +4 -5
  40. package/modules/copy-desk/docx.js +78 -5
  41. package/modules/copy-desk/module.json +1 -0
  42. package/modules/copy-desk/routes/copy-desk.js +9 -9
  43. package/modules/copy-desk/tests/copy_docx.mjs +107 -1
  44. package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
  45. package/modules/discord/module.json +1 -0
  46. package/modules/economy/module.json +1 -0
  47. package/modules/economy/routes/credits.js +1 -2
  48. package/modules/government/catalog.js +13 -17
  49. package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
  50. package/modules/government/module.json +1 -0
  51. package/modules/government/protected-surfaces.json +3 -11
  52. package/modules/government/resolver.js +3 -46
  53. package/modules/government/routes/government.js +0 -2
  54. package/modules/government/session-scopes.js +43 -83
  55. package/modules/government/session-scopes.json +8 -18
  56. package/modules/grading/module.json +1 -0
  57. package/modules/hall-ui/module.json +1 -0
  58. package/modules/hall-ui/public/board-lib.js +2 -2
  59. package/modules/hall-ui/public/builders.js +29 -40
  60. package/modules/hall-ui/public/collab.html +1 -1
  61. package/modules/hall-ui/public/collab.js +1 -1
  62. package/modules/hall-ui/public/diagrams.html +3 -3
  63. package/modules/hall-ui/public/dom-utils.js +1 -1
  64. package/modules/hall-ui/public/gate.js +2 -2
  65. package/modules/hall-ui/public/gate.states.json +1 -1
  66. package/modules/hall-ui/public/hall-render.js +43 -79
  67. package/modules/hall-ui/public/index.html +5 -1
  68. package/modules/hall-ui/public/modules.js +1 -0
  69. package/modules/hall-ui/public/oversight.css +5 -5
  70. package/modules/hall-ui/public/palette.js +0 -11
  71. package/modules/hall-ui/public/primer.js +1 -1
  72. package/modules/hall-ui/public/sessions.html +1 -1
  73. package/modules/hall-ui/public/settings-sessions.js +1 -1
  74. package/modules/hall-ui/public/settings.css +0 -1
  75. package/modules/hall-ui/public/settings.html +4 -33
  76. package/modules/hall-ui/public/settings.js +3 -33
  77. package/modules/hall-ui/public/settings.states.json +2 -2
  78. package/modules/hall-ui/public/studio.html +1 -1
  79. package/modules/hall-ui/public/style.css +10 -13
  80. package/modules/hall-ui/public/task.html +4 -1
  81. package/modules/hall-ui/public/task.js +36 -1
  82. package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
  83. package/modules/hall-ui/public/tweak-editor.css +16 -0
  84. package/modules/hall-ui/public/tweak-editor.html +26 -3
  85. package/modules/hall-ui/public/tweak-editor.js +32 -1
  86. package/modules/hall-ui/public/work.js +1 -1
  87. package/modules/hall-ui/records/tweak-editor.md +1 -0
  88. package/modules/ideas/module.json +1 -0
  89. package/modules/lifecycle/db-claim-reads.js +1 -58
  90. package/modules/lifecycle/db-ship.js +0 -5
  91. package/modules/lifecycle/db.js +0 -4
  92. package/modules/lifecycle/kickoff-checklist.js +0 -4
  93. package/modules/lifecycle/lifecycle.js +0 -8
  94. package/modules/lifecycle/module.json +1 -0
  95. package/modules/lifecycle/routes/tasks.js +5 -7
  96. package/modules/memory/module.json +1 -0
  97. package/modules/npm-release/module.json +1 -0
  98. package/modules/onboarding/module.json +1 -0
  99. package/modules/onboarding/onboarding-state.js +17 -36
  100. package/modules/onboarding/routes/access-requests.js +3 -3
  101. package/modules/platform-identity/module.json +1 -0
  102. package/modules/platform-identity/platform-identity.js +13 -5
  103. package/modules/platform-identity/routes/sso.js +2 -2
  104. package/modules/platform-identity/tests/platform-identity.mjs +17 -3
  105. package/modules/provisioning/module.json +1 -0
  106. package/modules/provisioning/provisioning.js +2 -2
  107. package/modules/provisioning/starter-bundles.js +5 -25
  108. package/modules/public-landing/module.json +1 -0
  109. package/modules/public-landing/public/assets/cosmos.css +1 -1
  110. package/modules/public-landing/public/contact.html +1 -1
  111. package/modules/public-landing/public/privacy.html +1 -1
  112. package/modules/public-landing/public/projects.html +101 -32
  113. package/modules/public-landing/public/projects.probes.json +2 -2
  114. package/modules/public-landing/public/projects.states.json +4 -3
  115. package/modules/public-landing/public/terms.html +1 -1
  116. package/modules/security/module.json +1 -0
  117. package/modules/sessions/module.json +1 -0
  118. package/modules/specialities/module.json +1 -0
  119. package/modules/status-ui/module.json +1 -0
  120. package/modules/ui-design/module.json +1 -0
  121. package/package-lock.json +2 -2
  122. package/package.json +1 -1
  123. package/release-notes.json +84 -0
  124. package/scripts/gds/artifact-format.js +203 -0
  125. package/scripts/gds/audit-authorship.sh +1 -13
  126. package/scripts/gds/claim.js +0 -15
  127. package/scripts/gds/cli-lib.js +8 -8
  128. package/scripts/gds/diagram-facts.js +12 -23
  129. package/scripts/gds/do-api.js +13 -80
  130. package/scripts/gds/doc-cli-guard.js +2 -0
  131. package/scripts/gds/doctor.js +1 -1
  132. package/scripts/gds/feedback-latest.js +139 -9
  133. package/scripts/gds/fitness-checks-identity.js +2 -7
  134. package/scripts/gds/fitness-checks-write-validation.js +4 -0
  135. package/scripts/gds/fitness-ratchets.js +4 -4
  136. package/scripts/gds/fitness.js +10 -10
  137. package/scripts/gds/gen-diagrams.js +49 -71
  138. package/scripts/gds/http-api-client.js +12 -19
  139. package/scripts/gds/init.js +1 -1
  140. package/scripts/gds/lint-infra-exec.js +1 -1
  141. package/scripts/gds/local-preview-lib.js +8 -39
  142. package/scripts/gds/local-preview.js +2 -15
  143. package/scripts/gds/migration-namespace.js +3 -3
  144. package/scripts/gds/module-artifact.js +212 -0
  145. package/scripts/gds/module.js +95 -6
  146. package/scripts/gds/package-core.js +5 -116
  147. package/scripts/gds/provision-config.js +2 -2
  148. package/scripts/gds/provision-net.js +2 -3
  149. package/scripts/gds/provision.js +9 -9
  150. package/scripts/gds/publish-manifest.js +1 -2
  151. package/scripts/gds/regen-instance-docs.js +1 -1
  152. package/scripts/gds/run-unit-tests.js +2 -16
  153. package/scripts/gds/sandbox-stage.js +39 -341
  154. package/scripts/gds/ship-flow.js +6 -6
  155. package/scripts/gds/ship-land.js +3 -4
  156. package/scripts/gds/ship-regen.js +10 -41
  157. package/scripts/gds/ship.js +4 -24
  158. package/scripts/gds/start.js +0 -15
  159. package/scripts/hall-preview/README.md +5 -5
  160. package/scripts/hall-preview/server.js +6 -6
  161. package/scripts/render-diagrams.sh +1 -1
  162. package/src/bongos/api-errors.js +1 -1
  163. package/src/bongos/auth-github.js +2 -3
  164. package/src/bongos/auth.js +51 -126
  165. package/src/bongos/db-kernel.js +2 -3
  166. package/src/bongos/db.js +5 -28
  167. package/src/bongos/module-scope-map.js +14 -29
  168. package/src/bongos/module-store.js +141 -0
  169. package/src/bongos/route-rank-check.js +3 -16
  170. package/src/bongos/routes/auth.js +0 -116
  171. package/src/bongos/routes/builders.js +5 -15
  172. package/src/bongos/routes/instance.js +8 -14
  173. package/src/bongos/routes/me.js +6 -22
  174. package/src/bongos/routes/modules.js +83 -0
  175. package/src/bongos/routes.js +5 -0
  176. package/src/module-api.js +13 -6
  177. package/src/module-loader/catalog.js +3 -0
  178. package/src/module-loader/loader.js +4 -4
  179. package/src/module-loader/manifest-schema.js +15 -7
  180. package/src/module-seams.js +2 -3
  181. package/src/modules.js +42 -14
  182. package/tests/agents_spend_guard.mjs +1 -1
  183. package/tests/api_client.mjs +0 -15
  184. package/tests/auth_page_gate.mjs +10 -10
  185. package/tests/canonical_profile_url.mjs +1 -3
  186. package/tests/claim_action.mjs +4 -1
  187. package/tests/claim_from_task_and_home.mjs +127 -0
  188. package/tests/consumer_layout_boot.mjs +2 -0
  189. package/tests/copy_desk_page_docx.mjs +33 -2
  190. package/tests/core_upgrade_runner.mjs +6 -6
  191. package/tests/deploy_divergence_line.mjs +11 -11
  192. package/tests/diagram_facts_offset.mjs +1 -1
  193. package/tests/do_api.mjs +106 -0
  194. package/tests/feedback_latest.mjs +122 -0
  195. package/tests/fitness.mjs +44 -42
  196. package/tests/gate_approvals.mjs +0 -1
  197. package/tests/go_live.mjs +3 -3
  198. package/tests/government_abuse_matrix.mjs +20 -19
  199. package/tests/government_ownership_scope.mjs +33 -35
  200. package/tests/government_parity.mjs +2 -2
  201. package/tests/government_protected_surfaces.mjs +56 -37
  202. package/tests/government_require_permission.mjs +3 -3
  203. package/tests/government_seed.mjs +37 -4
  204. package/tests/government_session_scope.mjs +92 -366
  205. package/tests/hall_error_envelope.mjs +4 -0
  206. package/tests/hall_landing_boot.mjs +4 -1
  207. package/tests/hall_palette.mjs +1 -16
  208. package/tests/hall_record_world.mjs +6 -0
  209. package/tests/hall_tweak_editor.mjs +52 -1
  210. package/tests/host_topology_skips.mjs +1 -2
  211. package/tests/html_comment_nesting.mjs +137 -0
  212. package/tests/infra_exec_paths.mjs +9 -10
  213. package/tests/init.mjs +4 -5
  214. package/tests/instance_manifest.mjs +18 -25
  215. package/tests/kickoff_checklist.mjs +1 -1
  216. package/tests/lib_sh_resolution.mjs +0 -221
  217. package/tests/local_preview.mjs +0 -12
  218. package/tests/module-scope-map.mjs +9 -9
  219. package/tests/module_catalog.mjs +1 -1
  220. package/tests/module_cli.mjs +15 -15
  221. package/tests/module_contributions.mjs +6 -6
  222. package/tests/module_loader.mjs +6 -6
  223. package/tests/module_manifest.mjs +13 -4
  224. package/tests/module_store_publish.mjs +307 -0
  225. package/tests/module_store_publish_route.mjs +121 -0
  226. package/tests/module_store_registry_migration.mjs +70 -0
  227. package/tests/modules.mjs +56 -26
  228. package/tests/onboarding_route_signals.mjs +21 -31
  229. package/tests/onboarding_state.mjs +35 -49
  230. package/tests/permission_path.mjs +0 -3
  231. package/tests/platform_boot.mjs +1 -2
  232. package/tests/profile_route.mjs +1 -6
  233. package/tests/projects_hub.mjs +5 -5
  234. package/tests/projects_hub_app_step.mjs +138 -0
  235. package/tests/projects_hub_module_picker.mjs +2 -2
  236. package/tests/projects_hub_pre_uat.mjs +4 -5
  237. package/tests/provision.mjs +7 -7
  238. package/tests/provisioning_starter_bundles.mjs +0 -22
  239. package/tests/publish_branch_route.mjs +10 -18
  240. package/tests/publish_manifest.mjs +1 -2
  241. package/tests/rank_tier_single_source.mjs +1 -13
  242. package/tests/repo_map.mjs +3 -4
  243. package/tests/runner_drift.mjs +3 -21
  244. package/tests/sandbox_stage.mjs +57 -390
  245. package/tests/session_rename_fallback.mjs +0 -21
  246. package/tests/ship_error_shape.mjs +1 -1
  247. package/tests/task_visual_slots.mjs +31 -0
  248. package/tests/terms_acceptance.mjs +1 -1
  249. package/tests/upgrade.mjs +4 -4
  250. package/tests/watch_sealed_floor.mjs +0 -1
  251. package/tests/wizard_draft_resume.mjs +2 -2
  252. package/tests/wizard_intent_resume.mjs +21 -10
  253. package/tests/wizard_preselect_why.mjs +18 -18
  254. package/docs/onboarding/browser-terminal-guide.md +0 -73
  255. package/docs/recipes/managed-settings-remote-control.md +0 -416
  256. package/modules/dev-box/CLAUDE.md +0 -15
  257. package/modules/dev-box/box-access.js +0 -623
  258. package/modules/dev-box/box-credential.js +0 -94
  259. package/modules/dev-box/box-onboard.js +0 -486
  260. package/modules/dev-box/boxes.js +0 -1038
  261. package/modules/dev-box/db.js +0 -39
  262. package/modules/dev-box/module.json +0 -19
  263. package/modules/dev-box/routes/box.js +0 -1223
  264. package/scripts/gds/box-auth-check.js +0 -160
  265. package/scripts/gds/box-infra.js +0 -321
  266. package/scripts/gds/box-sync.js +0 -216
  267. package/scripts/gds/box.js +0 -1332
  268. package/scripts/gds/cf-tunnel.js +0 -558
  269. package/scripts/gds/smoke-box.sh +0 -150
  270. package/scripts/gds/tree-preflight.js +0 -241
  271. package/src/bongos/app-pair.js +0 -172
  272. package/tests/app_pair.mjs +0 -168
  273. package/tests/box_access.mjs +0 -578
  274. package/tests/box_auth_check.mjs +0 -114
  275. package/tests/box_code_staleness.mjs +0 -129
  276. package/tests/box_connect_e2e.mjs +0 -196
  277. package/tests/box_cost.mjs +0 -70
  278. package/tests/box_credential.mjs +0 -125
  279. package/tests/box_dns_repoint.mjs +0 -153
  280. package/tests/box_env.mjs +0 -64
  281. package/tests/box_host_keys.mjs +0 -126
  282. package/tests/box_infra_resolve.mjs +0 -246
  283. package/tests/box_onboard.mjs +0 -317
  284. package/tests/box_scope_session.mjs +0 -119
  285. package/tests/box_sweep_docs.mjs +0 -119
  286. package/tests/box_sync.mjs +0 -55
  287. package/tests/box_sync_scope_report.mjs +0 -126
  288. package/tests/box_task_scope.mjs +0 -169
  289. package/tests/box_task_scope_pause.mjs +0 -66
  290. package/tests/box_terminal_ssrf.mjs +0 -96
  291. package/tests/boxes.mjs +0 -1585
  292. package/tests/cf_ruleset_preflight.mjs +0 -142
  293. package/tests/cf_tunnel.mjs +0 -496
  294. package/tests/docs_scrubber_damage.mjs +0 -111
  295. package/tests/module_scope_active_claims.mjs +0 -94
  296. package/tests/ship_api_client_pathspec.mjs +0 -100
  297. package/tests/tree_preflight.mjs +0 -243
@@ -65,14 +65,6 @@
65
65
  "name": "blockers",
66
66
  "description": "Endpoints under `/api/bongos/blockers`."
67
67
  },
68
- {
69
- "name": "box",
70
- "description": "Endpoints under `/api/bongos/box`."
71
- },
72
- {
73
- "name": "boxes",
74
- "description": "Endpoints under `/api/bongos/boxes`."
75
- },
76
68
  {
77
69
  "name": "bug-attachments",
78
70
  "description": "Endpoints under `/api/bongos/bug-attachments`."
@@ -273,6 +265,10 @@
273
265
  "name": "sso",
274
266
  "description": "Endpoints under `/api/bongos/sso`."
275
267
  },
268
+ {
269
+ "name": "store",
270
+ "description": "Endpoints under `/api/bongos/store`."
271
+ },
276
272
  {
277
273
  "name": "task-recommendations",
278
274
  "description": "Endpoints under `/api/bongos/task-recommendations`."
@@ -1206,164 +1202,6 @@
1206
1202
  ]
1207
1203
  }
1208
1204
  },
1209
- "/auth/app-pair/approve": {
1210
- "post": {
1211
- "operationId": "post_auth_app_pair_approve",
1212
- "tags": [
1213
- "auth"
1214
- ],
1215
- "summary": "POST /auth/app-pair/approve",
1216
- "description": "(own identity only) — approving signs the app in AS THE APPROVER. Cookie-gated exactly like /auth/cli-token/issue above: a leaked CLI bearer must not be able to approve pairings and mint itself siblings; only the httpOnly browser cookie — the click in the hall — can.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
1217
- "x-rank": "any-builder",
1218
- "x-source": "src/bongos/routes/auth.js",
1219
- "requestBody": {
1220
- "required": true,
1221
- "content": {
1222
- "application/json": {
1223
- "schema": {
1224
- "$ref": "#/components/schemas/PostAuthAppPairApproveRequest"
1225
- }
1226
- }
1227
- },
1228
- "x-validated": true
1229
- },
1230
- "responses": {
1231
- "200": {
1232
- "description": "Success.",
1233
- "content": {
1234
- "application/json": {
1235
- "schema": {
1236
- "$ref": "#/components/schemas/PostAuthAppPairApproveResponse"
1237
- }
1238
- }
1239
- }
1240
- },
1241
- "400": {
1242
- "$ref": "#/components/responses/ValidationFailed"
1243
- },
1244
- "401": {
1245
- "$ref": "#/components/responses/Unauthorized"
1246
- },
1247
- "403": {
1248
- "$ref": "#/components/responses/Forbidden"
1249
- }
1250
- },
1251
- "security": [
1252
- {
1253
- "builderSession": []
1254
- }
1255
- ]
1256
- }
1257
- },
1258
- "/auth/app-pair/info": {
1259
- "get": {
1260
- "operationId": "get_auth_app_pair_info",
1261
- "tags": [
1262
- "auth"
1263
- ],
1264
- "summary": "GET /auth/app-pair/info",
1265
- "description": "read-only: what the approve page shows BEFORE the click (device name/platform for the given code). Unknown and expired are indistinguishable (404) so the code space can't be probed for liveness.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
1266
- "x-rank": "any-builder",
1267
- "x-source": "src/bongos/routes/auth.js",
1268
- "responses": {
1269
- "200": {
1270
- "description": "Success.",
1271
- "content": {
1272
- "application/json": {
1273
- "schema": {
1274
- "$ref": "#/components/schemas/GetAuthAppPairInfoResponse"
1275
- }
1276
- }
1277
- }
1278
- },
1279
- "400": {
1280
- "$ref": "#/components/responses/BadRequest"
1281
- },
1282
- "401": {
1283
- "$ref": "#/components/responses/Unauthorized"
1284
- },
1285
- "403": {
1286
- "$ref": "#/components/responses/Forbidden"
1287
- }
1288
- },
1289
- "security": [
1290
- {
1291
- "builderSession": []
1292
- }
1293
- ]
1294
- }
1295
- },
1296
- "/auth/app-pair/poll": {
1297
- "post": {
1298
- "operationId": "post_auth_app_pair_poll",
1299
- "tags": [
1300
- "auth"
1301
- ],
1302
- "summary": "POST /auth/app-pair/poll",
1303
- "description": "poll with the secret poll_token; the token IS the bearer (mirrors /auth/device/poll, where the device_code is the bearer). Kept LAST in this router so route-rank-check's look-ahead window stays clear of any neighboring gated declaration (see the block comment above).\n\n**Rank:** `public` — No authentication — any caller.",
1304
- "x-rank": "public",
1305
- "x-source": "src/bongos/routes/auth.js",
1306
- "requestBody": {
1307
- "required": true,
1308
- "content": {
1309
- "application/json": {
1310
- "schema": {
1311
- "$ref": "#/components/schemas/PostAuthAppPairPollRequest"
1312
- }
1313
- }
1314
- },
1315
- "x-validated": true
1316
- },
1317
- "responses": {
1318
- "200": {
1319
- "description": "Success."
1320
- },
1321
- "400": {
1322
- "$ref": "#/components/responses/ValidationFailed"
1323
- }
1324
- },
1325
- "security": []
1326
- }
1327
- },
1328
- "/auth/app-pair/start": {
1329
- "post": {
1330
- "operationId": "post_auth_app_pair_start",
1331
- "tags": [
1332
- "auth"
1333
- ],
1334
- "summary": "POST /auth/app-pair/start",
1335
- "description": "starts a pairing; returns opaque codes only, no identity.\n\n**Rank:** `public` — No authentication — any caller.",
1336
- "x-rank": "public",
1337
- "x-source": "src/bongos/routes/auth.js",
1338
- "requestBody": {
1339
- "required": false,
1340
- "content": {
1341
- "application/json": {
1342
- "schema": {
1343
- "$ref": "#/components/schemas/PostAuthAppPairStartRequest"
1344
- }
1345
- }
1346
- },
1347
- "x-validated": true
1348
- },
1349
- "responses": {
1350
- "200": {
1351
- "description": "Success.",
1352
- "content": {
1353
- "application/json": {
1354
- "schema": {
1355
- "$ref": "#/components/schemas/PostAuthAppPairStartResponse"
1356
- }
1357
- }
1358
- }
1359
- },
1360
- "400": {
1361
- "$ref": "#/components/responses/ValidationFailed"
1362
- }
1363
- },
1364
- "security": []
1365
- }
1366
- },
1367
1205
  "/auth/backchannel-logout": {
1368
1206
  "post": {
1369
1207
  "operationId": "post_auth_backchannel_logout",
@@ -2412,1001 +2250,41 @@
2412
2250
  "type": "string"
2413
2251
  },
2414
2252
  "description": "Path parameter `id`."
2415
- }
2416
- ],
2417
- "requestBody": {
2418
- "required": true,
2419
- "content": {
2420
- "application/json": {
2421
- "schema": {
2422
- "$ref": "#/components/schemas/PostBlockersIdLinkRequest"
2423
- }
2424
- }
2425
- },
2426
- "x-validated": true
2427
- },
2428
- "responses": {
2429
- "200": {
2430
- "description": "Success.",
2431
- "content": {
2432
- "application/json": {
2433
- "schema": {
2434
- "$ref": "#/components/schemas/PostBlockersIdLinkResponse"
2435
- }
2436
- }
2437
- }
2438
- },
2439
- "400": {
2440
- "$ref": "#/components/responses/ValidationFailed"
2441
- },
2442
- "401": {
2443
- "$ref": "#/components/responses/Unauthorized"
2444
- },
2445
- "403": {
2446
- "$ref": "#/components/responses/Forbidden"
2447
- },
2448
- "404": {
2449
- "$ref": "#/components/responses/NotFound"
2450
- }
2451
- },
2452
- "security": [
2453
- {
2454
- "builderSession": []
2455
- }
2456
- ]
2457
- }
2458
- },
2459
- "/blockers/{id}/link/{taskId}": {
2460
- "delete": {
2461
- "operationId": "delete_blockers_id_link_taskId",
2462
- "tags": [
2463
- "blockers"
2464
- ],
2465
- "summary": "DELETE /blockers/:id/link/:taskId",
2466
- "description": "Unlink a blocker from a task — the inverse of /link (task 1003518). Both ids sit in the PATH, matching DELETE /tasks/:id/dependencies/:depId, the closest existing shape: an edge between two rows, removed by naming both. Post-core_223 a task is released only when its LAST open blocker resolves, so a wrong link is permanent: the only way out was to resolve a blocker that was never real, putting a falsehood in the blocker ledger to clear a bookkeeping artifact. This is the honest way out. Promotion is the TRIGGER's job here exactly as it is on /resolve: core_256 fires AFTER DELETE on task_blockers and re-uses core_223's predicate, so \"nothing open is left against this task\" has one definition, not two. blockers.unlinkBlockerFromTask only deletes and reads back what happened. `released` says whether the task actually moved, never a bare {ok:true} (task 1002559 — a write that reports success without saying what changed is how a dead loop hides). Metic+ (task 360 / ADR 0018) — same blocker-review grant as link and resolve. rank: metic+archon — unlink a blocker from a task; same blocker-review grant as link.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `blocker.review` (all required).",
2467
- "x-rank": "metic+archon",
2468
- "x-source": "modules/ideas/routes/blockers.js",
2469
- "x-permissions": [
2470
- "blocker.review"
2471
- ],
2472
- "parameters": [
2473
- {
2474
- "name": "id",
2475
- "in": "path",
2476
- "required": true,
2477
- "schema": {
2478
- "type": "string"
2479
- },
2480
- "description": "Path parameter `id`."
2481
- },
2482
- {
2483
- "name": "taskId",
2484
- "in": "path",
2485
- "required": true,
2486
- "schema": {
2487
- "type": "string"
2488
- },
2489
- "description": "Path parameter `taskId`."
2490
- }
2491
- ],
2492
- "responses": {
2493
- "200": {
2494
- "description": "Success.",
2495
- "content": {
2496
- "application/json": {
2497
- "schema": {
2498
- "$ref": "#/components/schemas/DeleteBlockersIdLinkTaskIdResponse"
2499
- }
2500
- }
2501
- }
2502
- },
2503
- "400": {
2504
- "$ref": "#/components/responses/BadRequest"
2505
- },
2506
- "401": {
2507
- "$ref": "#/components/responses/Unauthorized"
2508
- },
2509
- "403": {
2510
- "$ref": "#/components/responses/Forbidden"
2511
- },
2512
- "404": {
2513
- "$ref": "#/components/responses/NotFound"
2514
- }
2515
- },
2516
- "security": [
2517
- {
2518
- "builderSession": []
2519
- }
2520
- ]
2521
- }
2522
- },
2523
- "/blockers/{id}/resolve": {
2524
- "post": {
2525
- "operationId": "post_blockers_id_resolve",
2526
- "tags": [
2527
- "blockers"
2528
- ],
2529
- "summary": "POST /blockers/:id/resolve",
2530
- "description": "Resolve. Auto-promotes any blocked tasks linked via task_blockers. Metic+ (task 360 / ADR 0018): blocker review is one of the operations C2 assigns to Metic, so this widened from archon-only to metic+archon when the three-rank model went live. Any builder can still FILE a blocker (POST /blockers above); a Xenos cannot resolve or link them. rank: metic+archon — blocker review (auto-promotes linked blocked tasks); criterion C2 / task 360 / ADR 0018.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `blocker.review` (all required).",
2531
- "x-rank": "metic+archon",
2532
- "x-source": "modules/ideas/routes/blockers.js",
2533
- "x-permissions": [
2534
- "blocker.review"
2535
- ],
2536
- "parameters": [
2537
- {
2538
- "name": "id",
2539
- "in": "path",
2540
- "required": true,
2541
- "schema": {
2542
- "type": "string"
2543
- },
2544
- "description": "Path parameter `id`."
2545
- }
2546
- ],
2547
- "requestBody": {
2548
- "required": false,
2549
- "content": {
2550
- "application/json": {
2551
- "schema": {
2552
- "$ref": "#/components/schemas/PostBlockersIdResolveRequest"
2553
- }
2554
- }
2555
- },
2556
- "x-validated": true
2557
- },
2558
- "responses": {
2559
- "200": {
2560
- "description": "Success."
2561
- },
2562
- "400": {
2563
- "$ref": "#/components/responses/ValidationFailed"
2564
- },
2565
- "401": {
2566
- "$ref": "#/components/responses/Unauthorized"
2567
- },
2568
- "403": {
2569
- "$ref": "#/components/responses/Forbidden"
2570
- },
2571
- "404": {
2572
- "$ref": "#/components/responses/NotFound"
2573
- }
2574
- },
2575
- "security": [
2576
- {
2577
- "builderSession": []
2578
- }
2579
- ]
2580
- }
2581
- },
2582
- "/box/authorized-keys": {
2583
- "get": {
2584
- "operationId": "get_box_authorized_keys",
2585
- "tags": [
2586
- "box"
2587
- ],
2588
- "summary": "GET /box/authorized-keys",
2589
- "description": "GET /box/authorized-keys — the box pulls its builder's authorized_keys. rank: any authenticated builder (own resource) — but the ALLOW/DENY is decided INSIDE from the caller's LIVE rank+status (boxAccess.decideSourceAccess), NOT a static requireRank, mirroring /box/source-access: a demoted/deactivated builder is 403'd and the box-side fetch empties the managed authorized_keys block on the very next cron — instant SSH-login revocation (ADR 0016 extended to login). The body is text/plain: the managed key LINES (no markers — the box wraps them). An allowed builder with no registered keys gets an empty 200 (a valid empty block). task 919: allowBoxScope — box-authorized-keys-fetch.sh (cron */10) runs box-scoped.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
2590
- "x-rank": "any-builder",
2591
- "x-source": "modules/dev-box/routes/box.js",
2592
- "responses": {
2593
- "200": {
2594
- "description": "Success."
2595
- },
2596
- "400": {
2597
- "$ref": "#/components/responses/BadRequest"
2598
- },
2599
- "401": {
2600
- "$ref": "#/components/responses/Unauthorized"
2601
- },
2602
- "403": {
2603
- "$ref": "#/components/responses/Forbidden"
2604
- }
2605
- },
2606
- "security": [
2607
- {
2608
- "builderSession": []
2609
- }
2610
- ]
2611
- }
2612
- },
2613
- "/box/close": {
2614
- "post": {
2615
- "operationId": "post_box_close",
2616
- "tags": [
2617
- "box"
2618
- ],
2619
- "summary": "POST /box/close",
2620
- "description": "POST /box/close — the builder manually requests their box be deprovisioned. rank: any authenticated builder (own resource). Enqueues a 'deprovision' intent; the control-plane runner executes it so the web tier never holds DO_API_TOKEN (trust boundary holds, same as /box/ensure). Only valid for an active box — a box that isn't running has nothing to close.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
2621
- "x-rank": "any-builder",
2622
- "x-source": "modules/dev-box/routes/box.js",
2623
- "responses": {
2624
- "200": {
2625
- "description": "Success.",
2626
- "content": {
2627
- "application/json": {
2628
- "schema": {
2629
- "$ref": "#/components/schemas/PostBoxCloseResponse"
2630
- }
2631
- }
2632
- }
2633
- },
2634
- "400": {
2635
- "$ref": "#/components/responses/BadRequest"
2636
- },
2637
- "401": {
2638
- "$ref": "#/components/responses/Unauthorized"
2639
- },
2640
- "403": {
2641
- "$ref": "#/components/responses/Forbidden"
2642
- }
2643
- },
2644
- "security": [
2645
- {
2646
- "builderSession": []
2647
- }
2648
- ]
2649
- }
2650
- },
2651
- "/box/connect": {
2652
- "get": {
2653
- "operationId": "get_box_connect",
2654
- "tags": [
2655
- "box"
2656
- ],
2657
- "summary": "GET /box/connect",
2658
- "description": "GET /box/connect — the human/Claude-readable connect walkthrough. rank: public — no auth on purpose: a brand-new builder fetches this from a bare laptop (no token yet) to learn the one command that onboards them. It contains no secrets, only instructions + the public bootstrap URLs.\n\n**Rank:** `public` — No authentication — any caller.",
2659
- "x-rank": "public",
2660
- "x-source": "modules/dev-box/routes/box.js",
2661
- "responses": {
2662
- "200": {
2663
- "description": "Success."
2664
- },
2665
- "400": {
2666
- "$ref": "#/components/responses/BadRequest"
2667
- }
2668
- },
2669
- "security": []
2670
- }
2671
- },
2672
- "/box/connect.ps1": {
2673
- "get": {
2674
- "operationId": "get_box_connect_ps1",
2675
- "tags": [
2676
- "box"
2677
- ],
2678
- "summary": "GET /box/connect.ps1",
2679
- "description": "GET /box/connect.ps1 — the Windows (PowerShell) bootstrap script. rank: public — same posture as connect.sh (non-secret installer; self-auths).\n\n**Rank:** `public` — No authentication — any caller.",
2680
- "x-rank": "public",
2681
- "x-source": "modules/dev-box/routes/box.js",
2682
- "responses": {
2683
- "200": {
2684
- "description": "Success."
2685
- },
2686
- "400": {
2687
- "$ref": "#/components/responses/BadRequest"
2688
- }
2689
- },
2690
- "security": []
2691
- }
2692
- },
2693
- "/box/connect.sh": {
2694
- "get": {
2695
- "operationId": "get_box_connect_sh",
2696
- "tags": [
2697
- "box"
2698
- ],
2699
- "summary": "GET /box/connect.sh",
2700
- "description": "GET /box/connect.sh — the macOS/Linux bootstrap script. rank: public — non-secret installer (the rustup/nvm pattern). It does its OWN GitHub device-flow auth before it touches anything; serving it needs no auth.\n\n**Rank:** `public` — No authentication — any caller.",
2701
- "x-rank": "public",
2702
- "x-source": "modules/dev-box/routes/box.js",
2703
- "responses": {
2704
- "200": {
2705
- "description": "Success."
2706
- },
2707
- "400": {
2708
- "$ref": "#/components/responses/BadRequest"
2709
- }
2710
- },
2711
- "security": []
2712
- }
2713
- },
2714
- "/box/ensure": {
2715
- "post": {
2716
- "operationId": "post_box_ensure",
2717
- "tags": [
2718
- "box"
2719
- ],
2720
- "summary": "POST /box/ensure",
2721
- "description": "POST /box/ensure — \"make my box ready\". rank: any authenticated builder (own resource) — the ALLOW/DENY + the chosen ACTION are decided INSIDE from the caller's LIVE rank+status (boxes.decideEnsureAction), NOT a static requireRank, mirroring /box/source-access. This single call covers BOTH halves of task 701's gap: a builder with no box gets a provision queued (auto-provision), a builder whose box is parked gets a wake queued (wake-on-connect). It enqueues at most one open intent per builder; the actual DO work happens on the control plane.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
2722
- "x-rank": "any-builder",
2723
- "x-source": "modules/dev-box/routes/box.js",
2724
- "responses": {
2725
- "200": {
2726
- "description": "Success.",
2727
- "content": {
2728
- "application/json": {
2729
- "schema": {
2730
- "$ref": "#/components/schemas/PostBoxEnsureResponse"
2731
- }
2732
- }
2733
- }
2734
- },
2735
- "400": {
2736
- "$ref": "#/components/responses/BadRequest"
2737
- },
2738
- "401": {
2739
- "$ref": "#/components/responses/Unauthorized"
2740
- },
2741
- "403": {
2742
- "$ref": "#/components/responses/Forbidden"
2743
- }
2744
- },
2745
- "security": [
2746
- {
2747
- "builderSession": []
2748
- }
2749
- ]
2750
- }
2751
- },
2752
- "/box/heartbeat": {
2753
- "post": {
2754
- "operationId": "post_box_heartbeat",
2755
- "tags": [
2756
- "box"
2757
- ],
2758
- "summary": "POST /box/heartbeat",
2759
- "description": "POST /box/heartbeat — the box reports it is alive, resetting the idle clock. rank: any authenticated builder (own resource write). Optional body: { claude_active: bool, attached: bool }. `claude_active` records whether a `claude` process is running; the idle sweep honours it as a veto, but since task 1003507 only for BOX_UNATTENDED_MAX_HOURS (a running process is not a person, and an unbounded veto pinned boxes active forever). `attached` is the human signal — a login session, an inbound SSH connection, an open browser terminal, or an attached tmux client — and stamps last_attached_at, the clock that cap is measured against. A heartbeat that omits `attached` (any box still running the pre-1003507 script) leaves the column NULL, which the sweep reads as \"no data\" and not as \"unattended\". Heartbeats without a body behave as before (last_activity_at bumped only). task 919: allowBoxScope — box-heartbeat.sh (cron */5) runs with the box-scoped session.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
2760
- "x-rank": "any-builder",
2761
- "x-source": "modules/dev-box/routes/box.js",
2762
- "requestBody": {
2763
- "required": false,
2764
- "content": {
2765
- "application/json": {
2766
- "schema": {
2767
- "$ref": "#/components/schemas/PostBoxHeartbeatRequest"
2768
- }
2769
- }
2770
- },
2771
- "x-validated": true
2772
- },
2773
- "responses": {
2774
- "200": {
2775
- "description": "Success.",
2776
- "content": {
2777
- "application/json": {
2778
- "schema": {
2779
- "$ref": "#/components/schemas/PostBoxHeartbeatResponse"
2780
- }
2781
- }
2782
- }
2783
- },
2784
- "400": {
2785
- "$ref": "#/components/responses/ValidationFailed"
2786
- },
2787
- "401": {
2788
- "$ref": "#/components/responses/Unauthorized"
2789
- },
2790
- "403": {
2791
- "$ref": "#/components/responses/Forbidden"
2792
- }
2793
- },
2794
- "security": [
2795
- {
2796
- "builderSession": []
2797
- }
2798
- ]
2799
- }
2800
- },
2801
- "/box/host-keys": {
2802
- "post": {
2803
- "operationId": "post_box_host_keys",
2804
- "tags": [
2805
- "box"
2806
- ],
2807
- "summary": "POST /box/host-keys",
2808
- "description": "POST /box/host-keys — the box publishes its own PUBLIC SSH host keys (task 1187 / idea 235). rank: any authenticated builder (own resource) — the BOX calls this as its builder via the GDS token on the box; allowBoxScope so the box-scoped baked session (ADR 0053) can reach it, exactly like box-report-terminal.sh / the heartbeat reporter. Stored ONLY for an active box (setBoxHostKeys gates on it). Served back to the owner via GET /box/me; the connect path writes them to known_hosts authoritatively (no TOFU, no keyscan race), killing the post-re-provision \"Host denied\". host_keys are PUBLIC — never a secret — but the write is validated hard (anti-injection, >=1 ed25519).\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
2809
- "x-rank": "any-builder",
2810
- "x-source": "modules/dev-box/routes/box.js",
2811
- "requestBody": {
2812
- "required": true,
2813
- "content": {
2814
- "application/json": {
2815
- "schema": {
2816
- "$ref": "#/components/schemas/PostBoxHostKeysRequest"
2817
- }
2818
- }
2819
- },
2820
- "x-validated": true
2821
- },
2822
- "responses": {
2823
- "200": {
2824
- "description": "Success.",
2825
- "content": {
2826
- "application/json": {
2827
- "schema": {
2828
- "$ref": "#/components/schemas/PostBoxHostKeysResponse"
2829
- }
2830
- }
2831
- }
2832
- },
2833
- "400": {
2834
- "$ref": "#/components/responses/ValidationFailed"
2835
- },
2836
- "401": {
2837
- "$ref": "#/components/responses/Unauthorized"
2838
- },
2839
- "403": {
2840
- "$ref": "#/components/responses/Forbidden"
2841
- }
2842
- },
2843
- "security": [
2844
- {
2845
- "builderSession": []
2846
- }
2847
- ]
2848
- }
2849
- },
2850
- "/box/managed-settings": {
2851
- "get": {
2852
- "operationId": "get_box_managed_settings",
2853
- "tags": [
2854
- "box"
2855
- ],
2856
- "summary": "GET /box/managed-settings",
2857
- "description": "GET /box/managed-settings — the caller's Claude Desktop managed-settings.json. rank: any authenticated builder (own resource; non-secret — just their box hostname + the SSH identity path). Issued regardless of box state: the builder needs it on disk BEFORE the operator provisions, then connects once the box is up.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
2858
- "x-rank": "any-builder",
2859
- "x-source": "modules/dev-box/routes/box.js",
2860
- "responses": {
2861
- "200": {
2862
- "description": "Success."
2863
- },
2864
- "400": {
2865
- "$ref": "#/components/responses/BadRequest"
2866
- },
2867
- "401": {
2868
- "$ref": "#/components/responses/Unauthorized"
2869
- },
2870
- "403": {
2871
- "$ref": "#/components/responses/Forbidden"
2872
- }
2873
- },
2874
- "security": [
2875
- {
2876
- "builderSession": []
2877
- }
2878
- ]
2879
- }
2880
- },
2881
- "/box/me": {
2882
- "get": {
2883
- "operationId": "get_box_me",
2884
- "tags": [
2885
- "box"
2886
- ],
2887
- "summary": "GET /box/me",
2888
- "description": "GET /box/me — the caller's own box. rank: any authenticated builder (own resource read). Returns { box: { state:'none' } } when they have no box yet.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
2889
- "x-rank": "any-builder",
2890
- "x-source": "modules/dev-box/routes/box.js",
2891
- "responses": {
2892
- "200": {
2893
- "description": "Success.",
2894
- "content": {
2895
- "application/json": {
2896
- "schema": {
2897
- "$ref": "#/components/schemas/GetBoxMeResponse"
2898
- }
2899
- }
2900
- }
2901
- },
2902
- "400": {
2903
- "$ref": "#/components/responses/BadRequest"
2904
- },
2905
- "401": {
2906
- "$ref": "#/components/responses/Unauthorized"
2907
- },
2908
- "403": {
2909
- "$ref": "#/components/responses/Forbidden"
2910
- }
2911
- },
2912
- "security": [
2913
- {
2914
- "builderSession": []
2915
- }
2916
- ]
2917
- }
2918
- },
2919
- "/box/scope": {
2920
- "get": {
2921
- "operationId": "get_box_scope",
2922
- "tags": [
2923
- "box"
2924
- ],
2925
- "summary": "GET /box/scope",
2926
- "description": "GET /box/scope — the credential-FREE companion to /box/source-access (BV1.R100 / goal 1000051). Answers \"what code does my box hold RIGHT NOW?\" — the resolved sparse set + the module keys of the builder's active claims — WITHOUT issuing the pull credential. The laptop app reads it to confirm \"your box now has <module> for task N\" after a pick+claim; R104's proof reads it to VERIFY the scope is minimal (excludes other modules). Same live rank+ status + lifecycle-port scope logic as source-access, so the answer can't drift from the actual checkout — it just omits the secret. Deliberately NOT gated on a live box (boxCanHoldSource): the SCOPE is metadata, so a builder can ask \"what WOULD my box hold for my current claims\" the instant they claim, before the box has even synced. The ALLOW/DENY still comes from the live rank+status (a demoted/inactive builder gets the same 403 as source-access). allowBoxScope so it works both from the laptop app (full session) and on the box (box-scoped session) — safe here precisely because no credential is returned.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
2927
- "x-rank": "any-builder",
2928
- "x-source": "modules/dev-box/routes/box.js",
2929
- "responses": {
2930
- "200": {
2931
- "description": "Success."
2932
- },
2933
- "400": {
2934
- "$ref": "#/components/responses/BadRequest"
2935
- },
2936
- "401": {
2937
- "$ref": "#/components/responses/Unauthorized"
2938
- },
2939
- "403": {
2940
- "$ref": "#/components/responses/Forbidden"
2941
- }
2942
- },
2943
- "security": [
2944
- {
2945
- "builderSession": []
2946
- }
2947
- ]
2948
- }
2949
- },
2950
- "/box/source-access": {
2951
- "get": {
2952
- "operationId": "get_box_source_access",
2953
- "tags": [
2954
- "box"
2955
- ],
2956
- "summary": "GET /box/source-access",
2957
- "description": "GET /box/source-access — the box fetches its rank-scoped clone spec + the credential to pull it (ADR 0031 §6, task 600). rank: any authenticated builder (own resource) — the ALLOW/DENY and the SCOPE are decided INSIDE from the caller's LIVE rank+status, NOT by a static requireRank: a Xenos must still get their 'starter' credential, a Metic+ gets 'full', and a below-floor / inactive builder is denied (403). This is ADR 0016 server-enforcement extended from \"writes are gated\" to \"source is gated\": the box presents the BUILDER's session, the GDS reads the live DB rank (unforgeable from the box), so a demoted/deactivated builder is cut off on their VERY NEXT fetch — the instant, credential-layer half of the offboarding clawback (the droplet teardown is the slower control-plane half via `box.js reconcile`). The credential is returned ONLY here, from the prod env (never the repo — ADR 0022), and is NEVER logged or exposed by publicBox()/the roster. task 919: allowBoxScope — one of the 4 endpoints the on-box crons call, so a box-scoped session (source='box') is permitted here. box-source-fetch.sh (cron */10).\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
2958
- "x-rank": "any-builder",
2959
- "x-source": "modules/dev-box/routes/box.js",
2960
- "responses": {
2961
- "200": {
2962
- "description": "Success.",
2963
- "content": {
2964
- "application/json": {
2965
- "schema": {
2966
- "$ref": "#/components/schemas/GetBoxSourceAccessResponse"
2967
- }
2968
- }
2969
- }
2970
- },
2971
- "400": {
2972
- "$ref": "#/components/responses/BadRequest"
2973
- },
2974
- "401": {
2975
- "$ref": "#/components/responses/Unauthorized"
2976
- },
2977
- "403": {
2978
- "$ref": "#/components/responses/Forbidden"
2979
- }
2980
- },
2981
- "security": [
2982
- {
2983
- "builderSession": []
2984
- }
2985
- ]
2986
- }
2987
- },
2988
- "/box/ssh-key": {
2989
- "get": {
2990
- "operationId": "get_box_ssh_key",
2991
- "tags": [
2992
- "box"
2993
- ],
2994
- "summary": "GET /box/ssh-key",
2995
- "description": "GET /box/ssh-key — list the caller's registered keys. rank: any builder (own).\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
2996
- "x-rank": "any-builder",
2997
- "x-source": "modules/dev-box/routes/box.js",
2998
- "responses": {
2999
- "200": {
3000
- "description": "Success.",
3001
- "content": {
3002
- "application/json": {
3003
- "schema": {
3004
- "$ref": "#/components/schemas/GetBoxSshKeyResponse"
3005
- }
3006
- }
3007
- }
3008
- },
3009
- "400": {
3010
- "$ref": "#/components/responses/BadRequest"
3011
- },
3012
- "401": {
3013
- "$ref": "#/components/responses/Unauthorized"
3014
- },
3015
- "403": {
3016
- "$ref": "#/components/responses/Forbidden"
3017
- }
3018
- },
3019
- "security": [
3020
- {
3021
- "builderSession": []
3022
- }
3023
- ]
3024
- },
3025
- "post": {
3026
- "operationId": "post_box_ssh_key",
3027
- "tags": [
3028
- "box"
3029
- ],
3030
- "summary": "POST /box/ssh-key",
3031
- "description": "POST /box/ssh-key — register the caller's own public SSH key. rank: any authenticated builder (own resource write). The key is validated + normalized (box-onboard.validateSshPublicKey) so nothing attacker-shaped can reach the box's authorized_keys; the audit_log middleware records the write.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
3032
- "x-rank": "any-builder",
3033
- "x-source": "modules/dev-box/routes/box.js",
3034
- "requestBody": {
3035
- "required": true,
3036
- "content": {
3037
- "application/json": {
3038
- "schema": {
3039
- "$ref": "#/components/schemas/PostBoxSshKeyRequest"
3040
- }
3041
- }
3042
- },
3043
- "x-validated": true
3044
- },
3045
- "responses": {
3046
- "200": {
3047
- "description": "Success.",
3048
- "content": {
3049
- "application/json": {
3050
- "schema": {
3051
- "$ref": "#/components/schemas/PostBoxSshKeyResponse"
3052
- }
3053
- }
3054
- }
3055
- },
3056
- "400": {
3057
- "$ref": "#/components/responses/ValidationFailed"
3058
- },
3059
- "401": {
3060
- "$ref": "#/components/responses/Unauthorized"
3061
- },
3062
- "403": {
3063
- "$ref": "#/components/responses/Forbidden"
3064
- }
3065
- },
3066
- "security": [
3067
- {
3068
- "builderSession": []
3069
- }
3070
- ]
3071
- }
3072
- },
3073
- "/box/ssh-key/{id}": {
3074
- "delete": {
3075
- "operationId": "delete_box_ssh_key_id",
3076
- "tags": [
3077
- "box"
3078
- ],
3079
- "summary": "DELETE /box/ssh-key/:id",
3080
- "description": "DELETE /box/ssh-key/:id — remove one of the caller's keys. rank: any builder (own resource; the delete is builder_id-scoped in SQL so an id from another builder cannot be removed). 404 when the id isn't the caller's.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
3081
- "x-rank": "any-builder",
3082
- "x-source": "modules/dev-box/routes/box.js",
3083
- "parameters": [
3084
- {
3085
- "name": "id",
3086
- "in": "path",
3087
- "required": true,
3088
- "schema": {
3089
- "type": "string"
3090
- },
3091
- "description": "Path parameter `id`."
3092
- }
3093
- ],
3094
- "responses": {
3095
- "200": {
3096
- "description": "Success.",
3097
- "content": {
3098
- "application/json": {
3099
- "schema": {
3100
- "$ref": "#/components/schemas/DeleteBoxSshKeyIdResponse"
3101
- }
3102
- }
3103
- }
3104
- },
3105
- "400": {
3106
- "$ref": "#/components/responses/BadRequest"
3107
- },
3108
- "401": {
3109
- "$ref": "#/components/responses/Unauthorized"
3110
- },
3111
- "403": {
3112
- "$ref": "#/components/responses/Forbidden"
3113
- },
3114
- "404": {
3115
- "$ref": "#/components/responses/NotFound"
3116
- }
3117
- },
3118
- "security": [
3119
- {
3120
- "builderSession": []
3121
- }
3122
- ]
3123
- }
3124
- },
3125
- "/box/terminal": {
3126
- "get": {
3127
- "operationId": "get_box_terminal",
3128
- "tags": [
3129
- "box"
3130
- ],
3131
- "summary": "GET /box/terminal",
3132
- "description": "GET /box/terminal — the caller reads their box's web-terminal URL + credential (the Chromebook on-ramp, ADR 0038: ttyd behind a Cloudflare Tunnel). rank: any authenticated builder (own resource). Read-only by design: it does NOT wake a parked box (that mutation belongs to POST /box/ensure). The Chromebook flow is: POST /box/ensure → poll this until state='active' and a url appears → open the url, enter the basic-auth credential, run `claude` in the terminal. The url AND credential are returned ONLY to the box's own builder, and are null until the box's terminal reporter (box-report-terminal.sh) has published them.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
3133
- "x-rank": "any-builder",
3134
- "x-source": "modules/dev-box/routes/box.js",
3135
- "responses": {
3136
- "200": {
3137
- "description": "Success.",
3138
- "content": {
3139
- "application/json": {
3140
- "schema": {
3141
- "$ref": "#/components/schemas/GetBoxTerminalResponse"
3142
- }
3143
- }
3144
- }
3145
- },
3146
- "400": {
3147
- "$ref": "#/components/responses/BadRequest"
3148
- },
3149
- "401": {
3150
- "$ref": "#/components/responses/Unauthorized"
3151
- },
3152
- "403": {
3153
- "$ref": "#/components/responses/Forbidden"
3154
- }
3155
- },
3156
- "security": [
3157
- {
3158
- "builderSession": []
3159
- }
3160
- ]
3161
- },
3162
- "post": {
3163
- "operationId": "post_box_terminal",
3164
- "tags": [
3165
- "box"
3166
- ],
3167
- "summary": "POST /box/terminal",
3168
- "description": "POST /box/terminal — the box publishes its current web-terminal URL + the per-box basic-auth credential. rank: any authenticated builder (own resource) — the BOX calls this as its builder, using the GDS token already on the box. Both are stored ONLY when the box is active (boxes.setTerminalAccess enforces this), so a stale URL/credential can never be served after a park. The URL is validated to a bounded https URL; the credential is a bounded opaque string (a secret), only ever returned to its own builder (GET /box/terminal). task 919: allowBoxScope — box-report-terminal.sh (credsync) runs box-scoped. The GET above is the builder reading from the hall, so it stays full-auth (not box-scoped).\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
3169
- "x-rank": "any-builder",
3170
- "x-source": "modules/dev-box/routes/box.js",
3171
- "requestBody": {
3172
- "required": true,
3173
- "content": {
3174
- "application/json": {
3175
- "schema": {
3176
- "$ref": "#/components/schemas/PostBoxTerminalRequest"
3177
- }
3178
- }
3179
- },
3180
- "x-validated": true
3181
- },
3182
- "responses": {
3183
- "200": {
3184
- "description": "Success.",
3185
- "content": {
3186
- "application/json": {
3187
- "schema": {
3188
- "$ref": "#/components/schemas/PostBoxTerminalResponse"
3189
- }
3190
- }
3191
- }
3192
- },
3193
- "400": {
3194
- "$ref": "#/components/responses/ValidationFailed"
3195
- },
3196
- "401": {
3197
- "$ref": "#/components/responses/Unauthorized"
3198
- },
3199
- "403": {
3200
- "$ref": "#/components/responses/Forbidden"
3201
- }
3202
- },
3203
- "security": [
3204
- {
3205
- "builderSession": []
3206
- }
3207
- ]
3208
- }
3209
- },
3210
- "/box/version": {
3211
- "post": {
3212
- "operationId": "post_box_version",
3213
- "tags": [
3214
- "box"
3215
- ],
3216
- "summary": "POST /box/version",
3217
- "description": "POST /box/version — the box reports its /workspace HEAD commit so the server can flag a stale box (idea 332 / task 1316, ADR 0072). Body: { sha: \"<40-hex>\", committed_at: \"<ISO date>\" } rank: any-builder — own resource (the box reports its OWN code). task 919 pattern: allowBoxScope so the on-box cron (box-report-version.sh, box-scoped session) can call it, exactly like /box/host-keys + /box/heartbeat. Non-secret.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
3218
- "x-rank": "any-builder",
3219
- "x-source": "modules/dev-box/routes/box.js",
3220
- "requestBody": {
3221
- "required": true,
3222
- "content": {
3223
- "application/json": {
3224
- "schema": {
3225
- "$ref": "#/components/schemas/PostBoxVersionRequest"
3226
- }
3227
- }
3228
- },
3229
- "x-validated": true
3230
- },
3231
- "responses": {
3232
- "200": {
3233
- "description": "Success.",
3234
- "content": {
3235
- "application/json": {
3236
- "schema": {
3237
- "$ref": "#/components/schemas/PostBoxVersionResponse"
3238
- }
3239
- }
3240
- }
3241
- },
3242
- "400": {
3243
- "$ref": "#/components/responses/ValidationFailed"
3244
- },
3245
- "401": {
3246
- "$ref": "#/components/responses/Unauthorized"
3247
- },
3248
- "403": {
3249
- "$ref": "#/components/responses/Forbidden"
3250
- }
3251
- },
3252
- "security": [
3253
- {
3254
- "builderSession": []
3255
- }
3256
- ]
3257
- }
3258
- },
3259
- "/box/widen": {
3260
- "get": {
3261
- "operationId": "get_box_widen",
3262
- "tags": [
3263
- "box"
3264
- ],
3265
- "summary": "GET /box/widen",
3266
- "description": "GET /box/widen — the caller's own recorded widen set (task 1003087). rank: any authenticated builder, own resource. Reports what is STORED plus what is currently ADMITTED, because those differ whenever a request sits outside the builder's rank scope — showing only the stored list would let a Xenos believe a widen took effect that the fetch quietly drops.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
3267
- "x-rank": "any-builder",
3268
- "x-source": "modules/dev-box/routes/box.js",
3269
- "responses": {
3270
- "200": {
3271
- "description": "Success.",
3272
- "content": {
3273
- "application/json": {
3274
- "schema": {
3275
- "$ref": "#/components/schemas/GetBoxWidenResponse"
3276
- }
3277
- }
3278
- }
3279
- },
3280
- "400": {
3281
- "$ref": "#/components/responses/BadRequest"
3282
- },
3283
- "401": {
3284
- "$ref": "#/components/responses/Unauthorized"
3285
- },
3286
- "403": {
3287
- "$ref": "#/components/responses/Forbidden"
3288
- }
3289
- },
3290
- "security": [
3291
- {
3292
- "builderSession": []
3293
- }
3294
- ]
3295
- },
3296
- "post": {
3297
- "operationId": "post_box_widen",
3298
- "tags": [
3299
- "box"
3300
- ],
3301
- "summary": "POST /box/widen",
3302
- "description": "POST /box/widen — record extra sparse-checkout directories that must SURVIVE the */10 source-fetch (task 1003087, idea 1000793). rank: any authenticated builder, OWN box (builder_id-scoped in SQL, so no id from the body can point this at someone else's box). Deliberately NOT allowBoxScope. The on-box crons read source-access with a box-scoped session (ADR 0053); this is a WRITE that changes what the box pulls forever after, so it takes the builder's own session — the same posture that keeps a stolen box token from re-scoping the checkout it was stolen from. add/remove are applied to the stored set and the result is re-normalized, so the endpoint is idempotent: widening twice is not an error, and neither is removing something that was never there. A path outside the caller's rank scope is ACCEPTED into storage but reported in `ignored` rather than refused. That is the honest shape: rank can change, and a builder promoted to Metic should find the widen they asked for as a Xenos simply start working, instead of having been silently discarded months earlier. What it can never do is take effect early — admissibleWidenPaths is re-evaluated against the LIVE rank on every single fetch.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
3303
- "x-rank": "any-builder",
3304
- "x-source": "modules/dev-box/routes/box.js",
3305
- "requestBody": {
3306
- "required": false,
3307
- "content": {
3308
- "application/json": {
3309
- "schema": {
3310
- "$ref": "#/components/schemas/PostBoxWidenRequest"
3311
- }
3312
- }
3313
- },
3314
- "x-validated": true
3315
- },
3316
- "responses": {
3317
- "200": {
3318
- "description": "Success.",
3319
- "content": {
3320
- "application/json": {
3321
- "schema": {
3322
- "$ref": "#/components/schemas/PostBoxWidenResponse"
3323
- }
3324
- }
3325
- }
3326
- },
3327
- "400": {
3328
- "$ref": "#/components/responses/ValidationFailed"
3329
- },
3330
- "401": {
3331
- "$ref": "#/components/responses/Unauthorized"
3332
- },
3333
- "403": {
3334
- "$ref": "#/components/responses/Forbidden"
3335
- }
3336
- },
3337
- "security": [
3338
- {
3339
- "builderSession": []
3340
- }
3341
- ]
3342
- }
3343
- },
3344
- "/boxes": {
3345
- "get": {
3346
- "operationId": "get_boxes",
3347
- "tags": [
3348
- "boxes"
3349
- ],
3350
- "summary": "GET /boxes",
3351
- "description": "GET /boxes — full roster across all builders. rank: archon (exposes every builder's box + IP + cost; same gate as the rest of roster management).\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `box.fleet.manage` (all required).",
3352
- "x-rank": "metic+archon",
3353
- "x-source": "modules/dev-box/routes/box.js",
3354
- "x-permissions": [
3355
- "box.fleet.manage"
3356
- ],
3357
- "responses": {
3358
- "200": {
3359
- "description": "Success.",
3360
- "content": {
3361
- "application/json": {
3362
- "schema": {
3363
- "$ref": "#/components/schemas/GetBoxesResponse"
3364
- }
3365
- }
3366
- }
3367
- },
3368
- "400": {
3369
- "$ref": "#/components/responses/BadRequest"
3370
- },
3371
- "401": {
3372
- "$ref": "#/components/responses/Unauthorized"
3373
- },
3374
- "403": {
3375
- "$ref": "#/components/responses/Forbidden"
3376
- }
3377
- },
3378
- "security": [
3379
- {
3380
- "builderSession": []
3381
- }
3382
- ]
3383
- }
3384
- },
3385
- "/boxes/cost-ledger": {
3386
- "get": {
3387
- "operationId": "get_boxes_cost_ledger",
3388
- "tags": [
3389
- "boxes"
3390
- ],
3391
- "summary": "GET /boxes/cost-ledger",
3392
- "description": "GET /boxes/cost-ledger — the full per-builder container-cost ledger + totals (task 602). rank: archon — cost oversight across every builder without a DB query. Declared among the archon routes (archon-first) so the route-rank auditor's 20-line lookahead classifies it correctly.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `box.fleet.manage` (all required).",
3393
- "x-rank": "metic+archon",
3394
- "x-source": "modules/dev-box/routes/box.js",
3395
- "x-permissions": [
3396
- "box.fleet.manage"
2253
+ }
3397
2254
  ],
2255
+ "requestBody": {
2256
+ "required": true,
2257
+ "content": {
2258
+ "application/json": {
2259
+ "schema": {
2260
+ "$ref": "#/components/schemas/PostBlockersIdLinkRequest"
2261
+ }
2262
+ }
2263
+ },
2264
+ "x-validated": true
2265
+ },
3398
2266
  "responses": {
3399
2267
  "200": {
3400
- "description": "Success."
2268
+ "description": "Success.",
2269
+ "content": {
2270
+ "application/json": {
2271
+ "schema": {
2272
+ "$ref": "#/components/schemas/PostBlockersIdLinkResponse"
2273
+ }
2274
+ }
2275
+ }
3401
2276
  },
3402
2277
  "400": {
3403
- "$ref": "#/components/responses/BadRequest"
2278
+ "$ref": "#/components/responses/ValidationFailed"
3404
2279
  },
3405
2280
  "401": {
3406
2281
  "$ref": "#/components/responses/Unauthorized"
3407
2282
  },
3408
2283
  "403": {
3409
2284
  "$ref": "#/components/responses/Forbidden"
2285
+ },
2286
+ "404": {
2287
+ "$ref": "#/components/responses/NotFound"
3410
2288
  }
3411
2289
  },
3412
2290
  "security": [
@@ -3416,54 +2294,52 @@
3416
2294
  ]
3417
2295
  }
3418
2296
  },
3419
- "/boxes/{builderId}/block": {
3420
- "patch": {
3421
- "operationId": "patch_boxes_builderId_block",
2297
+ "/blockers/{id}/link/{taskId}": {
2298
+ "delete": {
2299
+ "operationId": "delete_blockers_id_link_taskId",
3422
2300
  "tags": [
3423
- "boxes"
2301
+ "blockers"
3424
2302
  ],
3425
- "summary": "PATCH /boxes/:builderId/block",
3426
- "description": "PATCH /boxes/:builderId/block — Archon bars (or un-bars) a builder from OPENING dev boxes (idea 257 / task 1163; migration 104). Body: { blocked: bool (required), reason?: string, // recorded on block; shown back to the builder close_current?: bool } // on block, ALSO deprovision a running box Block alone is \"no new opens\" (decideEnsureAction → BOX_PROVISION_BLOCKED); it does NOT touch a box that is already active. close_current adds the explicit teardown the owner asked be optional (\"options for both\"). rank: archon. Archon-first ordering (see above).\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `box.fleet.manage` (all required).",
2303
+ "summary": "DELETE /blockers/:id/link/:taskId",
2304
+ "description": "Unlink a blocker from a task — the inverse of /link (task 1003518). Both ids sit in the PATH, matching DELETE /tasks/:id/dependencies/:depId, the closest existing shape: an edge between two rows, removed by naming both. Post-core_223 a task is released only when its LAST open blocker resolves, so a wrong link is permanent: the only way out was to resolve a blocker that was never real, putting a falsehood in the blocker ledger to clear a bookkeeping artifact. This is the honest way out. Promotion is the TRIGGER's job here exactly as it is on /resolve: core_256 fires AFTER DELETE on task_blockers and re-uses core_223's predicate, so \"nothing open is left against this task\" has one definition, not two. blockers.unlinkBlockerFromTask only deletes and reads back what happened. `released` says whether the task actually moved, never a bare {ok:true} (task 1002559 — a write that reports success without saying what changed is how a dead loop hides). Metic+ (task 360 / ADR 0018) — same blocker-review grant as link and resolve. rank: metic+archon — unlink a blocker from a task; same blocker-review grant as link.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `blocker.review` (all required).",
3427
2305
  "x-rank": "metic+archon",
3428
- "x-source": "modules/dev-box/routes/box.js",
2306
+ "x-source": "modules/ideas/routes/blockers.js",
3429
2307
  "x-permissions": [
3430
- "box.fleet.manage"
2308
+ "blocker.review"
3431
2309
  ],
3432
2310
  "parameters": [
3433
2311
  {
3434
- "name": "builderId",
2312
+ "name": "id",
3435
2313
  "in": "path",
3436
2314
  "required": true,
3437
2315
  "schema": {
3438
2316
  "type": "string"
3439
2317
  },
3440
- "description": "Path parameter `builderId`."
2318
+ "description": "Path parameter `id`."
2319
+ },
2320
+ {
2321
+ "name": "taskId",
2322
+ "in": "path",
2323
+ "required": true,
2324
+ "schema": {
2325
+ "type": "string"
2326
+ },
2327
+ "description": "Path parameter `taskId`."
3441
2328
  }
3442
2329
  ],
3443
- "requestBody": {
3444
- "required": true,
3445
- "content": {
3446
- "application/json": {
3447
- "schema": {
3448
- "$ref": "#/components/schemas/PatchBoxesBuilderIdBlockRequest"
3449
- }
3450
- }
3451
- },
3452
- "x-validated": true
3453
- },
3454
2330
  "responses": {
3455
2331
  "200": {
3456
2332
  "description": "Success.",
3457
2333
  "content": {
3458
2334
  "application/json": {
3459
2335
  "schema": {
3460
- "$ref": "#/components/schemas/PatchBoxesBuilderIdBlockResponse"
2336
+ "$ref": "#/components/schemas/DeleteBlockersIdLinkTaskIdResponse"
3461
2337
  }
3462
2338
  }
3463
2339
  }
3464
2340
  },
3465
2341
  "400": {
3466
- "$ref": "#/components/responses/ValidationFailed"
2342
+ "$ref": "#/components/responses/BadRequest"
3467
2343
  },
3468
2344
  "401": {
3469
2345
  "$ref": "#/components/responses/Unauthorized"
@@ -3482,43 +2358,47 @@
3482
2358
  ]
3483
2359
  }
3484
2360
  },
3485
- "/boxes/{builderId}/close": {
2361
+ "/blockers/{id}/resolve": {
3486
2362
  "post": {
3487
- "operationId": "post_boxes_builderId_close",
2363
+ "operationId": "post_blockers_id_resolve",
3488
2364
  "tags": [
3489
- "boxes"
2365
+ "blockers"
3490
2366
  ],
3491
- "summary": "POST /boxes/:builderId/close",
3492
- "description": "POST /boxes/:builderId/close — Archon force-closes ANY builder's box (idea 257 / task 1163). Trust-safe by the same mechanism as the builder's own POST /box/close: it ENQUEUES a 'deprovision' intent and the control-plane runner executes it, so the web tier never holds DO_API_TOKEN (ADR 0031 §9.4). rank: archon (acts on another builder's paid infrastructure). Declared among the archon routes (archon-first) so the route-rank auditor's 20-line lookahead classifies it correctly.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `box.fleet.manage` (all required).",
2367
+ "summary": "POST /blockers/:id/resolve",
2368
+ "description": "Resolve. Auto-promotes any blocked tasks linked via task_blockers. Metic+ (task 360 / ADR 0018): blocker review is one of the operations C2 assigns to Metic, so this widened from archon-only to metic+archon when the three-rank model went live. Any builder can still FILE a blocker (POST /blockers above); a Xenos cannot resolve or link them. rank: metic+archon — blocker review (auto-promotes linked blocked tasks); criterion C2 / task 360 / ADR 0018.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `blocker.review` (all required).",
3493
2369
  "x-rank": "metic+archon",
3494
- "x-source": "modules/dev-box/routes/box.js",
2370
+ "x-source": "modules/ideas/routes/blockers.js",
3495
2371
  "x-permissions": [
3496
- "box.fleet.manage"
2372
+ "blocker.review"
3497
2373
  ],
3498
2374
  "parameters": [
3499
2375
  {
3500
- "name": "builderId",
2376
+ "name": "id",
3501
2377
  "in": "path",
3502
2378
  "required": true,
3503
2379
  "schema": {
3504
2380
  "type": "string"
3505
2381
  },
3506
- "description": "Path parameter `builderId`."
2382
+ "description": "Path parameter `id`."
3507
2383
  }
3508
2384
  ],
3509
- "responses": {
3510
- "200": {
3511
- "description": "Success.",
3512
- "content": {
3513
- "application/json": {
3514
- "schema": {
3515
- "$ref": "#/components/schemas/PostBoxesBuilderIdCloseResponse"
3516
- }
2385
+ "requestBody": {
2386
+ "required": false,
2387
+ "content": {
2388
+ "application/json": {
2389
+ "schema": {
2390
+ "$ref": "#/components/schemas/PostBlockersIdResolveRequest"
3517
2391
  }
3518
2392
  }
3519
2393
  },
2394
+ "x-validated": true
2395
+ },
2396
+ "responses": {
2397
+ "200": {
2398
+ "description": "Success."
2399
+ },
3520
2400
  "400": {
3521
- "$ref": "#/components/responses/BadRequest"
2401
+ "$ref": "#/components/responses/ValidationFailed"
3522
2402
  },
3523
2403
  "401": {
3524
2404
  "$ref": "#/components/responses/Unauthorized"
@@ -3969,7 +2849,7 @@
3969
2849
  "builders"
3970
2850
  ],
3971
2851
  "summary": "GET /builders/:id/profile",
3972
- "description": "GET /builders/:id/profile — the viewable builder profile page's read (task 1467, goal 25 \"bx-identity\"). Returns ONE builder's public portfolio: identity + rank + economy counters + shipped-work count + recent ships + unlocked achievements. Backs the shareable /builders/profile page. rank: public — every field returned here is ALREADY world-readable: the leaderboard (GET /public/leaderboard, no auth) exposes rank, total_credits, karma, and the top achievement ids; GET /builders/:id/onboarding is likewise an unauthenticated roster-visible read. This route is a per-builder re- presentation of the SAME already-public data, so a portfolio link is shareable without a sign-in wall (that IS the feature). It deliberately WHITELISTS the public column subset off the full builder row — the private columns getBuilderById also returns (anthropic_email / anthropic_user_id / monthly_budget_usd / system_role / box_blocked*) are NEVER spread into the JSON. A leak-prevention unit test (tests/profile_route.mjs) asserts their absence + the 404-on-unknown-id behaviour. THROTTLED since task 1002804. The global limiter meters WRITES only (rateLimitMiddleware returns early for GET) and the public read limiter is mounted under /public/*, which this is not — so this route answered at line rate. That was tolerable while it re-presented leaderboard data; it is not now that it also carries `handle`, because an id walk then yields the builder → canonical-profile-handle map in one call per builder. modules/platform-identity/routes/public-profile.js caps /profiles/:handle for exactly that mapping, so the two routes that publish it get the same ceiling. The EXISTING shared /public/* budget (120/60s per IP), not a new one — ADR 0209's rule that routes answering one question spend one budget.\n\n**Rank:** `public` — No authentication — any caller.",
2852
+ "description": "GET /builders/:id/profile — the viewable builder profile page's read (task 1467, goal 25 \"bx-identity\"). Returns ONE builder's public portfolio: identity + rank + economy counters + shipped-work count + recent ships + unlocked achievements. Backs the shareable /builders/profile page. rank: public — every field returned here is ALREADY world-readable: the leaderboard (GET /public/leaderboard, no auth) exposes rank, total_credits, karma, and the top achievement ids; GET /builders/:id/onboarding is likewise an unauthenticated roster-visible read. This route is a per-builder re- presentation of the SAME already-public data, so a portfolio link is shareable without a sign-in wall (that IS the feature). It deliberately WHITELISTS the public column subset off the full builder row — the private columns getBuilderById also returns (anthropic_email / anthropic_user_id / monthly_budget_usd / system_role) are NEVER spread into the JSON. A leak-prevention unit test (tests/profile_route.mjs) asserts their absence + the 404-on-unknown-id behaviour. THROTTLED since task 1002804. The global limiter meters WRITES only (rateLimitMiddleware returns early for GET) and the public read limiter is mounted under /public/*, which this is not — so this route answered at line rate. That was tolerable while it re-presented leaderboard data; it is not now that it also carries `handle`, because an id walk then yields the builder → canonical-profile-handle map in one call per builder. modules/platform-identity/routes/public-profile.js caps /profiles/:handle for exactly that mapping, so the two routes that publish it get the same ceiling. The EXISTING shared /public/* budget (120/60s per IP), not a new one — ADR 0209's rule that routes answering one question spend one budget.\n\n**Rank:** `public` — No authentication — any caller.",
3973
2853
  "x-rank": "public",
3974
2854
  "x-source": "src/bongos/routes/builders.js",
3975
2855
  "parameters": [
@@ -18811,6 +17691,74 @@
18811
17691
  "security": []
18812
17692
  }
18813
17693
  },
17694
+ "/store/modules/{key}/versions": {
17695
+ "post": {
17696
+ "operationId": "post_store_modules_key_versions",
17697
+ "tags": [
17698
+ "store"
17699
+ ],
17700
+ "summary": "POST /store/modules/:key/versions",
17701
+ "description": "POST /api/bongos/store/modules/:key/versions — publish one module version to the store (task 1004271, ADR 0338 D1). Body: the gzip tarball `bongos module publish` builds (scripts/gds/module-artifact.js), sent as application/gzip. The route trusts nothing but the bytes: it recomputes every hash, re-runs the publish denylist and validates module.json itself (verifyModuleArtifact), then keeps the tarball on the control plane's disk and INSERTs the version row in one transaction (module-store.js). A key's first publish makes the caller its author; after that only the author may publish, a delisted key takes nothing new, and a version is published once and never changed. rank: metic+archon — it puts code in a shared store other instances install from. Gate: requirePermission('module.submit') — the atom that already guards filing a module into a shared queue; a dedicated `module.publish` atom is a follow-up once who-may-sell is decided.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `module.submit` (all required).",
17702
+ "x-rank": "metic+archon",
17703
+ "x-source": "src/bongos/routes/modules.js",
17704
+ "x-permissions": [
17705
+ "module.submit"
17706
+ ],
17707
+ "parameters": [
17708
+ {
17709
+ "name": "key",
17710
+ "in": "path",
17711
+ "required": true,
17712
+ "schema": {
17713
+ "type": "string"
17714
+ },
17715
+ "description": "Path parameter `key`."
17716
+ }
17717
+ ],
17718
+ "requestBody": {
17719
+ "required": false,
17720
+ "content": {
17721
+ "application/json": {
17722
+ "schema": {
17723
+ "type": "object",
17724
+ "additionalProperties": true
17725
+ }
17726
+ }
17727
+ },
17728
+ "x-validated": false,
17729
+ "x-body-undocumented": true
17730
+ },
17731
+ "responses": {
17732
+ "200": {
17733
+ "description": "Success.",
17734
+ "content": {
17735
+ "application/json": {
17736
+ "schema": {
17737
+ "$ref": "#/components/schemas/PostStoreModulesKeyVersionsResponse"
17738
+ }
17739
+ }
17740
+ }
17741
+ },
17742
+ "400": {
17743
+ "$ref": "#/components/responses/BadRequest"
17744
+ },
17745
+ "401": {
17746
+ "$ref": "#/components/responses/Unauthorized"
17747
+ },
17748
+ "403": {
17749
+ "$ref": "#/components/responses/Forbidden"
17750
+ },
17751
+ "404": {
17752
+ "$ref": "#/components/responses/NotFound"
17753
+ }
17754
+ },
17755
+ "security": [
17756
+ {
17757
+ "builderSession": []
17758
+ }
17759
+ ]
17760
+ }
17761
+ },
18814
17762
  "/task-recommendations": {
18815
17763
  "post": {
18816
17764
  "operationId": "post_task_recommendations",
@@ -19465,7 +18413,7 @@
19465
18413
  "tasks"
19466
18414
  ],
19467
18415
  "summary": "POST /tasks/:id/attest-gate",
19468
- "description": "POST /tasks/:id/attest-gate — body: { head_sha }. The LAPTOP ci-land analogue of the publish-branch attestation (task 1169). ship.js's local push path (pushVia 'local') pushes the branch + opens the PR with its OWN gh credential but CANNOT forge the trustless gate-author-trust commit status — only the server can (it holds req.builder's server-resolved rank). Without this, an ENFORCING ADR-0058 gate-review BLOCKS a Metic+ author's gate-surface PR shipped from a laptop and they can't self-approve their own PR (hit live on task 1162 / PR task 131). AUTH mirrors publish-branch EXACTLY: requireBuilder + gateTaskOwnership, and deliberately NOT allowBoxScope (a box-scoped session 403s — it must reauth). The attestation power is bounded: gate-author-trust clears a NEEDS_TRUST surface for a Metic+ author only and NEVER the hard floor (which always needs an owner review), so a Metic+ vouching for their own task's head stays within their tier.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
18416
+ "description": "POST /tasks/:id/attest-gate — body: { head_sha }. The LAPTOP ci-land analogue of the publish-branch attestation (task 1169). ship.js's local push path (pushVia 'local') pushes the branch + opens the PR with its OWN gh credential but CANNOT forge the trustless gate-author-trust commit status — only the server can (it holds req.builder's server-resolved rank). Without this, an ENFORCING ADR-0058 gate-review BLOCKS a Metic+ author's gate-surface PR shipped from a laptop and they can't self-approve their own PR (hit live on task 1162 / PR task 131). AUTH mirrors publish-branch EXACTLY: requireBuilder + gateTaskOwnership. The attestation power is bounded: gate-author-trust clears a NEEDS_TRUST surface for a Metic+ author only and NEVER the hard floor (which always needs an owner review), so a Metic+ vouching for their own task's head stays within their tier.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
19469
18417
  "x-rank": "any-builder",
19470
18418
  "x-source": "modules/lifecycle/routes/tasks.js",
19471
18419
  "parameters": [
@@ -21420,19 +20368,6 @@
21420
20368
  "released"
21421
20369
  ]
21422
20370
  },
21423
- "DeleteBoxSshKeyIdResponse": {
21424
- "type": "object",
21425
- "properties": {
21426
- "ok": {
21427
- "type": "boolean"
21428
- },
21429
- "deleted": {}
21430
- },
21431
- "required": [
21432
- "ok",
21433
- "deleted"
21434
- ]
21435
- },
21436
20371
  "DeleteConnectionsIdResponse": {
21437
20372
  "type": "object",
21438
20373
  "properties": {
@@ -21768,21 +20703,6 @@
21768
20703
  "page"
21769
20704
  ]
21770
20705
  },
21771
- "GetAuthAppPairInfoResponse": {
21772
- "type": "object",
21773
- "properties": {
21774
- "pair_code": {},
21775
- "device_name": {},
21776
- "platform": {},
21777
- "status": {}
21778
- },
21779
- "required": [
21780
- "pair_code",
21781
- "device_name",
21782
- "platform",
21783
- "status"
21784
- ]
21785
- },
21786
20706
  "GetAuthWebAdmissionStatusResponse": {
21787
20707
  "type": "object",
21788
20708
  "properties": {
@@ -21857,98 +20777,6 @@
21857
20777
  "page"
21858
20778
  ]
21859
20779
  },
21860
- "GetBoxMeResponse": {
21861
- "type": "object",
21862
- "properties": {
21863
- "box": {},
21864
- "open_intent": {},
21865
- "cost": {}
21866
- },
21867
- "required": [
21868
- "box",
21869
- "open_intent",
21870
- "cost"
21871
- ]
21872
- },
21873
- "GetBoxSourceAccessResponse": {
21874
- "type": "object",
21875
- "properties": {
21876
- "scope": {},
21877
- "mode": {},
21878
- "sparse_paths": {},
21879
- "repo_url": {},
21880
- "credential": {},
21881
- "configured": {},
21882
- "separate_starter_repo": {},
21883
- "expires_at": {},
21884
- "ttl_seconds": {},
21885
- "guidance": {}
21886
- },
21887
- "required": [
21888
- "scope",
21889
- "mode",
21890
- "sparse_paths",
21891
- "repo_url",
21892
- "credential",
21893
- "configured",
21894
- "separate_starter_repo",
21895
- "expires_at",
21896
- "ttl_seconds",
21897
- "guidance"
21898
- ]
21899
- },
21900
- "GetBoxSshKeyResponse": {
21901
- "type": "object",
21902
- "properties": {
21903
- "keys": {}
21904
- },
21905
- "required": [
21906
- "keys"
21907
- ]
21908
- },
21909
- "GetBoxTerminalResponse": {
21910
- "type": "object",
21911
- "properties": {
21912
- "state": {},
21913
- "url": {},
21914
- "credential": {},
21915
- "hint": {},
21916
- "warming_up": {
21917
- "type": "boolean"
21918
- },
21919
- "terminal_at": {}
21920
- },
21921
- "required": [
21922
- "state",
21923
- "url",
21924
- "credential",
21925
- "hint"
21926
- ]
21927
- },
21928
- "GetBoxWidenResponse": {
21929
- "type": "object",
21930
- "properties": {
21931
- "widen_paths": {},
21932
- "admitted": {},
21933
- "ignored": {},
21934
- "max": {}
21935
- },
21936
- "required": [
21937
- "widen_paths",
21938
- "admitted",
21939
- "ignored",
21940
- "max"
21941
- ]
21942
- },
21943
- "GetBoxesResponse": {
21944
- "type": "object",
21945
- "properties": {
21946
- "boxes": {}
21947
- },
21948
- "required": [
21949
- "boxes"
21950
- ]
21951
- },
21952
20780
  "GetBuildersDirectoryResponse": {
21953
20781
  "type": "object",
21954
20782
  "properties": {
@@ -23676,69 +22504,35 @@
23676
22504
  "required": [
23677
22505
  "status"
23678
22506
  ],
23679
- "additionalProperties": false
23680
- },
23681
- "PatchAccessRequestsIdResponse": {
23682
- "type": "object",
23683
- "properties": {
23684
- "ok": {
23685
- "type": "boolean"
23686
- },
23687
- "request": {},
23688
- "unchanged": {
23689
- "type": "boolean"
23690
- }
23691
- },
23692
- "required": [
23693
- "ok",
23694
- "request"
23695
- ]
23696
- },
23697
- "PatchAgentsNameResponse": {
23698
- "type": "object",
23699
- "properties": {
23700
- "ok": {
23701
- "type": "boolean"
23702
- },
23703
- "agent": {}
23704
- },
23705
- "required": [
23706
- "ok",
23707
- "agent"
23708
- ]
22507
+ "additionalProperties": false
23709
22508
  },
23710
- "PatchBoxesBuilderIdBlockRequest": {
22509
+ "PatchAccessRequestsIdResponse": {
23711
22510
  "type": "object",
23712
22511
  "properties": {
23713
- "blocked": {
22512
+ "ok": {
23714
22513
  "type": "boolean"
23715
22514
  },
23716
- "reason": {
23717
- "type": "string",
23718
- "maxLength": 500
23719
- },
23720
- "close_current": {
22515
+ "request": {},
22516
+ "unchanged": {
23721
22517
  "type": "boolean"
23722
22518
  }
23723
22519
  },
23724
22520
  "required": [
23725
- "blocked"
23726
- ],
23727
- "additionalProperties": false
22521
+ "ok",
22522
+ "request"
22523
+ ]
23728
22524
  },
23729
- "PatchBoxesBuilderIdBlockResponse": {
22525
+ "PatchAgentsNameResponse": {
23730
22526
  "type": "object",
23731
22527
  "properties": {
23732
22528
  "ok": {
23733
22529
  "type": "boolean"
23734
22530
  },
23735
- "blocked": {},
23736
- "close_queued": {}
22531
+ "agent": {}
23737
22532
  },
23738
22533
  "required": [
23739
22534
  "ok",
23740
- "blocked",
23741
- "close_queued"
22535
+ "agent"
23742
22536
  ]
23743
22537
  },
23744
22538
  "PatchBuildersIdBudgetRequest": {
@@ -24604,76 +23398,6 @@
24604
23398
  "agent"
24605
23399
  ]
24606
23400
  },
24607
- "PostAuthAppPairApproveRequest": {
24608
- "type": "object",
24609
- "properties": {
24610
- "pair_code": {
24611
- "type": "string",
24612
- "maxLength": 32
24613
- }
24614
- },
24615
- "required": [
24616
- "pair_code"
24617
- ],
24618
- "additionalProperties": false
24619
- },
24620
- "PostAuthAppPairApproveResponse": {
24621
- "type": "object",
24622
- "properties": {
24623
- "ok": {
24624
- "type": "boolean"
24625
- }
24626
- },
24627
- "required": [
24628
- "ok"
24629
- ]
24630
- },
24631
- "PostAuthAppPairPollRequest": {
24632
- "type": "object",
24633
- "properties": {
24634
- "poll_token": {
24635
- "type": "string",
24636
- "maxLength": 128
24637
- }
24638
- },
24639
- "required": [
24640
- "poll_token"
24641
- ],
24642
- "additionalProperties": false
24643
- },
24644
- "PostAuthAppPairStartRequest": {
24645
- "type": "object",
24646
- "properties": {
24647
- "device_name": {
24648
- "type": "string",
24649
- "maxLength": 80
24650
- },
24651
- "platform": {
24652
- "type": "string",
24653
- "maxLength": 20
24654
- }
24655
- },
24656
- "additionalProperties": false
24657
- },
24658
- "PostAuthAppPairStartResponse": {
24659
- "type": "object",
24660
- "properties": {
24661
- "pair_code": {},
24662
- "poll_token": {},
24663
- "verify_path": {},
24664
- "verify_url": {},
24665
- "expires_in": {},
24666
- "interval": {}
24667
- },
24668
- "required": [
24669
- "pair_code",
24670
- "poll_token",
24671
- "verify_path",
24672
- "verify_url",
24673
- "expires_in",
24674
- "interval"
24675
- ]
24676
- },
24677
23401
  "PostAuthBackchannelLogoutRequest": {
24678
23402
  "type": "object",
24679
23403
  "properties": {
@@ -24999,235 +23723,6 @@
24999
23723
  "blocker": {}
25000
23724
  }
25001
23725
  },
25002
- "PostBoxCloseResponse": {
25003
- "type": "object",
25004
- "properties": {
25005
- "ok": {
25006
- "type": "boolean"
25007
- },
25008
- "queued": {},
25009
- "action": {},
25010
- "message": {}
25011
- },
25012
- "required": [
25013
- "ok",
25014
- "queued",
25015
- "action",
25016
- "message"
25017
- ]
25018
- },
25019
- "PostBoxEnsureResponse": {
25020
- "type": "object",
25021
- "properties": {
25022
- "state": {},
25023
- "action": {},
25024
- "queued": {
25025
- "type": "boolean"
25026
- },
25027
- "intent": {}
25028
- },
25029
- "required": [
25030
- "state",
25031
- "action",
25032
- "queued"
25033
- ]
25034
- },
25035
- "PostBoxHeartbeatRequest": {
25036
- "type": "object",
25037
- "properties": {
25038
- "claude_active": {
25039
- "type": "boolean"
25040
- },
25041
- "attached": {
25042
- "type": "boolean"
25043
- }
25044
- },
25045
- "additionalProperties": true
25046
- },
25047
- "PostBoxHeartbeatResponse": {
25048
- "type": "object",
25049
- "properties": {
25050
- "ok": {
25051
- "type": "boolean"
25052
- },
25053
- "state": {}
25054
- },
25055
- "required": [
25056
- "ok",
25057
- "state"
25058
- ]
25059
- },
25060
- "PostBoxHostKeysRequest": {
25061
- "type": "object",
25062
- "properties": {
25063
- "host_keys": {
25064
- "type": "string",
25065
- "maxLength": 4096
25066
- }
25067
- },
25068
- "required": [
25069
- "host_keys"
25070
- ],
25071
- "additionalProperties": false
25072
- },
25073
- "PostBoxHostKeysResponse": {
25074
- "type": "object",
25075
- "properties": {
25076
- "ok": {
25077
- "type": "boolean"
25078
- },
25079
- "state": {},
25080
- "count": {}
25081
- },
25082
- "required": [
25083
- "ok",
25084
- "state",
25085
- "count"
25086
- ]
25087
- },
25088
- "PostBoxSshKeyRequest": {
25089
- "type": "object",
25090
- "properties": {
25091
- "public_key": {
25092
- "type": "string",
25093
- "maxLength": 20000
25094
- },
25095
- "label": {
25096
- "type": "string",
25097
- "maxLength": 64
25098
- }
25099
- },
25100
- "required": [
25101
- "public_key"
25102
- ],
25103
- "additionalProperties": false
25104
- },
25105
- "PostBoxSshKeyResponse": {
25106
- "type": "object",
25107
- "properties": {
25108
- "ok": {
25109
- "type": "boolean"
25110
- },
25111
- "key": {}
25112
- },
25113
- "required": [
25114
- "ok",
25115
- "key"
25116
- ]
25117
- },
25118
- "PostBoxTerminalRequest": {
25119
- "type": "object",
25120
- "properties": {
25121
- "url": {
25122
- "type": "string",
25123
- "maxLength": 2048
25124
- },
25125
- "credential": {
25126
- "type": "string",
25127
- "maxLength": 512
25128
- }
25129
- },
25130
- "required": [
25131
- "url"
25132
- ],
25133
- "additionalProperties": false
25134
- },
25135
- "PostBoxTerminalResponse": {
25136
- "type": "object",
25137
- "properties": {
25138
- "ok": {
25139
- "type": "boolean"
25140
- },
25141
- "state": {}
25142
- },
25143
- "required": [
25144
- "ok",
25145
- "state"
25146
- ]
25147
- },
25148
- "PostBoxVersionRequest": {
25149
- "type": "object",
25150
- "properties": {
25151
- "sha": {
25152
- "type": "string",
25153
- "maxLength": 64
25154
- },
25155
- "committed_at": {
25156
- "type": "string",
25157
- "maxLength": 64
25158
- }
25159
- },
25160
- "required": [
25161
- "sha"
25162
- ],
25163
- "additionalProperties": false
25164
- },
25165
- "PostBoxVersionResponse": {
25166
- "type": "object",
25167
- "properties": {
25168
- "ok": {
25169
- "type": "boolean"
25170
- },
25171
- "state": {}
25172
- },
25173
- "required": [
25174
- "ok",
25175
- "state"
25176
- ]
25177
- },
25178
- "PostBoxWidenRequest": {
25179
- "type": "object",
25180
- "properties": {
25181
- "add": {
25182
- "type": "array"
25183
- },
25184
- "remove": {
25185
- "type": "array"
25186
- },
25187
- "clear": {
25188
- "type": "boolean"
25189
- }
25190
- },
25191
- "additionalProperties": false
25192
- },
25193
- "PostBoxWidenResponse": {
25194
- "type": "object",
25195
- "properties": {
25196
- "ok": {
25197
- "type": "boolean"
25198
- },
25199
- "widen_paths": {},
25200
- "admitted": {},
25201
- "ignored": {},
25202
- "max": {}
25203
- },
25204
- "required": [
25205
- "ok",
25206
- "widen_paths",
25207
- "admitted",
25208
- "ignored",
25209
- "max"
25210
- ]
25211
- },
25212
- "PostBoxesBuilderIdCloseResponse": {
25213
- "type": "object",
25214
- "properties": {
25215
- "ok": {
25216
- "type": "boolean"
25217
- },
25218
- "queued": {},
25219
- "action": {},
25220
- "state": {},
25221
- "message": {}
25222
- },
25223
- "required": [
25224
- "ok",
25225
- "queued",
25226
- "action",
25227
- "state",
25228
- "message"
25229
- ]
25230
- },
25231
23726
  "PostClaimsBatchReleaseRequest": {
25232
23727
  "type": "object",
25233
23728
  "properties": {
@@ -28339,6 +26834,21 @@
28339
26834
  "avatar_url"
28340
26835
  ]
28341
26836
  },
26837
+ "PostStoreModulesKeyVersionsResponse": {
26838
+ "type": "object",
26839
+ "properties": {
26840
+ "ok": {
26841
+ "type": "boolean"
26842
+ },
26843
+ "created_module": {},
26844
+ "version": {}
26845
+ },
26846
+ "required": [
26847
+ "ok",
26848
+ "created_module",
26849
+ "version"
26850
+ ]
26851
+ },
28342
26852
  "PostTaskRecommendationsRequest": {
28343
26853
  "type": "object",
28344
26854
  "properties": {
@@ -29206,9 +27716,9 @@
29206
27716
  "description": "A required dependency/feature is not configured or is temporarily down."
29207
27717
  }
29208
27718
  },
29209
- "x-endpoint-count": 460,
29210
- "x-schema-count": 495,
29211
- "x-undocumented-bodies": 10,
29212
- "x-response-schemas": 338,
27719
+ "x-endpoint-count": 433,
27720
+ "x-schema-count": 466,
27721
+ "x-undocumented-bodies": 11,
27722
+ "x-response-schemas": 319,
29213
27723
  "x-generated-by": "scripts/gds/gen-api-docs.js"
29214
27724
  }