@bongos/core 1.21.113 → 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 (260) hide show
  1. package/.bongos-core.json +603 -183
  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 +2 -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 +122 -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-verify.js +21 -2
  180. package/scripts/gds/government-walk.js +638 -4
  181. package/scripts/gds/pulse.js +9 -6
  182. package/scripts/gds/ship-escalation.js +22 -3
  183. package/scripts/gds/ship-finish.js +5 -4
  184. package/scripts/gds/ship-merge-gate.js +18 -6
  185. package/scripts/hall-preview/README.md +41 -1
  186. package/scripts/hall-preview/pref-writes.js +140 -0
  187. package/scripts/hall-preview/refresh-fixtures.js +101 -7
  188. package/scripts/hall-preview/scrub-identity.js +366 -7
  189. package/scripts/hall-preview/server.js +27 -1
  190. package/scripts/render-diagrams.sh +4 -2
  191. package/src/bongos/auth-idp.js +6 -1
  192. package/src/bongos/project-settings.js +41 -0
  193. package/src/bongos/route-rank-check.js +27 -0
  194. package/src/bongos/routes/me.js +68 -7
  195. package/src/bongos/serve-internal.js +5 -1
  196. package/src/module-api.js +7 -1
  197. package/src/platform-server.js +10 -0
  198. package/tests/application_lifecycle.mjs +47 -5
  199. package/tests/conductor_main_e2e.mjs +21 -0
  200. package/tests/connections_api.mjs +18 -0
  201. package/tests/craft_mode_nav.mjs +5 -3
  202. package/tests/craft_mode_tallies.mjs +52 -16
  203. package/tests/discord_approvals.mjs +11 -0
  204. package/tests/discord_channels.mjs +20 -0
  205. package/tests/fixtures/gov-seated.mjs +64 -0
  206. package/tests/fixtures/government-ask-cards.json +1284 -0
  207. package/tests/government_act_alone_buildings.mjs +1 -0
  208. package/tests/government_agent_asks.mjs +322 -0
  209. package/tests/government_asks.mjs +408 -0
  210. package/tests/government_build_rights.mjs +253 -0
  211. package/tests/government_buildouts.mjs +382 -0
  212. package/tests/government_counts.mjs +143 -0
  213. package/tests/government_decision_rules.mjs +5 -1
  214. package/tests/government_laws.mjs +488 -0
  215. package/tests/government_laws_session.mjs +321 -0
  216. package/tests/government_leads.mjs +375 -0
  217. package/tests/government_positions.mjs +127 -0
  218. package/tests/government_presets.mjs +215 -0
  219. package/tests/government_routes.mjs +8 -1
  220. package/tests/government_routing.mjs +118 -0
  221. package/tests/government_seed.mjs +5 -0
  222. package/tests/government_treasury.mjs +394 -0
  223. package/tests/governor_docket.mjs +26 -2
  224. package/tests/hall_buildouts.mjs +208 -0
  225. package/tests/hall_city.mjs +14 -6
  226. package/tests/hall_city_living.mjs +617 -0
  227. package/tests/hall_gov_counts.mjs +336 -0
  228. package/tests/hall_gov_living.mjs +636 -0
  229. package/tests/hall_gov_real_site.mjs +655 -0
  230. package/tests/hall_matter_card.mjs +6 -1
  231. package/tests/hall_my_agents.mjs +481 -0
  232. package/tests/hall_nav.mjs +107 -4
  233. package/tests/hall_page_gate_map.mjs +4 -0
  234. package/tests/hall_preview_governor_fixtures.mjs +30 -3
  235. package/tests/hall_preview_scrub.mjs +59 -1
  236. package/tests/hall_preview_security.mjs +169 -0
  237. package/tests/hall_reading_world.mjs +44 -0
  238. package/tests/hall_roster_world.mjs +6 -2
  239. package/tests/hall_street.mjs +18 -2
  240. package/tests/hall_street_amend.mjs +1 -1
  241. package/tests/hall_street_deck.mjs +609 -0
  242. package/tests/hall_street_depth.mjs +559 -0
  243. package/tests/hall_street_desk.mjs +1 -1
  244. package/tests/hall_street_inside.mjs +29 -5
  245. package/tests/hall_tweak_editor.mjs +4 -1
  246. package/tests/hub_account_page.mjs +94 -3
  247. package/tests/hub_connections_page.mjs +507 -0
  248. package/tests/hub_invite_badge.mjs +1 -1
  249. package/tests/idp_device_exchange.mjs +18 -0
  250. package/tests/landing_legal_pages.mjs +8 -5
  251. package/tests/landing_page.mjs +23 -4
  252. package/tests/nav_permission_atoms.mjs +4 -1
  253. package/tests/profile_connections_ui.mjs +88 -25
  254. package/tests/projects_hub.mjs +15 -0
  255. package/tests/render_check.mjs +2 -2
  256. package/tests/ship_broadcast_routing.mjs +57 -16
  257. package/tests/ship_broadcast_server.mjs +40 -7
  258. package/tests/ship_merge_gate.mjs +19 -0
  259. package/tests/ui_design_kit.mjs +2 -2
  260. package/tests/watch_applications_queue.mjs +23 -0
