@bongos/core 1.24.78 → 1.24.80

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 (369) hide show
  1. package/.bongos-core.json +498 -353
  2. package/.claude/skills/ask-for-help/SKILL.md +1 -1
  3. package/.claude/skills/builder-sequence/SKILL.md +1 -1
  4. package/.claude/skills/design/SKILL.md +1 -1
  5. package/.claude/skills/goal-create/SKILL.md +1 -1
  6. package/.claude/skills/planning-session/SKILL.md +1 -1
  7. package/clients/bongos-client/README.md +1 -1
  8. package/clients/bongos-client/bongos-client.global.js +8 -0
  9. package/clients/bongos-client/index.cjs +8 -0
  10. package/clients/bongos-client/index.d.ts +11 -0
  11. package/clients/bongos-client/index.mjs +8 -0
  12. package/docs/adr/0274-one-kernel-three-role-packs.md +2 -2
  13. package/docs/adr/0399-the-visor-is-glass-over-the-scene-and-owns-no-camera.md +91 -0
  14. package/docs/adr/0400-a-chain-reserves-work-and-each-sub-quest-is-still-claimed-alone.md +109 -0
  15. package/docs/adr/0401-one-options-menu-opens-every-settings-screen-in-a-frame.md +53 -0
  16. package/docs/adr/README.md +3 -0
  17. package/docs/api/openapi.json +195 -3
  18. package/docs/api-reference.md +8 -4
  19. package/docs/architecture.md +7 -3
  20. package/docs/copy-inventory.md +390 -370
  21. package/docs/copy-registry.json +719 -518
  22. package/docs/design/characters/character-sprites-source.webp +0 -0
  23. package/docs/file-map.md +7 -8
  24. package/docs/module-api-changelog.md +4 -0
  25. package/docs/modules-contract.md +5 -0
  26. package/docs/onboarding/slash-commands.md +1 -1
  27. package/docs/packs/{engineer.md → artisan.md} +5 -5
  28. package/docs/packs/artist.md +50 -4
  29. package/docs/page-inventory.json +15 -4
  30. package/docs/page-readings.json +3182 -3336
  31. package/docs/{packs/ideator.md → recipes/ideation-playbook.md} +5 -48
  32. package/migrations/core_296_crafts_artisan_artist.sql +72 -0
  33. package/migrations/core_297_builder_character.sql +30 -0
  34. package/modules/copy-desk/proposals.js +1 -1
  35. package/modules/economy/credits-by-craft.js +29 -15
  36. package/modules/hall-ui/public/ai-studio.html +4 -4
  37. package/modules/hall-ui/public/atlas.html +5 -5
  38. package/modules/hall-ui/public/atlas.js +3 -1
  39. package/modules/hall-ui/public/blockers.html +5 -5
  40. package/modules/hall-ui/public/board-room.html +4 -4
  41. package/modules/hall-ui/public/characters/artisan.png +0 -0
  42. package/modules/hall-ui/public/characters/artist.png +0 -0
  43. package/modules/hall-ui/public/characters/patron.png +0 -0
  44. package/modules/hall-ui/public/city.css +2 -1
  45. package/modules/hall-ui/public/city.html +9 -6
  46. package/modules/hall-ui/public/city.js +7 -1
  47. package/modules/hall-ui/public/collab.html +4 -4
  48. package/modules/hall-ui/public/commands.html +4 -4
  49. package/modules/hall-ui/public/copy-desk.html +4 -4
  50. package/modules/hall-ui/public/craft-mode.js +258 -64
  51. package/modules/hall-ui/public/deploy.html +4 -4
  52. package/modules/hall-ui/public/diagram-viewer.js +2 -1
  53. package/modules/hall-ui/public/dom-utils.js +1 -1
  54. package/modules/hall-ui/public/goal-map.html +9 -6
  55. package/modules/hall-ui/public/goals.html +5 -5
  56. package/modules/hall-ui/public/government.html +4 -4
  57. package/modules/hall-ui/public/hall-render.js +3 -1
  58. package/modules/hall-ui/public/home-city-draw.js +3 -3
  59. package/modules/hall-ui/public/home-city.css +4 -0
  60. package/modules/hall-ui/public/home-city.js +17 -9
  61. package/modules/hall-ui/public/home-gallery.js +8 -1
  62. package/modules/hall-ui/public/home-idea-box.css +2 -2
  63. package/modules/hall-ui/public/home-idea-box.js +3 -3
  64. package/modules/hall-ui/public/idea.html +5 -5
  65. package/modules/hall-ui/public/ideas.html +5 -5
  66. package/modules/hall-ui/public/index.html +11 -7
  67. package/modules/hall-ui/public/market-watch-item.html +4 -4
  68. package/modules/hall-ui/public/market-watch-rules.html +4 -4
  69. package/modules/hall-ui/public/market-watch.html +6 -9
  70. package/modules/hall-ui/public/market-watch.js +22 -45
  71. package/modules/hall-ui/public/modules.html +4 -4
  72. package/modules/hall-ui/public/money.html +4 -4
  73. package/modules/hall-ui/public/my-agents.html +4 -4
  74. package/modules/hall-ui/public/options-menu.js +409 -0
  75. package/modules/hall-ui/public/paintbrush.js +3 -3
  76. package/modules/hall-ui/public/palette.js +11 -13
  77. package/modules/hall-ui/public/primer.html +5 -5
  78. package/modules/hall-ui/public/profile-crafts.js +1 -1
  79. package/modules/hall-ui/public/profile-roles.js +23 -19
  80. package/modules/hall-ui/public/profile.css +1 -1
  81. package/modules/hall-ui/public/profile.html +19 -18
  82. package/modules/hall-ui/public/profile.js +19 -18
  83. package/modules/hall-ui/public/profile.states.json +4 -4
  84. package/modules/hall-ui/public/project-settings.html +4 -4
  85. package/modules/hall-ui/public/roster.html +4 -4
  86. package/modules/hall-ui/public/roster.js +3 -3
  87. package/modules/hall-ui/public/settings.css +24 -4
  88. package/modules/hall-ui/public/settings.html +21 -17
  89. package/modules/hall-ui/public/settings.js +44 -44
  90. package/modules/hall-ui/public/shell-game.css +65 -0
  91. package/modules/hall-ui/public/shell-game.js +283 -3
  92. package/modules/hall-ui/public/shell.js +7 -26
  93. package/modules/hall-ui/public/sky-interaction.js +7 -0
  94. package/modules/hall-ui/public/speciality-tools.js +1 -2
  95. package/modules/hall-ui/public/street-inside.js +3 -4
  96. package/modules/hall-ui/public/street.html +4 -4
  97. package/modules/hall-ui/public/studio.html +5 -5
  98. package/modules/hall-ui/public/style.css +73 -47
  99. package/modules/hall-ui/public/task-detail.js +5 -4
  100. package/modules/hall-ui/public/task.html +5 -5
  101. package/modules/hall-ui/public/thinking.css +10 -0
  102. package/modules/hall-ui/public/thinking.html +9 -6
  103. package/modules/hall-ui/public/tweak-editor.html +5 -5
  104. package/modules/hall-ui/public/tweak-editor.js +5 -1
  105. package/modules/hall-ui/public/update-pill.js +91 -2
  106. package/modules/hall-ui/public/visor.css +80 -0
  107. package/modules/hall-ui/public/visor.js +316 -0
  108. package/modules/hall-ui/public/watch-repo-invite.js +159 -0
  109. package/modules/hall-ui/public/watch.html +6 -5
  110. package/modules/hall-ui/public/watch.js +17 -2
  111. package/modules/hall-ui/public/welcome-boot.js +15 -6
  112. package/modules/hall-ui/public/work.html +5 -5
  113. package/modules/hall-ui/records/builders-roster.md +2 -2
  114. package/modules/hall-ui/records/craft-mode.md +13 -11
  115. package/modules/hall-ui/records/game-mode.md +2 -2
  116. package/modules/hall-ui/records/governor-street.md +1 -1
  117. package/modules/hall-ui/records/home-city.md +3 -1
  118. package/modules/hall-ui/records/market-watch-settings.md +2 -0
  119. package/modules/hall-ui/records/options-menu.md +15 -0
  120. package/modules/hall-ui/records/profile-roles.md +11 -11
  121. package/modules/hall-ui/records/sky.md +3 -3
  122. package/modules/hall-ui/records/start-screen.md +21 -0
  123. package/modules/hall-ui/records/tweak-editor.md +1 -1
  124. package/modules/hall-ui/records/update-pill.md +12 -0
  125. package/modules/hall-ui/records/visor.md +23 -0
  126. package/modules/hall-ui/records/welcome-tower.md +5 -5
  127. package/modules/ideas/ideator-stats.js +3 -1
  128. package/modules/ideas/inbox.js +2 -2
  129. package/modules/lifecycle/activity.js +1 -1
  130. package/modules/lifecycle/cascade.js +4 -4
  131. package/modules/lifecycle/goal-spec-task.js +1 -1
  132. package/modules/lifecycle/help-requests.js +19 -15
  133. package/modules/lifecycle/kickoff-checklist.js +3 -3
  134. package/modules/lifecycle/kickoff-seed.js +1 -1
  135. package/modules/lifecycle/migrations/lifecycle_021_crafts_artisan_artist.sql +29 -0
  136. package/modules/lifecycle/mingle.js +4 -4
  137. package/modules/lifecycle/page-tweak-write.js +1 -1
  138. package/modules/lifecycle/routes/task-write-routes.js +3 -1
  139. package/modules/lifecycle/routes/tasks.js +3 -3
  140. package/modules/lifecycle/ship-card.js +2 -3
  141. package/modules/lifecycle/task-classifier.js +39 -29
  142. package/modules/lifecycle/tests/cascade.mjs +7 -7
  143. package/modules/onboarding/founding-mode.js +3 -0
  144. package/modules/onboarding/genesis-home.js +21 -1
  145. package/modules/onboarding/routes/onboarding.js +2 -0
  146. package/modules/page-notes/notes.js +3 -2
  147. package/modules/page-notes/split.js +1 -1
  148. package/modules/platform-identity/craft-rollup.js +35 -7
  149. package/modules/platform-identity/migrations/platform_identity_036_crafts_artisan_artist.sql +45 -0
  150. package/modules/platform-identity/platform-identity.js +2 -2
  151. package/modules/platform-identity/routes/community-leaderboard.js +2 -1
  152. package/modules/platform-identity/routes/community-search.js +2 -1
  153. package/modules/platform-identity/routes/sso.js +2 -1
  154. package/modules/platform-identity/tests/platform-identity.mjs +6 -6
  155. package/modules/public-landing/public/community.html +3 -5
  156. package/modules/public-landing/public/index.html +1 -1
  157. package/modules/public-landing/public/projects.html +1 -1
  158. package/modules/specialities/migrations/specialities_004_crafts_artisan_artist.sql +30 -0
  159. package/modules/specialities/routes/specialities.js +2 -2
  160. package/modules/status-ui/public/status.js +1 -1
  161. package/modules/ui-design/kit/fixtures/community-leaderboard.json +10 -10
  162. package/modules/ui-design/kit/fixtures/community-search.json +2 -2
  163. package/modules/ui-design/kit/fixtures/scouting-recruiter.json +1 -1
  164. package/package-lock.json +2 -2
  165. package/package.json +1 -1
  166. package/release-notes.json +76 -0
  167. package/scripts/bongos/autobongos-host.js +19 -4
  168. package/scripts/bongos/autobongos-loop.js +1 -1
  169. package/scripts/bongos/claude-materialize.js +20 -1
  170. package/scripts/bongos/discipline-modes.json +7 -12
  171. package/scripts/bongos/fitness.js +1 -1
  172. package/scripts/bongos/gen-hall-sitemap.js +7 -7
  173. package/scripts/bongos/grade-smoke.js +2 -2
  174. package/scripts/bongos/init.js +2 -2
  175. package/scripts/bongos/newcomer-floor.config.json +1 -2
  176. package/scripts/bongos/newcomer-floor.js +4 -3
  177. package/scripts/bongos/newcomer-floor.templates.json +10 -12
  178. package/scripts/bongos/newcomer-floor.test.js +47 -38
  179. package/scripts/bongos/role-pack-guard.js +5 -4
  180. package/scripts/bongos/seed-tasks.example.json +3 -3
  181. package/scripts/bongos/setup.js +20 -107
  182. package/scripts/bongos/start.js +2 -2
  183. package/scripts/bongos/task.js +5 -4
  184. package/scripts/bongos/triage.js +1 -1
  185. package/scripts/bongos/worktree.js +16 -4
  186. package/scripts/setup-builder.js +6 -46
  187. package/src/bongos/auth-admission.js +8 -8
  188. package/src/bongos/craft-mode.js +2 -0
  189. package/src/bongos/db.js +39 -1
  190. package/src/bongos/github-repos.js +78 -0
  191. package/src/bongos/module-scope-map.js +22 -0
  192. package/src/bongos/role-stats.js +27 -11
  193. package/src/bongos/routes/github.js +100 -2
  194. package/src/bongos/routes/me.js +33 -0
  195. package/src/bongos/serve-internal.js +26 -9
  196. package/src/crafts.js +66 -0
  197. package/src/module-api.js +6 -1
  198. package/src/module-loader/manifest-schema.js +16 -0
  199. package/src/modules.js +2 -2
  200. package/src/role-responsibilities.js +6 -5
  201. package/tests/acclaim_feed_evidence_db.mjs +1 -1
  202. package/tests/activity_rollup_as_of.mjs +1 -1
  203. package/tests/api_alias_caller_ratchet.mjs +3 -3
  204. package/tests/application_lifecycle.mjs +1 -1
  205. package/tests/artist_review.mjs +1 -1
  206. package/tests/artist_review_goal.mjs +1 -1
  207. package/tests/autobongos_host.mjs +19 -1
  208. package/tests/blocker_hall_live_proof.mjs +1 -1
  209. package/tests/blocker_tasks_named_db.mjs +2 -2
  210. package/tests/canonical_profile_url.mjs +1 -1
  211. package/tests/cap_newcomer_tasks.mjs +13 -13
  212. package/tests/character_creation.mjs +294 -0
  213. package/tests/claim_gate_open_blocker.mjs +2 -2
  214. package/tests/claim_gate_rebase.mjs +6 -6
  215. package/tests/claude_materialize.mjs +75 -1
  216. package/tests/clawback_confirmed_backlog.mjs +3 -3
  217. package/tests/collab_page.mjs +8 -9
  218. package/tests/collab_self_settling.mjs +2 -2
  219. package/tests/community_leaderboard_route.mjs +13 -5
  220. package/tests/community_proof_db.mjs +3 -0
  221. package/tests/community_search_route.mjs +7 -6
  222. package/tests/conductor.mjs +1 -1
  223. package/tests/conductor_main_e2e.mjs +8 -8
  224. package/tests/copy_desk_flags_db.mjs +1 -1
  225. package/tests/copy_desk_page_approve.mjs +2 -2
  226. package/tests/copy_desk_page_claim.mjs +2 -2
  227. package/tests/copy_desk_page_next.mjs +1 -1
  228. package/tests/copy_desk_page_release.mjs +1 -1
  229. package/tests/copy_desk_page_submit.mjs +2 -2
  230. package/tests/core_258_box_session_claims_db.mjs +1 -1
  231. package/tests/craft_mode.mjs +48 -34
  232. package/tests/craft_mode_home.mjs +20 -20
  233. package/tests/craft_mode_looks.mjs +16 -15
  234. package/tests/craft_mode_nav.mjs +31 -29
  235. package/tests/craft_review_mode.mjs +46 -41
  236. package/tests/crafts_vocabulary.mjs +70 -0
  237. package/tests/cross_discipline_priority_direction.mjs +5 -5
  238. package/tests/fitness.mjs +7 -11
  239. package/tests/funded_project_pays_proof_db.mjs +1 -1
  240. package/tests/genesis_home.mjs +21 -1
  241. package/tests/github_chip.mjs +3 -3
  242. package/tests/github_repo_invite.mjs +379 -0
  243. package/tests/goal_criterion_confirm_ui.mjs +1 -1
  244. package/tests/goal_invite_consent.mjs +1 -1
  245. package/tests/goal_map_page.mjs +2 -2
  246. package/tests/goal_sort_staleness.mjs +1 -1
  247. package/tests/goal_spec_task.mjs +1 -1
  248. package/tests/goal_spec_task_db.mjs +3 -3
  249. package/tests/grade_engine.mjs +2 -2
  250. package/tests/grader_no_artifact.mjs +1 -1
  251. package/tests/grader_prompt_fencing.mjs +1 -1
  252. package/tests/grader_prompt_window_bound.mjs +1 -1
  253. package/tests/grader_reader_lens.mjs +2 -2
  254. package/tests/grader_severity_gate.mjs +1 -1
  255. package/tests/grader_unbacked_score.mjs +1 -1
  256. package/tests/hall_approval_queue.mjs +1 -1
  257. package/tests/hall_board_world.mjs +2 -2
  258. package/tests/hall_builders_page_pass.mjs +1 -1
  259. package/tests/hall_city.mjs +1 -1
  260. package/tests/hall_fullscreen.mjs +27 -3
  261. package/tests/hall_kit_charts.mjs +1 -1
  262. package/tests/hall_landing_boot.mjs +1 -1
  263. package/tests/hall_my_agents.mjs +16 -16
  264. package/tests/hall_nav.mjs +22 -15
  265. package/tests/hall_options_menu.mjs +247 -0
  266. package/tests/hall_oversight_world.mjs +1 -1
  267. package/tests/hall_palette.mjs +8 -9
  268. package/tests/hall_profile_world.mjs +3 -3
  269. package/tests/hall_record_world.mjs +2 -2
  270. package/tests/hall_settings_world.mjs +2 -1
  271. package/tests/hall_sitemap.mjs +6 -5
  272. package/tests/hall_start_screen.mjs +244 -0
  273. package/tests/hall_street.mjs +2 -2
  274. package/tests/hall_street_inside.mjs +17 -9
  275. package/tests/hall_studio_world.mjs +3 -3
  276. package/tests/hall_visor.mjs +224 -0
  277. package/tests/help_requests.mjs +47 -15
  278. package/tests/helpers.mjs +4 -4
  279. package/tests/home_city.mjs +6 -4
  280. package/tests/home_city_widget.mjs +33 -14
  281. package/tests/home_gallery_widget.mjs +4 -4
  282. package/tests/home_idea_box.mjs +6 -6
  283. package/tests/hub_community_page.mjs +3 -3
  284. package/tests/hub_craft_rollup.mjs +75 -36
  285. package/tests/hub_recent_ships.mjs +3 -2
  286. package/tests/idea_cascade.mjs +2 -2
  287. package/tests/idea_credit_streams_db.mjs +1 -1
  288. package/tests/idea_detail_page.mjs +1 -1
  289. package/tests/idea_ideate_full.mjs +5 -2
  290. package/tests/idea_spark_hall.mjs +1 -1
  291. package/tests/init.mjs +3 -3
  292. package/tests/instance_manifest.mjs +2 -2
  293. package/tests/kickoff_checklist.mjs +1 -1
  294. package/tests/leaderboard_roles.mjs +34 -25
  295. package/tests/lifecycle_claim_ladder_refs.mjs +55 -0
  296. package/tests/lightbulb.mjs +14 -10
  297. package/tests/main_discipline.mjs +8 -8
  298. package/tests/markdown_line_endings.mjs +1 -1
  299. package/tests/market_watch_pages.mjs +52 -74
  300. package/tests/me_streak_leaderboard_queries.mjs +3 -3
  301. package/tests/migration_allocation.mjs +1 -1
  302. package/tests/mingle.mjs +36 -21
  303. package/tests/module-scope-map.mjs +22 -2
  304. package/tests/module_api.mjs +1 -0
  305. package/tests/module_contributions.mjs +13 -13
  306. package/tests/module_manifest.mjs +29 -0
  307. package/tests/module_skills_hook.mjs +2 -2
  308. package/tests/modules.mjs +14 -7
  309. package/tests/nav_permission_atoms.mjs +2 -1
  310. package/tests/needs_rebase.mjs +1 -1
  311. package/tests/newcomer_restock.mjs +13 -13
  312. package/tests/page_notes_panel.mjs +22 -22
  313. package/tests/page_tweak_no_block.mjs +1 -1
  314. package/tests/paintbrush.mjs +7 -7
  315. package/tests/privacy_matrix.mjs +1 -0
  316. package/tests/profile_activity.mjs +20 -23
  317. package/tests/profile_earnings.mjs +1 -1
  318. package/tests/profile_nudge_prefs.mjs +4 -4
  319. package/tests/profile_role_headline.mjs +73 -46
  320. package/tests/profile_role_stats.mjs +55 -45
  321. package/tests/profile_rollup_consent.mjs +19 -6
  322. package/tests/profile_route.mjs +12 -12
  323. package/tests/profile_ui_cross_project.mjs +4 -4
  324. package/tests/project_invite_suggestions.mjs +2 -2
  325. package/tests/promote_goal_id.mjs +1 -1
  326. package/tests/publish_manifest.mjs +1 -1
  327. package/tests/rank_floor_wiring.mjs +1 -1
  328. package/tests/regrade_actual_minutes.mjs +1 -1
  329. package/tests/removal_proposal.mjs +2 -2
  330. package/tests/reward_gate.mjs +4 -4
  331. package/tests/reward_mode_cost_plus_only.mjs +1 -1
  332. package/tests/role_responsibilities.mjs +18 -12
  333. package/tests/role_stats_db.mjs +17 -16
  334. package/tests/scouting_rollup_fanout.mjs +5 -5
  335. package/tests/seed_tasks.mjs +5 -2
  336. package/tests/session_earnings_mirror_db.mjs +1 -1
  337. package/tests/session_reward_on_land_db.mjs +1 -1
  338. package/tests/session_reward_reconcile_db.mjs +2 -2
  339. package/tests/sessions_kit_pager.mjs +1 -1
  340. package/tests/settings_art_key_absent.mjs +2 -2
  341. package/tests/settings_main_role.mjs +21 -20
  342. package/tests/setup_flow.mjs +3 -3
  343. package/tests/setup_smoke.mjs +20 -0
  344. package/tests/ship_card.mjs +6 -6
  345. package/tests/specialities.mjs +16 -4
  346. package/tests/speciality_db_shape.mjs +6 -6
  347. package/tests/speciality_injection.mjs +17 -17
  348. package/tests/speciality_offer_ui.mjs +5 -5
  349. package/tests/speciality_offered.mjs +6 -5
  350. package/tests/speciality_session_skills.mjs +7 -7
  351. package/tests/speciality_skills.mjs +11 -11
  352. package/tests/speciality_walkthrough_ui.mjs +2 -2
  353. package/tests/sso_checkin_identity_spoof.mjs +2 -1
  354. package/tests/task_classifier_vocab.mjs +10 -0
  355. package/tests/task_cli.mjs +11 -3
  356. package/tests/task_criteria_unlink_db.mjs +2 -2
  357. package/tests/task_discipline_required.mjs +38 -16
  358. package/tests/task_goal_required.mjs +4 -4
  359. package/tests/task_needs_db.mjs +1 -1
  360. package/tests/tweak_replaces_artist_review.mjs +1 -1
  361. package/tests/update_pill.mjs +153 -2
  362. package/tests/upkeep_home.mjs +1 -1
  363. package/tests/v3_c1_options_proof.mjs +145 -0
  364. package/tests/visual_review.mjs +3 -3
  365. package/tests/watch_applications_queue.mjs +8 -5
  366. package/tests/watch_sealed_floor.mjs +3 -1
  367. package/tests/welcome_boot.mjs +12 -9
  368. package/tests/welcome_boot_browser.mjs +13 -13
  369. package/tests/worktree_helper.mjs +16 -1
