@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
@@ -5,7 +5,7 @@
5
5
  // pass over a throwaway test project's government, producing a pass/fail table
6
6
  // (task 1004658, goal 1000111, criterion gov-proven-and-kept-proven).
7
7
  //
8
- // WHAT IT WALKS, in four sections:
8
+ // WHAT IT WALKS, in nine sections:
9
9
  // 1. Genesis — start founding, decide with a chosen government, close all five
10
10
  // stages, come alive; the constitution reads as the choice.
11
11
  // 2. Every available form (Monarchy / BDFL, Timocratic Republic, Direct Democracy,
@@ -15,6 +15,17 @@
15
15
  // 3. Every building: set it to decide by vote (a decision-rules amendment), try to
16
16
  // act alone on each of its bound routes (refused), restore it (reachable again).
17
17
  // 4. Every matter action: open the matter, pass it, confirm the effect happened.
18
+ // 5. Laws (task 1004719): proposed, passed by the board in force, read into every
19
+ // session (the government port GET /me carries), a lock refused on write and
20
+ // applied on read, repealed; strictness presets switched by one vote each.
21
+ // 6. Leads and build-outs: a Lead appointed by a People vote, then under each
22
+ // form of government what the Lead may build at once and what goes to a vote.
23
+ // 7. Asks: the shape enforced, ask_required for an AI session's blocker, the ask
24
+ // routed to its seat and passed on, and GET /government/counts agreeing with
25
+ // the docket and the hall's own count rule.
26
+ // 8. The treasury: a pot given, run dry, the sweep files one borrow, answered once.
27
+ // 9. Positions: the seats GET /me carries and the governing tab they gate; the
28
+ // Lead removed by a vote; nothing the walk put up is left standing.
18
29
  //
19
30
  // HOW REAL IT IS. Every request is real HTTP through the real routers (government,
20
31
  // onboarding/genesis, and every building's own router), with the real
