@bongos/core 1.21.112 → 1.21.114

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 (265) hide show
  1. package/.bongos-core.json +616 -186
  2. package/.claude/skills/ask-for-help/SKILL.md +1 -1
  3. package/.claude/skills/bongos-feedback/SKILL.md +1 -1
  4. package/.claude/skills/builder-sequence/SKILL.md +1 -1
  5. package/.claude/skills/builder-ship/SKILL.md +1 -0
  6. package/GOVERNMENT.md +128 -11
  7. package/clients/bongos-client/README.md +1 -1
  8. package/clients/bongos-client/bongos-client.global.js +28 -0
  9. package/clients/bongos-client/index.cjs +28 -0
  10. package/clients/bongos-client/index.d.ts +45 -2
  11. package/clients/bongos-client/index.mjs +28 -0
  12. package/docs/adr/0372-an-ask-is-stored-per-docket-matter-and-the-server-refuses-slop.md +33 -0
  13. package/docs/adr/0373-an-ai-session-files-a-blocker-with-an-ask.md +31 -0
  14. package/docs/adr/0374-a-building-lead-is-a-held-position-appointed-by-a-people-matter.md +30 -0
  15. package/docs/adr/0375-who-may-build-without-a-vote-comes-from-the-form-of-government.md +38 -0
  16. package/docs/adr/0376-every-ask-is-routed-to-a-real-seat-on-the-server.md +30 -0
  17. package/docs/adr/0377-governance-is-a-held-position-read-on-me.md +24 -0
  18. package/docs/adr/0378-a-leads-build-outs-are-rows-built-at-once-or-by-a-passed-vote.md +33 -0
  19. package/docs/adr/0379-the-treasury-is-pots-per-goal-a-reserve-and-borrowing-over-real-spend.md +32 -0
  20. package/docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md +26 -0
  21. package/docs/adr/README.md +10 -1
  22. package/docs/api/openapi.json +989 -87
  23. package/docs/api-reference.md +18 -4
  24. package/docs/copy-inventory.md +343 -262
  25. package/docs/copy-registry.json +1223 -426
  26. package/docs/file-map.md +2 -1
  27. package/docs/module-api-changelog.md +4 -0
  28. package/docs/packs/engineer.md +2 -1
  29. package/docs/page-inventory.json +91 -6
  30. package/docs/page-readings.json +5017 -4458
  31. package/modules/builder-settings/law-locks.js +202 -0
  32. package/modules/builder-settings/routes/settings.js +66 -6
  33. package/modules/builder-settings/settings.js +7 -0
  34. package/modules/discord/ship-broadcast.js +22 -13
  35. package/modules/government/asks-db.js +54 -0
  36. package/modules/government/asks.js +269 -0
  37. package/modules/government/board-idea-reads.js +68 -0
  38. package/modules/government/board.js +21 -57
  39. package/modules/government/build-rights.js +148 -0
  40. package/modules/government/buildouts.js +160 -0
  41. package/modules/government/counts.js +81 -0
  42. package/modules/government/db.js +242 -0
  43. package/modules/government/decision-board.js +234 -8
  44. package/modules/government/decision-rules.js +2 -0
  45. package/modules/government/docket.js +114 -6
  46. package/modules/government/law-board.js +131 -0
  47. package/modules/government/laws.js +241 -0
  48. package/modules/government/leads.js +170 -0
  49. package/modules/government/migrations/government_027_laws.sql +143 -0
  50. package/modules/government/migrations/government_028_asks.sql +57 -0
  51. package/modules/government/migrations/government_029_leads.sql +135 -0
  52. package/modules/government/migrations/government_030_buildouts.sql +111 -0
  53. package/modules/government/migrations/government_031_treasury.sql +107 -0
  54. package/modules/government/module.json +2 -1
  55. package/modules/government/pollers/treasury-borrow.js +62 -0
  56. package/modules/government/positions.js +107 -0
  57. package/modules/government/presets.js +149 -0
  58. package/modules/government/routes/government.js +425 -2
  59. package/modules/government/routing.js +154 -0
  60. package/modules/government/treasury-db.js +166 -0
  61. package/modules/government/treasury.js +241 -0
  62. package/modules/hall-ui/public/atlas.html +2 -2
  63. package/modules/hall-ui/public/blockers.html +2 -2
  64. package/modules/hall-ui/public/board-room.html +2 -2
  65. package/modules/hall-ui/public/board-room.js +2 -0
  66. package/modules/hall-ui/public/city-draw.js +604 -11
  67. package/modules/hall-ui/public/city.css +157 -1
  68. package/modules/hall-ui/public/city.html +37 -7
  69. package/modules/hall-ui/public/city.js +441 -21
  70. package/modules/hall-ui/public/city.states.json +110 -5
  71. package/modules/hall-ui/public/collab.html +2 -2
  72. package/modules/hall-ui/public/commands.html +2 -2
  73. package/modules/hall-ui/public/copy-desk.html +2 -2
  74. package/modules/hall-ui/public/craft-mode.js +112 -29
  75. package/modules/hall-ui/public/deploy.html +2 -2
  76. package/modules/hall-ui/public/diagram-viewer.css +12 -0
  77. package/modules/hall-ui/public/diagram-viewer.js +4 -0
  78. package/modules/hall-ui/public/diagrams.html +2 -2
  79. package/modules/hall-ui/public/drachmae.html +2 -2
  80. package/modules/hall-ui/public/fleet.html +2 -2
  81. package/modules/hall-ui/public/gate.html +2 -2
  82. package/modules/hall-ui/public/goal-map.html +2 -2
  83. package/modules/hall-ui/public/goals.html +2 -2
  84. package/modules/hall-ui/public/gov-feeds.js +141 -0
  85. package/modules/hall-ui/public/gov-living.js +1002 -0
  86. package/modules/hall-ui/public/gov-positions.js +97 -0
  87. package/modules/hall-ui/public/government.html +2 -2
  88. package/modules/hall-ui/public/hall-render.js +3 -0
  89. package/modules/hall-ui/public/idea.html +2 -2
  90. package/modules/hall-ui/public/ideas.html +2 -2
  91. package/modules/hall-ui/public/index.html +3 -3
  92. package/modules/hall-ui/public/modules.html +2 -2
  93. package/modules/hall-ui/public/my-agents.css +334 -0
  94. package/modules/hall-ui/public/my-agents.html +72 -0
  95. package/modules/hall-ui/public/my-agents.js +603 -0
  96. package/modules/hall-ui/public/my-agents.states.json +37 -0
  97. package/modules/hall-ui/public/primer.html +2 -2
  98. package/modules/hall-ui/public/profile-connections.js +40 -28
  99. package/modules/hall-ui/public/profile.html +2 -2
  100. package/modules/hall-ui/public/project-settings.html +2 -2
  101. package/modules/hall-ui/public/ranks.html +2 -2
  102. package/modules/hall-ui/public/roadmap.html +2 -2
  103. package/modules/hall-ui/public/room.css +8 -0
  104. package/modules/hall-ui/public/roster.html +2 -2
  105. package/modules/hall-ui/public/sessions.html +2 -2
  106. package/modules/hall-ui/public/settings.html +2 -2
  107. package/modules/hall-ui/public/shell.js +116 -16
  108. package/modules/hall-ui/public/street-card.js +18 -1
  109. package/modules/hall-ui/public/street-deck.css +299 -0
  110. package/modules/hall-ui/public/street-deck.js +1145 -0
  111. package/modules/hall-ui/public/street-depth.js +712 -0
  112. package/modules/hall-ui/public/street-desk.js +5 -2
  113. package/modules/hall-ui/public/street-draw.js +109 -16
  114. package/modules/hall-ui/public/street-inside.js +137 -22
  115. package/modules/hall-ui/public/street-office.js +705 -0
  116. package/modules/hall-ui/public/street.css +161 -11
  117. package/modules/hall-ui/public/street.html +41 -11
  118. package/modules/hall-ui/public/street.js +78 -66
  119. package/modules/hall-ui/public/street.states.json +129 -21
  120. package/modules/hall-ui/public/studio.html +2 -2
  121. package/modules/hall-ui/public/style.css +5 -1
  122. package/modules/hall-ui/public/task.html +2 -2
  123. package/modules/hall-ui/public/thinking.html +2 -2
  124. package/modules/hall-ui/public/tweak-editor.html +2 -2
  125. package/modules/hall-ui/public/watch.html +2 -2
  126. package/modules/hall-ui/public/watch.js +14 -1
  127. package/modules/hall-ui/public/work.html +2 -2
  128. package/modules/hall-ui/records/living-city.md +57 -0
  129. package/modules/hall-ui/records/my-agents.md +21 -0
  130. package/modules/hall-ui/records/oversight-family.md +1 -0
  131. package/modules/hall-ui/records/profile-connections.md +10 -0
  132. package/modules/hall-ui/records/reading-rooms.md +2 -0
  133. package/modules/ideas/routes/blockers.js +92 -3
  134. package/modules/lifecycle/module.json +2 -1
  135. package/modules/lifecycle/moving.js +17 -1
  136. package/modules/lifecycle/routes/moving.js +6 -0
  137. package/modules/lifecycle/routes/project-settings.js +23 -0
  138. package/modules/onboarding/approval-broadcast.js +8 -0
  139. package/modules/onboarding/routes/access-requests.js +16 -6
  140. package/modules/platform-identity/connections.js +13 -2
  141. package/modules/platform-identity/routes/connections.js +7 -1
  142. package/modules/public-landing/public/account-private.states.json +2 -2
  143. package/modules/public-landing/public/account.html +65 -5
  144. package/modules/public-landing/public/account.states.json +3 -3
  145. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
  146. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
  147. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +13 -0
  148. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
  149. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
  150. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +13 -0
  151. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
  152. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
  153. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +13 -0
  154. package/modules/public-landing/public/assets/world.css +4 -4
  155. package/modules/public-landing/public/connections-empty.states.json +14 -0
  156. package/modules/public-landing/public/connections-inactive.states.json +14 -0
  157. package/modules/public-landing/public/connections.html +450 -0
  158. package/modules/public-landing/public/connections.probes.json +414 -0
  159. package/modules/public-landing/public/connections.states.json +17 -0
  160. package/modules/public-landing/public/contact.html +2 -2
  161. package/modules/public-landing/public/guild.html +2 -2
  162. package/modules/public-landing/public/index.html +2 -2
  163. package/modules/public-landing/public/invitations.html +2 -2
  164. package/modules/public-landing/public/privacy.html +2 -2
  165. package/modules/public-landing/public/projects.html +2 -2
  166. package/modules/public-landing/public/projects.probes.json +6 -2
  167. package/modules/public-landing/public/terms.html +2 -2
  168. package/modules/ui-design/kit/fixtures/connections-empty.json +12 -0
  169. package/modules/ui-design/kit/fixtures/connections-inactive.json +7 -0
  170. package/modules/ui-design/kit/fixtures/connections.json +22 -0
  171. package/modules/ui-design/kit/serve.js +95 -0
  172. package/package-lock.json +2 -2
  173. package/package.json +1 -1
  174. package/release-notes.json +128 -0
  175. package/scripts/discord/README.md +8 -5
  176. package/scripts/discord/post.js +12 -1
  177. package/scripts/gds/artifact-audit.js +20 -3
  178. package/scripts/gds/ask.js +164 -0
  179. package/scripts/gds/autobongos-run.js +20 -5
  180. package/scripts/gds/autobongos-tweak-lane.js +262 -0
  181. package/scripts/gds/autobongos-verify.js +21 -2
  182. package/scripts/gds/claim.js +2 -0
  183. package/scripts/gds/government-walk.js +638 -4
  184. package/scripts/gds/pulse.js +9 -6
  185. package/scripts/gds/ship-escalation.js +22 -3
  186. package/scripts/gds/ship-finish.js +5 -4
  187. package/scripts/gds/ship-merge-gate.js +18 -6
  188. package/scripts/gds/tweak-halt.js +1 -0
  189. package/scripts/hall-preview/README.md +41 -1
  190. package/scripts/hall-preview/pref-writes.js +140 -0
  191. package/scripts/hall-preview/refresh-fixtures.js +101 -7
  192. package/scripts/hall-preview/scrub-identity.js +366 -7
  193. package/scripts/hall-preview/server.js +27 -1
  194. package/scripts/render-diagrams.sh +4 -2
  195. package/src/bongos/auth-idp.js +6 -1
  196. package/src/bongos/project-settings.js +41 -0
  197. package/src/bongos/route-rank-check.js +27 -0
  198. package/src/bongos/routes/me.js +68 -7
  199. package/src/bongos/serve-internal.js +5 -1
  200. package/src/module-api.js +7 -1
  201. package/src/platform-server.js +10 -0
  202. package/tests/application_lifecycle.mjs +47 -5
  203. package/tests/autobongos_tweak_lane.mjs +208 -0
  204. package/tests/conductor_main_e2e.mjs +21 -0
  205. package/tests/connections_api.mjs +18 -0
  206. package/tests/craft_mode_nav.mjs +5 -3
  207. package/tests/craft_mode_tallies.mjs +52 -16
  208. package/tests/discord_approvals.mjs +11 -0
  209. package/tests/discord_channels.mjs +20 -0
  210. package/tests/fixtures/gov-seated.mjs +64 -0
  211. package/tests/fixtures/government-ask-cards.json +1284 -0
  212. package/tests/government_act_alone_buildings.mjs +1 -0
  213. package/tests/government_agent_asks.mjs +322 -0
  214. package/tests/government_asks.mjs +408 -0
  215. package/tests/government_build_rights.mjs +253 -0
  216. package/tests/government_buildouts.mjs +382 -0
  217. package/tests/government_counts.mjs +143 -0
  218. package/tests/government_decision_rules.mjs +5 -1
  219. package/tests/government_laws.mjs +488 -0
  220. package/tests/government_laws_session.mjs +321 -0
  221. package/tests/government_leads.mjs +375 -0
  222. package/tests/government_positions.mjs +127 -0
  223. package/tests/government_presets.mjs +215 -0
  224. package/tests/government_routes.mjs +8 -1
  225. package/tests/government_routing.mjs +118 -0
  226. package/tests/government_seed.mjs +5 -0
  227. package/tests/government_treasury.mjs +394 -0
  228. package/tests/governor_docket.mjs +26 -2
  229. package/tests/hall_buildouts.mjs +208 -0
  230. package/tests/hall_city.mjs +14 -6
  231. package/tests/hall_city_living.mjs +617 -0
  232. package/tests/hall_gov_counts.mjs +336 -0
  233. package/tests/hall_gov_living.mjs +636 -0
  234. package/tests/hall_gov_real_site.mjs +655 -0
  235. package/tests/hall_matter_card.mjs +6 -1
  236. package/tests/hall_my_agents.mjs +481 -0
  237. package/tests/hall_nav.mjs +107 -4
  238. package/tests/hall_page_gate_map.mjs +4 -0
  239. package/tests/hall_preview_governor_fixtures.mjs +30 -3
  240. package/tests/hall_preview_scrub.mjs +59 -1
  241. package/tests/hall_preview_security.mjs +169 -0
  242. package/tests/hall_reading_world.mjs +44 -0
  243. package/tests/hall_roster_world.mjs +6 -2
  244. package/tests/hall_street.mjs +18 -2
  245. package/tests/hall_street_amend.mjs +1 -1
  246. package/tests/hall_street_deck.mjs +609 -0
  247. package/tests/hall_street_depth.mjs +559 -0
  248. package/tests/hall_street_desk.mjs +1 -1
  249. package/tests/hall_street_inside.mjs +29 -5
  250. package/tests/hall_tweak_editor.mjs +4 -1
  251. package/tests/hub_account_page.mjs +94 -3
  252. package/tests/hub_connections_page.mjs +507 -0
  253. package/tests/hub_invite_badge.mjs +1 -1
  254. package/tests/idp_device_exchange.mjs +18 -0
  255. package/tests/landing_legal_pages.mjs +8 -5
  256. package/tests/landing_page.mjs +23 -4
  257. package/tests/nav_permission_atoms.mjs +4 -1
  258. package/tests/profile_connections_ui.mjs +88 -25
  259. package/tests/projects_hub.mjs +15 -0
  260. package/tests/render_check.mjs +2 -2
  261. package/tests/ship_broadcast_routing.mjs +57 -16
  262. package/tests/ship_broadcast_server.mjs +40 -7
  263. package/tests/ship_merge_gate.mjs +19 -0
  264. package/tests/ui_design_kit.mjs +2 -2
  265. package/tests/watch_applications_queue.mjs +23 -0