@@ -76,7 +76,7 @@ async function fileNote(note, { grade, capture, db, log } = {}) {
76
76
  // an empty list means nothing filed and the caller says so.
77
77
  async function fileIdeatorNotes(submit, { grade, capture, log, authorId, authorName } = {}) {
78
78
  const page = { page_id: submit.page_id, page_path: submit.page_path, page_title: submit.page_title, body_md: submit.body_md };
79
- let ideas = await aiIdeas({ note: page, grade, voice: 'ideator', timeoutMs: IDEATOR_TIMEOUT_MS });
79
+ let ideas = await aiIdeas({ note: page, grade, voice: 'notes', timeoutMs: IDEATOR_TIMEOUT_MS });
80
80
  let splitBy = 'ai';
81
81
  if (!ideas) { ideas = [N.wholeDocIdea({ pageTitle: page.page_title, pageId: page.page_id, bodyMd: page.body_md })]; splitBy = 'whole'; }
82
82
  const filed = [];
@@ -5,15 +5,16 @@
5
5
  // A reporting instance sends, per craft the builder has EARNED credits in, that
6
6
  // craft's headline counts (src/bongos/auth-admission.js readCraftRollup):
7
7
  //
8
- // engineer { shipped, credits }
9
- // artist { pages_approved, credits }
10
- // ideator { filed, full_ideas, ratified, credits }
8
+ // artisan { shipped, credits }
9
+ // artist { pages_approved, filed, full_ideas, ratified, credits }
10
+ //
11
+ // (the old engineer / artist / ideator shape is folded in at ingest, foldLegacy)
11
12
  //
12
13
  // Two halves live here, next to each other so they cannot disagree on the shape:
13
14
  //
14
15
  // sanitizeCrafts — what upsertActivity BINDS. The report is third-party
15
16
  // self-report (ADR 0171 D7), so the shape and the caps are the hub's, not
16
- // the sender's: only the three crafts, only the named counts, each a
17
+ // the sender's: only the two crafts, only the named counts, each a
17
18
  // non-negative integer under a ceiling or null. Anything else is dropped.
18
19
  // A count is never text, so nothing here can carry a title or wording.
19
20
  //
@@ -30,10 +31,36 @@
30
31
  'use strict';
31
32
 
32
33
  const CRAFT_FIELDS = Object.freeze({
33
- engineer: Object.freeze(['shipped', 'credits']),
34
- artist: Object.freeze(['pages_approved', 'credits']),
35
- ideator: Object.freeze(['filed', 'full_ideas', 'ratified', 'credits']),
34
+ artisan: Object.freeze(['shipped', 'credits']),
35
+ artist: Object.freeze(['pages_approved', 'filed', 'full_ideas', 'ratified', 'credits']),
36
36
  });
37
+
38
+ // A reporting instance on a core older than task 1005141 still sends the old
39
+ // crafts: `engineer`, and `artist` + `ideator` as two blocks. They are folded
40
+ // into today's shape BEFORE the hub's caps run, so an old report and a new one
41
+ // land identically: engineer becomes artisan, and the ideator's counts join the
42
+ // artist block with the two credit figures summed. A craft already reported under
43
+ // its new name wins over an old-name block for the same craft.
44
+ function foldLegacy(raw) {
45
+ const out = { ...raw };
46
+ if (out.engineer && !out.artisan) out.artisan = out.engineer;
47
+ delete out.engineer;
48
+ const ideator = out.ideator;
49
+ delete out.ideator;
50
+ if (ideator && typeof ideator === 'object' && !Array.isArray(ideator)) {
51
+ const art = out.artist && typeof out.artist === 'object' && !Array.isArray(out.artist) ? out.artist : {};
52
+ // Each half is capped BEFORE the sum: a hostile -3 on one side must not
53
+ // quietly shave the other side's honest count.
54
+ const a = cleanCount(art.credits);
55
+ const i = cleanCount(ideator.credits);
56
+ out.artist = {
57
+ ...art,
58
+ filed: ideator.filed, full_ideas: ideator.full_ideas, ratified: ideator.ratified,
59
+ credits: a == null && i == null ? null : (a || 0) + (i || 0),
60
+ };
61
+ }
62
+ return out;
63
+ }
37
64
  // Far above any honest count, low enough that a sum over every project stays a
38
65
  // safe integer and a hostile report cannot dominate a profile by one number.
39
66
  const MAX_CRAFT_COUNT = 10_000_000;
@@ -45,6 +72,7 @@ function cleanCount(v) {
45
72
  function sanitizeCrafts(raw) {
46
73
  const out = {};
47
74
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return out;
75
+ raw = foldLegacy(raw);
48
76
  for (const [craft, fields] of Object.entries(CRAFT_FIELDS)) {
49
77
  const block = raw[craft];
50
78
  if (!block || typeof block !== 'object' || Array.isArray(block)) continue;
@@ -0,0 +1,45 @@
1
+ -- platform_identity_036 — the hub's per-project activity rollup follows core_296's craft
2
+ -- rename (task 1005141, BONGOS-V3 C1): engineer → artisan, ideator → artist.
3
+ --
4
+ -- `disciplines` is mapped and deduped. `crafts` (platform_identity_029) is keyed by
5
+ -- craft: the engineer block becomes the artisan block, and the ideator block's counts
6
+ -- (filed, full_ideas, ratified) merge into the artist block, with credits summed —
7
+ -- the shape modules/platform-identity/craft-rollup.js now sanitizes. A report from an
8
+ -- instance still on an older core is folded the same way at ingest, so these rows and
9
+ -- fresh reports agree.
10
+
11
+ BEGIN;
12
+
13
+ UPDATE platform_identity_builder_project_activity a
14
+ SET disciplines = ARRAY(
15
+ SELECT m.craft FROM (
16
+ SELECT CASE d WHEN 'engineer' THEN 'artisan' WHEN 'ideator' THEN 'artist' ELSE d END AS craft,
17
+ min(ord) AS first_seen
18
+ FROM unnest(a.disciplines) WITH ORDINALITY AS u(d, ord)
19
+ GROUP BY 1
20
+ ) m ORDER BY m.first_seen)
21
+ WHERE a.disciplines && ARRAY['engineer', 'ideator']::text[];
22
+
23
+ UPDATE platform_identity_builder_project_activity
24
+ SET crafts = (crafts - 'engineer') || jsonb_build_object('artisan', crafts -> 'engineer')
25
+ WHERE crafts ? 'engineer' AND NOT crafts ? 'artisan';
26
+
27
+ UPDATE platform_identity_builder_project_activity
28
+ SET crafts = (crafts - 'ideator') || jsonb_build_object('artist',
29
+ coalesce(crafts -> 'artist', '{}'::jsonb)
30
+ || jsonb_strip_nulls(jsonb_build_object(
31
+ 'filed', crafts -> 'ideator' -> 'filed',
32
+ 'full_ideas', crafts -> 'ideator' -> 'full_ideas',
33
+ 'ratified', crafts -> 'ideator' -> 'ratified'))
34
+ || jsonb_build_object('credits',
35
+ CASE WHEN jsonb_typeof(crafts -> 'artist' -> 'credits') = 'number'
36
+ OR jsonb_typeof(crafts -> 'ideator' -> 'credits') = 'number'
37
+ THEN to_jsonb(coalesce((crafts -> 'artist' ->> 'credits')::bigint, 0)
38
+ + coalesce((crafts -> 'ideator' ->> 'credits')::bigint, 0))
39
+ ELSE 'null'::jsonb END))
40
+ WHERE crafts ? 'ideator';
41
+
42
+ INSERT INTO schema_migrations (version) VALUES ('platform_identity_036_crafts_artisan_artist')
43
+ ON CONFLICT (version) DO NOTHING;
44
+
45
+ COMMIT;
@@ -671,8 +671,8 @@ async function upsertActivity({ clientId, githubId, credits = 0, tasksShipped =
671
671
  // api.LIVE_RANK_LADDER is a published doorway export (src/module-api.js, a
672
672
  // lazy getter over db-kernel's canonical four-rank ladder).
673
673
  const safeRank = api.LIVE_RANK_LADDER.includes(rank) ? rank : null;
674
- const disc = (Array.isArray(disciplines) ? disciplines : [])
675
- .filter((d) => typeof d === 'string' && d.length > 0 && d.length <= MAX_DISCIPLINE_LENGTH)
674
+ const disc = api.crafts.normalizeCrafts((Array.isArray(disciplines) ? disciplines : []) // an older core's engineer/ideator → today's crafts, as foldLegacy (task 1005193)
675
+ .filter((d) => typeof d === 'string' && d.length > 0 && d.length <= MAX_DISCIPLINE_LENGTH))
676
676
  .slice(0, MAX_ROLLUP_DISCIPLINES);
677
677
  const ships = sanitizeRecentShips(recentShips, clientOrigin);
678
678
  // Each craft's own counts (task 1004440), shaped and capped here — see craft-rollup.js.
@@ -55,7 +55,8 @@ module.exports = function communityLeaderboardRoutes() {
55
55
  if (!leaderboard.isWindow(window)) {
56
56
  return res.fail('bad_window', { status: 400, message: `window must be one of: ${leaderboard.WINDOWS.join(', ')}` });
57
57
  }
58
- const discipline = (typeof req.query.discipline === 'string' ? req.query.discipline : '').trim();
58
+ // An old link's engineer/ideator reads as today's craft (task 1005141).
59
+ const discipline = require('../../../src/module-api').crafts.normalizeCraft((typeof req.query.discipline === 'string' ? req.query.discipline : '').trim());
59
60
  if (discipline.length > leaderboard.MAX_DISCIPLINE_CHARS) {
60
61
  return res.fail('discipline_too_long', { status: 400, message: `discipline must be at most ${leaderboard.MAX_DISCIPLINE_CHARS} characters` });
61
62
  }
@@ -63,7 +63,8 @@ module.exports = function communitySearchRoutes() {
63
63
  if (q.length > builderSearch.MAX_QUERY_CHARS) {
64
64
  return res.fail('query_too_long', { status: 400, message: `q must be at most ${builderSearch.MAX_QUERY_CHARS} characters` });
65
65
  }
66
- const discipline = (typeof req.query.discipline === 'string' ? req.query.discipline : '').trim();
66
+ // An old link's engineer/ideator reads as today's craft (task 1005141).
67
+ const discipline = require('../../../src/module-api').crafts.normalizeCraft((typeof req.query.discipline === 'string' ? req.query.discipline : '').trim());
67
68
  if (discipline.length > builderSearch.MAX_DISCIPLINE_CHARS) {
68
69
  return res.fail('discipline_too_long', { status: 400, message: `discipline must be at most ${builderSearch.MAX_DISCIPLINE_CHARS} characters` });
69
70
  }
@@ -691,7 +691,8 @@ module.exports = function ssoRoutes() {
691
691
  tasksShipped: b.tasks_shipped,
692
692
  karma: b.karma,
693
693
  rank: b.rank || null,
694
- disciplines: Array.isArray(b.disciplines) ? b.disciplines : [],
694
+ // An instance on an older core still reports engineer/ideator (task 1005141).
695
+ disciplines: Array.isArray(b.disciplines) ? api.crafts.normalizeCrafts(b.disciplines) : [],
695
696
  recentShips: b.recent_ships,
696
697
  crafts: b.crafts,
697
698
  // The AUTHENTICATED client's registered origin — the bound every pushed
@@ -606,14 +606,14 @@ const upsertCall = (pool) => pool.calls.find((c) => /INSERT INTO platform_identi
606
606
 
607
607
  await ta('upsertActivity upserts per (client_id, github_id) with the talent snapshot', async () => {
608
608
  const pool = activityPool();
609
- await P.upsertActivity({ clientId: 'c1', githubId: 42, credits: 100, tasksShipped: 5, karma: 3, rank: 'metic', disciplines: ['engineer'] }, { pool });
609
+ await P.upsertActivity({ clientId: 'c1', githubId: 42, credits: 100, tasksShipped: 5, karma: 3, rank: 'metic', disciplines: ['artisan'] }, { pool });
610
610
  const c = upsertCall(pool);
611
611
  assert.match(c.text, /ON CONFLICT \(client_id, github_id\) DO UPDATE/);
612
612
  // recent_ships (task 1002803) rides the same row as a jsonb STRING param —
613
613
  // an empty list when the sender reported none, never NULL. Its own cap,
614
614
  // shape and link bound are covered by tests/hub_recent_ships.mjs. The last param
615
615
  // is the report's as-of stamp (task 1004268): null when the sender gave none.
616
- assert.deepEqual(c.params, ['c1', 42, 100, 5, 3, 'metic', ['engineer'], '[]', null, '{}']);
616
+ assert.deepEqual(c.params, ['c1', 42, 100, 5, 3, 'metic', ['artisan'], '[]', null, '{}']);
617
617
  });
618
618
 
619
619
  await ta('upsertActivity sanitizes third-party self-report (ADR 0171 D7): off-ladder rank → null, disciplines validated + capped', async () => {
@@ -622,7 +622,7 @@ await ta('upsertActivity sanitizes third-party self-report (ADR 0171 D7): off-la
622
622
  clientId: 'c1', githubId: 42,
623
623
  rank: 'Supreme Overlord', // not one of the four core rank keys → stored null
624
624
  disciplines: [
625
- 'engineer', 42, null, { evil: true }, // non-strings dropped
625
+ 'artisan', 42, null, { evil: true }, // non-strings dropped
626
626
  'x'.repeat(33), // over-length dropped
627
627
  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', // valid tail — capped to 8 total
628
628
  ],
@@ -633,7 +633,7 @@ await ta('upsertActivity sanitizes third-party self-report (ADR 0171 D7): off-la
633
633
  assert.ok(disc.length <= 8, `disciplines capped at 8 (got ${disc.length})`);
634
634
  assert.ok(disc.every((d) => typeof d === 'string' && d.length > 0 && d.length <= 32),
635
635
  'only bounded string elements survive');
636
- assert.equal(disc[0], 'engineer', 'valid elements pass through in order');
636
+ assert.equal(disc[0], 'artisan', 'valid elements pass through in order');
637
637
  });
638
638
 
639
639
  await ta('upsertActivity keeps a legitimate core rank key', async () => {
@@ -733,7 +733,7 @@ await ta('getRollupForBuilder guards non-numeric github_id BEFORE any query (no
733
733
 
734
734
  await ta('getRollupForBuilder aggregates un-fanned + names only publicly-catalogued projects', async () => {
735
735
  const pool = mockPool([
736
- { rows: [{ projects: 2, credits: 150, works_shipped: 7, karma: 4, last_active: 'T', disciplines: ['engineer', 'ui'] }] },
736
+ { rows: [{ projects: 2, credits: 150, works_shipped: 7, karma: 4, last_active: 'T', disciplines: ['artisan', 'ui'] }] },
737
737
  { rows: [
738
738
  { name: 'Public Proj', origin: 'https://pub.example', rank_held: 'metic', is_public: true },
739
739
  { name: 'Secret Proj', origin: 'https://sec.example', rank_held: 'archon', is_public: false },
@@ -741,7 +741,7 @@ await ta('getRollupForBuilder aggregates un-fanned + names only publicly-catalog
741
741
  ]);
742
742
  const out = await P.getRollupForBuilder(42, { pool });
743
743
  assert.deepEqual(out.totals, { projects: 2, credits: 150, works_shipped: 7, karma: 4 });
744
- assert.deepEqual(out.disciplines, ['engineer', 'ui']);
744
+ assert.deepEqual(out.disciplines, ['artisan', 'ui']);
745
745
  assert.equal(out.projects.length, 1, 'only the public catalog row is named');
746
746
  // recent_ships rides the project row it belongs to (task 1002803), so a work
747
747
  // title can only surface where the project name already may.
@@ -262,9 +262,8 @@
262
262
  <div class="commFilters">
263
263
  <div class="commTabs" id="craftTabs" role="group" aria-label="Role">
264
264
  <button class="commTab" type="button" data-craft="" aria-pressed="true">Everyone</button>
265
- <button class="commTab" type="button" data-craft="engineer" aria-pressed="false">Engineers</button>
265
+ <button class="commTab" type="button" data-craft="artisan" aria-pressed="false">Artisans</button>
266
266
  <button class="commTab" type="button" data-craft="artist" aria-pressed="false">Artists</button>
267
- <button class="commTab" type="button" data-craft="ideator" aria-pressed="false">Ideators</button>
268
267
  <button class="commTab" type="button" data-craft="ui" aria-pressed="false">Designers</button>
269
268
  </div>
270
269
  <div class="commWhen">
@@ -314,9 +313,8 @@
314
313
  <label class="vh" for="findCraft">Role</label>
315
314
  <select id="findCraft">
316
315
  <option value="">Any role</option>
317
- <option value="engineer">Engineers</option>
316
+ <option value="artisan">Artisans</option>
318
317
  <option value="artist">Artists</option>
319
- <option value="ideator">Ideators</option>
320
318
  <option value="ui">Designers</option>
321
319
  </select>
322
320
  <button id="findBtn" type="submit" class="btn small">Search</button>
@@ -365,7 +363,7 @@
365
363
  var WINDOWS = ['7d', '30d', '365d', 'all'];
366
364
  var QUERY_MIN = 2;
367
365
  var QUERY_MAX = 100;
368
- var CRAFT_WORD = { engineer: 'engineer', artist: 'artist', ideator: 'ideator', ui: 'designer' };
366
+ var CRAFT_WORD = { artisan: 'artisan', artist: 'artist', ui: 'designer' };
369
367
  // The five sub-pages this file is served as (platform-server.js's /community routes);
370
368
  // the path picks one, and its menu items are marked current. The home adds no title.
371
369
  var VIEWS = {
@@ -774,7 +774,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
774
774
  <div class="layer">
775
775
  <h1 data-brand-tagline><span class="l1">Building Digitally,</span><span class="l2">as simple as Bongos.</span></h1>
776
776
 
777
- <p class="lede">Bringing AI to the builder, ideator, and artist in everyone.</p>
777
+ <p class="lede">Bringing AI to the artisan and the artist in everyone.</p>
778
778
 
779
779
  <div class="doors">
780
780
  <div class="doorbar">
@@ -1389,7 +1389,7 @@ summary{min-height:24px;padding:3px 0;}
1389
1389
  <div class="card mSec" id="mInvite">
1390
1390
  <h2>Invite a builder</h2>
1391
1391
  <div id="mInv">
1392
- <p class="finePrint">By GitHub username. The invite is sent from your project’s own hall — that is the invite that lets them in — and they see it on their projects page here too.</p>
1392
+ <p class="finePrint">By GitHub username. The invite is sent from your project’s own hall — that is the invite that lets them in — and they see it on their projects page here too. The hall’s invite box can also invite them to the project’s GitHub repository, which is a separate invite they accept on GitHub.</p>
1393
1393
  <div class="attach">
1394
1394
  <label class="vh" for="mInviteWho">GitHub username</label>
1395
1395
  <input type="text" id="mInviteWho" placeholder="GitHub username" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="200">
@@ -0,0 +1,30 @@
1
+ -- specialities_004 — specialities follow core_296's craft rename (task 1005141,
2
+ -- BONGOS-V3 C1): engineer → artisan, ideator → artist.
3
+ --
4
+ -- A speciality's discipline is immutable to its OWNER (the update path refuses a
5
+ -- change); this is a vocabulary rename, not a move between crafts, so it rewrites the
6
+ -- catalog and the adoption copy together.
7
+ --
8
+ -- The fold has one collision: specialities_adoptions allows ONE active adoption per
9
+ -- builder per craft. A builder with an active artist speciality AND an active ideator
10
+ -- one would hold two artist adoptions after the rename, so the ideator adoption is
11
+ -- deactivated first (deactivating is how a builder switches; nothing is deleted, and
12
+ -- they can re-adopt it from their speciality page).
13
+
14
+ BEGIN;
15
+
16
+ UPDATE specialities_adoptions i
17
+ SET active = FALSE
18
+ WHERE i.active AND i.discipline = 'ideator'
19
+ AND EXISTS (SELECT 1 FROM specialities_adoptions a
20
+ WHERE a.builder_id = i.builder_id AND a.active AND a.discipline = 'artist');
21
+
22
+ UPDATE specialities_catalog SET discipline = 'artisan' WHERE discipline = 'engineer';
23
+ UPDATE specialities_catalog SET discipline = 'artist' WHERE discipline = 'ideator';
24
+ UPDATE specialities_adoptions SET discipline = 'artisan' WHERE discipline = 'engineer';
25
+ UPDATE specialities_adoptions SET discipline = 'artist' WHERE discipline = 'ideator';
26
+
27
+ INSERT INTO schema_migrations (version) VALUES ('specialities_004_crafts_artisan_artist')
28
+ ON CONFLICT (version) DO NOTHING;
29
+
30
+ COMMIT;
@@ -192,7 +192,7 @@ module.exports = function buildSpecialitiesRouter() {
192
192
 
193
193
  const draft = {
194
194
  name: String(b.name).trim(),
195
- discipline: b.discipline,
195
+ discipline: api.crafts.normalizeCraft(b.discipline), // old spellings read as today's (task 1005141)
196
196
  summary: b.summary || '',
197
197
  contract_md: b.contract_md || '',
198
198
  visibility: b.visibility || 'private',
@@ -247,7 +247,7 @@ module.exports = function buildSpecialitiesRouter() {
247
247
  const b = req.body || {};
248
248
  const draft = {
249
249
  name: String(b.name).trim(),
250
- discipline: b.discipline,
250
+ discipline: api.crafts.normalizeCraft(b.discipline), // old spellings read as today's (task 1005141)
251
251
  summary: b.summary || '',
252
252
  contract_md: b.contract_md || '',
253
253
  visibility: b.visibility || 'private',
@@ -987,7 +987,7 @@ function renderAchievementRow(ids) {
987
987
  // task 1004437 (WA6.RP07): each row names its roles, main first — the crafts
988
988
  // the builder has EARNED in, from the row's own `roles.shown`, not only what
989
989
  // they declared. The same words the hall's roster uses. Text only, never markup.
990
- const ROLE_WORD = { engineer: 'Engineer', artist: 'Artist', ideator: 'Ideator' };
990
+ const ROLE_WORD = { artisan: 'Artisan', artist: 'Artist' };
991
991
  function renderRoles(roles) {
992
992
  const shown = roles && Array.isArray(roles.shown) ? roles.shown.filter((c) => ROLE_WORD[c]) : [];
993
993
  if (!shown.length) return null;
@@ -2,27 +2,27 @@
2
2
  "_": "GET /community/leaderboard for the kit stub (task 1003363): one ranked list per window, already in the route's order (credits, ships, karma, handle). The stub filters by discipline and numbers `place`. Names are invented; the long one tests the row's wrap.",
3
3
  "boards": {
4
4
  "all": [
5
- { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 18420, "tasks_shipped": 212, "karma": 64, "disciplines": ["engineer", "ui"] },
6
- { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": 9310, "tasks_shipped": 131, "karma": 40, "disciplines": ["engineer"] },
5
+ { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 18420, "tasks_shipped": 212, "karma": 64, "disciplines": ["artisan", "ui"] },
6
+ { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": 9310, "tasks_shipped": 131, "karma": 40, "disciplines": ["artisan"] },
7
7
  { "handle": "ada-okafor", "github_login": "adaokafor", "display_name": "Ada Okafor", "avatar_url": "/__kit/avatar.svg", "credits": 6125, "tasks_shipped": 77, "karma": 52, "disciplines": ["artist"] },
8
- { "handle": "lin-wei", "github_login": "linwei", "display_name": "Lin Wei", "avatar_url": null, "credits": 2480, "tasks_shipped": 31, "karma": 18, "disciplines": ["ideator", "engineer"] },
8
+ { "handle": "lin-wei", "github_login": "linwei", "display_name": "Lin Wei", "avatar_url": null, "credits": 2480, "tasks_shipped": 31, "karma": 18, "disciplines": ["artist", "artisan"] },
9
9
  { "handle": "maximiliana-van-der-berghe-lindqvist", "github_login": "mvdbl", "display_name": "Maximiliana van der Berghe-Lindqvist", "avatar_url": "/__kit/avatar.svg", "credits": 940, "tasks_shipped": 12, "karma": 7, "disciplines": ["ui"] },
10
- { "handle": "jo-park", "github_login": "jopark", "display_name": null, "avatar_url": "/__kit/avatar.svg", "credits": 310, "tasks_shipped": 1, "karma": 0, "disciplines": ["ideator"] }
10
+ { "handle": "jo-park", "github_login": "jopark", "display_name": null, "avatar_url": "/__kit/avatar.svg", "credits": 310, "tasks_shipped": 1, "karma": 0, "disciplines": ["artist"] }
11
11
  ],
12
12
  "365d": [
13
- { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 12200, "tasks_shipped": 150, "karma": 41, "disciplines": ["engineer", "ui"] },
13
+ { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 12200, "tasks_shipped": 150, "karma": 41, "disciplines": ["artisan", "ui"] },
14
14
  { "handle": "ada-okafor", "github_login": "adaokafor", "display_name": "Ada Okafor", "avatar_url": "/__kit/avatar.svg", "credits": 6125, "tasks_shipped": 77, "karma": 52, "disciplines": ["artist"] },
15
- { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": 4020, "tasks_shipped": 58, "karma": 22, "disciplines": ["engineer"] },
16
- { "handle": "lin-wei", "github_login": "linwei", "display_name": "Lin Wei", "avatar_url": null, "credits": 2480, "tasks_shipped": 31, "karma": 18, "disciplines": ["ideator", "engineer"] }
15
+ { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": 4020, "tasks_shipped": 58, "karma": 22, "disciplines": ["artisan"] },
16
+ { "handle": "lin-wei", "github_login": "linwei", "display_name": "Lin Wei", "avatar_url": null, "credits": 2480, "tasks_shipped": 31, "karma": 18, "disciplines": ["artist", "artisan"] }
17
17
  ],
18
18
  "30d": [
19
19
  { "handle": "ada-okafor", "github_login": "adaokafor", "display_name": "Ada Okafor", "avatar_url": "/__kit/avatar.svg", "credits": 1340, "tasks_shipped": 19, "karma": 9, "disciplines": ["artist"] },
20
- { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 880, "tasks_shipped": 11, "karma": 4, "disciplines": ["engineer", "ui"] },
21
- { "handle": "jo-park", "github_login": "jopark", "display_name": null, "avatar_url": "/__kit/avatar.svg", "credits": 310, "tasks_shipped": 1, "karma": 0, "disciplines": ["ideator"] }
20
+ { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 880, "tasks_shipped": 11, "karma": 4, "disciplines": ["artisan", "ui"] },
21
+ { "handle": "jo-park", "github_login": "jopark", "display_name": null, "avatar_url": "/__kit/avatar.svg", "credits": 310, "tasks_shipped": 1, "karma": 0, "disciplines": ["artist"] }
22
22
  ],
23
23
  "7d": [
24
24
  { "handle": "ada-okafor", "github_login": "adaokafor", "display_name": "Ada Okafor", "avatar_url": "/__kit/avatar.svg", "credits": 420, "tasks_shipped": 6, "karma": 3, "disciplines": ["artist"] },
25
- { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": -15, "tasks_shipped": 0, "karma": 1, "disciplines": ["engineer"] }
25
+ { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": -15, "tasks_shipped": 0, "karma": 1, "disciplines": ["artisan"] }
26
26
  ]
27
27
  }
28
28
  }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "_": "GET /community/search for the kit stub (task 1003363): the public, existence-visible builders the stub matches by name or handle. Sam Rivera hides their numbers, so `disciplines` is null: found by name, dropped by any craft filter (builder-search.js).",
3
3
  "directory": [
4
- { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "disciplines": ["engineer", "ui"] },
4
+ { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "disciplines": ["artisan", "ui"] },
5
5
  { "handle": "priyanka-shah", "github_login": "pshah", "display_name": "Priyanka Shah", "avatar_url": "/__kit/avatar.svg", "disciplines": ["artist"] },
6
- { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "disciplines": ["engineer"] },
6
+ { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "disciplines": ["artisan"] },
7
7
  { "handle": "ada-okafor", "github_login": "adaokafor", "display_name": "Ada Okafor", "avatar_url": "/__kit/avatar.svg", "disciplines": ["artist"] },
8
8
  { "handle": "sam-rivera", "github_login": "samrivera", "display_name": "Sam Rivera", "avatar_url": "/__kit/avatar.svg", "disciplines": null }
9
9
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_": "GET /scouting for a viewer the recruiting door admits — a project owner (task 1003363). `builders` is the ranked tier, which the community page never draws (the leaderboard already shows those numbers to everyone); `name_only` is ADR 0334's hidden-but-open-to-recruiters tier, login, name and avatar only, alphabetical by login as listScoutingNameOnly sends it.",
3
3
  "builders": [
4
- { "github_id": "9001", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "project_count": 3, "total_credits": 18420, "total_tasks_shipped": 212, "total_karma": 64, "last_active": "2026-10-20T10:00:00Z", "disciplines": ["engineer", "ui"] }
4
+ { "github_id": "9001", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "project_count": 3, "total_credits": 18420, "total_tasks_shipped": 212, "total_karma": 64, "last_active": "2026-10-20T10:00:00Z", "disciplines": ["artisan", "ui"] }
5
5
  ],
6
6
  "name_only": [
7
7
  { "github_login": "kemi-a", "display_name": "Kemi Adeyemi", "avatar_url": "/__kit/avatar.svg" },
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.78",
3
+ "version": "1.24.80",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.78",
9
+ "version": "1.24.80",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.78",
3
+ "version": "1.24.80",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -11507,5 +11507,81 @@
11507
11507
  "id": "1005094",
11508
11508
  "text": "Running Autobongos on a device is now one button. In Settings, Add a device gives you one line to paste on any Windows PC or Mac; its card then appears by itself with a Run button. The button's face tells you what the device i"
11509
11509
  }
11510
+ ],
11511
+ "1.24.79": [
11512
+ {
11513
+ "id": "1005186",
11514
+ "text": "Closed a gap opened by letting Autobongos run on any device. The runner now keeps its own sign-in in its own folder, and the rule that stops an AI session from opening sign-in files did not yet cover that folder. It does now,"
11515
+ }
11516
+ ],
11517
+ "1.24.80": [
11518
+ {
11519
+ "id": "1005193",
11520
+ "text": "New versions can be published again: the checks broken by the craft rename pass, and projects on older versions no longer show the old craft name on the hub."
11521
+ },
11522
+ {
11523
+ "id": "1005148",
11524
+ "text": "In game mode, each builder now sees the project through a visor: their character's actions on the glass, their own character and credits in the corner, and a list of every action on the A key."
11525
+ },
11526
+ {
11527
+ "id": "1005145",
11528
+ "text": "Checked and locked in: after signing in and pressing Start there is no sidebar, and sound, the command-line key and module switches are all reached inside the one options menu; the old settings pages now only send you there."
11529
+ },
11530
+ {
11531
+ "id": "1004794",
11532
+ "text": "Modules can now say what language they are written in, and one naming a language Bongos cannot run yet is turned away with a clear reason."
11533
+ },
11534
+ {
11535
+ "id": "1004793",
11536
+ "text": "Work on the module store can now be planned under a goal, because its code finally has an owner in the project map."
11537
+ },
11538
+ {
11539
+ "id": "1005147",
11540
+ "text": "Builders now pick one of three characters (Artisan, Artist or Patron, drawn from the owner's art) when they join; the pick follows them to every device and shows as their sprite in the top bar."
11541
+ },
11542
+ {
11543
+ "id": "1005144",
11544
+ "text": "Every setting now lives in one options menu with a search box, and in game mode Escape opens it and closes it again."
11545
+ },
11546
+ {
11547
+ "id": "1005191",
11548
+ "text": "A project that is behind now asks its owner, in a clear pop-up, whether to update the builders hall."
11549
+ },
11550
+ {
11551
+ "id": "1005143",
11552
+ "text": "Signing in now opens a start screen (Start, Options, Achievements, Extras, Quit). Start plays a short load screen where the project city builds up week by week with a tip, then the hall opens full screen."
11553
+ },
11554
+ {
11555
+ "id": "1005158",
11556
+ "text": "Design written for claiming work as a chain: a chain reserves a run of tasks for you and your party, while each task inside it is still claimed on its own through the same safety checks as today. Nobody outside the party can g"
11557
+ },
11558
+ {
11559
+ "id": "1005190",
11560
+ "text": "A project owner can now skip the setup stages and open the full hall straight away, with the skip recorded."
11561
+ },
11562
+ {
11563
+ "id": "1005189",
11564
+ "text": "Setting up as a new builder no longer stops to ask for an optional image-generation key."
11565
+ },
11566
+ {
11567
+ "id": "1005146",
11568
+ "text": "Decided how the game's first-person visor sits over the city and the sky, and what the Escape key closes first. Also found a small bug where closing a city card brings the sidebar back."
11569
+ },
11570
+ {
11571
+ "id": "1005066",
11572
+ "text": "Project owners can now invite someone to the project's GitHub repository from the hall's invite box, always read-only, so a person who signs in to the hall can also see the code on their own GitHub once they accept GitHub's em"
11573
+ },
11574
+ {
11575
+ "id": "1005185",
11576
+ "text": "Making a new worktree on a hosted project now tells you the claim command that actually works there (npx bongos claim), instead of one that fails."
11577
+ },
11578
+ {
11579
+ "id": "1005183",
11580
+ "text": "The notes that guide agents around the claim rules pointed at the wrong lines of code. They now name the code directly, and a test stops them going stale again."
11581
+ },
11582
+ {
11583
+ "id": "1005141",
11584
+ "text": "The builders' crafts now carry their game names: Engineers are Artisans, Ideators join the Artists, and Patron is the third character; old links and old records keep working."
11585
+ }
11510
11586
  ]
11511
11587
  }
@@ -74,16 +74,31 @@ const LOG_ROTATE_BYTES = 20 * 1024 * 1024;
74
74
  function runnerHome(env = process.env, home = os.homedir(), repoRoot = REPO_ROOT) {
75
75
  if (env.AUTOBONGOS_HOME) return path.resolve(env.AUTOBONGOS_HOME);
76
76
  if (path.basename(repoRoot) === 'repo' && isRunnerCopy(repoRoot)) return path.dirname(repoRoot);
77
- return path.join(home, '.cloudbongos-runner');
77
+ return path.join(home, RUNNER_DIR);
78
78
  }
79
79
 
80
- // hostPaths — every file this program owns, from one root. `config` is handed to
81
- // the supervisor as BONGOS_CONFIG_ROOT, which is what keeps its login apart.
80
+ // The runner's folder in the person's home. Named once: the read-deny rule that
81
+ // keeps agents out of the runner's login names this same folder
82
+ // (scripts/bongos/claude-materialize.js RUNNER_CONFIG_DENY), and a test holds the
83
+ // two together.
84
+ const RUNNER_DIR = '.cloudbongos-runner';
85
+
86
+ // hostPaths — every file this program owns. `configRoot` is handed to the
87
+ // supervisor as BONGOS_CONFIG_ROOT, which is what keeps its login apart.
88
+ //
89
+ // THE LOGIN LIVES IN ONE PLACE ON EVERY DEVICE (task 1005186): configRoot is always
90
+ // <home>/.cloudbongos-runner/config, WHATEVER AUTOBONGOS_HOME says. That variable
91
+ // moves the runner's copy of the project, its logs and its lock; it does not move
92
+ // the login. The reason is the deny rule: agents are refused reads under exactly
93
+ // that folder, and a login that could be put somewhere else by an environment
94
+ // variable would be a login the rule does not cover — on precisely the device
95
+ // someone had customised. One runner per device already, so there is nothing a
96
+ // second config folder would be for.
82
97
  function hostPaths(env = process.env, home = os.homedir()) {
83
98
  const root = runnerHome(env, home);
84
99
  return {
85
100
  root,
86
- configRoot: path.join(root, 'config'),
101
+ configRoot: path.join(home, RUNNER_DIR, 'config'),
87
102
  logDir: path.join(root, 'logs'),
88
103
  log: path.join(root, 'logs', 'autobongos.log'),
89
104
  lock: path.join(root, 'host.json'),
@@ -82,7 +82,7 @@ function buildWorkerArgs({ sessionId, model = DEFAULT_MODEL, isRoot = false } =
82
82
  // runs unattended with the power to ship, so that text must never be able to
83
83
  // instruct it. Reuses the grader's hardened fence rather than re-typing one, so
84
84
  // there is a single place this class of text is scrubbed and delimited.
85
- function buildWorkerPrompt({ task, packPath = 'docs/packs/engineer.md' } = {}) {
85
+ function buildWorkerPrompt({ task, packPath = 'docs/packs/artisan.md' } = {}) {
86
86
  if (!task || !task.id) throw new Error('buildWorkerPrompt: a task with an id is required');
87
87
  return [
88
88
  `You are an autonomous builder working ONE claimed task end to end. Read CLAUDE.md and ${packPath} for context. The task is ${task.id}.`,
@@ -481,10 +481,29 @@ const STARTER_DENY_SECRETS = [
481
481
  'Read(**/*.pem)', 'Read(**/id_rsa*)', 'Read(**/id_ed25519*)',
482
482
  ];
483
483
  const SESSION_STORE_DENY = `Read(~/.config/${ic.FALLBACK_DIR}/instances/**)`;
484
+ // An Autobongos runner keeps ITS OWN login and files in a folder of its own (task 1005095:
485
+ // BONGOS_CONFIG_ROOT, so adding a laptop as a runner never touches the builder's CLI session).
486
+ // That put the runner's session file, and the session store beside it, outside every rule above,
487
+ // which all stop at ~/.config — so on a runner device a worker could open the file and read the
488
+ // runner's login (task 1005186). The whole folder is denied rather than one file name: the
489
+ // runner's heartbeat and run log sit there too and no agent has a reason to read any of it, and
490
+ // a rule on the folder cannot be stepped around by a session file under a new name.
491
+ // The folder name is the device program's (scripts/bongos/autobongos-host.js hostPaths); it is
492
+ // spelled here rather than required from there because this file is the installer and must not
493
+ // load the runner, and tests/claude_materialize.mjs fails if the two ever name different places.
494
+ const RUNNER_CONFIG_DENY = 'Read(~/.cloudbongos-runner/config/**)';
495
+
496
+ // The rules that name a FIXED place, whatever the instance is called.
497
+ const FIXED_PLACE_DENY = [SESSION_STORE_DENY, RUNNER_CONFIG_DENY];
484
498
 
485
499
  // A checkout's settings.json takes the identity rebrand like every copied file, and the store's
486
500
  // dir is one of the core's `.config/<dir>` literals it rewrites. The store does not move with the
487
501
  // brand (task 1003741), so its rule is put back where the CLI reads it (task 1004155).
502
+ //
503
+ // The runner rule needs no putting back: the rebrand rewrites `.config/<core dir>` literals, and
504
+ // the runner's folder is not one (measured: with the put-back removed, the three-brand case in
505
+ // tests/claude_materialize.mjs still holds). That case is what would notice a future rebrand
506
+ // rule broad enough to carry it off.
488
507
  function rebrandSettings(text, rebrand) {
489
508
  const moved = rebrand(SESSION_STORE_DENY);
490
509
  return rebrand(text).split(JSON.stringify(moved)).join(JSON.stringify(SESSION_STORE_DENY));
@@ -504,7 +523,7 @@ function credentialDeny(branding = {}) {
504
523
  return [
505
524
  ...STARTER_DENY_SECRETS,
506
525
  ...dirs.flatMap((dir) => credentialFiles.map((file) => `Read(~/.config/${dir}/${file})`)),
507
- SESSION_STORE_DENY,
526
+ ...FIXED_PLACE_DENY,
508
527
  ];
509
528
  }
510
529