@@ -0,0 +1,208 @@
1
+ // tests/hall_buildouts.mjs — A LEAD'S BUILD-OUTS IN THE HALL (task 1004711, goal
2
+ // 1000111; ADR 0378). GET /government/leads carries each building's `structure`
3
+ // (tests/government_buildouts.mjs pins the server); this file pins what the hall
4
+ // draws from it:
5
+ // 1. THE MODEL (gov-living.js): a build-out going up is a live site at its real
6
+ // progress; one waiting on a vote is a ghost whose tally is its sitting's on
7
+ // the docket; a committee room is a real room; a floor that stands built is a
8
+ // real floor, and every one of these fields is labelled real or derived (an
9
+ // `overlay` handed in changes nothing: task 1004717 deleted it).
10
+ // 2. THE CITY (city-draw.js): the live site (scaffold, crane, rising floor) and
11
+ // the ghost (parked crane, dashed glass, tally arc) are drawn from it; the
12
+ // real rooms and floors plug into the core.
13
+ // 3. THE OFFICE (street-office.js): it stands where the real Leads are read; its works are the real build-outs; a tap goes to POST
14
+ // /government/buildouts; and THE PROPOSAL PICTURE RENDERS FROM THE REAL
15
+ // BUILDING — what stands is drawn solid, the change dashed on top of it.
16
+ //
17
+ // Run: node --test tests/hall_buildouts.mjs
18
+
19
+ import { test } from 'node:test';
20
+ import assert from 'node:assert/strict';
21
+ import fs from 'node:fs';
22
+ import path from 'node:path';
23
+ import { createRequire } from 'node:module';
24
+ import { fileURLToPath } from 'node:url';
25
+
26
+ const require = createRequire(import.meta.url);
27
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
28
+ const PUB = path.join(ROOT, 'modules', 'hall-ui', 'public');
29
+ const FX = path.join(ROOT, 'scripts', 'hall-preview', 'fixtures');
30
+ const City = require(path.join(PUB, 'city-draw.js'));
31
+ require(path.join(PUB, 'street-desk.js'));
32
+ require(path.join(PUB, 'street-deck.js'));
33
+ const L = require(path.join(PUB, 'gov-living.js'));
34
+ const O = require(path.join(PUB, 'street-office.js'));
35
+ const fx = (n) => JSON.parse(fs.readFileSync(path.join(FX, n + '.json'), 'utf8'));
36
+ const j = (v) => JSON.parse(JSON.stringify(v));
37
+ const NOW = Date.parse('2026-10-10T06:00:00.000Z');
38
+
39
+ // the hub's leads read with a Lead at Release and the structure the server serves
40
+ const DOCKET_ID = 'board:91';
41
+ function leadsWith() {
42
+ const r = j(fx('government__leads'));
43
+ const at = (k) => r.buildings.find((b) => b.building === k);
44
+ const rel = at('release');
45
+ rel.lead = { id: '5', building: 'release', builder: { id: '777', login: 'quill', name: 'Quill' }, charter: 'Releases.', since: '2026-10-09T12:00:00.000Z', appointed_by: { item_id: '40' } };
46
+ rel.answers = { seat: 'lead:release', builder_ids: ['777'], fell_to_owner: false };
47
+ rel.structure = {
48
+ standing: [
49
+ { id: '1', building: 'release', capsule: 'floor', name: 'Staging Deck', line: 'Try each release first.', state: 'building', progress: 0.4, started_at: '2026-10-10T00:00:00.000Z', builder: null, allowed: { path: 'at_once', ground: 'form', why: 'The founder decides alone.' } },
50
+ { id: '2', building: 'release', capsule: 'committee', name: 'Gate Room', line: 'Who checks a release.', state: 'built', progress: 1, started_at: '2026-10-01T00:00:00.000Z', builder: null, allowed: { path: 'vote', item_id: '44' } },
51
+ { id: '3', building: 'release', capsule: 'floor', name: 'Canary Floor', line: '', state: 'built', progress: 1, started_at: '2026-09-30T00:00:00.000Z', builder: null, allowed: { path: 'vote', item_id: '43' } },
52
+ ],
53
+ proposed: [],
54
+ };
55
+ at('treasury').structure = {
56
+ standing: [],
57
+ proposed: [{ item_id: '91', matter_id: '9', docket_id: DOCKET_ID, building: 'treasury', capsule: 'annex', name: 'Reserve Wing', line: 'Where the reserve is kept.', opened_at: '2026-10-09T10:00:00.000Z', closes_at: null, href: '/board-room?item=91' }],
58
+ };
59
+ return r;
60
+ }
61
+ // the docket with the build-out's sitting open, 2 for and 1 against of 5 seats
62
+ function docketWith() {
63
+ const d = j(fx('government__docket'));
64
+ d.matters.push({ id: DOCKET_ID, source: 'board', building: 'treasury', rule: 'majority', state: 'needs', title: 'Add an annex to Treasury: Reserve Wing', short: 'matter', opened_at: '2026-10-09T10:00:00.000Z', tally: { for: 2, against: 1, open: 2, needs: 'x' }, ref: { type: 'board_item', id: '91' }, acts: [] });
65
+ return d;
66
+ }
67
+ function inputs(over = {}) {
68
+ return {
69
+ docket: docketWith(), constitution: fx('government__constitution'), goals: fx('goals'), rollups: fx('goals__rollups'), blockers: fx('blockers'),
70
+ claims: fx('tasks__claimable'), tasks: fx('tasks__status-active'), moving: fx('moving'), cost: fx('public__cost-summary'), roster: fx('builders__roster'),
71
+ me: fx('me'), leads: leadsWith(), now: NOW, ...over,
72
+ };
73
+ }
74
+ const bOf = (lv, k) => lv.buildings.find((b) => b.key === k);
75
+
76
+ // ── 1. the model ────────────────────────────────────────────────────────────
77
+
78
+ test('the model: a build-out going up is a live site at its real progress; a vote is a ghost tied to its sitting', () => {
79
+ const lv = L.livingModel(inputs());
80
+ const rel = bOf(lv, 'release');
81
+ const site = rel.construction.find((c) => c.authorised);
82
+ assert.deepEqual(j([site.kind, site.capsule, site.id, site.label, site.progress, site.authorised_by]), ['buildout', 'floor', 'buildout:1', 'Staging Deck', 0.4, 'The founder decides alone.']);
83
+ assert.equal(rel.construction.indexOf(site), 0, 'drawn ahead of any other sitting there');
84
+ const tre = bOf(lv, 'treasury');
85
+ const ghost = tre.construction[0];
86
+ assert.deepEqual(j([ghost.kind, ghost.capsule, ghost.authorised, ghost.ask_id, ghost.sitting_id, ghost.needs]), ['scaffold', 'annex', false, DOCKET_ID, '91', 'a vote']);
87
+ assert.equal(tre.construction.filter((c) => c.id === DOCKET_ID).length, 1, 'its sitting is drawn once, as its ghost');
88
+ for (const c of [site, ghost]) for (const [k, v] of Object.entries(c._sources)) assert.ok(v === 'real' || v === 'derived', `${c.id}.${k}`);
89
+ assert.equal(rel._sources['counts.building'], 'derived');
90
+ });
91
+
92
+ test('the model: a committee room is a real room, a floor that stands built a real floor; no deputies are invented', () => {
93
+ const lv = L.livingModel(inputs());
94
+ const rel = bOf(lv, 'release');
95
+ assert.deepEqual(j(rel.structure.committees), [{ key: 'b2', name: 'Gate Room', line: 'Who checks a release.', members: [], real: true, state: 'built' }]);
96
+ assert.deepEqual(j(rel.structure.built), [{ id: 'buildout:3', capsule: 'floor', name: 'Canary Floor', line: '' }]);
97
+ assert.deepEqual(Object.keys(rel.structure).sort(), ['built', 'committees'], 'no table holds deputies: none is drawn');
98
+ assert.equal(rel._sources.structure, 'real');
99
+ assert.equal(bOf(lv, 'treasury')._sources.structure, 'real', 'an unseated building\'s structure is real too');
100
+ });
101
+
102
+ test('the model: an overlay handed in changes nothing (the prototype overlay is gone)', () => {
103
+ const a = L.livingModel(inputs());
104
+ const overlay = { buildouts: [{ id: 'x', building: 'release', authorised: true, label: 'Scrambled', progress: 0.9 }, { id: 'y', building: 'treasury', authorised: false, label: 'Other', ask_id: 'proto:x' }] };
105
+ const b = L.livingModel(inputs({ overlay }));
106
+ for (const k of ['release', 'treasury']) assert.deepEqual(j(bOf(a, k).construction), j(bOf(b, k).construction), k);
107
+ });
108
+
109
+ // ── 2. the city ─────────────────────────────────────────────────────────────
110
+
111
+ function cityOf(over = {}) {
112
+ const inp = inputs(over);
113
+ const living = L.livingModel(inp);
114
+ return City.cityModel({ docket: inp.docket, goals: inp.goals.goals || inp.goals, rollups: inp.rollups.rollups || inp.rollups, mode: 'dark', now: NOW, living, constitution: inp.constitution });
115
+ }
116
+
117
+ test('the city: a live site with scaffold, crane and a rising floor at the real progress, unmarked', () => {
118
+ const m = cityOf();
119
+ const rel = m.civic.find((c) => c.key === 'release');
120
+ assert.deepEqual(j([rel.site.kind, rel.site.progress, rel.site.built]), ['live', 0.4, 1]);
121
+ const parts = m.blds.find((b) => b.key === 'release').parts;
122
+ for (const cls of ['city-scaffold', 'city-crane', 'city-jib', 'city-rise', 'city-climb']) {
123
+ const p = parts.find((x) => x.cls === cls);
124
+ assert.ok(p, cls);
125
+ assert.equal('proto' in p, false, cls + ' carries no prototype mark');
126
+ }
127
+ // the structure plugs into the core: the real Lead, the real room and the real floor
128
+ assert.deepEqual(j(rel.floors.map((f) => [f.kind, f.name])), [['lead', 'Quill'], ['committee', 'Gate Room'], ['floor', 'Canary Floor']]);
129
+ const plugs = parts.filter((p) => /city-plug/.test(p.cls || '') && ['lead', 'committee', 'floor'].includes(p.floor));
130
+ assert.equal(plugs.length, 6, 'a shell and its windows for each of the three');
131
+ });
132
+
133
+ test('the city: a build-out on a vote is a parked crane, a dashed ghost and its sitting\'s own tally', () => {
134
+ const m = cityOf();
135
+ const tre = m.civic.find((c) => c.key === 'treasury');
136
+ assert.deepEqual(j([tre.site.kind, tre.site.label, tre.site.ask_id]), ['pending', 'Reserve Wing', DOCKET_ID]);
137
+ assert.deepEqual(j(tre.tally), { seats: 5, yes: 2, no: 1 }, 'one dot a seat, filled as the docket counts them');
138
+ const parts = m.blds.find((b) => b.key === 'treasury').parts;
139
+ const ghost = parts.find((p) => p.cls === 'city-ghost');
140
+ assert.ok(ghost);
141
+ assert.ok(ghost.polys.filter((p) => p.f !== 'none').every((p) => p.dash === '4 3'), 'every face dashed');
142
+ assert.ok(parts.some((p) => p.cls === 'city-crane city-crane--parked'));
143
+ assert.ok(tre.shaft, 'its open sitting is a light shaft');
144
+ });
145
+
146
+ // ── 3. the office and the picture ───────────────────────────────────────────
147
+
148
+ test('the office stands from the real Leads; its works are the real build-outs', () => {
149
+ const lv = L.livingModel(inputs());
150
+ const off = O.officeOf(lv, 'release');
151
+ assert.ok(off, 'the Leads were read: the office stands');
152
+ assert.deepEqual(j([off.seated, off.holders]), [true, ['777']]);
153
+ assert.deepEqual(j(off.rooms.map((r) => [r.name, r.proposed])), [['Gate Room', false]]);
154
+ assert.deepEqual(j(O.worksOf(lv, 'release').map((w) => [w.kind, w.state, w.label, w.progress])), [['floor', 'site', 'Staging Deck', 0.4]]);
155
+ assert.deepEqual(j(O.worksOf(lv, 'treasury').map((w) => [w.kind, w.state, w.label, w.ask])), [['annex', 'proposed', 'Reserve Wing', DOCKET_ID]]);
156
+ assert.equal(O.officeOf(L.livingModel(inputs({ leads: null })), 'release'), null, 'the Leads not read: no office is invented');
157
+ });
158
+
159
+ test('THE PROPOSAL PICTURE RENDERS FROM THE REAL BUILDING: what stands is solid, the change dashed on top of it', () => {
160
+ const lv = L.livingModel(inputs());
161
+ const struct = bOf(lv, 'release').structure;
162
+ const bare = O.isoPicture('release', 'floor', 'dark', null);
163
+ const real = O.isoPicture('release', 'floor', 'dark', struct);
164
+ assert.deepEqual(j(real.stands), { floors: 1, rooms: 1, annex: false, district: false }, 'the real floor and room are drawn');
165
+ assert.deepEqual(j(bare.stands), { floors: 0, rooms: 0, annex: false, district: false });
166
+ const solid = (p) => p.paths.filter((x) => x.dash !== '4 3' && x.f !== 'none').length;
167
+ const dashed = (p) => p.paths.filter((x) => x.dash === '4 3');
168
+ assert.ok(solid(real) > solid(bare), 'more of the building stands solid');
169
+ assert.ok(dashed(real).length > 0 && dashed(bare).length > 0, 'the change, dashed, on both');
170
+ // the ghost floor sits on top of what stands: higher on the page than on the bare building
171
+ const topOf = (paths) => Math.min(...paths.flatMap((p) => [...p.d.matchAll(/[ML]\s*(-?[\d.]+)\s+(-?[\d.]+)/g)].map((m) => Number(m[2]))));
172
+ assert.ok(topOf(dashed(real)) < topOf(dashed(bare)), 'the change is drawn above the floors that stand');
173
+ // an annex beside a built annex stands further out
174
+ const withAnnex = O.isoPicture('release', 'annex', 'dark', { built: [{ capsule: 'annex' }], committees: [] });
175
+ const rightOf = (paths) => Math.max(...paths.flatMap((p) => [...p.d.matchAll(/[ML]\s*(-?[\d.]+)\s+(-?[\d.]+)/g)].map((m) => Number(m[1]))));
176
+ assert.ok(rightOf(dashed(withAnnex)) > rightOf(dashed(O.isoPicture('release', 'annex', 'dark', null))), 'the second annex is drawn beyond the first');
177
+ });
178
+
179
+ test('a real build-out tap goes to the server: POST /government/buildouts, then the Leads and the docket are read again', async () => {
180
+ const sent = [];
181
+ const said = [];
182
+ let loads = 0;
183
+ const refreshed = [];
184
+ const saved = globalThis.GovFeeds;
185
+ globalThis.GovFeeds = { refresh: (k) => refreshed.push(k) };
186
+ const lv = L.livingModel(inputs());
187
+ const env = {
188
+ data: () => ({ docket: { viewer: { id: '777' } }, living: lv }), mode: () => 'dark', say: (t) => said.push(t), draw() {}, decided() {}, load: () => { loads += 1; },
189
+ send: async (req) => { sent.push(req); return { ok: true, status: 201, body: { ok: true, path: 'at_once', rights: { why: 'The founder decides alone.' } } }; },
190
+ };
191
+ try {
192
+ globalThis.GovDeck.page.living = () => lv;
193
+ O.page.mount(env);
194
+ const out = await O.page.buildOut('release', 'committee');
195
+ assert.deepEqual(j(sent), [{ method: 'POST', url: '/api/bongos/government/buildouts', body: { building: 'release', capsule: 'committee' } }]);
196
+ assert.equal(out.built, true);
197
+ assert.equal(out.path, 'at_once');
198
+ assert.match(said[0], /^Building a committee room now\. The founder decides alone\./);
199
+ assert.deepEqual(refreshed, [['leads', 'docket']]);
200
+ assert.equal(loads, 1);
201
+ assert.deepEqual(j(O.page.state), { presetFiled: {} }, 'nothing drawn for this visit only: the server\'s read draws it');
202
+ // a refusal is said, and nothing is drawn
203
+ env.send = async () => ({ ok: false, status: 403, body: { error: { code: 'not_the_holder', what_this_means: 'Release is led by Quill; only they grow its structure.' } } });
204
+ const no = await O.page.buildOut('release', 'floor');
205
+ assert.equal(no.refused, true);
206
+ assert.equal(said[said.length - 1], 'Release is led by Quill; only they grow its structure.');
207
+ } finally { globalThis.GovFeeds = saved; }
208
+ });
@@ -120,12 +120,15 @@ test('a pin per open matter, in its state, over its building', () => {
120
120
  ];
121
121
  const m = City.cityModel({ docket: docket(ms), goals: [], rollups: [], mode: 'dark' });
122
122
  assert.deepEqual(m.pins.map((p) => [p.key, p.building, p.state]), [
123
- ['blockers:2', 'planning', 'moving'], ['board:1', 'charter', 'needs'], ['removal_proposals:3', 'disputes', 'settled'],
123
+ // back to front: the Charter stands in the back corner since task 1004718
124
+ ['board:1', 'charter', 'needs'], ['blockers:2', 'planning', 'moving'], ['removal_proposals:3', 'disputes', 'settled'],
124
125
  ]);
125
- // the prototype's own pin position for a matter at the Charter: P(8, 8, 158 + 8)
126
+ // the prototype's own pin position for a matter at the Charter: P(slot + 1, slot + 1, 158 + 8)
127
+ // (task 1004718 moved the Charter to the back corner, so Planning is not behind it)
126
128
  const charter = m.pins.find((p) => p.building === 'charter');
127
129
  const k = City.isoKit(60, 30, 720, 176, 30 / 28, City.isoTheme('dark'), false);
128
- const top = k.P(8, 8, 166);
130
+ const cs = City.CIVIC_SLOTS.find((s) => s[0] === 'charter');
131
+ const top = k.P(cs[1] + 1, cs[2] + 1, 166);
129
132
  assert.equal(charter.x, Math.round((top[0] - 15) * 10) / 10);
130
133
  assert.equal(charter.y, Math.round((top[1] - 52) * 10) / 10);
131
134
  });