@@ -1,11 +1,15 @@
1
1
  // tests/craft_mode_tallies.mjs — Governing mode's header tallies (task 1004563, goal
2
- // 1000095 WA6): "needs you N · moving M" beside the eyebrow.
2
+ // 1000095 WA6): "to do N · moving M" beside the eyebrow, on the city and the
3
+ // street only (task 1004701, owner ruling 2026-10-09).
3
4
  //
4
5
  // Runs the REAL craft-mode.js over a small DOM with a stubbed fetch, and the REAL
5
6
  // server count (modules/lifecycle/moving.js) over a fake pool, so the two halves are
6
7
  // pinned where they actually decide: which statuses "moving" counts (owner decision
7
8
  // 2026-10-08: active + confirmed), and that a figure the page could not read is left
8
- // out rather than drawn as a zero that says nothing waits on you.
9
+ // out rather than drawn as a zero that says nothing waits on you. Since the living
10
+ // city (task 1004685) both figures are the city's own (gov-living.js countsOf over
11
+ // gov-feeds.js), so the real gov-living.js and gov-feeds.js run beside it here;
12
+ // tests/hall_gov_counts.mjs holds the tally equal to every other surface.
9
13
  import assert from 'node:assert/strict';
10
14
  import { test } from 'node:test';
11
15
  import fs from 'node:fs';
