@bongos/core 1.21.113 → 1.21.115

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 +608 -188
  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 +709 -626
  25. package/docs/copy-registry.json +1673 -858
  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 +4875 -4748
  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-hall-live.states.json +0 -5
  166. package/modules/public-landing/public/projects.html +169 -6
  167. package/modules/public-landing/public/projects.probes.json +10 -6
  168. package/modules/public-landing/public/projects.states.json +13 -13
  169. package/modules/public-landing/public/terms.html +2 -2
  170. package/modules/ui-design/kit/fixtures/connections-empty.json +12 -0
  171. package/modules/ui-design/kit/fixtures/connections-inactive.json +7 -0
  172. package/modules/ui-design/kit/fixtures/connections.json +22 -0
  173. package/modules/ui-design/kit/serve.js +95 -0
  174. package/package-lock.json +2 -2
  175. package/package.json +1 -1
  176. package/release-notes.json +128 -0
  177. package/scripts/discord/README.md +8 -5
  178. package/scripts/discord/post.js +12 -1
  179. package/scripts/gds/artifact-audit.js +20 -3
  180. package/scripts/gds/ask.js +164 -0
  181. package/scripts/gds/autobongos-verify.js +21 -2
  182. package/scripts/gds/government-walk.js +638 -4
  183. package/scripts/gds/pulse.js +9 -6
  184. package/scripts/gds/ship-escalation.js +22 -3
  185. package/scripts/gds/ship-finish.js +5 -4
  186. package/scripts/gds/ship-merge-gate.js +18 -6
  187. package/scripts/hall-preview/README.md +41 -1
  188. package/scripts/hall-preview/pref-writes.js +140 -0
  189. package/scripts/hall-preview/refresh-fixtures.js +101 -7
  190. package/scripts/hall-preview/scrub-identity.js +366 -7
  191. package/scripts/hall-preview/server.js +27 -1
  192. package/scripts/render-diagrams.sh +4 -2
  193. package/src/bongos/auth-idp.js +6 -1
  194. package/src/bongos/project-settings.js +41 -0
  195. package/src/bongos/route-rank-check.js +27 -0
  196. package/src/bongos/routes/me.js +68 -7
  197. package/src/bongos/serve-internal.js +5 -1
  198. package/src/module-api.js +7 -1
  199. package/src/platform-server.js +10 -0
  200. package/tests/application_lifecycle.mjs +47 -5
  201. package/tests/conductor_main_e2e.mjs +21 -0
  202. package/tests/connections_api.mjs +18 -0
  203. package/tests/craft_mode_nav.mjs +5 -3
  204. package/tests/craft_mode_tallies.mjs +52 -16
  205. package/tests/discord_approvals.mjs +11 -0
  206. package/tests/discord_channels.mjs +20 -0
  207. package/tests/fixtures/gov-seated.mjs +64 -0
  208. package/tests/fixtures/government-ask-cards.json +1284 -0
  209. package/tests/government_act_alone_buildings.mjs +1 -0
  210. package/tests/government_agent_asks.mjs +322 -0
  211. package/tests/government_asks.mjs +408 -0
  212. package/tests/government_build_rights.mjs +253 -0
  213. package/tests/government_buildouts.mjs +382 -0
  214. package/tests/government_counts.mjs +143 -0
  215. package/tests/government_decision_rules.mjs +5 -1
  216. package/tests/government_laws.mjs +488 -0
  217. package/tests/government_laws_session.mjs +321 -0
  218. package/tests/government_leads.mjs +375 -0
  219. package/tests/government_positions.mjs +127 -0
  220. package/tests/government_presets.mjs +215 -0
  221. package/tests/government_routes.mjs +8 -1
  222. package/tests/government_routing.mjs +118 -0
  223. package/tests/government_seed.mjs +5 -0
  224. package/tests/government_treasury.mjs +394 -0
  225. package/tests/governor_docket.mjs +26 -2
  226. package/tests/hall_buildouts.mjs +208 -0
  227. package/tests/hall_city.mjs +14 -6
  228. package/tests/hall_city_living.mjs +617 -0
  229. package/tests/hall_gov_counts.mjs +336 -0
  230. package/tests/hall_gov_living.mjs +636 -0
  231. package/tests/hall_gov_real_site.mjs +655 -0
  232. package/tests/hall_matter_card.mjs +6 -1
  233. package/tests/hall_my_agents.mjs +481 -0
  234. package/tests/hall_nav.mjs +107 -4
  235. package/tests/hall_page_gate_map.mjs +4 -0
  236. package/tests/hall_preview_governor_fixtures.mjs +30 -3
  237. package/tests/hall_preview_scrub.mjs +59 -1
  238. package/tests/hall_preview_security.mjs +169 -0
  239. package/tests/hall_reading_world.mjs +44 -0
  240. package/tests/hall_roster_world.mjs +6 -2
  241. package/tests/hall_street.mjs +18 -2
  242. package/tests/hall_street_amend.mjs +1 -1
  243. package/tests/hall_street_deck.mjs +609 -0
  244. package/tests/hall_street_depth.mjs +559 -0
  245. package/tests/hall_street_desk.mjs +1 -1
  246. package/tests/hall_street_inside.mjs +29 -5
  247. package/tests/hall_tweak_editor.mjs +4 -1
  248. package/tests/hub_account_page.mjs +94 -3
  249. package/tests/hub_connections_page.mjs +507 -0
  250. package/tests/hub_invite_badge.mjs +1 -1
  251. package/tests/idp_device_exchange.mjs +18 -0
  252. package/tests/landing_legal_pages.mjs +8 -5
  253. package/tests/landing_page.mjs +23 -4
  254. package/tests/nav_permission_atoms.mjs +4 -1
  255. package/tests/profile_connections_ui.mjs +88 -25
  256. package/tests/projects_hub.mjs +15 -0
  257. package/tests/projects_hub_pre_uat.mjs +4 -2
  258. package/tests/render_check.mjs +2 -2
  259. package/tests/ship_broadcast_routing.mjs +57 -16
  260. package/tests/ship_broadcast_server.mjs +40 -7
  261. package/tests/ship_merge_gate.mjs +19 -0
  262. package/tests/ui_design_kit.mjs +2 -2
  263. package/tests/watch_applications_queue.mjs +23 -0
  264. package/tests/wizard_physics.mjs +1 -1
  265. package/tests/wizard_physics_more.mjs +133 -0