@@ -71,6 +82,7 @@ const REQUESTS = Object.freeze({
71
82
  'PATCH /project-settings/rot-days': ['PATCH', '/project-settings/rot-days', { days: 'soon' }],
72
83
  'PATCH /project-settings/ship-mode': ['PATCH', '/project-settings/ship-mode', { mode: 'sometimes' }],
73
84
  'PATCH /project-settings/tweak-autopickup': ['PATCH', '/project-settings/tweak-autopickup', { mode: 'sometimes' }],
85
+ 'PATCH /project-settings/spend-cap': ['PATCH', '/project-settings/spend-cap', { usd: 'lots' }],
74
86
  'PATCH /work-categories/:id': ['PATCH', `/work-categories/${BAD}`, {}],
75
87
  'POST /security/reports/:id/confirm': ['POST', `/security/reports/${BAD}/confirm`, {}],
76
88
  'POST /security/reports/:id/dispute': ['POST', `/security/reports/${BAD}/dispute`, {}],
@@ -104,6 +116,8 @@ const REQUESTS = Object.freeze({
104
116
  const MATTER_PLAN = Object.freeze([
105
117
  ['people', 'Give Eve the tester rank', { type: 'assign_rank', builder_id: 46, rank_key: 'tester' }, 'Eve holds tester'],
106
118
  ['people', 'Take the tester rank from Eve', { type: 'unassign_rank', builder_id: 46, rank_key: 'tester' }, 'Eve no longer holds tester'],
119
+ ['people', 'Dee leads Planning', { type: 'appoint_lead', building: 'planning', builder_id: 45, charter: 'Goals, priorities and the order of work.' }, 'Dee leads Planning'],
120
+ ['people', 'Planning has no Lead', { type: 'remove_lead', building: 'planning' }, 'Planning has no Lead'],
107
121
  ['automation', 'Switch unattended building on', { type: 'switch_on' }, 'fence on'],
108
122
  ['automation', 'Switch unattended building off', { type: 'switch_off' }, 'fence off'],
109
123
  ['automation', 'Allow goal 77 to build unattended', { type: 'allow_goal', goal_id: 77 }, 'goal 77 allowed'],
@@ -131,6 +145,9 @@ const ROUTERS = Object.freeze([
131
145
  'modules/provisioning/routes/core-upgrade.js',
132
146
  'src/bongos/routes/builders.js',
133
147
  'modules/security/routes/reports.js',
148
+ // the new machinery's other doors: a blocker and its ask, a builder's settings
149
+ 'modules/ideas/routes/blockers.js',
150
+ 'modules/builder-settings/routes/settings.js',
134
151
  ]);
135
152
 
136
153
  // ── the in-memory government store ──────────────────────────────────────────
@@ -150,6 +167,9 @@ function makeStore(catalog) {
150
167
  votes: [],
151
168
  amendments: new Map(),
152
169
  matters: new Map(),
170
+ leads: [], // { id, building, builder_id, charter, appointed_by_item_id, removed_by_item_id, removed_at }
171
+ laws: [], // { id, text, effect_on_ai, locks, proposed_by, passed_by_item_id, passed_at, repealed_by_item_id, repealed_at }
172
+ buildouts: [], // { id, building, capsule, name, line, built_by, authorised_by_item_id, ground, why, started_at }
153
173
  rules: new Map(),
154
174
  ideas: new Map(),
155
175
  tasks: new Map(),
@@ -307,7 +327,125 @@ function makeStore(catalog) {
307
327
  return copy(row);
308
328
  },
309
329
  async getMatter(id) { return copy(s.matters.get(String(id))) || null; },
330
+ // The Lead of a building (task 1004709): government_029's guard — written only
331
+ // by a passed People matter whose action names this very building and builder.
332
+ async listLeads({ includePast = false } = {}) {
333
+ return s.leads.filter((l) => includePast || !l.removed_at).map((l) => {
334
+ const b = s.builders.get(l.builder_id) || {};
335
+ return { ...copy(l), github_login: b.github_login || null, display_name: b.display_name || null };
336
+ });
337
+ },
338
+ async getActiveBuilder(id) {
339
+ const b = s.builders.get(String(id));
340
+ return isLive(b) ? { id: b.id, github_login: b.github_login, display_name: b.display_name } : null;
341
+ },
342
+ async applyLeadAct({ itemId, action }) {
343
+ const i = s.items.find((x) => x.id === String(itemId));
344
+ const m = i && s.matters.get(i.subject_id);
345
+ const a = m && m.action;
346
+ const allowed = i && a && i.subject_type === 'people_matter' && i.outcome === 'passed'
347
+ && a.type === action.type && a.building === action.building
348
+ && (a.type === 'remove_lead' || (String(a.builder_id) === String(action.builder_id) && a.charter === action.charter));
349
+ if (!allowed) throw new Error(`lead not written (item ${itemId} is not a passed People matter that says so)`);
350
+ const sitting = s.leads.find((l) => l.building === action.building && !l.removed_at) || null;
351
+ if (action.type === 'remove_lead' && !sitting) return { done: false, reason: 'no_lead' };
352
+ if (s.leads.some((l) => l.appointed_by_item_id === String(itemId))) return { done: true, reason: 'already_carried_out' };
353
+ if (sitting) { sitting.removed_by_item_id = String(itemId); sitting.removed_at = tick(); }
354
+ if (action.type === 'remove_lead') return { done: true, lead: null, ended: copy(sitting) };
355
+ const row = { id: next(), building: action.building, builder_id: String(action.builder_id), charter: action.charter, appointed_by_item_id: String(itemId), appointed_at: tick(), removed_by_item_id: null, removed_at: null };
356
+ s.leads.push(row);
357
+ return { done: true, lead: copy(row), ended: copy(sitting) };
358
+ },
310
359
  async getMatterSubjects(ids) { return ids.map((id) => s.matters.get(String(id))).filter(Boolean).map(copy); },
360
+ // The law book (task 1004706): government_027's guard — a law is written, or
361
+ // repealed, only by a PASSED laws amendment, inside the close that passes it.
362
+ async listLaws({ includeRepealed = false } = {}) {
363
+ return s.laws.filter((l) => includeRepealed || !l.repealed_at).map((l) => {
364
+ const i = s.items.find((x) => x.id === l.passed_by_item_id) || {};
365
+ const b = s.builders.get(l.proposed_by) || {};
366
+ return { ...copy(l), decided_under: copy(i.constitution), proposer_login: b.github_login || null, proposer_name: b.display_name || null };
367
+ });
368
+ },
369
+ async listYesVoters(itemIds) {
370
+ const want = (itemIds || []).map(String);
371
+ return s.votes.filter((v) => want.includes(v.item_id) && v.direction === 'yes').map(voteRow);
372
+ },
373
+ async applyLaws(_client, { itemId, enact = [], repeal = [], proposedBy = null }) {
374
+ const i = s.items.find((x) => x.id === String(itemId));
375
+ const a = i && s.amendments.get(i.subject_id);
376
+ if (!(i && a && i.subject_type === 'constitutional_amendment' && i.outcome === 'passed' && a.kind === 'laws')) {
377
+ throw new Error(`law not written (item ${itemId} is not a passed laws amendment)`);
378
+ }
379
+ const enacted = enact.map((law) => {
380
+ const row = { id: next(), text: law.text, effect_on_ai: law.effect_on_ai ?? null, locks: copy(law.locks || []), proposed_by: proposedBy == null ? null : String(proposedBy), passed_by_item_id: String(itemId), passed_at: tick(), repealed_by_item_id: null, repealed_at: null };
381
+ s.laws.push(row);
382
+ return copy(row);
383
+ });
384
+ const repealed = [];
385
+ for (const l of s.laws) {
386
+ if (repeal.map(String).includes(l.id) && !l.repealed_at) {
387
+ l.repealed_by_item_id = String(itemId);
388
+ l.repealed_at = tick();
389
+ repealed.push({ id: l.id, repealed_at: l.repealed_at });
390
+ }
391
+ }
392
+ return { enacted, repealed };
393
+ },
394
+ // The owner seat: every active Archon, oldest id first.
395
+ async listActiveArchons() {
396
+ return [...s.builders.values()].filter((b) => isLive(b) && b.rank === 'archon')
397
+ .sort((a, b) => Number(a.id) - Number(b.id))
398
+ .map((b) => ({ id: b.id, github_login: b.github_login, display_name: b.display_name }));
399
+ },
400
+ // A Lead's build-outs (task 1004711): government_030's guard — a vote's
401
+ // build-out only from a passed build_out matter that says so, and an at-once
402
+ // one only with the build-rights ground that allowed it, never a district.
403
+ async listBuildouts() {
404
+ return s.buildouts.map((o) => {
405
+ const b = s.builders.get(o.built_by) || {};
406
+ return { ...copy(o), github_login: b.github_login || null, display_name: b.display_name || null };
407
+ });
408
+ },
409
+ async listPendingBuildouts() {
410
+ return s.items.filter((i) => !i.closed_at && /_matter$/.test(i.subject_type)).map((i) => {
411
+ const m = s.matters.get(i.subject_id);
412
+ return m && m.action && m.action.type === 'build_out'
413
+ ? { item_id: i.id, matter_id: m.id, action: copy(m.action), opened_at: i.opened_at, closes_at: i.closes_at } : null;
414
+ }).filter(Boolean);
415
+ },
416
+ async insertBuildout({ building, capsule, name, line = '', builtBy = null, itemId = null, ground = null, why = null }) {
417
+ if (itemId != null) {
418
+ const i = s.items.find((x) => x.id === String(itemId));
419
+ const m = i && s.matters.get(i.subject_id);
420
+ const a = m && m.action;
421
+ const ok = i && i.outcome === 'passed' && a && a.type === 'build_out' && a.building === building && a.capsule === capsule;
422
+ if (!ok) throw new Error(`build-out not written (item ${itemId} is not a passed build_out matter that says so)`);
423
+ if (s.buildouts.some((o) => o.authorised_by_item_id === String(itemId))) return { done: true, reason: 'already_carried_out' };
424
+ } else if (!ground || !why || capsule === 'district') {
425
+ throw new Error('build-out not written (no vote and no build-rights ground)');
426
+ }
427
+ const row = { id: next(), building, capsule, name, line, built_by: builtBy == null ? null : String(builtBy), authorised_by_item_id: itemId == null ? null : String(itemId), ground, why, started_at: tick() };
428
+ s.buildouts.push(row);
429
+ return { done: true, buildout: copy(row) };
430
+ },
431
+ // The room's reads (the docket's charter floors and the board source).
432
+ async listRanksWithGrants() {
433
+ const sys = Object.keys(catalog.RANK_SEED).map((k) => ({ rank_key: k, label: k, description: null, is_system: true, seeded_rank: k, permissions: [...catalog.RANK_SEED[k]].sort() }));
434
+ return sys.concat([...s.customRanks.values()].map((r) => ({ ...copy(r), permissions: [] })));
435
+ },
436
+ async listOpenBoardItems() { return s.items.filter((i) => !i.closed_at).map(withExpiry).reverse(); },
437
+ async listClosedBoardItems(limit = 20) { return s.items.filter((i) => i.closed_at).sort((a, b) => (a.closed_at < b.closed_at ? 1 : -1)).slice(0, limit).map(copy); },
438
+ async getIdeaSubjects(ids) {
439
+ return ids.map(String).filter((id) => s.ideas.has(id)).map((id) => ({ id, title: `Idea ${id}`, captured_by: s.ideas.get(id).captured_by, field_values: {}, template_key: null, completeness_score: s.ideas.get(id).completeness, recommendation: null }));
440
+ },
441
+ async getAmendmentSubjects(ids) { return ids.map((id) => s.amendments.get(String(id))).filter(Boolean).map(copy); },
442
+ async openBoardItemsAwaitingVoter(voterId) {
443
+ return s.items.filter((i) => !i.closed_at).map((i) => ({
444
+ id: i.id, subject_type: i.subject_type, subject_id: i.subject_id, constitution: copy(i.constitution), opened_at: i.opened_at, closes_at: i.closes_at,
445
+ author_id: i.subject_type === 'full_idea' ? ((s.ideas.get(i.subject_id) || {}).captured_by || null) : i.subject_type === 'genesis_stage' ? null : i.opened_by,
446
+ already_voted: s.votes.some((v) => v.item_id === i.id && v.voter_id === String(voterId)),
447
+ }));
448
+ },
311
449
  async getTaskStatuses(ids) {
312
450
  return ids.map(String).filter((id) => s.tasks.has(id)).map((id) => ({ id, status: s.tasks.get(id).status }));
313
451
  },
@@ -337,6 +475,119 @@ function installStore(govDb, fns, unexpected) {
337
475
  }
338
476
  }
339
477
 
478
+ // The walk's fixed clock for the treasury (mid-month), so a borrow's amount, which
479
+ // follows the month's pace, reads the same on every run.
480
+ const TREASURY_NOW = Date.UTC(2026, 9, 15, 12);
481
+ const WALK_GOAL = '77';
482
+
483
+ // The other stores the new machinery reads and writes, in memory: the asks
484
+ // (government_028), the pots and borrows (government_031), each builder's
485
+ // personal settings (builders.behavior_prefs) and the blockers. Each fake keeps
486
+ // the guard its table carries. The shared Postgres pool itself refuses every
487
+ // query, so a read the walk does not model fails loudly in its own building
488
+ // (a docket floor reads as unavailable) and never reaches a real database.
489
+ function installSideStores(req, store) {
490
+ const poolMod = req('src/bongos/pool.js');
491
+ const refuse = async () => { throw new Error('walk: no database'); };
492
+ poolMod.pool.query = refuse;
493
+ poolMod.pool.connect = refuse;
494
+
495
+ const asks = req('modules/government/asks.js');
496
+ const asksDb = req('modules/government/asks-db.js');
497
+ const asked = new Map(); // matter id → row
498
+ asksDb.saveAsk = async ({ matterId, ask, builderId }) => {
499
+ if (!asks.isMatterId(matterId)) throw Object.assign(new Error('bad matter id'), { code: 'BAD_MATTER_ID' });
500
+ const row = { matter_id: matterId, question: ask.question, options: JSON.parse(JSON.stringify(ask.options)), recommend_key: ask.recommend.key, recommend_why: ask.recommend.why, waits_on: ask.waits_on, if_nobody_answers: ask.if_nobody_answers, detail: ask.detail || null, written_by: builderId == null ? null : String(builderId), written_at: new Date().toISOString() };
501
+ asked.set(matterId, row);
502
+ return asks.fromRow(row);
503
+ };
504
+ asksDb.asksFor = async (ids) => {
505
+ const out = new Map();
506
+ for (const id of Array.isArray(ids) ? ids : []) if (asked.has(id)) out.set(id, asks.fromRow(asked.get(id)));
507
+ return out;
508
+ };
509
+
510
+ const treasury = req('modules/government/treasury.js');
511
+ const treasuryDb = req('modules/government/treasury-db.js');
512
+ const t = { goals: new Map([[WALK_GOAL, { id: WALK_GOAL, title: 'Walk goal', status: 'open' }]]), pots: new Map(), spend: new Map(), borrows: [], seq: 900 };
513
+ const borrowsOf = (month) => t.borrows.filter((b) => b.month === month || b.state === 'open').map((b) => ({ ...b }));
514
+ const withTitle = (b) => ({ ...b, title: (t.goals.get(b.goal_id) || {}).title || null });
515
+ treasuryDb.readTreasury = async () => {
516
+ const month = treasury.monthKey(TREASURY_NOW);
517
+ const total = [...t.spend.values()].reduce((a, v) => a + v, 0);
518
+ return treasury.buildTreasury({
519
+ now: TREASURY_NOW, goals: [...t.goals.values()], pots: [...t.pots.values()],
520
+ spend: { month_usd: total, last_month_usd: 0, by_goal: Object.fromEntries(t.spend) },
521
+ borrows: borrowsOf(month), cap: { usd: null, source: 'default' },
522
+ });
523
+ };
524
+ treasuryDb.goalById = async (id) => (t.goals.has(String(id)) ? { ...t.goals.get(String(id)) } : null);
525
+ treasuryDb.setPot = async ({ goalId, budget, builderId }) => {
526
+ const id = String(goalId);
527
+ if (budget == null) return { removed: t.pots.delete(id) };
528
+ const pot = { goal_id: id, budget_usd: budget, set_by: String(builderId), set_at: null };
529
+ t.pots.set(id, pot);
530
+ return { pot: { ...pot } };
531
+ };
532
+ // The real sweep's loop over the in-memory rows, with its one-open guard.
533
+ treasuryDb.fileBorrows = async () => {
534
+ const view = await treasuryDb.readTreasury();
535
+ const filed = [];
536
+ for (const pot of view.pots) {
537
+ if (!treasury.needsBorrow(pot, borrowsOf(view.month), view.month)) continue;
538
+ if (t.borrows.some((b) => b.goal_id === pot.goal_id && b.state === 'open')) continue;
539
+ const amount = treasury.borrowAmount(pot, TREASURY_NOW);
540
+ const ask = treasury.borrowAsk({ title: pot.title, amount_usd: amount, spent_usd: pot.spent_usd, budget_usd: pot.budget_usd, lent_usd: pot.lent_usd, cap: view.cap, month_usd: view.spend.month_usd });
541
+ const row = { id: String(++t.seq), goal_id: pot.goal_id, month: view.month, amount_usd: amount, budget_usd: pot.budget_usd + pot.lent_usd, spent_usd: pot.spent_usd, filed_at: new Date().toISOString(), state: 'open', lent_usd: null, answer_key: null, answered_by: null, answered_at: null };
542
+ t.borrows.push(row);
543
+ await asksDb.saveAsk({ matterId: treasury.matterId(row.id), ask, builderId: null });
544
+ filed.push({ goal_id: pot.goal_id, borrow_id: row.id, amount_usd: amount });
545
+ }
546
+ return filed;
547
+ };
548
+ treasuryDb.getBorrow = async (id) => {
549
+ const b = t.borrows.find((x) => x.id === String(id));
550
+ return b ? withTitle(b) : null;
551
+ };
552
+ // government_031's trigger: a borrow is answered once.
553
+ treasuryDb.answerBorrow = async ({ id, answer, builderId }) => {
554
+ const b = t.borrows.find((x) => x.id === String(id) && x.state === 'open');
555
+ if (!b || !answer) return null;
556
+ Object.assign(b, { state: answer.state, lent_usd: answer.lent_usd, answer_key: answer.answer_key, answered_by: String(builderId), answered_at: new Date().toISOString() });
557
+ return { ...b };
558
+ };
559
+ treasuryDb.openBorrowRows = async () => {
560
+ const open = t.borrows.filter((b) => b.state === 'open');
561
+ const stored = await asksDb.asksFor(open.map((b) => treasury.matterId(b.id)));
562
+ return open.map((b) => ({ row: withTitle(b), ask: stored.get(treasury.matterId(b.id)) || null }));
563
+ };
564
+
565
+ // Each builder's personal settings (builders.behavior_prefs).
566
+ const prefs = new Map();
567
+ const settingsDb = req('modules/builder-settings/db.js');
568
+ settingsDb.getBuilderBehaviorPrefs = async (id) => JSON.parse(JSON.stringify(prefs.get(String(id)) || {}));
569
+ settingsDb.setBuilderBehaviorPrefs = async (id, p) => { prefs.set(String(id), JSON.parse(JSON.stringify(p || {}))); return JSON.parse(JSON.stringify(p || {})); };
570
+ const api = req('src/module-api.js');
571
+ api.getBuilderById = async (id) => {
572
+ const b = store.builders.get(String(id));
573
+ return b ? { id: Number(b.id), rank: b.rank, github_login: b.github_login, display_name: b.display_name, status: b.status } : null;
574
+ };
575
+
576
+ // The blockers (ideas): open ones are the docket's Planning matters.
577
+ const blockers = req('modules/ideas/blockers.js');
578
+ const filedBlockers = [];
579
+ blockers.createBlocker = async ({ title, bodyMd, source, sourceRef }) => {
580
+ const row = { id: String(++t.seq), title, body_md: bodyMd || '', status: 'open', created_at: new Date().toISOString(), source: source || null, source_ref: sourceRef || null, blocking_task_ids: [] };
581
+ filedBlockers.push(row);
582
+ return { ...row };
583
+ };
584
+ blockers.getBlocker = async (id) => { const b = filedBlockers.find((x) => x.id === String(id)); return b ? { ...b } : null; };
585
+ blockers.listOpenBlockers = async () => filedBlockers.filter((b) => b.status === 'open').map((b) => ({ ...b }));
586
+ blockers.countOpenBlockers = async () => filedBlockers.filter((b) => b.status === 'open').length;
587
+
588
+ return { asked, treasury: t, prefs, blockers: filedBlockers };
589
+ }
590
+
340
591
  // ── the walk ────────────────────────────────────────────────────────────────
341
592
 
342
593
  async function runWalk() {
@@ -373,6 +624,9 @@ async function runWalk() {
373
624
 
374
625
  const { s: store, fns } = makeStore(catalog);
375
626
  installStore(govDb, fns, store.unexpected);
627
+ // The new machinery's own tables (laws, asks, build-outs, pots), and the rows
628
+ // other modules keep (a builder's settings, the blockers), in memory too.
629
+ const side = installSideStores(req, store);
376
630
 
377
631
  // Project settings (founding mode + the plan) live in memory.
378
632
  const settings = new Map();
@@ -412,10 +666,13 @@ async function runWalk() {
412
666
 
413
667
  // The session: whoever the request says it is, from this project's people.
414
668
  const asHeader = 'x-walk-builder';
669
+ const sessionHeader = 'x-walk-session';
415
670
  const fakeBuilder = (r, res, nextFn) => {
416
671
  const b = store.builders.get(String(r.headers[asHeader] || ''));
417
672
  if (!b || b.status !== 'active') return res.status(401).json({ error: { code: 'not_signed_in' } });
418
673
  r.builder = { id: Number(b.id), rank: b.rank, github_login: b.github_login, display_name: b.display_name };
674
+ // an AI session's CLI token, or a person in the hall (task 1004704's line)
675
+ r.gdsSession = { source: r.headers[sessionHeader] === 'cli' ? 'cli' : 'web' };
419
676
  return nextFn();
420
677
  };
421
678
  api.requireBuilder = fakeBuilder;
@@ -429,10 +686,10 @@ async function runWalk() {
429
686
  await new Promise((r) => server.listen(0, '127.0.0.1', r));
430
687
  const BASE = `http://127.0.0.1:${server.address().port}`;
431
688
 
432
- async function call(as, method, p, body) {
689
+ async function call(as, method, p, body, session = 'web') {
433
690
  const res = await fetch(`${BASE}${p}`, {
434
691
  method,
435
- headers: { [asHeader]: as, ...(body === undefined ? {} : { 'content-type': 'application/json' }) },
692
+ headers: { [asHeader]: as, [sessionHeader]: session, ...(body === undefined ? {} : { 'content-type': 'application/json' }) },
436
693
  body: body === undefined ? undefined : JSON.stringify(body),
437
694
  });
438
695
  let json = null;
@@ -620,6 +877,7 @@ async function runWalk() {
620
877
  await check(`${tag}: someone not seated cannot vote`, '403 not_board_member', async () => {
621
878
  const id = await probe();
622
879
  const r = await vote(form.outsider, id);
880
+ await voteAll(id, form.seated, 'no'); // close the probe: the walk leaves no sitting open
623
881
  return { got: `${r.status} ${code(r)}`, pass: r.status === 403 && code(r) === 'not_board_member' };
624
882
  });
625
883
  }
@@ -707,6 +965,8 @@ async function runWalk() {
707
965
  const HAPPENED = {
708
966
  'people.assign_rank': () => holds('46', 'tester'),
709
967
  'people.unassign_rank': () => !holds('46', 'tester'),
968
+ 'people.appoint_lead': () => store.leads.some((l) => l.building === 'planning' && l.builder_id === '45' && !l.removed_at),
969
+ 'people.remove_lead': () => !store.leads.some((l) => l.building === 'planning' && !l.removed_at),
710
970
  'automation.switch_on': () => effects.fence === true,
711
971
  'automation.switch_off': () => effects.fence === false,
712
972
  'automation.allow_goal': () => effects.allowedGoals.includes('77'),
@@ -747,6 +1007,9 @@ async function runWalk() {
747
1007
  return { got: `${out.ok ? '' : `${out.why}; `}${sitting.length ? `still sitting: ${sitting.join(', ')}` : 'all as today'}`, pass: out.ok && sitting.length === 0 };
748
1008
  });
749
1009
 
1010
+ // ══ 5–9. The new machinery (task 1004719) ══
1011
+ await walkNewMachinery({ call, code, vote, voteAll, check, section, stateOf, store, side, req });
1012
+
750
1013
  // Any storage the fake did not model is a finding about the walk itself.
751
1014
  section('S Store');
752
1015
  await check('No code path reached storage the walk does not model', 'none', async () => {
@@ -759,6 +1022,372 @@ async function runWalk() {
759
1022
  return { mode: 'in-memory', rows };
760
1023
  }
761
1024
 
1025
+ // ── the new machinery (task 1004719) ────────────────────────────────────────
1026
+ //
1027
+ // Laws and presets, Leads and build-outs, asks and routing, the treasury, and
1028
+ // positions with the counts, walked on the same throwaway project after section
1029
+ // 4 left it a Monarchy with every building decided as today. Everything it
1030
+ // puts up it takes down again: no law, Lead, pot or open borrow survives it.
1031
+
1032
+ // A card in the one ask shape (asks.js), and a wall of text that is not one.
1033
+ const GOOD_ASK = Object.freeze({
1034
+ question: 'Ship the walk table today?',
1035
+ options: [
1036
+ { key: 'ship', label: 'Ship it', result: 'The table lands today.' },
1037
+ { key: 'wait', label: 'Wait a day', result: 'It lands tomorrow instead.' },
1038
+ ],
1039
+ recommend: { key: 'ship', why: 'Every row of it passes.' },
1040
+ waits_on: 'lead:planning',
1041
+ if_nobody_answers: 'It ships tomorrow as planned.',
1042
+ });
1043
+ const SLOP_ASK = Object.freeze({
1044
+ question: 'I have been looking into this for a while and wanted to check with you about what we should do about the walk table and whether it is ready?',
1045
+ options: [{ key: 'ok', label: 'ok', result: 'ok' }],
1046
+ recommend: { key: 'maybe', why: '' },
1047
+ waits_on: 'someone',
1048
+ if_nobody_answers: '',
1049
+ });
1050
+ // A law that locks a personal setting: every agent answers short.
1051
+ const SHORT_LAW = Object.freeze({ text: 'Answer in plain, short words', effect_on_ai: 'Replies lead with the answer and stop.', locks: [{ setting: 'verbosity', value: 'concise' }] });
1052
+
1053
+ async function walkNewMachinery({ call, code, vote, voteAll, check, section, stateOf, store, side, req }) {
1054
+ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
1055
+ const FOUNDER_ID = '42';
1056
+ const BEN = '43';
1057
+ const CY = '44';
1058
+ const EVE = '46';
1059
+ const positionsPort = () => req('src/module-seams.js').resolveOptional('government');
1060
+ const GovPositions = req('modules/hall-ui/public/gov-positions.js');
1061
+ const GovLiving = req('modules/hall-ui/public/gov-living.js');
1062
+ const treasuryMod = req('modules/government/treasury.js');
1063
+ const sweep = req('modules/government/pollers/treasury-borrow.js');
1064
+ const lawsInForce = async () => (await call(FOUNDER_ID, 'GET', '/government/laws')).json;
1065
+ const itemIdOf = (r) => r.json && r.json.item && r.json.item.id;
1066
+
1067
+ // ══ 5. Laws and strictness presets ══
1068
+ section('L Laws');
1069
+ let lawItem = null;
1070
+ await check('Before any law the book is empty and no session is handed a laws block', '0 in force; no laws block', async () => {
1071
+ const book = await lawsInForce();
1072
+ const s = await positionsPort().lawsForSession();
1073
+ return { got: `${book.in_force} in force; ${s.inject ? 'a laws block' : 'no laws block'}`, pass: book.in_force === 0 && s.inject === false };
1074
+ });
1075
+ await check('A law is proposed: filed as a laws amendment, and proposing writes nothing', '201, sitting open; still 0 in force', async () => {
1076
+ const r = await call(FOUNDER_ID, 'POST', '/government/board/laws', { enact: [SHORT_LAW] });
1077
+ lawItem = itemIdOf(r);
1078
+ const book = await lawsInForce();
1079
+ return { got: `${r.status}, sitting ${stateOf(lawItem)}; still ${book.in_force} in force`, pass: r.status === 201 && stateOf(lawItem) === 'open' && book.in_force === 0 };
1080
+ });
1081
+ await check('Someone the board does not seat cannot pass it', '403 not_board_member', async () => {
1082
+ const r = await vote(BEN, lawItem);
1083
+ return { got: `${r.status} ${code(r)}`, pass: r.status === 403 && code(r) === 'not_board_member' };
1084
+ });
1085
+ let lawId = null;
1086
+ await check('The government in force passes it, and the law book names the vote', 'passed; 1 in force, passed under Monarchy / BDFL with yes from ada', async () => {
1087
+ const v = await vote(FOUNDER_ID, lawItem);
1088
+ const book = await lawsInForce();
1089
+ const law = (book.laws || [])[0] || {};
1090
+ lawId = law.id || null;
1091
+ const pb = law.passed_by || {};
1092
+ const yes = (pb.yes || []).map((x) => (x && (x.login || x.github_login)) || String(x && x.id));
1093
+ const ok = v.json && v.json.outcome === 'passed' && book.in_force === 1 && law.text === SHORT_LAW.text;
1094
+ return { got: `${v.json && v.json.outcome}; ${book.in_force} in force${ok ? `, passed under ${pb.government} with yes from ${yes.join(', ') || 'nobody'}` : ''}`, pass: ok && pb.government === 'Monarchy / BDFL' && same(yes, ['ada']) };
1095
+ });
1096
+ await check('Every AI session is handed the law, with its lock, ahead of its own settings', 'a laws block naming the law and "verbosity is fixed at concise"', async () => {
1097
+ const s = await positionsPort().lawsForSession();
1098
+ const ok = s.inject === true && s.contract.includes(SHORT_LAW.text) && s.contract.includes('verbosity is fixed at concise');
1099
+ return { got: ok ? 'a laws block naming the law and "verbosity is fixed at concise"' : `inject ${s.inject}: ${s.contract.slice(0, 80)}`, pass: ok };
1100
+ });
1101
+ await check('The lock bites on a write: a builder cannot set what the law forbids', '409 locked_by_law', async () => {
1102
+ const r = await call(BEN, 'PATCH', '/me/interaction', { verbosity: 'detailed' });
1103
+ return { got: `${r.status} ${code(r)}`, pass: r.status === 409 && code(r) === 'locked_by_law' };
1104
+ });
1105
+ await check('The lock bites on a read: a value stored before the law reads as the law says', 'stored detailed, reads concise', async () => {
1106
+ side.prefs.set(CY, { interaction: { verbosity: 'detailed' } });
1107
+ const r = await call(CY, 'GET', '/me/interaction');
1108
+ const eff = r.json && (r.json.effective ? r.json.effective.verbosity : r.json.verbosity);
1109
+ return { got: `stored detailed, reads ${eff}`, pass: r.status === 200 && eff === 'concise' };
1110
+ });
1111
+ await check('A value the law allows still saves', '200', async () => {
1112
+ const r = await call(BEN, 'PATCH', '/me/interaction', { verbosity: 'concise' });
1113
+ return { got: `${r.status}`, pass: r.status === 200 };
1114
+ });
1115
+ await check('Repealed by a vote, the law leaves every session and unlocks the setting', 'passed; 0 in force; no laws block; 200', async () => {
1116
+ const r = await call(FOUNDER_ID, 'POST', '/government/board/laws', { repeal: [lawId] });
1117
+ const v = await vote(FOUNDER_ID, itemIdOf(r));
1118
+ const book = await lawsInForce();
1119
+ const s = await positionsPort().lawsForSession();
1120
+ const w = await call(BEN, 'PATCH', '/me/interaction', { verbosity: 'detailed' });
1121
+ return { got: `${v.json && v.json.outcome}; ${book.in_force} in force; ${s.inject ? 'a laws block' : 'no laws block'}; ${w.status}`, pass: v.json && v.json.outcome === 'passed' && book.in_force === 0 && !s.inject && w.status === 200 };
1122
+ });
1123
+ const presetNow = async () => (await call(FOUNDER_ID, 'GET', '/government/presets')).json;
1124
+ await check('With no laws, no strictness preset is in force', 'none', async () => {
1125
+ const p = await presetNow();
1126
+ return { got: p.current || 'none', pass: p.current == null };
1127
+ });
1128
+ // [to, from, what an exploratory wandering write answers once it is in force]
1129
+ for (const [key, from, wander] of [['guild', 'none', '409 locked_by_law'], ['factory', 'guild', '409 locked_by_law'], ['open', 'factory', '200']]) {
1130
+ await check(`Strictness: ${from} to ${key} is one vote, and its locks bite`, `one sitting; passed; ${key} in force; exploratory wandering ${wander}`, async () => {
1131
+ const r = await call(FOUNDER_ID, 'POST', '/government/board/presets', { preset: key });
1132
+ const id = itemIdOf(r);
1133
+ const v = await vote(FOUNDER_ID, id);
1134
+ const p = await presetNow();
1135
+ const w = await call(BEN, 'PATCH', '/me/wandering', { wandering: 3 });
1136
+ const ws = `${w.status}${w.status === 200 ? '' : ` ${code(w)}`}`;
1137
+ return { got: `${r.status === 201 && id ? 'one sitting' : `${r.status} ${code(r)}`}; ${v.json && v.json.outcome}; ${p.current || 'none'} in force; exploratory wandering ${ws}`, pass: r.status === 201 && v.json && v.json.outcome === 'passed' && p.current === key && ws === wander };
1138
+ });
1139
+ }
1140
+ await check('Every preset law is repealed by one more vote, so the book ends empty', 'passed; 0 in force; no preset', async () => {
1141
+ const ids = ((await lawsInForce()).laws || []).map((l) => l.id);
1142
+ const r = await call(FOUNDER_ID, 'POST', '/government/board/laws', { repeal: ids });
1143
+ const v = await vote(FOUNDER_ID, itemIdOf(r));
1144
+ const book = await lawsInForce();
1145
+ const p = await presetNow();
1146
+ return { got: `${v.json && v.json.outcome}; ${book.in_force} in force; ${p.current ? `${p.current} in force` : 'no preset'}`, pass: book.in_force === 0 && p.current == null };
1147
+ });
1148
+
1149
+ // ══ 6. Leads and build-outs ══
1150
+ section('D Leads');
1151
+ const leads = async () => (await call(FOUNDER_ID, 'GET', '/government/leads')).json;
1152
+ const building = async (key) => ((await leads()).buildings || []).find((b) => b.building === key) || {};
1153
+ await check('Ben is appointed Planning\'s Lead by a People vote', 'passed; Planning led by ben, by that vote', async () => {
1154
+ const r = await call(FOUNDER_ID, 'POST', '/government/board/matters', { area: 'people', title: 'Ben leads Planning', action: { type: 'appoint_lead', building: 'planning', builder_id: Number(BEN), charter: 'Goals, priorities and the order of work.' } });
1155
+ const id = itemIdOf(r);
1156
+ const v = await vote(FOUNDER_ID, id);
1157
+ const p = await building('planning');
1158
+ const lead = p.lead || {};
1159
+ const by = lead.appointed_by && (lead.appointed_by.item_id || lead.appointed_by.id);
1160
+ const login = lead.builder && (lead.builder.login || lead.builder.github_login);
1161
+ return { got: `${v.json && v.json.outcome}; Planning led by ${login || 'nobody'}${String(by) === String(id) ? ', by that vote' : ''}`, pass: v.json && v.json.outcome === 'passed' && login === 'ben' && String(by) === String(id) };
1162
+ });
1163
+ await check('An unseated building answers to the owner', 'Rules answers to the owner: builder 42', async () => {
1164
+ const a = (await building('rules')).answers || {};
1165
+ return { got: `Rules answers to ${a.fell_to_owner ? 'the owner' : a.label}: builder ${(a.builder_ids || []).join(', ')}`, pass: a.fell_to_owner === true && same(a.builder_ids, [FOUNDER_ID]) };
1166
+ });
1167
+ await check('Someone who does not hold Planning cannot grow it', '403 not_the_holder', async () => {
1168
+ const r = await call(CY, 'POST', '/government/buildouts', { building: 'planning', capsule: 'committee', name: 'Not mine' });
1169
+ return { got: `${r.status} ${code(r)}`, pass: r.status === 403 && code(r) === 'not_the_holder' };
1170
+ });
1171
+ await check('A district always goes to a vote, even under a Monarchy', 'vote (city); returned, nothing built', async () => {
1172
+ const before = store.buildouts.length;
1173
+ const r = await call(BEN, 'POST', '/government/buildouts', { building: 'planning', capsule: 'district', name: 'Planning quarter' });
1174
+ const id = itemIdOf(r);
1175
+ if (id) await vote(FOUNDER_ID, id, 'no');
1176
+ return { got: `${r.json && r.json.path} (${r.json && r.json.rights && r.json.rights.ground}); ${stateOf(id)}, ${store.buildouts.length === before ? 'nothing built' : 'BUILT'}`, pass: r.status === 201 && r.json.path === 'vote' && stateOf(id) === 'returned' && store.buildouts.length === before };
1177
+ });
1178
+ // Under each form, what the form says Planning's Lead may build without a vote:
1179
+ // [label, the switch (null: in force already), who sits once it is in force, the path].
1180
+ const BUILD_FORMS = [
1181
+ ['Monarchy / BDFL', null, [FOUNDER_ID], 'at_once'],
1182
+ ['Timocratic Republic (the Lead sits on the board)', { form: 'timocratic-republic' }, [FOUNDER_ID, BEN, CY], 'at_once'],
1183
+ ['Direct Democracy', { form: 'direct-democracy' }, ACTIVE_IDS, 'vote'],
1184
+ ['Foundation (council rank)', { form: 'foundation', council_rank: 'council', acknowledge_self_removal: true }, COUNCIL, 'vote'],
1185
+ ];
1186
+ let seated = [FOUNDER_ID];
1187
+ for (const [label, body, seats, want] of BUILD_FORMS) {
1188
+ if (body) {
1189
+ const r = await call(FOUNDER_ID, 'POST', '/government/board/charter', body);
1190
+ await voteAll(itemIdOf(r), seated);
1191
+ seated = seats;
1192
+ }
1193
+ await check(`${label}: Planning's Lead adds a committee room`, want === 'at_once' ? 'rights at once (form); built at once' : 'rights a vote (form); filed as a vote, which passed and built it', async () => {
1194
+ const rights = (((await building('planning')).build_rights || {}).capsules || {}).committee || {};
1195
+ const r = await call(BEN, 'POST', '/government/buildouts', { building: 'planning', capsule: 'committee', name: `${label.split(' ')[0]} room` });
1196
+ const took = r.json && r.json.path;
1197
+ let got = `rights ${rights.without_vote ? 'at once' : 'a vote'} (${rights.ground}); `;
1198
+ let built = false;
1199
+ if (took === 'at_once') {
1200
+ built = !!(r.json.buildout && r.json.buildout.ground === 'form');
1201
+ got += built ? 'built at once' : 'NOT built';
1202
+ } else if (took === 'vote') {
1203
+ const id = itemIdOf(r);
1204
+ await voteAll(id, seated);
1205
+ built = store.buildouts.some((o) => o.authorised_by_item_id === String(id));
1206
+ got += `filed as a vote, which ${stateOf(id)}${built ? ' and built it' : ''}`;
1207
+ } else got += `${r.status} ${code(r)}`;
1208
+ return { got, pass: took === want && rights.without_vote === (want === 'at_once') && rights.ground === 'form' && built };
1209
+ });
1210
+ }
1211
+ // back to the Monarchy the rest of the walk runs under
1212
+ {
1213
+ const r = await call(FOUNDER_ID, 'POST', '/government/board/charter', { form: 'monarchy' });
1214
+ await voteAll(itemIdOf(r), seated);
1215
+ }
1216
+ await check('Back under a Monarchy, the build-outs stand on the record with what allowed each', '2 at once (form), 2 by a vote', async () => {
1217
+ const mine = store.buildouts.filter((o) => o.building === 'planning');
1218
+ const atOnce = mine.filter((o) => !o.authorised_by_item_id && o.ground === 'form').length;
1219
+ const voted = mine.filter((o) => o.authorised_by_item_id).length;
1220
+ const form = charterForm(req);
1221
+ return { got: `${atOnce} at once (form), ${voted} by a vote${form === 'monarchy' ? '' : `; form ${form}`}`, pass: atOnce === 2 && voted === 2 && form === 'monarchy' };
1222
+ });
1223
+
1224
+ // ══ 7. Asks and routing ══
1225
+ section('A Asks');
1226
+ await check('A slop ask is refused, every fix named, nothing stored', '400 ask_refused', async () => {
1227
+ const before = side.asked.size;
1228
+ const r = await call(BEN, 'POST', '/government/asks/check', SLOP_ASK, 'cli');
1229
+ const n = (((r.json || {}).error || {}).details || {}).errors;
1230
+ return { got: `${r.status} ${code(r)}${Array.isArray(n) && n.length ? '' : ' (no fixes named)'}${side.asked.size === before ? '' : ', STORED'}`, pass: r.status === 400 && code(r) === 'ask_refused' && Array.isArray(n) && n.length > 0 && side.asked.size === before };
1231
+ });
1232
+ await check('A card in the ask shape passes the check', '200 ok', async () => {
1233
+ const r = await call(BEN, 'POST', '/government/asks/check', GOOD_ASK, 'cli');
1234
+ return { got: `${r.status} ${r.json && r.json.ok ? 'ok' : code(r)}`, pass: r.status === 200 && r.json.ok === true };
1235
+ });
1236
+ await check('An AI session filing a blocker without an ask is refused, and nothing is filed', '400 ask_required; no blocker', async () => {
1237
+ const before = side.blockers.length;
1238
+ const r = await call(CY, 'POST', '/blockers', { title: 'Walk needs a decision', body_md: 'A long explanation.' }, 'cli');
1239
+ return { got: `${r.status} ${code(r)}; ${side.blockers.length === before ? 'no blocker' : 'FILED'}`, pass: r.status === 400 && code(r) === 'ask_required' && side.blockers.length === before };
1240
+ });
1241
+ await check('A slop ask on a blocker files nothing', '400 ask_refused; no blocker', async () => {
1242
+ const before = side.blockers.length;
1243
+ const r = await call(CY, 'POST', '/blockers', { title: 'Walk needs a decision', ask: SLOP_ASK }, 'cli');
1244
+ return { got: `${r.status} ${code(r)}; ${side.blockers.length === before ? 'no blocker' : 'FILED'}`, pass: r.status === 400 && code(r) === 'ask_refused' && side.blockers.length === before };
1245
+ });
1246
+ let matter = null;
1247
+ await check('With an ask, the AI session\'s blocker is filed and carries its card', '201; the blocker reads back with its question', async () => {
1248
+ const r = await call(CY, 'POST', '/blockers', { title: 'Walk needs a decision', ask: GOOD_ASK }, 'cli');
1249
+ const id = r.json && r.json.blocker && r.json.blocker.id;
1250
+ matter = id ? `blockers:${id}` : null;
1251
+ const back = await call(CY, 'GET', `/blockers/${id}`);
1252
+ const q = back.json && back.json.blocker && back.json.blocker.ask && back.json.blocker.ask.question;
1253
+ return { got: `${r.status}; ${q === GOOD_ASK.question ? 'the blocker reads back with its question' : `read back ${q}`}`, pass: r.status === 201 && q === GOOD_ASK.question };
1254
+ });
1255
+ const routeFor = async (as) => {
1256
+ const d = (await call(as, 'GET', '/government/docket')).json || {};
1257
+ const m = (d.matters || []).find((x) => x.id === matter) || null;
1258
+ return { d, m, route: (m && m.route) || {} };
1259
+ };
1260
+ await check('Routed to its seat: the ask waits on Planning\'s Lead, and is on Ben, not the owner', 'routed; the Planning Lead; Ben yes, founder no', async () => {
1261
+ const ben = await routeFor(BEN);
1262
+ const ada = await routeFor(FOUNDER_ID);
1263
+ return { got: `${ben.d.routed ? 'routed' : 'NOT routed'}; ${ben.route.label}; Ben ${ben.route.yours ? 'yes' : 'no'}, founder ${ada.route.yours ? 'yes' : 'no'}`, pass: ben.d.routed === true && ben.route.seat === 'lead:planning' && ben.route.yours === true && ada.route.yours === false };
1264
+ });
1265
+ await check('Only whoever it waits on may pass it on', '403 not_yours', async () => {
1266
+ const r = await call(FOUNDER_ID, 'POST', `/government/asks/${matter}/pass`, { seat: 'owner' });
1267
+ return { got: `${r.status} ${code(r)}`, pass: r.status === 403 && code(r) === 'not_yours' };
1268
+ });
1269
+ await check('Ben passes it on to the owner: word for word, it leaves Ben and lands on the founder', '200; same question; Ben no, founder yes', async () => {
1270
+ const r = await call(BEN, 'POST', `/government/asks/${matter}/pass`, { seat: 'owner' });
1271
+ const ben = await routeFor(BEN);
1272
+ const ada = await routeFor(FOUNDER_ID);
1273
+ const kept = r.json && r.json.ask && r.json.ask.question === GOOD_ASK.question;
1274
+ return { got: `${r.status}; ${kept ? 'same question' : 'CHANGED'}; Ben ${ben.route.yours ? 'yes' : 'no'}, founder ${ada.route.yours ? 'yes' : 'no'}`, pass: r.status === 200 && kept && ben.route.yours === false && ada.route.yours === true };
1275
+ });
1276
+ await check('The counts agree: GET /government/counts, the docket and the hall all say what is on each of them', 'founder 1 to do, Ben 0 to do; the hall reads the same', async () => {
1277
+ const out = [];
1278
+ let ok = true;
1279
+ for (const [who, want] of [[FOUNDER_ID, 1], [BEN, 0]]) {
1280
+ const c = (await call(who, 'GET', '/government/counts')).json || {};
1281
+ const d = (await call(who, 'GET', '/government/docket')).json || {};
1282
+ const me = { builder: { id: Number(who), rank: store.builders.get(who).rank } };
1283
+ const hallServed = GovLiving.countsOf(GovLiving.livingModel({ counts: c, me, now: Date.now() }));
1284
+ const hallDocket = GovLiving.countsOf(GovLiving.livingModel({ docket: d, me, now: Date.now() }));
1285
+ const docketYours = (d.counts && d.counts.yours);
1286
+ const agree = c.on_you === want && docketYours === want && hallServed.on_you === want && hallDocket.on_you === want;
1287
+ ok = ok && agree;
1288
+ out.push(agree ? `${who === FOUNDER_ID ? 'founder' : 'Ben'} ${want} to do` : `${who}: counts ${c.on_you}, docket ${docketYours}, hall ${hallServed.on_you}/${hallDocket.on_you}`);
1289
+ }
1290
+ return { got: `${out.join(', ')}${ok ? '; the hall reads the same' : ''}`, pass: ok };
1291
+ });
1292
+
1293
+ // ══ 8. The treasury ══
1294
+ section('T Treasury');
1295
+ const treasuryAs = async (as) => (await call(as, 'GET', '/government/treasury')).json || {};
1296
+ await check('No pots, no spend limit, and the unseated Treasury answers to the owner', '0 pots; limit off; owner seat, not Eve\'s', async () => {
1297
+ const t = await treasuryAs(EVE);
1298
+ const a = t.answers || {};
1299
+ return { got: `${(t.pots || []).length} pots; limit ${t.cap && t.cap.on ? 'ON' : 'off'}; ${a.fell_to_owner ? 'owner seat' : a.label}, ${a.yours ? 'Eve\'s' : 'not Eve\'s'}`, pass: (t.pots || []).length === 0 && t.cap && t.cap.on === false && a.fell_to_owner === true && a.yours === false };
1300
+ });
1301
+ await check('Someone who does not answer for the Treasury cannot give a goal a pot', '403 not_the_holder', async () => {
1302
+ const r = await call(EVE, 'PUT', `/government/treasury/pots/${WALK_GOAL}`, { budget_usd: 100 });
1303
+ return { got: `${r.status} ${code(r)}`, pass: r.status === 403 && code(r) === 'not_the_holder' };
1304
+ });
1305
+ await check('The owner gives the walk goal a $100 pot', '200, $100', async () => {
1306
+ const r = await call(FOUNDER_ID, 'PUT', `/government/treasury/pots/${WALK_GOAL}`, { budget_usd: 100 });
1307
+ return { got: `${r.status}, $${r.json && r.json.pot && r.json.pot.budget_usd}`, pass: r.status === 200 && r.json.pot.budget_usd === 100 };
1308
+ });
1309
+ await check('Spending $150 on it runs the pot dry', 'dry, $50 over', async () => {
1310
+ side.treasury.spend.set(WALK_GOAL, 150);
1311
+ const pot = ((await treasuryAs(FOUNDER_ID)).pots || [])[0] || {};
1312
+ return { got: `${pot.dry ? 'dry' : 'not dry'}, $${-pot.left_usd} over`, pass: pot.dry === true && pot.left_usd === -50 };
1313
+ });
1314
+ let borrow = null;
1315
+ await check('The sweep files one borrow from the reserve, its ask waiting on the Treasury; a second sweep files none', 'filed 1 for $250, waiting on lead:treasury; then 0', async () => {
1316
+ const first = await sweep.runSweep({ now: () => TREASURY_NOW, log: () => {}, error: () => {} });
1317
+ borrow = side.treasury.borrows[0] || null;
1318
+ const ask = borrow ? side.asked.get(treasuryMod.matterId(borrow.id)) : null;
1319
+ const second = await sweep.runSweep({ now: () => TREASURY_NOW, log: () => {}, error: () => {} });
1320
+ return { got: `filed ${first.filed}${borrow ? ` for $${borrow.amount_usd}` : ''}, waiting on ${ask ? ask.waits_on : 'nothing'}; then ${second.filed}`, pass: first.filed === 1 && !!ask && ask.waits_on === 'lead:treasury' && second.filed === 0 };
1321
+ });
1322
+ await check('The borrow is on the owner\'s to-do, and only waits on others for Eve', 'founder: to do; Eve: on others', async () => {
1323
+ const mid = borrow && treasuryMod.matterId(borrow.id);
1324
+ const a = (await call(FOUNDER_ID, 'GET', '/government/counts')).json || {};
1325
+ const e = (await call(EVE, 'GET', '/government/counts')).json || {};
1326
+ const where = (c) => ((c.yours || []).includes(mid) ? 'to do' : (c.others || []).includes(mid) ? 'on others' : 'absent');
1327
+ return { got: `founder: ${where(a)}; Eve: ${where(e)}`, pass: where(a) === 'to do' && where(e) === 'on others' };
1328
+ });
1329
+ await check('Someone it does not wait on cannot answer the borrow', '403 not_yours', async () => {
1330
+ const r = await call(EVE, 'POST', `/government/treasury/borrows/${borrow && borrow.id}/answer`, { key: 'lend' });
1331
+ return { got: `${r.status} ${code(r)}`, pass: r.status === 403 && code(r) === 'not_yours' };
1332
+ });
1333
+ await check('The owner lends it all: the pot refills; a second answer is refused', '200 lent $250; pot not dry; then 409 already_answered', async () => {
1334
+ const r = await call(FOUNDER_ID, 'POST', `/government/treasury/borrows/${borrow && borrow.id}/answer`, { key: 'lend' });
1335
+ const pot = ((await treasuryAs(FOUNDER_ID)).pots || [])[0] || {};
1336
+ const again = await call(FOUNDER_ID, 'POST', `/government/treasury/borrows/${borrow && borrow.id}/answer`, { key: 'no' });
1337
+ const b = (r.json && r.json.borrow) || {};
1338
+ return { got: `${r.status} ${b.state} $${b.lent_usd}; pot ${pot.dry ? 'dry' : 'not dry'}; then ${again.status} ${code(again)}`, pass: r.status === 200 && b.state === 'lent' && b.lent_usd === 250 && pot.dry === false && again.status === 409 && code(again) === 'already_answered' };
1339
+ });
1340
+ await check('The pot is taken away again', '200 removed; 0 pots', async () => {
1341
+ const r = await call(FOUNDER_ID, 'PUT', `/government/treasury/pots/${WALK_GOAL}`, { budget_usd: null });
1342
+ const t = await treasuryAs(FOUNDER_ID);
1343
+ return { got: `${r.status} ${r.json && r.json.removed ? 'removed' : 'KEPT'}; ${(t.pots || []).length} pots`, pass: r.status === 200 && r.json.removed === true && (t.pots || []).length === 0 };
1344
+ });
1345
+
1346
+ // ══ 9. Positions and the governing tabs ══
1347
+ section('P Positions');
1348
+ const SHELL = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', 'shell.js'), 'utf8');
1349
+ const cityGated = /\{ id: 'city',[^}]*position: true/.test(SHELL);
1350
+ const held = async (id) => {
1351
+ const p = await positionsPort().positionsFor(id);
1352
+ const b = store.builders.get(id);
1353
+ const view = GovPositions.positionsFor({ me: { builder: { id: Number(id), rank: b.rank }, positions: p } });
1354
+ return { p, view, kinds: ((p && p.held) || []).map((x) => x.kind) };
1355
+ };
1356
+ // The shell's rule (shell.js): a governing tab needs a held seat once the seats are known.
1357
+ const tabShows = (view) => !(cityGated && view.known && !view.has_position);
1358
+ for (const [who, name, want, kinds] of [[FOUNDER_ID, 'The founder', 'owner, board; the City tab shows', ['owner', 'board']], [BEN, 'Ben, Planning\'s Lead', 'lead (Planning Lead); the City tab shows', ['lead']], [EVE, 'Eve, who holds no seat', 'none; the City tab is hidden', []]]) {
1359
+ await check(`${name}: the seats GET /me carries, and the governing tab`, want, async () => {
1360
+ const h = await held(who);
1361
+ const title = h.p && h.p.title;
1362
+ const shows = tabShows(h.view);
1363
+ const got = `${h.kinds.join(', ') || 'none'}${who === BEN ? ` (${title})` : ''}; the City tab ${shows ? 'shows' : 'is hidden'}`;
1364
+ return { got, pass: cityGated && same(h.kinds, kinds) && shows === kinds.length > 0 && (who !== BEN || title === 'Planning Lead') };
1365
+ });
1366
+ }
1367
+ await check('Ben is removed by a People vote and holds no seat; Planning falls to the owner', 'passed; Ben none; Planning answers to the owner', async () => {
1368
+ const r = await call(FOUNDER_ID, 'POST', '/government/board/matters', { area: 'people', title: 'Planning has no Lead', action: { type: 'remove_lead', building: 'planning' } });
1369
+ const v = await vote(FOUNDER_ID, itemIdOf(r));
1370
+ const h = await held(BEN);
1371
+ const a = (await building('planning')).answers || {};
1372
+ return { got: `${v.json && v.json.outcome}; Ben ${h.kinds.join(', ') || 'none'}; Planning answers to ${a.fell_to_owner ? 'the owner' : a.label}`, pass: v.json && v.json.outcome === 'passed' && h.kinds.length === 0 && a.fell_to_owner === true };
1373
+ });
1374
+ await check('Nothing the walk put up is left standing: no law, Lead, pot, open borrow or open sitting', 'none left', async () => {
1375
+ const left = [];
1376
+ if (store.laws.some((l) => !l.repealed_at)) left.push('a law');
1377
+ if (store.leads.some((l) => !l.removed_at)) left.push('a Lead');
1378
+ if (side.treasury.pots.size) left.push('a pot');
1379
+ if (side.treasury.borrows.some((b) => b.state === 'open')) left.push('an open borrow');
1380
+ if (store.items.some((i) => !i.closed_at)) left.push('an open sitting');
1381
+ return { got: left.length ? left.join(', ') : 'none left', pass: left.length === 0 };
1382
+ });
1383
+ }
1384
+
1385
+ function charterForm(req) {
1386
+ const charter = req('modules/government/charter.js');
1387
+ const config = req('modules/government/config.js');
1388
+ return charter.readCharter(config.loadGovernmentConfig().board).form;
1389
+ }
1390
+
762
1391
  // ── the table, as Markdown ──────────────────────────────────────────────────
763
1392
 
764
1393
  const SECTION_TITLES = {
@@ -766,6 +1395,11 @@ const SECTION_TITLES = {
766
1395
  F: '2. Every form: who can vote and what passes',
767
1396
  B: '3. Every building: decide by vote refuses acting alone',
768
1397
  M: '4. Every matter action: passed and carried out',
1398
+ L: '5. Laws: passed by a vote, read in every session, locks enforced; strictness presets',
1399
+ D: '6. Leads and build-outs under each form of government',
1400
+ A: '7. Asks: the shape enforced, routed to a seat, passed on, counted',
1401
+ T: '8. The treasury: a pot runs dry and borrows from the reserve',
1402
+ P: '9. Positions: the seats GET /me carries and the governing tabs',
769
1403
  S: 'The walk itself',
770
1404
  };
771
1405
  const cell = (v) => String(v).replace(/\|/g, '\\|').replace(/\n/g, ' ');
@@ -778,7 +1412,7 @@ function renderTable({ mode, rows }) {
778
1412
  out.push('');
779
1413
  out.push('One repeatable walk over a throwaway test project\'s government (task 1004658, goal 1000111). Re-run it with `node scripts/gds/government-walk.js`; `tests/government_walk.mjs` re-runs it in CI and fails if any row below changes, so this table is kept true, not just written once.');
780
1414
  out.push('');
781
- out.push(`**Mode: ${mode}.** Every request is real HTTP through the real routers, and every decision is the real code: the permission gate, board membership, the vote, the tally, the close, the charter apply (written to a throwaway folder), the decision-rules apply, the act-alone gate on every building, and each module's matter act. The storage underneath is an in-memory stand-in for the government tables with the same guards (no Postgres was available where this was built), and the writes a passed matter makes into other modules (the automation fence, override requests, criteria, the GitHub gate approval, budgets) are recorded in memory. A real-database run of this walk is not built yet.`);
1415
+ out.push(`**Mode: ${mode}.** Every request is real HTTP through the real routers, and every decision is the real code: the permission gate, board membership, the vote, the tally, the close, the charter apply (written to a throwaway folder), the decision-rules apply, the act-alone gate on every building, and each module's matter act. The storage underneath is an in-memory stand-in for the government tables with the same guards (no Postgres was available where this was built), and the writes a passed matter makes into other modules (the automation fence, override requests, criteria, the GitHub gate approval, budgets) are recorded in memory. So are the new machinery's own tables (laws, asks, build-outs, pots and borrows), a builder's personal settings and the blockers; the shared database pool refuses every query. The treasury runs on a fixed mid-month clock, so a borrow's amount reads the same every run. Everything the walk puts up it takes down again: no law, Lead, pot, open borrow or open sitting is left standing (P5). A real-database run of this walk is not built yet.`);
782
1416
  out.push('');
783
1417
  out.push('The test project: Ada founds it (Archon); Ben and Cy are trusted builders (Metic) and sit on a Council rank; Dee (Thetes) and Eve (Xenos) are newer builders; Fay has left and only authors the probe ideas, so no probe is decided by its own author.');
784
1418
  out.push('');