@@ -16,6 +20,8 @@ import { fileURLToPath } from 'node:url';
16
20
 
17
21
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
18
22
  const CRAFT = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/craft-mode.js'), 'utf8');
23
+ const LIVING = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/gov-living.js'), 'utf8');
24
+ const FEEDS = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/gov-feeds.js'), 'utf8');
19
25
  const require = createRequire(import.meta.url);
20
26
  process.env.NODE_ENV = 'test';
21
27
  const { countMoving, movingHandler, MOVING_STATUSES } = require('../modules/lifecycle/moving.js');
@@ -44,7 +50,8 @@ function node(tag, id, cls) {
44
50
  function all(root) { return root.children.flatMap((c) => [c, ...all(c)]); }
45
51
 
46
52
  // answers: path → JSON body, or a number (an HTTP failure status), or 'throw'.
47
- function boot(mode, answers) {
53
+ // page: the body's data-page (the tally stands on the city and the street only)
54
+ function boot(mode, answers, page = 'city') {
48
55
  const sidebar = node('NAV', 'app-sidebar');
49
56
  sidebar.appendChild(node('DIV', '', 'app-sidebar__brand'));
50
57
  const topbar = node('DIV', 'app-topbar');
@@ -52,7 +59,7 @@ function boot(mode, answers) {
52
59
  const root = node('HTML');
53
60
  const asked = [];
54
61
  const document = {
55
- documentElement: root, body: node('BODY'), activeElement: null,
62
+ documentElement: root, body: (() => { const b = node('BODY'); if (page) b.setAttribute('data-page', page); return b; })(), activeElement: null,
56
63
  getElementById: (id) => [sidebar, topbar, ...all(sidebar), ...all(topbar)].find((n) => n.id === id) || null,
57
64
  querySelectorAll: () => [],
58
65
  createElement: (t) => node(t.toUpperCase()),
@@ -68,20 +75,34 @@ function boot(mode, answers) {
68
75
  if (typeof a === 'number') return Promise.resolve({ ok: false, status: a, json: () => Promise.resolve({}) });
69
76
  return Promise.resolve({ ok: true, status: 200, json: () => Promise.resolve(a) });
70
77
  };
71
- const sandbox = { window, document, fetch, CustomEvent: class { constructor(t, i) { this.type = t; Object.assign(this, i); } } };
78
+ window.fetch = fetch;
79
+ const sandbox = { window, document, fetch, Date, CustomEvent: class { constructor(t, i) { this.type = t; Object.assign(this, i); } } };
72
80
  vm.createContext(sandbox);
81
+ // the living city's one counter and one reader, as the city and the street carry them
82
+ // (task 1004685): the tally prints their numbers, read through gov-feeds.js
83
+ vm.runInContext(LIVING, sandbox, { filename: 'gov-living.js' });
84
+ vm.runInContext(FEEDS, sandbox, { filename: 'gov-feeds.js' });
73
85
  vm.runInContext(CRAFT, sandbox, { filename: 'craft-mode.js' });
74
86
  const craft = window.OTBCraftMode;
75
87
  craft.mount();
76
88
  const box = () => all(topbar).find((n) => n.id === 'craft-tallies') || null;
77
89
  return { craft, topbar, asked, box };
78
90
  }
79
- const settle = () => new Promise((r) => setImmediate(r));
91
+ const settle = async () => { for (let i = 0; i < 20; i++) await new Promise((r) => setImmediate(r)); };
80
92
  const text = (html) => html.replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
81
93
 
82
- const BOTH = { '/blockers?limit=1': { blockers: [{ id: 1 }], page: { limit: 1, offset: 0, total: 3 } }, '/moving': { moving: 12, active: 9, confirmed: 3 } };
94
+ // Since the living city (task 1004685) the tally prints the city's own counts:
95
+ // gov-living.js countsOf over gov-feeds.js COUNT_FEEDS, and since task 1004716 those
96
+ // stand on ONE server read, GET /government/counts. Here three matters on the
97
+ // viewer, no agent at work, twelve moving.
98
+ const COUNTS = { viewer: { id: '1' }, on_you: 3, on_others: 0, open: 3, agents_at_work: 0, moving: 12, routed: true, yours: ['b:1', 'b:2', 'b:3'], others: [] };
99
+ const BOTH = {
100
+ '/government/counts': COUNTS,
101
+ '/me': { builder: { id: '1', github_login: 'builder-1', rank: 'archon' } },
102
+ };
103
+ const COUNT_PATHS = ['/government/counts', '/me'];
83
104
 
84
- test('Governing reads both counts and draws "needs you 3 · moving 12" right after the eyebrow', async () => {
105
+ test('Governing reads the city\'s counts and draws "to do 3 · moving 12" right after the eyebrow', async () => {
85
106
  const { topbar, box, asked } = boot('governing', BOTH);
86
107
  await settle();
87
108
  const b = box();
@@ -89,24 +110,25 @@ test('Governing reads both counts and draws "needs you 3 · moving 12" right aft
89
110
  const eyebrow = all(topbar).find((n) => n.id === 'craft-eyebrow');
90
111
  assert.equal(eyebrow.nextSibling, b, 'they sit directly after the mode words');
91
112
  assert.equal(b.getAttribute('hidden'), null);
92
- assert.equal(text(b.innerHTML), 'needs you 3 · moving 12');
113
+ assert.equal(text(b.innerHTML), 'to do 3 · moving 12');
93
114
  assert.match(b.innerHTML, /craft-tally craft-tally--due/, 'something waits, so needs-you carries the warm dot');
94
- assert.deepEqual(asked.map((u) => u.replace('/api/bongos', '')).sort(), ['/blockers?limit=1', '/moving'], 'one page of one blocker for the total, one count — never a task list');
115
+ assert.deepEqual(asked.map((u) => u.replace('/api/bongos', '')).sort(), COUNT_PATHS,
116
+ 'one server count, once, beside the viewer the shell already reads — never a task list');
95
117
  });
96
118
 
97
- test('nothing waiting draws "needs you 0" without the dot', async () => {
98
- const { box } = boot('governing', { ...BOTH, '/blockers?limit=1': { blockers: [], page: { limit: 1, offset: 0, total: 0 } } });
119
+ test('nothing waiting draws "to do 0" without the dot', async () => {
120
+ const { box } = boot('governing', { ...BOTH, '/government/counts': { ...COUNTS, on_you: 0, open: 0, yours: [] } });
99
121
  await settle();
100
- assert.equal(text(box().innerHTML), 'needs you 0 · moving 12');
122
+ assert.equal(text(box().innerHTML), 'to do 0 · moving 12');
101
123
  assert.doesNotMatch(box().innerHTML, /craft-tally--due/);
102
124
  });
103
125
 
104
126
  test('a count that cannot be read is LEFT OUT, never drawn as zero', async () => {
105
- const noMoving = boot('governing', { ...BOTH, '/moving': 401 });
127
+ const noMoving = boot('governing', { ...BOTH, '/government/counts': { ...COUNTS, moving: null } });
106
128
  await settle();
107
- assert.equal(text(noMoving.box().innerHTML), 'needs you 3', 'a signed-out or failed /moving drops only its own figure');
129
+ assert.equal(text(noMoving.box().innerHTML), 'to do 3', 'a moving count the server could not read drops only its own figure');
108
130
 
109
- const offline = boot('governing', { '/blockers?limit=1': 'throw', '/moving': { moving: 'lots' } });
131
+ const offline = boot('governing', { '/government/counts': 'throw' });
110
132
  await settle();
111
133
  assert.equal(offline.box().getAttribute('hidden'), '', 'nothing readable: the slot stays hidden, no zeros');
112
134
  assert.equal(offline.box().innerHTML, '');
@@ -126,6 +148,20 @@ test('only Governing wears the tallies, and leaving it removes them', async () =
126
148
  assert.equal(box(), null, 'switching away takes them down');
127
149
  });
128
150
 
151
+ test('the top bar carries the tally on the city and the street only (owner ruling, task 1004701)', async () => {
152
+ for (const page of ['city', 'street']) {
153
+ const { box } = boot('governing', BOTH, page);
154
+ await settle();
155
+ assert.ok(box(), `${page} carries the tally`);
156
+ }
157
+ for (const page of ['my-agents', 'board', 'home', null]) {
158
+ const { box, asked } = boot('governing', BOTH, page);
159
+ await settle();
160
+ assert.equal(box(), null, `${page || 'a page with no name'} draws no tally`);
161
+ assert.deepEqual(asked, [], `${page || 'a page with no name'} asks the server for nothing`);
162
+ }
163
+ });
164
+
129
165
  test('the server counts active + confirmed only, and sums them', async () => {
130
166
  assert.deepEqual([...MOVING_STATUSES], ['active', 'confirmed']);
131
167
  let seen;
@@ -72,6 +72,17 @@ await t('memberApprovalLine round-trips through parseApprovalMarker', () => {
72
72
  assert.deepEqual(ap.parseApprovalMarker(line), { kind: 'member', id: 99 });
73
73
  });
74
74
 
75
+ // A vouched takeover (ADR 0333 D4) re-posts the request with the row's words now; it
76
+ // must still lead with the SAME marker, or a 👍 on it would resolve nothing.
77
+ await t('the refiled line carries the new words and round-trips to the same request', () => {
78
+ const line = bcast.memberApprovalLine({ id: 99, github_login: 'carol', display_name: 'Carol', note: 'the real me', refiled: true });
79
+ assert.deepEqual(ap.parseApprovalMarker(line), { kind: 'member', id: 99 });
80
+ assert.match(line, /Carol/);
81
+ assert.match(line, /the real me/);
82
+ assert.match(line, /replace the earlier line for #99/);
83
+ assert.doesNotMatch(bcast.memberApprovalLine({ id: 99, github_login: 'carol' }), /replace/, 'a first line says nothing about replacing');
84
+ });
85
+
75
86
  // ---- recordApprovalReaction (orchestration via fakes) ----------------------
76
87
  const GOOD_ID = '123456789012345678';
77
88
  const archon = { id: 1, rank: 'archon', github_login: 'example-owner' };
@@ -361,6 +361,26 @@ await t('the real config declares the board-room channel, visible to every build
361
361
  assert.ok(board.topic && board.topic.length > 0, 'board-room needs a topic');
362
362
  });
363
363
 
364
+ // task 1002536: the AI-news feed is the ZERO-CODE form the owner chose at the
365
+ // 2026-07-29 triage — an off-the-shelf RSS service posts into one channel, and
366
+ // nothing on our host polls anything. The channel is the whole repo surface, so
367
+ // pin it: builders-only by INHERITING the BUILDERS category (spelling `view`
368
+ // out would repeat the category and drift), and a topic that names #ideas,
369
+ // because pasting a link there is the article → idea path (the inbound handler
370
+ // reads only typed text, never a forwarded post, so Forward files no link).
371
+ await t('the real config declares ai-news, builders-only, pointing readers at #ideas', () => {
372
+ const cfg = ch.loadConfig();
373
+ const cat = cfg.categories.find((c) => (c.channels || []).some((x) => x.key === 'ai-news'));
374
+ assert.ok(cat, 'expected a channel keyed ai-news');
375
+ const news = cat.channels.find((x) => x.key === 'ai-news');
376
+ assert.equal(ch.slugify(news.name), 'ai-news', 'the name must slug to its key, or apply creates a duplicate');
377
+ assert.equal(cat.name, 'BUILDERS');
378
+ assert.equal(news.view, undefined, 'ai-news must inherit, not repeat, its category view');
379
+ assert.equal(cat.view, 'xenos', 'so every builder, and only builders, can read it');
380
+ assert.ok(news.topic && news.topic.length <= 1024, 'Discord refuses a topic over 1024 characters');
381
+ assert.match(news.topic, /#ideas/, 'the topic must say where an article becomes an idea');
382
+ });
383
+
364
384
  await t('the real config plans cleanly against an empty guild and never deletes', () => {
365
385
  const cfg = ch.loadConfig();
366
386
  const { ops } = ch.planChannelReconcile({ categories: [], channels: [] }, cfg, CTX_BOT);
@@ -0,0 +1,64 @@
1
+ // tests/fixtures/gov-seated.mjs — A SEATED GOVERNMENT, as test data for the
2
+ // hall's living-city tests (task 1004717). The hub has seated no Lead and built
3
+ // nothing, so the harness fixtures alone cannot show a Lead's floor, a build-out
4
+ // going up or one waiting on its vote. This builds them in the exact shapes the
5
+ // routes answer — GET /government/leads with five Leads appointed by People votes,
6
+ // Review's Testers room standing, a floor going up at Release and an annex
7
+ // waiting on its sitting at Treasury — and routes the docket to those seats by
8
+ // the server's own rule (modules/government/routing.js). The hall never reads it.
9
+
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import { createRequire } from 'node:module';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const require = createRequire(import.meta.url);
16
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
17
+ const routing = require(path.join(ROOT, 'modules', 'government', 'routing.js'));
18
+ const fx = (name) => JSON.parse(fs.readFileSync(path.join(ROOT, 'scripts', 'hall-preview', 'fixtures', name + '.json'), 'utf8'));
19
+
20
+ // five Leads (the fixture's viewer, 1000025, leads Planning)
21
+ export const SEATED = Object.freeze([
22
+ { building: 'planning', lead: '1000025', since: '2026-09-12', scope: 'Goals, priorities, the order of work.' },
23
+ { building: 'release', lead: '1000027', since: '2026-09-05', scope: 'Release floors and the path to going live.' },
24
+ { building: 'review', lead: '1000095', since: '2026-09-20', scope: 'Who checks finished work, and how.' },
25
+ { building: 'treasury', lead: '1000094', since: '2026-09-28', scope: 'Pots and the reserve.' },
26
+ { building: 'people', lead: '1000070', since: '2026-10-02', scope: 'Who joins, and the ranks they hold.' },
27
+ ]);
28
+
29
+ // GET /government/leads with them in office and the three build-outs on the record
30
+ export function leadsRead() {
31
+ const r = fx('government__leads');
32
+ const roster = fx('builders__roster').builders;
33
+ const at = (k) => r.buildings.find((b) => b.building === k);
34
+ for (const l of SEATED) {
35
+ const p = roster.find((x) => String(x.id) === l.lead) || { id: l.lead };
36
+ at(l.building).lead = { id: 'L' + l.building, building: l.building, builder: { id: String(p.id), login: p.github_login || null, name: p.display_name || null }, charter: l.scope, since: l.since, appointed_by: { item_id: '1' } };
37
+ at(l.building).answers = { seat: 'lead:' + l.building, builder_ids: [String(p.id)], fell_to_owner: false };
38
+ }
39
+ at('review').structure = { standing: [{ id: 2, capsule: 'committee', name: 'Testers', line: 'Who checks finished work.', state: 'built', progress: 1 }], proposed: [] };
40
+ at('release').structure = { standing: [{ id: 1, capsule: 'floor', name: 'Staging floor', line: '', state: 'building', progress: 0.55, allowed: { path: 'at_once', why: 'Its holder builds it out at once.' } }], proposed: [] };
41
+ at('treasury').structure = { standing: [], proposed: [{ capsule: 'annex', name: 'Investments arm', line: 'A wing for the reserve.', docket_id: 'board:77', item_id: '77' }] };
42
+ return r;
43
+ }
44
+
45
+ // the annex's sitting, open on the docket: nobody has voted yet
46
+ function sitting(seats) {
47
+ return {
48
+ id: 'board:77', source: 'board', building: 'treasury', rule: 'majority', state: 'moving', title: 'Add an annex to Treasury: Investments arm', short: 'an annex',
49
+ opened_at: '2026-10-08T10:00:00.000Z', deadline: null, tally: { for: 0, against: 0, open: seats }, ref: { type: 'board_item', id: '77' }, acts: [],
50
+ };
51
+ }
52
+
53
+ // GET /government/docket with the sitting, every matter routed to those seats
54
+ export function docketRouted() {
55
+ const d = fx('government__docket');
56
+ d.matters.push(sitting(d.buildings.find((b) => b.key === 'charter').holders.length));
57
+ const read = leadsRead();
58
+ const seats = { leads: read.buildings.filter((b) => b.lead).map((b) => ({ building: b.building, builder_id: b.lead.builder.id })), owner: read.owner, holders: {} };
59
+ d.matters.forEach((m) => {
60
+ const { route, ...bare } = m;
61
+ m.route = routing.routeMatter(bare, d.viewer.id, seats, Date.parse(d.generated_at));
62
+ });
63
+ return d;
64
+ }