@@ -154,7 +157,8 @@ test('one pin per building, in the tier of its most important matter, with the c
154
157
  assert.deepEqual([m.pins[0].key, m.pins[0].state, m.pins[0].count], ['blockers:9', 'urgent', 4], 'urgent (needs you, due within a day); four open');
155
158
  assert.deepEqual(m.pins[0].matters, ['blockers:9', 'blockers:1', 'blockers:0', 'blockers:2', 'blockers:3']);
156
159
  const k = City.isoKit(60, 30, 720, 176, 30 / 28, City.isoTheme('dark'), false);
157
- const top = k.P(6, 6, City.ROOF.planning + 8);
160
+ const ps = City.CIVIC_SLOTS.find((s) => s[0] === 'planning');
161
+ const top = k.P(ps[1] + 1, ps[2] + 1, City.ROOF.planning + 8);
158
162
  assert.equal(m.pins[0].x, Math.round((top[0] - 15) * 10) / 10, 'where the prototype stands a single pin');
159
163
  const tier = (list) => City.cityModel({ docket: docket(list), mode: 'dark', now: NOW }).pins[0].state;
160
164
  assert.equal(tier([matter({ state: 'needs', deadline: '2026-10-05T12:00:00.000Z' })]), 'needs', 'due after a day');
@@ -280,13 +284,17 @@ test('the city sits in the hall chrome, behind the shell\'s full-screen switch',
280
284
  for (const id of ['app-sidebar', 'app-scrim', 'app-topbar']) assert.match(html, new RegExp(`id="${id}"`), id);
281
285
  assert.match(html, /<main id="hall-main" class="city-main">\s*<div class="city-top">/, 'the stage is the one <main>, the strip inside it');
282
286
  const scripts = [...html.matchAll(/<script src="\/builders\/([a-z-]+)\.js/g)].map((m) => m[1]);
283
- assert.deepEqual(scripts, ['shell-trail', 'craft-mode', 'shell', 'palette', 'city-draw', 'city'], 'the shell first, as on every hall page');
287
+ // task 1004685 (the living city): gov-living.js, the model under the drawing, and
288
+ // gov-feeds.js, its one reader per page (shared with the shell's tally), load before it
289
+ // task 1004702: the card answers its first ask in place, so the deck's card and sender (and the desk rows under them) load before the page
290
+ assert.deepEqual(scripts, ['shell-trail', 'craft-mode', 'shell', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'street-draw', 'street-card', 'street-inside', 'street-desk', 'street-deck', 'city'], 'the shell first, as on every hall page');
284
291
  const css = read('city.css').replace(/\/\*[\s\S]*?\*\//g, '');
285
292
  assert.match(css, /\.city-main \{ position: fixed; top: var\(--topbar-h, 56px\); left: var\(--sidebar-w-rail, 64px\);[^}]*contain: layout paint;/);
286
293
  assert.match(css, /html\[data-fullscreen="on"\] \.city-main \{ top: 0; left: 0; \}/);
287
294
  assert.match(css, /html\[data-fullscreen="on"\] \.city-top \{ right: 184px; \}/, 'the strip steps clear of the corner exit');
288
295
  const shell = read('shell.js');
289
- assert.match(shell, /\{ id: 'city', label: 'City', icon: 'city', href: '\/city', gate: 'perm:board\.vote\.cast' \}/, 'the city is a tab, gated on its page\'s atom');
296
+ // (task 1004685's governing-as-a-seat pass marks the tab `position: true`; the gate is unchanged)
297
+ assert.match(shell, /\{ id: 'city', label: 'City', icon: 'city', href: '\/city', gate: 'perm:board\.vote\.cast'(, [^}]*)? \}/, 'the city is a tab, gated on its page\'s atom');
290
298
  assert.doesNotMatch(shell, /href:\s*'(\/builders)?\/street'/, 'the street has no sidebar link: the way in is the city');
291
299
  });
292
300