@@ -0,0 +1,655 @@
1
+ // tests/hall_gov_real_site.mjs — ON A REAL SITE THE PROTOTYPE IS NOT THERE
2
+ // (task 1004701: land the living-city prototype as the hall's visual base).
3
+ //
4
+ // WHAT IT GUARDS. The living city, the street, the decision deck, the inside of
5
+ // every building and My agents were built over real reads plus ONE labelled
6
+ // prototype overlay (GET /proto/governor-overlay), served by the hall-preview
7
+ // harness only. Task 1004717 deleted it: no page asks for it any more. This file
8
+ // boots every page with every real fixture, a stale server still answering the
9
+ // overlay route, and requires that:
10
+ // * no page asks for the overlay, and nothing on any page carries `data-proto`;
11
+ // * no text the overlay used to invent (a Lead's charter, a law, a prototype
12
+ // setting, a human rewrite of an ask) appears anywhere in the page's text,
13
+ // attributes or markup;
14
+ // * no prototype key, office storey, law book of invented laws or pot is drawn;
15
+ // * the real reads still draw (the docket's asks, the readings, the real rows);
16
+ // * a law the project really passed (GET /government/laws, task 1004706) stands
17
+ // as real, while the overlay's invented laws still do not;
18
+ // * who leads each building (GET /government/leads, task 1004709) stands as
19
+ // real: a Lead the government appointed on their building, and the owner on
20
+ // every unseated one, with nothing marked prototype;
21
+ // * the shell's seat-gated nav stays today's nav (unknown is never "none").
22
+ // And, as the control, that the same scan DOES find those words when a real read
23
+ // carries them, so a scan that quietly finds nothing cannot pass.
24
+ //
25
+ // Run: node --test --test-reporter=tap tests/hall_gov_real_site.mjs
26
+
27
+ import { test } from 'node:test';
28
+ import assert from 'node:assert/strict';
29
+ import fs from 'node:fs';
30
+ import path from 'node:path';
31
+ import vm from 'node:vm';
32
+ import { createRequire } from 'node:module';
33
+ import { fileURLToPath } from 'node:url';
34
+
35
+ const require = createRequire(import.meta.url);
36
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
37
+ const PUB = path.join(ROOT, 'modules', 'hall-ui', 'public');
38
+ const FX = path.join(ROOT, 'scripts', 'hall-preview', 'fixtures');
39
+ const read = (f) => fs.readFileSync(path.join(PUB, f), 'utf8');
40
+ const fx = (name) => JSON.parse(fs.readFileSync(path.join(FX, name + '.json'), 'utf8'));
41
+ const GovPositions = require(path.join(PUB, 'gov-positions.js'));
42
+ const BUILDINGS = ['charter', 'rules', 'disputes', 'people', 'planning', 'automation', 'review', 'release', 'treasury'];
43
+ const OVERLAY_URL = '/api/bongos/proto/governor-overlay';
44
+
45
+ // ── the deleted overlay's own words ─────────────────────────────────────────
46
+ // Phrases the prototype overlay used to invent (its laws, a Lead's charter, its
47
+ // prototype settings, a rewrite of an ask), that no real fixture and no page
48
+ // source holds: if one of these is on a page, something invented it.
49
+ const PAGE_SOURCES = ['city.html', 'street.html', 'my-agents.html', 'city.js', 'city-draw.js', 'street.js', 'street-draw.js', 'street-depth.js',
50
+ 'street-deck.js', 'street-office.js', 'street-inside.js', 'street-card.js', 'street-desk.js', 'street-amend.js', 'my-agents.js', 'gov-living.js', 'gov-feeds.js', 'gov-positions.js']
51
+ .map(read).join('\n');
52
+ const REAL_TEXT = fs.readdirSync(FX).filter((f) => f.endsWith('.json')).map((f) => fs.readFileSync(path.join(FX, f), 'utf8')).join('\n');
53
+ const OVERLAY_PHRASES = ['Ask everything up front', 'Build the approved design exactly', 'Planning floors: goals, priorities, the order of work.',
54
+ 'When an agent is stuck', 'Go live without asking', 'Judge a high-severity security report?'];
55
+ // a stale server that still answers the old route, with those words in it
56
+ const OLD_OVERLAY = {
57
+ _proto: 'Prototype data: concepts not yet in the database',
58
+ owner: { id: '3' },
59
+ laws: [{ id: 'skip', text: OVERLAY_PHRASES[0] }, { id: 'land', text: OVERLAY_PHRASES[1] }],
60
+ leads: [{ building: 'planning', lead: '1000025', charter: { scope: OVERLAY_PHRASES[2] }, deputies: [], committees: [] }],
61
+ personal_settings: [{ key: 'on_stuck', label: OVERLAY_PHRASES[3], values: ['ask'], value: 'ask' }, { key: 'auto_go_live', label: OVERLAY_PHRASES[4], values: ['no'], value: 'no' }],
62
+ asks: [{ id: 'redteam_reports:1000009', question: OVERLAY_PHRASES[5], options: [{ key: 'a', label: 'A', result: 'a' }, { key: 'b', label: 'B', result: 'b' }], recommend: { key: 'a', why: 'x' }, waits_on: 'owner', if_nobody_answers: 'x' }],
63
+ };
64
+ const esc = (s) => s.replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
65
+
66
+ // ── one fake DOM for every page ─────────────────────────────────────────────
67
+ // Every element of the page's HTML that has an id stands as a node, with its
68
+ // attributes and its static markup; scripts build the rest with createElement or
69
+ // innerHTML. Enough of querySelector/closest/matches for the pages' own selectors.
70
+ function fakeDom(htmlFile) {
71
+ const byId = new Map();
72
+ const roots = [];
73
+ const parseSel = (sel) => sel.split(',').map((s) => s.trim()).filter(Boolean);
74
+ const matchOne = (el, w) => {
75
+ let m;
76
+ if ((m = /^\[([\w-]+)="([^"]*)"\]$/.exec(w))) return el.attrs[m[1]] === m[2];
77
+ if ((m = /^\[([\w-]+)\]$/.exec(w))) return m[1] in el.attrs;
78
+ if ((m = /^\.([\w-]+)$/.exec(w))) return String(el.className).split(' ').includes(m[1]);
79
+ if ((m = /^#([\w-]+)$/.exec(w))) return el.attrs.id === m[1];
80
+ if (/^[a-z]+$/.test(w)) return el.tagName === w;
81
+ return false;
82
+ };
83
+ const mk = (tag) => {
84
+ const el = {
85
+ tagName: tag, attrs: {}, kids: [], className: '', listeners: {}, _text: '', _html: '', parentNode: null,
86
+ // the property and the attribute are one, as in a browser (a page may use either)
87
+ get hidden() { return 'hidden' in this.attrs; },
88
+ set hidden(v) { if (v) this.attrs.hidden = ''; else delete this.attrs.hidden; },
89
+ style: { setProperty(k, v) { this[k] = v; }, removeProperty(k) { delete this[k]; } },
90
+ clientWidth: 1440, clientHeight: 708, offsetLeft: 0, offsetTop: 0, scrollLeft: 0, scrollTop: 0,
91
+ get children() { const a = this.kids.slice(); a[Symbol.isConcatSpreadable] = false; return a; },
92
+ get firstChild() { return this.kids[0] || null; },
93
+ setAttribute(k, v) { this.attrs[k] = String(v); if (k === 'id') byId.set(String(v), this); },
94
+ getAttribute(k) { return k in this.attrs ? this.attrs[k] : null; },
95
+ hasAttribute(k) { return k in this.attrs; },
96
+ removeAttribute(k) { delete this.attrs[k]; },
97
+ appendChild(c) { this.kids.push(c); c.parentNode = this; return c; },
98
+ insertBefore(c, ref) { const i = this.kids.indexOf(ref); if (i < 0) this.kids.push(c); else this.kids.splice(i, 0, c); c.parentNode = this; return c; },
99
+ removeChild(c) { this.kids = this.kids.filter((x) => x !== c); return c; },
100
+ remove() { if (this.parentNode) this.parentNode.removeChild(this); },
101
+ addEventListener(t, fn) { (this.listeners[t] = this.listeners[t] || []).push(fn); },
102
+ removeEventListener() {},
103
+ fire(t, e) { (this.listeners[t] || []).forEach((fn) => fn({ type: t, target: this, preventDefault() {}, stopPropagation() {}, ...e })); },
104
+ focus() {}, blur() {},
105
+ matches(sel) { return parseSel(sel).some((w) => matchOne(this, w)); },
106
+ closest(sel) { let n = this; while (n) { if (n.matches && n.matches(sel)) return n; n = n.parentNode; } return null; },
107
+ querySelectorAll(sel) { const out = []; const walk = (n) => n.kids.forEach((k) => { if (k.matches(sel)) out.push(k); walk(k); }); walk(this); return out; },
108
+ querySelector(sel) { return this.querySelectorAll(sel)[0] || null; },
109
+ getBoundingClientRect() { return { left: 0, top: 0, right: 1440, bottom: 708, width: 1440, height: 708, x: 0, y: 0 }; },
110
+ set textContent(v) { this._text = String(v); this._html = ''; this.kids = []; },
111
+ get textContent() { return this._text + this.kids.map((k) => k.textContent || '').join(''); },
112
+ set innerHTML(v) { this._html = String(v); this._text = ''; this.kids = []; },
113
+ get innerHTML() { return this._html; },
114
+ };
115
+ el.classList = {
116
+ add: (...c) => { const s = new Set(String(el.className).split(' ').filter(Boolean)); c.forEach((x) => s.add(x)); el.className = [...s].join(' '); },
117
+ remove: (...c) => { el.className = String(el.className).split(' ').filter((x) => x && !c.includes(x)).join(' '); },
118
+ toggle: (c, on) => { const has = String(el.className).split(' ').includes(c); const want = on === undefined ? !has : !!on; if (want) el.classList.add(c); else el.classList.remove(c); return want; },
119
+ contains: (c) => String(el.className).split(' ').includes(c),
120
+ };
121
+ return el;
122
+ };
123
+ const html = read(htmlFile).replace(/<!--[\s\S]*?-->/g, '');
124
+ const VOID = /^(meta|link|img|input|br|hr|source|use|path|circle|rect|line|polygon|stop)$/;
125
+ // [start, end) of an element's inner markup, its own tag balanced
126
+ const innerOf = (tag, from) => {
127
+ const re = new RegExp(`<(/?)${tag}\\b[^>]*?(/?)>`, 'g');
128
+ re.lastIndex = from;
129
+ let depth = 1, m;
130
+ while ((m = re.exec(html))) {
131
+ if (m[2]) continue;
132
+ depth += m[1] ? -1 : 1;
133
+ if (!depth) return [from, m.index];
134
+ }
135
+ return [from, from];
136
+ };
137
+ const spans = [];
138
+ for (const m of html.matchAll(/<([a-z][a-z0-9]*)\b([^>]*)>/g)) {
139
+ const idm = /\bid="([A-Za-z][\w-]*)"/.exec(m[2]);
140
+ if (!idm) continue;
141
+ const el = mk(m[1]);
142
+ for (const a of m[2].matchAll(/\s([\w-]+)(?:="([^"]*)")?/g)) {
143
+ if (a[1] === 'class') el.className = a[2] || '';
144
+ else el.setAttribute(a[1], a[2] === undefined ? '' : a[2]);
145
+ }
146
+ const inner = VOID.test(m[1]) || /\/$/.test(m[2]) ? [0, 0] : innerOf(m[1], m.index + m[0].length);
147
+ spans.push({ el, outer: [m.index, inner[1]], inner });
148
+ roots.push(el);
149
+ }
150
+ // each element's own static markup: its inner markup less every id'd element inside it
151
+ // (those stand as nodes of their own, and are judged by their own `hidden`)
152
+ for (const s of spans) {
153
+ let text = '';
154
+ let at = s.inner[0];
155
+ spans.filter((o) => o !== s && o.outer[0] >= s.inner[0] && o.outer[0] < s.inner[1])
156
+ .sort((a, b) => a.outer[0] - b.outer[0])
157
+ .forEach((o) => { if (o.outer[0] >= at) { text += html.slice(at, o.outer[0]); at = Math.max(at, o.outer[1]); } });
158
+ text += html.slice(at, s.inner[1]);
159
+ s.el._html = text;
160
+ // the innermost id'd element holding it: a hidden one hides it too
161
+ const holders = spans.filter((o) => o !== s && s.outer[0] >= o.inner[0] && s.outer[0] < o.inner[1]);
162
+ s.el.staticParent = holders.sort((a, b) => b.inner[0] - a.inner[0])[0]?.el || null;
163
+ }
164
+ const document = {
165
+ documentElement: mk('html'),
166
+ getElementById: (id) => byId.get(id) || null,
167
+ querySelector: (sel) => { for (const r of roots) { if (r.matches(sel)) return r; const f = r.querySelector(sel); if (f) return f; } return null; },
168
+ querySelectorAll: (sel) => roots.flatMap((r) => (r.matches(sel) ? [r] : []).concat(r.querySelectorAll(sel))),
169
+ createElement: (t) => mk(t), createElementNS: (_n, t) => mk(t),
170
+ createTextNode: (t) => { const n = mk('#text'); n._text = String(t); return n; },
171
+ addEventListener() {},
172
+ };
173
+ return { document, byId, roots };
174
+ }
175
+
176
+ // Everything a page shows, as one string (text, markup and attribute values of
177
+ // every element not hidden), and every shown element carrying data-proto.
178
+ function dump(roots) {
179
+ const parts = [];
180
+ const proto = [];
181
+ const shut = (el) => el.hidden;
182
+ const walk = (el) => {
183
+ if (shut(el)) return;
184
+ for (let p = el.staticParent; p; p = p.staticParent) if (shut(p)) return;
185
+ parts.push(el._text, el._html, ...Object.values(el.attrs));
186
+ if ('data-proto' in el.attrs) proto.push((el.attrs.id || el.tagName) + '.' + el.className);
187
+ if (/\sdata-proto(?=[\s>=])/.test(el._html)) proto.push((el.attrs.id || el.tagName) + ' (markup)');
188
+ el.kids.forEach(walk);
189
+ };
190
+ roots.forEach(walk);
191
+ return { text: parts.join('\n'), proto, shown: parts.length };
192
+ }
193
+
194
+ // ── the feeds: every real fixture, and the overlay as production answers it ──
195
+ const FEED = {
196
+ '/api/bongos/government/docket': 'government__docket', '/api/bongos/government/counts': 'government__counts', '/api/bongos/goals': 'goals', '/api/bongos/goals/rollups': 'goals__rollups',
197
+ '/api/bongos/me': 'me', '/api/bongos/builders/roster': 'builders__roster', '/api/bongos/blockers': 'blockers',
198
+ '/api/bongos/public/cost-summary': 'public__cost-summary', '/api/bongos/tasks/claimable': 'tasks__claimable',
199
+ '/api/bongos/tasks/claimable?limit=1': 'tasks__claimable', '/api/bongos/tasks?status=active&limit=200': 'tasks__status-active',
200
+ '/api/bongos/blockers?limit=100': 'blockers', '/api/bongos/moving': 'moving', '/api/bongos/autonomy/fence': 'autonomy__fence',
201
+ '/api/bongos/government/constitution': 'government__constitution',
202
+ // the hub's law book, as the route answers it (no law passed yet)
203
+ '/api/bongos/government/laws': 'government__laws',
204
+ // the strictness presets as the route answers the hub (no law, so no preset in force; task 1004715)
205
+ '/api/bongos/government/presets': 'government__presets',
206
+ };
207
+ // a law a project really passed, as GET /government/laws answers it (not one of the
208
+ // overlay's: the words are this test's own, so the overlay scan cannot excuse them)
209
+ const A_REAL_LAW = 'Every screen change carries two shots';
210
+ const REAL_BOOK = {
211
+ laws: [{
212
+ id: '11', text: A_REAL_LAW, effect_on_ai: 'Agents attach a before and an after picture to screen work.', locks: [],
213
+ in_force: true, passed_at: '2026-10-09T12:00:00.000Z', repealed_at: null, repealed_by_item_id: null,
214
+ passed_by: { item_id: '31', href: '/board-room?item=31', government: 'Timocratic Republic', pass_rule: 'unanimous', proposer: { id: '42', login: 'ada', name: 'Ada' }, yes: [] },
215
+ }],
216
+ in_force: 1,
217
+ };
218
+ let lawBook = null; // a test may serve REAL_BOOK in place of the hub's empty book
219
+ // GET /me's laws block as the server answers it on a project with a law in force
220
+ // (task 1004707): what every session reads, and what the lock did to this viewer
221
+ let meLaws = null;
222
+ const ME_LAWS = {
223
+ inject: true,
224
+ contract: 'Project laws — passed by a vote of this project\'s government.\n1. ' + A_REAL_LAW,
225
+ laws: [{ id: '11', no: 1, text: A_REAL_LAW, effect_on_ai: 'Agents attach a before and an after picture to screen work.', locks: [{ setting: 'verbosity', value: 'detailed' }] }],
226
+ locks: [{ setting: 'verbosity', law_id: '11', law_no: 1, law_text: A_REAL_LAW, value: 'detailed', route: 'interaction', enforced: true, from: 'concise', to: 'detailed' }],
227
+ };
228
+ // A stored ask (task 1004703), as GET /government/docket carries it on a matter
229
+ // whose agent wrote its card in the ask shape. Its words are this test's own, so
230
+ // the overlay scan cannot excuse them.
231
+ const A_REAL_ASK = {
232
+ question: 'Pick the host for the new site?',
233
+ options: [
234
+ { key: 'cheap', label: 'The cheap host', result: 'Ten dollars a month, slower builds.', act: 'go ahead' },
235
+ { key: 'fast', label: 'The fast host', result: 'Forty dollars a month, quick builds.' },
236
+ ],
237
+ recommend: { key: 'cheap', why: 'The site is small for now.' },
238
+ waits_on: 'owner',
239
+ if_nobody_answers: 'The site waits, unbuilt.',
240
+ detail: null,
241
+ written_at: '2026-10-09T12:00:00.000Z',
242
+ };
243
+ let docketAsks = false; // a test may serve the docket with every open matter carrying A_REAL_ASK
244
+ // Who leads each building, as GET /government/leads answers (task 1004709): Planning
245
+ // led by a builder the government appointed, every other building the owner's. The
246
+ // Lead's name and charter are this test's own words, so the overlay scan cannot excuse them.
247
+ const A_REAL_LEAD = 'Quill Appointee';
248
+ const LEADS_READ = {
249
+ buildings: BUILDINGS.map((b) => ({
250
+ building: b, name: b,
251
+ lead: b === 'planning' ? { id: '1', building: b, builder: { id: '777', login: 'quill', name: A_REAL_LEAD }, charter: 'Orders the work by what ships next.', since: '2026-10-09T12:00:00.000Z', appointed_by: { item_id: '40', href: '/board-room?item=40' } } : null,
252
+ answers: b === 'planning' ? { seat: 'lead:planning', builder_ids: ['777'], fell_to_owner: false } : { seat: 'owner', builder_ids: ['3'], fell_to_owner: true },
253
+ })),
254
+ owner: { seat: 'owner', rank: 'archon', builders: [{ id: '3', login: 'builder-2013', name: 'Builder 2013' }] },
255
+ seated: 1,
256
+ };
257
+ let leadsBook = null; // a test may serve LEADS_READ; otherwise the read answers 404 (an older server)
258
+ // The treasury (task 1004712), as GET /government/treasury answers it: one goal's pot,
259
+ // dry, with its borrow open on the docket (the fixture's treasury_spending:1).
260
+ const TREASURY_READ = (() => {
261
+ const t = fx('government__treasury');
262
+ t.pots = [t.pots.find((p) => p.dry)];
263
+ return t;
264
+ })();
265
+ let treasuryBook = null; // a test may serve TREASURY_READ; otherwise the read answers 404 (an older server)
266
+ function makeFetch(withOverlay, calls) {
267
+ return (url, init) => {
268
+ calls.push(url);
269
+ if (init && init.method && init.method !== 'GET') return Promise.resolve({ ok: false, status: 501, json: async () => ({}) });
270
+ if (url === '/api/bongos/government/docket' && docketAsks) {
271
+ const d = fx('government__docket');
272
+ d.matters.forEach((m) => { if (m.state === 'needs') m.ask = JSON.parse(JSON.stringify(A_REAL_ASK)); });
273
+ return Promise.resolve({ ok: true, status: 200, json: async () => d });
274
+ }
275
+ if (url === '/api/bongos/government/laws' && lawBook) return Promise.resolve({ ok: true, status: 200, json: async () => JSON.parse(JSON.stringify(lawBook)) });
276
+ if (url === '/api/bongos/government/leads' && leadsBook) return Promise.resolve({ ok: true, status: 200, json: async () => JSON.parse(JSON.stringify(leadsBook)) });
277
+ if (url === '/api/bongos/government/treasury' && treasuryBook) return Promise.resolve({ ok: true, status: 200, json: async () => JSON.parse(JSON.stringify(treasuryBook)) });
278
+ if (url === '/api/bongos/me' && meLaws) {
279
+ const me = fx('me');
280
+ me.laws = JSON.parse(JSON.stringify(meLaws));
281
+ me.interaction.effective = { ...me.interaction.effective, verbosity: 'detailed' };
282
+ return Promise.resolve({ ok: true, status: 200, json: async () => me });
283
+ }
284
+ if (url === OVERLAY_URL && withOverlay) return Promise.resolve({ ok: true, status: 200, json: async () => JSON.parse(JSON.stringify(OLD_OVERLAY)) });
285
+ const name = FEED[url];
286
+ if (!name) return Promise.resolve({ ok: false, status: 404, json: async () => ({ error: { code: 'not_found' } }) });
287
+ return Promise.resolve({ ok: true, status: 200, json: async () => fx(name) });
288
+ };
289
+ }
290
+ function sandbox(window, document, fetch) {
291
+ class MutationObserver { observe() {} disconnect() {} }
292
+ const ctx = vm.createContext({
293
+ window, document, fetch, Promise, Math, String, Number, Array, Object, JSON, Date, console, encodeURIComponent, decodeURIComponent,
294
+ isNaN, isFinite, Map, Set, setTimeout, clearTimeout, MutationObserver, Symbol, Error, RegExp, Boolean,
295
+ });
296
+ ctx.globalThis = ctx;
297
+ return ctx;
298
+ }
299
+ function baseWindow(hash = '') {
300
+ const store = new Map();
301
+ const ls = { getItem: (k) => (store.has(k) ? store.get(k) : null), setItem: (k, v) => store.set(k, String(v)), removeItem: (k) => store.delete(k) };
302
+ return {
303
+ innerWidth: 1440, innerHeight: 900, location: { hash, href: '', search: '', pathname: '/' },
304
+ localStorage: ls, sessionStorage: ls,
305
+ matchMedia: () => ({ matches: true, addEventListener() {} }), addEventListener() {}, removeEventListener() {},
306
+ history: { pushState() {}, replaceState() {} },
307
+ };
308
+ }
309
+ const settle = async (n = 8) => { for (let i = 0; i < n; i++) await new Promise((r) => setImmediate(r)); };
310
+
311
+ async function bootCity(withOverlay) {
312
+ const { document, byId, roots } = fakeDom('city.html');
313
+ const calls = [];
314
+ const window = baseWindow();
315
+ window.fetch = makeFetch(withOverlay, calls);
316
+ const ctx = sandbox(window, document, window.fetch);
317
+ // city.html loads the deck's card and sender for the card's first ask (task 1004702)
318
+ for (const f of ['gov-living.js', 'city-draw.js', 'street-draw.js', 'street-card.js', 'street-inside.js', 'street-desk.js', 'street-deck.js', 'city.js']) vm.runInContext(read(f), ctx, { filename: f });
319
+ await window.GovCityPage.ready;
320
+ await settle();
321
+ return { byId, roots, calls, window };
322
+ }
323
+ async function bootStreet(hash, withOverlay) {
324
+ const { document, byId, roots } = fakeDom('street.html');
325
+ const calls = [];
326
+ const window = baseWindow(hash);
327
+ window.fetch = makeFetch(withOverlay, calls);
328
+ const ctx = sandbox(window, document, window.fetch);
329
+ for (const f of ['gov-living.js', 'gov-feeds.js', 'city-draw.js', 'street-draw.js', 'street-card.js', 'street-inside.js', 'street-depth.js',
330
+ 'street-desk.js', 'street-amend.js', 'street-deck.js', 'street-office.js', 'street.js']) vm.runInContext(read(f), ctx, { filename: f });
331
+ await window.GovStreetPage.ready;
332
+ await settle();
333
+ return { byId, roots, calls, window };
334
+ }
335
+ async function bootMyAgents(withOverlay) {
336
+ const { document, byId, roots } = fakeDom('my-agents.html');
337
+ const calls = [];
338
+ const window = baseWindow();
339
+ const fetch = makeFetch(withOverlay, calls);
340
+ window.fetch = fetch;
341
+ // the page's client: the same reads, through the same 404
342
+ window.BongosClient = {
343
+ createClient: () => ({
344
+ request: (method, p) => fetch(p, { method }).then(async (r) => ({ ok: r.ok, status: r.status, data: r.ok ? await r.json() : null })),
345
+ }),
346
+ };
347
+ const ctx = sandbox(window, document, fetch);
348
+ for (const f of ['gov-living.js', 'gov-positions.js', 'my-agents.js']) vm.runInContext(read(f), ctx, { filename: f });
349
+ await settle(12);
350
+ return { byId, roots, calls, window };
351
+ }
352
+ function noPrototype(page, where, { seatsRead = false } = {}) {
353
+ const { text, proto, shown } = dump(page.roots);
354
+ assert.ok(shown >= 3, `${where}: the scan saw the page (${shown} elements)`);
355
+ assert.deepEqual(proto, [], `${where}: nothing shown carries data-proto`);
356
+ assert.ok(!page.calls.includes(OVERLAY_URL), `${where}: no overlay is asked for`);
357
+ const leaked = OVERLAY_PHRASES.filter((s) => text.includes(s) || text.includes(esc(s)));
358
+ assert.deepEqual(leaked, [], `${where}: no word the overlay used to invent is on the page`);
359
+ // who leads or holds a building is known only from GET /government/leads (task
360
+ // 1004709); without that read the page names no Lead and no owner's seat
361
+ if (!seatsRead) assert.doesNotMatch(text, /\bLeads?\b|\bled by\b|held by the owner|the owner holds/, `${where}: no Lead, no owner's seat`);
362
+ return text;
363
+ }
364
+
365
+ // ── the control: the scan finds those words when a real read carries them ───
366
+ test('control: the phrases are no real fixture\'s and no page\'s; a stale overlay route changes nothing; a real law carrying one shows', async () => {
367
+ for (const s of OVERLAY_PHRASES) assert.ok(!REAL_TEXT.includes(s) && !PAGE_SOURCES.includes(s), s);
368
+ // a stale server still answers the old route: no page asks for it, nothing of it shows
369
+ const pages = [['city', await bootCity(true)], ['street', await bootStreet('', true)], ['deck', await bootStreet('#deck', true)],
370
+ ['inside planning', await bootStreet('#building=planning', true)], ['my agents', await bootMyAgents(true)]];
371
+ for (const [where, page] of pages) noPrototype(page, where + ' (a stale overlay route)');
372
+ for (const f of ['city.js', 'gov-feeds.js', 'my-agents.js', 'street.js', 'street-depth.js']) assert.doesNotMatch(read(f), /governor-overlay/, f + ' names no overlay route');
373
+ // no prototype tag is left in the code of any living-city page, the shell or their sheets
374
+ // (comments may still tell the history; code, markup and storage keys may not)
375
+ const code = (src) => src.replace(/<!--[\s\S]*?-->/g, '').replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:'"`\\])\/\/.*$/gm, '$1');
376
+ for (const f of ['city.html', 'street.html', 'my-agents.html', 'city.js', 'city-draw.js', 'street.js', 'street-draw.js', 'street-depth.js', 'street-deck.js',
377
+ 'street-office.js', 'street-inside.js', 'my-agents.js', 'gov-living.js', 'gov-feeds.js', 'gov-positions.js', 'shell.js', 'city.css', 'street.css', 'street-deck.css', 'my-agents.css']) {
378
+ assert.doesNotMatch(code(read(f)), /data-proto|proto-tag|proto-key|proto_note|overlay_note|protoAsk|['"]proto[-:'"]|\.proto\b|\bproto:/, f + ': no prototype tag in its code');
379
+ }
380
+ // the scan is not vacuous: a real law in those words is on the street
381
+ lawBook = { laws: [{ ...REAL_BOOK.laws[0], text: OVERLAY_PHRASES[0] }], in_force: 1 };
382
+ try {
383
+ const p = await bootStreet('', false);
384
+ const { text } = dump(p.roots);
385
+ assert.ok(text.includes(OVERLAY_PHRASES[0]) || text.includes(esc(OVERLAY_PHRASES[0])), 'the scan sees the words when a page shows them');
386
+ } finally { lawBook = null; }
387
+ });
388
+
389
+ // ── a real site ─────────────────────────────────────────────────────────────
390
+ test('the city on a real site: the real city, no Lead, law, pot, build-out, rewrite or prototype key', async () => {
391
+ const p = await bootCity(false);
392
+ noPrototype(p, 'city');
393
+ assert.doesNotMatch(read('city.html'), /cityKey|data-proto/, 'no prototype key');
394
+ assert.equal(p.byId.get('cityHits').children.length, 9, 'the nine buildings still answer');
395
+ assert.ok(p.byId.get('cityHits').children.every((b) => !/led by/.test(b.attrs['aria-label'])), 'nobody "leads" a building');
396
+ assert.match(p.byId.get('cityLine').textContent, /\d+ agents? working/, 'the real reads still count');
397
+ // a building's card names nobody as holding it, and carries no rewritten ask:
398
+ // its first ask (task 1004702) is the docket's own, in its own words
399
+ for (const b of BUILDINGS) {
400
+ p.byId.get('cityHits').children.find((h) => h.attrs['data-building'] === b).fire('click');
401
+ const card = p.byId.get('cityCard');
402
+ assert.equal(card.attrs['data-building'], b);
403
+ assert.doesNotMatch(card.textContent, /\bLead\b|owner/, `${b}: no Lead, no owner's seat`);
404
+ const asks = card.querySelectorAll('.city-ask');
405
+ assert.ok(asks.length <= 1, `${b}: at most its first ask`);
406
+ // answers are drawn only for a matter whose agent stored its ask (the harness's treasury borrow)
407
+ const stored = fx('government__docket').matters.filter((m) => m.ask).map((m) => m.ask.question);
408
+ if (asks.length && asks[0].querySelectorAll('.ask__opt').length) assert.ok(stored.includes(asks[0].querySelectorAll('.ask__q')[0].textContent), b + ': answers only on a stored ask');
409
+ noPrototype(p, 'city card ' + b);
410
+ }
411
+ });
412
+
413
+ test('the street on a real site: the real street, its readings and asks, no prototype layer', async () => {
414
+ const p = await bootStreet('', false);
415
+ noPrototype(p, 'street');
416
+ assert.doesNotMatch(read('street.html'), /streetDepthKey|data-proto/, 'no prototype key');
417
+ assert.ok(p.byId.get('streetSigns').children.some((a) => !a.hidden), 'the docket\'s readings stand');
418
+ assert.match(p.byId.get('hudNeeds').textContent, /^\d+$/, 'the header counts the real asks');
419
+ });
420
+
421
+ test('the decision deck on a real site: the docket\'s own asks in their own words, no rewrite, no invented ask', async () => {
422
+ const p = await bootStreet('#deck', false);
423
+ noPrototype(p, 'deck');
424
+ const deck = p.byId.get('streetDeck');
425
+ assert.equal(deck.hidden, false, 'the deck opens');
426
+ const docketTitles = fx('government__docket').matters.map((m) => m.title).concat(fx('blockers').blockers.map((b) => b.title));
427
+ assert.ok(docketTitles.some((t) => deck.textContent.includes(t)), 'a real ask, as its source words it');
428
+ // the format's refusal is the prototype's: a real ask goes to its floor's desk, where its real acts are
429
+ assert.doesNotMatch(deck.textContent, /sent back|until its agent writes it|prototype data|pass it on/, 'no refusal, no key, no seats to pass to');
430
+ const walk = (el, out = []) => { el.kids.forEach((k) => { out.push(k); walk(k, out); }); return out; };
431
+ const desk = walk(deck).find((k) => k.className === 'ask__desk');
432
+ assert.ok(desk, 'the card sends you to its desk');
433
+ assert.match(desk.attrs.href, /^\/builders\/street#building=[a-z]+$/);
434
+ assert.match(walk(deck).find((k) => /\bask__waits\b/.test(k.className)).textContent, /^waits on (you|others)$/, 'it waits on you, or on others unnamed');
435
+ });
436
+
437
+ test('a matter whose agent stored its ask: the deck draws the card, as real, on a real site', async () => {
438
+ docketAsks = true;
439
+ try {
440
+ // at Planning, where the blockers stand: a stored answer's act ('go ahead') is the blocker's own decide chip
441
+ const p = await bootStreet('#deck=planning', false);
442
+ noPrototype(p, 'deck with stored asks');
443
+ const deck = p.byId.get('streetDeck');
444
+ const walk = (el, out = []) => { el.kids.forEach((k) => { out.push(k); walk(k, out); }); return out; };
445
+ const all = walk(deck);
446
+ const card = all.find((k) => k.tagName === 'article' && /\bask\b/.test(k.className));
447
+ assert.ok(card, 'a card is drawn');
448
+ const q = all.find((k) => /\bask__q\b/.test(k.className));
449
+ assert.equal(q.textContent, A_REAL_ASK.question, 'its question is the stored one');
450
+ assert.equal('data-proto' in q.attrs, false, 'and its words are not marked prototype');
451
+ const opts = all.filter((k) => /\bask__opt\b/.test(k.className) && k.tagName === 'button');
452
+ assert.deepEqual(opts.map((o) => o.attrs['data-key']), ['cheap', 'fast'], 'its two tap answers');
453
+ assert.equal('aria-disabled' in opts[0].attrs, false, 'the pick takes the real decide act of the blocker, so it is live');
454
+ assert.ok(deck.textContent.includes('Ten dollars a month, slower builds.'), 'each with its one-line result');
455
+ assert.ok(deck.textContent.includes("I'd pick the cheap host. The site is small for now."), 'the pick and why, in the asker\'s voice');
456
+ assert.ok(deck.textContent.includes('Left alone, the site waits, unbuilt'), 'what happens if nobody answers');
457
+ assert.ok(all.some((k) => /\bask__pick\b/.test(k.className) && k.textContent === 'my pick'), 'the pick is marked on its answer');
458
+ assert.ok(!all.some((k) => /\bproto-tag\b/.test(k.className)), 'no real-or-prototype tag: every card is real');
459
+ // a stored ask is answered on the card itself (task 1004702); only a matter without one sends you to its desk
460
+ assert.doesNotMatch(deck.textContent, /sent back|until its agent writes it|prototype data|pass it on/);
461
+ // the city's card answers its first ask in place (task 1004702), the same stored card
462
+ const city = await bootCity(false);
463
+ noPrototype(city, 'city with stored asks');
464
+ for (const b of BUILDINGS) {
465
+ city.byId.get('cityHits').children.find((h) => h.attrs['data-building'] === b).fire('click');
466
+ const asks = city.byId.get('cityCard').querySelectorAll('.city-ask');
467
+ assert.ok(asks.length <= 1, b + ': at most its first ask');
468
+ if (b === 'planning') assert.ok(asks.length === 1 && asks[0].querySelectorAll('.ask__opt').length === 2, 'Planning answers its stored ask in place');
469
+ noPrototype(city, 'city card with stored asks ' + b);
470
+ }
471
+ } finally { docketAsks = false; }
472
+ });
473
+
474
+ test('inside every building on a real site: no Lead, no office storey, no build-out, no invented law, no pot', async () => {
475
+ for (const b of BUILDINGS) {
476
+ const p = await bootStreet('#building=' + b, false);
477
+ const text = noPrototype(p, 'inside ' + b);
478
+ // task 1004715: the Charter's book is the real presets read, so it stands on a real site
479
+ if (b === 'charter') assert.match(text, /how strict the agents run[\s\S]*no preset yet[\s\S]*Open workshop[\s\S]*Factory/, 'the Charter shows the real presets');
480
+ assert.ok(!p.byId.get('insideFloors').kids.some((k) => k.getAttribute('data-floor') === 'office'), `${b}: no office storey`);
481
+ }
482
+ });
483
+
484
+ test('My agents on a real site: the real rows only; no laws, prototype settings or key; Make it a law and the preset strip are real', async () => {
485
+ const p = await bootMyAgents(false);
486
+ noPrototype(p, 'my agents');
487
+ // task 1004715: the strictness strip is GET /government/presets, so it stands on a real site
488
+ const strict = p.byId.get('ma-strict');
489
+ assert.equal('hidden' in strict.attrs, false, 'the strictness strip stands');
490
+ assert.match(strict.innerHTML, /No preset yet/, 'the hub has no law, so no preset is in force');
491
+ assert.match(strict.innerHTML, /data-preset="factory"[\s\S]*4 of \d+ lock/, 'Factory says what it would lock of your rows');
492
+ assert.doesNotMatch(strict.innerHTML, /data-proto/, 'and nothing on it is prototype');
493
+ assert.ok(p.calls.includes('/api/bongos/government/presets'), 'read from the route');
494
+ assert.equal(p.byId.get('ma-key'), undefined, 'no prototype key');
495
+ const rows = p.byId.get('ma-rows').innerHTML;
496
+ assert.match(rows, /data-row="technical_level"/, 'the real rows from GET /me stand');
497
+ for (const k of ['on_stuck', 'ask_timing', 'ask_format', 'parallel_agents', 'auto_go_live']) assert.doesNotMatch(rows, new RegExp(`data-row="${k}"`), `${k} is a prototype setting`);
498
+ const desk = rows.split('data-group="habits"')[0];
499
+ assert.doesNotMatch(desk, /law/i, 'no row is locked or capped by a law: none is in force');
500
+ // task 1004714: Make it a law files a real laws vote, so it stands on a real site too
501
+ assert.match(rows, /data-group="habits"[\s\S]*data-propose="machinery"/, 'Make it a law is offered to a builder who may open a board item');
502
+ assert.doesNotMatch(p.byId.get('ma-reads-body').innerHTML, /law/i, 'what the agent reads is only what it really reads');
503
+ assert.doesNotMatch(p.byId.get('ma-sub').textContent || p.byId.get('ma-sub')._html, /law/i, 'the subtitle names no laws');
504
+ });
505
+
506
+ test('My agents on a real site with a law in force: the law is what the agent reads first, its lock holds the row, nothing proto', async () => {
507
+ meLaws = ME_LAWS;
508
+ try {
509
+ const p = await bootMyAgents(false);
510
+ const text = noPrototype(p, 'my agents with a real law');
511
+ const reads = p.byId.get('ma-reads-body').innerHTML;
512
+ assert.ok(reads.includes(esc(A_REAL_LAW)), 'the law is in what the agent reads');
513
+ assert.ok(reads.indexOf(esc(A_REAL_LAW)) < reads.indexOf('Yours'), 'first, before the builder\'s own');
514
+ const rows = p.byId.get('ma-rows').innerHTML;
515
+ assert.match(rows, /data-row="verbosity"[^>]*>[\s\S]*?class="ma-lock">/, 'the verbosity row is locked, and the lock is not marked prototype');
516
+ assert.ok(rows.includes(esc(A_REAL_LAW) + ' · Law 1'), 'the lock names the law and its number');
517
+ assert.ok(text.includes('The project\'s laws come first'), 'the subtitle now says so');
518
+ } finally { meLaws = null; }
519
+ });
520
+
521
+ test('the seat-gated nav on a real site reads /me\'s positions: real seats, no overlay; no block is unknown, never "none"', () => {
522
+ const overlay = OLD_OVERLAY;
523
+ const constitution = fx('government__constitution');
524
+ // no positions block (module off, read failed): unknown, whatever the overlay or constitution say
525
+ for (const viewer of [{ builder: fx('me').builder }, { builder: { id: '424242', rank: 'thetes' } }]) {
526
+ for (const o of [overlay, null]) {
527
+ const p = GovPositions.positionsFor({ me: viewer, overlay: o, constitution });
528
+ assert.equal(p.known, false, `${viewer.builder.rank}, overlay ${o ? 'read' : 'unread'}: unknown, so nothing hides`);
529
+ }
530
+ }
531
+ // the server's block: known, every seat real, and the overlay changes nothing
532
+ const me = fx('me');
533
+ const p = GovPositions.positionsFor({ me, overlay: null, constitution });
534
+ const q = GovPositions.positionsFor({ me, overlay, constitution });
535
+ assert.equal(p.known, true);
536
+ assert.deepEqual(JSON.parse(JSON.stringify(p)), JSON.parse(JSON.stringify(q)), 'the overlay decides no seat');
537
+ assert.ok(p.positions.every((s) => s.source === 'real'));
538
+ });
539
+
540
+ test('a Lead the government appointed stands on their building on a real site; every unseated building shows the owner; nothing proto', async () => {
541
+ leadsBook = LEADS_READ;
542
+ try {
543
+ const city = await bootCity(false);
544
+ assert.ok(city.calls.includes('/api/bongos/government/leads'), 'the city reads who leads each building');
545
+ const text = noPrototype(city, 'city with real Leads', { seatsRead: true });
546
+ assert.ok(text.includes(A_REAL_LEAD), 'the Lead\'s name stands on their building');
547
+ const plates = city.byId.get('citySky').querySelectorAll('.city-plate');
548
+ const planningPlate = plates.find((p) => p.attrs['data-building'] === 'planning');
549
+ assert.ok(planningPlate && planningPlate.textContent === A_REAL_LEAD, 'Planning\'s plate names its Lead');
550
+ assert.ok(!('data-proto' in planningPlate.attrs), 'and the plate is real');
551
+ const ownerPlates = plates.filter((p) => /city-plate--owner/.test(p.className));
552
+ assert.equal(ownerPlates.length, 8, 'the eight unseated buildings each carry the owner\'s plate');
553
+ assert.ok(ownerPlates.every((p) => p.textContent === 'owner' && !('data-proto' in p.attrs)));
554
+ const hit = (b) => city.byId.get('cityHits').children.find((h) => h.attrs['data-building'] === b);
555
+ assert.match(hit('planning').attrs['aria-label'], new RegExp('led by ' + A_REAL_LEAD));
556
+ assert.match(hit('release').attrs['aria-label'], /held by the owner/);
557
+ hit('planning').fire('click');
558
+ assert.match(city.byId.get('cityCard').textContent, new RegExp('Led by ' + A_REAL_LEAD + '\\.'));
559
+ hit('treasury').fire('click');
560
+ assert.match(city.byId.get('cityCard').textContent, /hold(s)? it until a Lead is seated\./);
561
+ noPrototype(city, 'city card with real Leads', { seatsRead: true });
562
+ const street = await bootStreet('', false);
563
+ assert.ok(street.calls.includes('/api/bongos/government/leads'), 'the street reads it too');
564
+ noPrototype(street, 'street with real Leads', { seatsRead: true });
565
+ const doors = street.byId.get('streetDoors').children;
566
+ assert.match(doors.find((d) => d.getAttribute('data-door') === 'planning').attrs['aria-label'], new RegExp('led by ' + A_REAL_LEAD));
567
+ assert.match(doors.find((d) => d.getAttribute('data-door') === 'review').attrs['aria-label'], /held by the owner/);
568
+ } finally { leadsBook = null; }
569
+ });
570
+
571
+ test('a law the project really passed stands on a real site: the street names it, the city counts it, nothing proto', async () => {
572
+ lawBook = REAL_BOOK;
573
+ try {
574
+ const city = await bootCity(false);
575
+ assert.ok(city.calls.includes('/api/bongos/government/laws'), 'the city reads the law book');
576
+ noPrototype(city, 'city with a real law');
577
+ const plaque = city.byId.get('cityPlaque');
578
+ const before = fx('government__docket').readings.some((r) => r && r.key === 'rules' && r.status === 'live') ? 1 : 0;
579
+ const tile = plaque.kids.find((k) => k.attrs['data-key'] === 'laws');
580
+ assert.ok(tile, 'the plaque has a laws tile');
581
+ assert.equal(tile.kids[0].textContent, String(before + 1), "it counts the real law beside the docket's rule");
582
+ assert.ok(!('data-proto' in tile.attrs), 'and the count is real');
583
+ const street = await bootStreet('', false);
584
+ assert.ok(street.calls.includes('/api/bongos/government/laws'), 'the street reads the law book');
585
+ const text = noPrototype(street, 'street with a real law');
586
+ assert.ok(text.includes(A_REAL_LAW), 'the Rules reading names the law the project passed');
587
+ } finally { lawBook = null; }
588
+ });
589
+
590
+ // A Lead's build-outs (task 1004711), as GET /government/leads carries them: a floor
591
+ // going up and a committee room built at Planning, and Release held by the owner seat
592
+ // the viewer sits in. The names are this test's own words, so the overlay scan cannot
593
+ // excuse them.
594
+ const A_REAL_FLOOR = 'Quill Ledger Floor';
595
+ const A_REAL_ROOM = 'Quill Triage Room';
596
+ const LEADS_BUILT = (() => {
597
+ const r = JSON.parse(JSON.stringify(LEADS_READ));
598
+ r.buildings.forEach((b) => { b.structure = { standing: [], proposed: [] }; });
599
+ const pl = r.buildings.find((b) => b.building === 'planning');
600
+ pl.structure.standing = [
601
+ { id: '1', building: 'planning', capsule: 'floor', name: A_REAL_FLOOR, line: 'Where the next quarter is planned.', state: 'building', progress: 0.4, started_at: '2026-10-09T20:00:00.000Z', builder: null, allowed: { path: 'at_once', ground: 'form', why: 'The founder decides alone.' } },
602
+ { id: '2', building: 'planning', capsule: 'committee', name: A_REAL_ROOM, line: 'Sorts what comes in.', state: 'built', progress: 1, started_at: '2026-10-01T00:00:00.000Z', builder: null, allowed: { path: 'vote', item_id: '44', href: '/board-room?item=44' } },
603
+ ];
604
+ const rel = r.buildings.find((b) => b.building === 'release');
605
+ rel.answers = { seat: 'owner', builder_ids: [String(fx('government__docket').viewer.id)], fell_to_owner: true };
606
+ rel.build_rights = { form: 'monarchy', form_name: 'Monarchy / BDFL', holder: 'owner', capsules: Object.fromEntries(['committee', 'floor', 'annex', 'district'].map((c) => [c, { without_vote: c !== 'district', why: 'The founder decides alone.', ground: c === 'district' ? 'city' : 'form' }])) };
607
+ return r;
608
+ })();
609
+
610
+ test('a Lead\'s build-outs stand on a real site: the office storey, its floor going up and its room are real; nothing proto', async () => {
611
+ leadsBook = LEADS_BUILT;
612
+ try {
613
+ const p = await bootStreet('#building=planning', false);
614
+ const office = p.byId.get('insideFloors').kids.find((k) => k.getAttribute('data-floor') === 'office');
615
+ assert.ok(office, 'the office storey stands on a real site once the Leads are read');
616
+ const site = p.byId.get('insideFloors').kids.find((k) => /Open the build/.test(k.getAttribute('aria-label') || ''));
617
+ assert.ok(site && site.getAttribute('aria-label').includes(A_REAL_FLOOR), 'the floor going up is a site of its own');
618
+ let text = noPrototype(p, 'inside planning with real build-outs', { seatsRead: true });
619
+ assert.ok(text.includes(A_REAL_FLOOR), 'the floor going up is drawn');
620
+ office.fire('click');
621
+ await settle();
622
+ text = noPrototype(p, 'the real Lead\'s office', { seatsRead: true });
623
+ assert.ok(text.includes(A_REAL_ROOM), 'its committee room is a row of the office');
624
+ assert.ok(text.includes('40% built'), 'the floor going up says how far, from its real start');
625
+ assert.ok(!p.roots.some((r) => r.querySelectorAll('[data-capsule]').length), 'the viewer does not hold Planning: no build-out chips');
626
+ // Release is held by the owner seat the viewer sits in: the chips stand, in the server's words
627
+ const r = await bootStreet('#building=release', false);
628
+ r.byId.get('insideFloors').kids.find((k) => k.getAttribute('data-floor') === 'office').fire('click');
629
+ await settle();
630
+ noPrototype(r, 'the owner\'s office at Release', { seatsRead: true });
631
+ const chips = r.roots.flatMap((x) => x.querySelectorAll('[data-capsule]'));
632
+ assert.deepEqual(chips.map((c) => c.getAttribute('data-capsule')), ['committee', 'floor', 'annex', 'district']);
633
+ assert.deepEqual(chips.map((c) => c.kids[c.kids.length - 1].textContent), ['builds now', 'builds now', 'builds now', 'a vote']);
634
+ // a tap is the real route
635
+ chips[1].fire('click');
636
+ await settle();
637
+ assert.ok(r.calls.includes('/api/bongos/government/buildouts'), 'the tap posts to the server');
638
+ } finally { leadsBook = null; }
639
+ });
640
+
641
+ test('the treasury stands on a real site: real pots, the reserve, the cap off, the dry pot\'s borrow in place; nothing proto (task 1004712)', async () => {
642
+ leadsBook = LEADS_READ;
643
+ treasuryBook = TREASURY_READ;
644
+ try {
645
+ const p = await bootStreet('#building=treasury', false);
646
+ assert.ok(p.calls.includes('/api/bongos/government/treasury'), 'the street reads the treasury');
647
+ const text = noPrototype(p, 'inside the treasury with a real treasury', { seatsRead: true });
648
+ assert.ok(text.includes('of $750 · dry'), 'the real dry pot is drawn: its real spend against its budget');
649
+ assert.ok(text.includes('no spend limit'), 'the cap is off: no spend limit');
650
+ assert.ok(text.includes('in reserve'), 'the reserve is a real tile');
651
+ assert.ok(text.includes('Lend'), 'the dry pot\'s borrow is answered in place, in its stored words');
652
+ const city = await bootCity(false);
653
+ noPrototype(city, 'the city with a real treasury', { seatsRead: true });
654
+ } finally { leadsBook = null; treasuryBook = null; }
655
+ });