@bongos/core 1.21.113 → 1.21.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/.bongos-core.json +603 -183
  2. package/.claude/skills/ask-for-help/SKILL.md +1 -1
  3. package/.claude/skills/bongos-feedback/SKILL.md +1 -1
  4. package/.claude/skills/builder-sequence/SKILL.md +1 -1
  5. package/.claude/skills/builder-ship/SKILL.md +1 -0
  6. package/GOVERNMENT.md +128 -11
  7. package/clients/bongos-client/README.md +1 -1
  8. package/clients/bongos-client/bongos-client.global.js +28 -0
  9. package/clients/bongos-client/index.cjs +28 -0
  10. package/clients/bongos-client/index.d.ts +45 -2
  11. package/clients/bongos-client/index.mjs +28 -0
  12. package/docs/adr/0372-an-ask-is-stored-per-docket-matter-and-the-server-refuses-slop.md +33 -0
  13. package/docs/adr/0373-an-ai-session-files-a-blocker-with-an-ask.md +31 -0
  14. package/docs/adr/0374-a-building-lead-is-a-held-position-appointed-by-a-people-matter.md +30 -0
  15. package/docs/adr/0375-who-may-build-without-a-vote-comes-from-the-form-of-government.md +38 -0
  16. package/docs/adr/0376-every-ask-is-routed-to-a-real-seat-on-the-server.md +30 -0
  17. package/docs/adr/0377-governance-is-a-held-position-read-on-me.md +24 -0
  18. package/docs/adr/0378-a-leads-build-outs-are-rows-built-at-once-or-by-a-passed-vote.md +33 -0
  19. package/docs/adr/0379-the-treasury-is-pots-per-goal-a-reserve-and-borrowing-over-real-spend.md +32 -0
  20. package/docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md +26 -0
  21. package/docs/adr/README.md +10 -1
  22. package/docs/api/openapi.json +989 -87
  23. package/docs/api-reference.md +18 -4
  24. package/docs/copy-inventory.md +343 -262
  25. package/docs/copy-registry.json +1223 -426
  26. package/docs/file-map.md +2 -1
  27. package/docs/module-api-changelog.md +2 -0
  28. package/docs/packs/engineer.md +2 -1
  29. package/docs/page-inventory.json +91 -6
  30. package/docs/page-readings.json +5017 -4458
  31. package/modules/builder-settings/law-locks.js +202 -0
  32. package/modules/builder-settings/routes/settings.js +66 -6
  33. package/modules/builder-settings/settings.js +7 -0
  34. package/modules/discord/ship-broadcast.js +22 -13
  35. package/modules/government/asks-db.js +54 -0
  36. package/modules/government/asks.js +269 -0
  37. package/modules/government/board-idea-reads.js +68 -0
  38. package/modules/government/board.js +21 -57
  39. package/modules/government/build-rights.js +148 -0
  40. package/modules/government/buildouts.js +160 -0
  41. package/modules/government/counts.js +81 -0
  42. package/modules/government/db.js +242 -0
  43. package/modules/government/decision-board.js +234 -8
  44. package/modules/government/decision-rules.js +2 -0
  45. package/modules/government/docket.js +114 -6
  46. package/modules/government/law-board.js +131 -0
  47. package/modules/government/laws.js +241 -0
  48. package/modules/government/leads.js +170 -0
  49. package/modules/government/migrations/government_027_laws.sql +143 -0
  50. package/modules/government/migrations/government_028_asks.sql +57 -0
  51. package/modules/government/migrations/government_029_leads.sql +135 -0
  52. package/modules/government/migrations/government_030_buildouts.sql +111 -0
  53. package/modules/government/migrations/government_031_treasury.sql +107 -0
  54. package/modules/government/module.json +2 -1
  55. package/modules/government/pollers/treasury-borrow.js +62 -0
  56. package/modules/government/positions.js +107 -0
  57. package/modules/government/presets.js +149 -0
  58. package/modules/government/routes/government.js +425 -2
  59. package/modules/government/routing.js +154 -0
  60. package/modules/government/treasury-db.js +166 -0
  61. package/modules/government/treasury.js +241 -0
  62. package/modules/hall-ui/public/atlas.html +2 -2
  63. package/modules/hall-ui/public/blockers.html +2 -2
  64. package/modules/hall-ui/public/board-room.html +2 -2
  65. package/modules/hall-ui/public/board-room.js +2 -0
  66. package/modules/hall-ui/public/city-draw.js +604 -11
  67. package/modules/hall-ui/public/city.css +157 -1
  68. package/modules/hall-ui/public/city.html +37 -7
  69. package/modules/hall-ui/public/city.js +441 -21
  70. package/modules/hall-ui/public/city.states.json +110 -5
  71. package/modules/hall-ui/public/collab.html +2 -2
  72. package/modules/hall-ui/public/commands.html +2 -2
  73. package/modules/hall-ui/public/copy-desk.html +2 -2
  74. package/modules/hall-ui/public/craft-mode.js +112 -29
  75. package/modules/hall-ui/public/deploy.html +2 -2
  76. package/modules/hall-ui/public/diagram-viewer.css +12 -0
  77. package/modules/hall-ui/public/diagram-viewer.js +4 -0
  78. package/modules/hall-ui/public/diagrams.html +2 -2
  79. package/modules/hall-ui/public/drachmae.html +2 -2
  80. package/modules/hall-ui/public/fleet.html +2 -2
  81. package/modules/hall-ui/public/gate.html +2 -2
  82. package/modules/hall-ui/public/goal-map.html +2 -2
  83. package/modules/hall-ui/public/goals.html +2 -2
  84. package/modules/hall-ui/public/gov-feeds.js +141 -0
  85. package/modules/hall-ui/public/gov-living.js +1002 -0
  86. package/modules/hall-ui/public/gov-positions.js +97 -0
  87. package/modules/hall-ui/public/government.html +2 -2
  88. package/modules/hall-ui/public/hall-render.js +3 -0
  89. package/modules/hall-ui/public/idea.html +2 -2
  90. package/modules/hall-ui/public/ideas.html +2 -2
  91. package/modules/hall-ui/public/index.html +3 -3
  92. package/modules/hall-ui/public/modules.html +2 -2
  93. package/modules/hall-ui/public/my-agents.css +334 -0
  94. package/modules/hall-ui/public/my-agents.html +72 -0
  95. package/modules/hall-ui/public/my-agents.js +603 -0
  96. package/modules/hall-ui/public/my-agents.states.json +37 -0
  97. package/modules/hall-ui/public/primer.html +2 -2
  98. package/modules/hall-ui/public/profile-connections.js +40 -28
  99. package/modules/hall-ui/public/profile.html +2 -2
  100. package/modules/hall-ui/public/project-settings.html +2 -2
  101. package/modules/hall-ui/public/ranks.html +2 -2
  102. package/modules/hall-ui/public/roadmap.html +2 -2
  103. package/modules/hall-ui/public/room.css +8 -0
  104. package/modules/hall-ui/public/roster.html +2 -2
  105. package/modules/hall-ui/public/sessions.html +2 -2
  106. package/modules/hall-ui/public/settings.html +2 -2
  107. package/modules/hall-ui/public/shell.js +116 -16
  108. package/modules/hall-ui/public/street-card.js +18 -1
  109. package/modules/hall-ui/public/street-deck.css +299 -0
  110. package/modules/hall-ui/public/street-deck.js +1145 -0
  111. package/modules/hall-ui/public/street-depth.js +712 -0
  112. package/modules/hall-ui/public/street-desk.js +5 -2
  113. package/modules/hall-ui/public/street-draw.js +109 -16
  114. package/modules/hall-ui/public/street-inside.js +137 -22
  115. package/modules/hall-ui/public/street-office.js +705 -0
  116. package/modules/hall-ui/public/street.css +161 -11
  117. package/modules/hall-ui/public/street.html +41 -11
  118. package/modules/hall-ui/public/street.js +78 -66
  119. package/modules/hall-ui/public/street.states.json +129 -21
  120. package/modules/hall-ui/public/studio.html +2 -2
  121. package/modules/hall-ui/public/style.css +5 -1
  122. package/modules/hall-ui/public/task.html +2 -2
  123. package/modules/hall-ui/public/thinking.html +2 -2
  124. package/modules/hall-ui/public/tweak-editor.html +2 -2
  125. package/modules/hall-ui/public/watch.html +2 -2
  126. package/modules/hall-ui/public/watch.js +14 -1
  127. package/modules/hall-ui/public/work.html +2 -2
  128. package/modules/hall-ui/records/living-city.md +57 -0
  129. package/modules/hall-ui/records/my-agents.md +21 -0
  130. package/modules/hall-ui/records/oversight-family.md +1 -0
  131. package/modules/hall-ui/records/profile-connections.md +10 -0
  132. package/modules/hall-ui/records/reading-rooms.md +2 -0
  133. package/modules/ideas/routes/blockers.js +92 -3
  134. package/modules/lifecycle/module.json +2 -1
  135. package/modules/lifecycle/moving.js +17 -1
  136. package/modules/lifecycle/routes/moving.js +6 -0
  137. package/modules/lifecycle/routes/project-settings.js +23 -0
  138. package/modules/onboarding/approval-broadcast.js +8 -0
  139. package/modules/onboarding/routes/access-requests.js +16 -6
  140. package/modules/platform-identity/connections.js +13 -2
  141. package/modules/platform-identity/routes/connections.js +7 -1
  142. package/modules/public-landing/public/account-private.states.json +2 -2
  143. package/modules/public-landing/public/account.html +65 -5
  144. package/modules/public-landing/public/account.states.json +3 -3
  145. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
  146. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
  147. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +13 -0
  148. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
  149. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
  150. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +13 -0
  151. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
  152. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
  153. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +13 -0
  154. package/modules/public-landing/public/assets/world.css +4 -4
  155. package/modules/public-landing/public/connections-empty.states.json +14 -0
  156. package/modules/public-landing/public/connections-inactive.states.json +14 -0
  157. package/modules/public-landing/public/connections.html +450 -0
  158. package/modules/public-landing/public/connections.probes.json +414 -0
  159. package/modules/public-landing/public/connections.states.json +17 -0
  160. package/modules/public-landing/public/contact.html +2 -2
  161. package/modules/public-landing/public/guild.html +2 -2
  162. package/modules/public-landing/public/index.html +2 -2
  163. package/modules/public-landing/public/invitations.html +2 -2
  164. package/modules/public-landing/public/privacy.html +2 -2
  165. package/modules/public-landing/public/projects.html +2 -2
  166. package/modules/public-landing/public/projects.probes.json +6 -2
  167. package/modules/public-landing/public/terms.html +2 -2
  168. package/modules/ui-design/kit/fixtures/connections-empty.json +12 -0
  169. package/modules/ui-design/kit/fixtures/connections-inactive.json +7 -0
  170. package/modules/ui-design/kit/fixtures/connections.json +22 -0
  171. package/modules/ui-design/kit/serve.js +95 -0
  172. package/package-lock.json +2 -2
  173. package/package.json +1 -1
  174. package/release-notes.json +122 -0
  175. package/scripts/discord/README.md +8 -5
  176. package/scripts/discord/post.js +12 -1
  177. package/scripts/gds/artifact-audit.js +20 -3
  178. package/scripts/gds/ask.js +164 -0
  179. package/scripts/gds/autobongos-verify.js +21 -2
  180. package/scripts/gds/government-walk.js +638 -4
  181. package/scripts/gds/pulse.js +9 -6
  182. package/scripts/gds/ship-escalation.js +22 -3
  183. package/scripts/gds/ship-finish.js +5 -4
  184. package/scripts/gds/ship-merge-gate.js +18 -6
  185. package/scripts/hall-preview/README.md +41 -1
  186. package/scripts/hall-preview/pref-writes.js +140 -0
  187. package/scripts/hall-preview/refresh-fixtures.js +101 -7
  188. package/scripts/hall-preview/scrub-identity.js +366 -7
  189. package/scripts/hall-preview/server.js +27 -1
  190. package/scripts/render-diagrams.sh +4 -2
  191. package/src/bongos/auth-idp.js +6 -1
  192. package/src/bongos/project-settings.js +41 -0
  193. package/src/bongos/route-rank-check.js +27 -0
  194. package/src/bongos/routes/me.js +68 -7
  195. package/src/bongos/serve-internal.js +5 -1
  196. package/src/module-api.js +7 -1
  197. package/src/platform-server.js +10 -0
  198. package/tests/application_lifecycle.mjs +47 -5
  199. package/tests/conductor_main_e2e.mjs +21 -0
  200. package/tests/connections_api.mjs +18 -0
  201. package/tests/craft_mode_nav.mjs +5 -3
  202. package/tests/craft_mode_tallies.mjs +52 -16
  203. package/tests/discord_approvals.mjs +11 -0
  204. package/tests/discord_channels.mjs +20 -0
  205. package/tests/fixtures/gov-seated.mjs +64 -0
  206. package/tests/fixtures/government-ask-cards.json +1284 -0
  207. package/tests/government_act_alone_buildings.mjs +1 -0
  208. package/tests/government_agent_asks.mjs +322 -0
  209. package/tests/government_asks.mjs +408 -0
  210. package/tests/government_build_rights.mjs +253 -0
  211. package/tests/government_buildouts.mjs +382 -0
  212. package/tests/government_counts.mjs +143 -0
  213. package/tests/government_decision_rules.mjs +5 -1
  214. package/tests/government_laws.mjs +488 -0
  215. package/tests/government_laws_session.mjs +321 -0
  216. package/tests/government_leads.mjs +375 -0
  217. package/tests/government_positions.mjs +127 -0
  218. package/tests/government_presets.mjs +215 -0
  219. package/tests/government_routes.mjs +8 -1
  220. package/tests/government_routing.mjs +118 -0
  221. package/tests/government_seed.mjs +5 -0
  222. package/tests/government_treasury.mjs +394 -0
  223. package/tests/governor_docket.mjs +26 -2
  224. package/tests/hall_buildouts.mjs +208 -0
  225. package/tests/hall_city.mjs +14 -6
  226. package/tests/hall_city_living.mjs +617 -0
  227. package/tests/hall_gov_counts.mjs +336 -0
  228. package/tests/hall_gov_living.mjs +636 -0
  229. package/tests/hall_gov_real_site.mjs +655 -0
  230. package/tests/hall_matter_card.mjs +6 -1
  231. package/tests/hall_my_agents.mjs +481 -0
  232. package/tests/hall_nav.mjs +107 -4
  233. package/tests/hall_page_gate_map.mjs +4 -0
  234. package/tests/hall_preview_governor_fixtures.mjs +30 -3
  235. package/tests/hall_preview_scrub.mjs +59 -1
  236. package/tests/hall_preview_security.mjs +169 -0
  237. package/tests/hall_reading_world.mjs +44 -0
  238. package/tests/hall_roster_world.mjs +6 -2
  239. package/tests/hall_street.mjs +18 -2
  240. package/tests/hall_street_amend.mjs +1 -1
  241. package/tests/hall_street_deck.mjs +609 -0
  242. package/tests/hall_street_depth.mjs +559 -0
  243. package/tests/hall_street_desk.mjs +1 -1
  244. package/tests/hall_street_inside.mjs +29 -5
  245. package/tests/hall_tweak_editor.mjs +4 -1
  246. package/tests/hub_account_page.mjs +94 -3
  247. package/tests/hub_connections_page.mjs +507 -0
  248. package/tests/hub_invite_badge.mjs +1 -1
  249. package/tests/idp_device_exchange.mjs +18 -0
  250. package/tests/landing_legal_pages.mjs +8 -5
  251. package/tests/landing_page.mjs +23 -4
  252. package/tests/nav_permission_atoms.mjs +4 -1
  253. package/tests/profile_connections_ui.mjs +88 -25
  254. package/tests/projects_hub.mjs +15 -0
  255. package/tests/render_check.mjs +2 -2
  256. package/tests/ship_broadcast_routing.mjs +57 -16
  257. package/tests/ship_broadcast_server.mjs +40 -7
  258. package/tests/ship_merge_gate.mjs +19 -0
  259. package/tests/ui_design_kit.mjs +2 -2
  260. package/tests/watch_applications_queue.mjs +23 -0
@@ -0,0 +1,215 @@
1
+ // tests/government_presets.mjs — STRICTNESS PRESETS: Open workshop, Guild,
2
+ // Factory (task 1004715, goal 1000111 "government is how a project steers its AI").
3
+ //
4
+ // What this pins (the task's done-when lines, and the walls behind them):
5
+ // 1. EACH PRESET APPLIES ITS LAWS. A switch to a preset is ONE laws proposal
6
+ // that, once passed, leaves exactly that preset's laws in force (and the
7
+ // project's own laws untouched); every move between presets is a proposal
8
+ // laws.js accepts against the book it would be filed on; every lock a
9
+ // preset carries is ENFORCED on a real personal setting (law-locks.js).
10
+ // 2. FACTORY IS STRICT: caps on every model slot, off-task fixed at never, the
11
+ // ask format and no unattended runs among its laws.
12
+ // 3. WHAT EACH WOULD LOCK: presetsView names, per preset, the locks in force
13
+ // after a switch to it, including a law of the project's own it would keep.
14
+ // 4. SWITCHING IS A VOTE. POST /government/board/presets files the proposal
15
+ // through board.proposeLaws (the one path that ever writes a law, on
16
+ // passing); it writes nothing itself. Nothing is seeded: a project with no
17
+ // laws is "no preset".
18
+ // DB-free: the routes run through the real router with listLaws and proposeLaws
19
+ // replaced by fakes.
20
+ //
21
+ // Run: node --test tests/government_presets.mjs
22
+
23
+ import { strict as assert } from 'node:assert';
24
+ import { test, after } from 'node:test';
25
+ import http from 'node:http';
26
+ import path from 'node:path';
27
+ import { createRequire } from 'node:module';
28
+ import { fileURLToPath } from 'node:url';
29
+
30
+ const require = createRequire(import.meta.url);
31
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
32
+ const GOV = path.join(ROOT, 'modules', 'government');
33
+ const P = require(path.join(GOV, 'presets.js'));
34
+ const laws = require(path.join(GOV, 'laws.js'));
35
+ const lawLocks = require(path.join(ROOT, 'modules', 'builder-settings', 'law-locks.js'));
36
+
37
+ const KEYS = ['open', 'guild', 'factory'];
38
+ // The book after a passed proposal: what applyLaws would leave in force.
39
+ let nextId = 100;
40
+ function pass(book, proposal) {
41
+ const kept = book.filter((r) => !proposal.repeal.includes(String(r.id)));
42
+ return kept.concat(proposal.enact.map((l) => ({ id: String(nextId++), ...l, repealed_at: null })));
43
+ }
44
+ const textsOf = (book) => book.map((r) => r.text).sort();
45
+ const presetTexts = (key) => P.PRESETS.find((p) => p.key === key).laws.map((l) => l.text).sort();
46
+ const OWN = { id: '7', text: 'Every screen change carries two shots', effect_on_ai: null, locks: [{ setting: 'verbosity', value: 'detailed' }], repealed_at: null };
47
+
48
+ test('nothing is seeded: a project with no laws runs no preset', () => {
49
+ assert.equal(P.currentPreset([]), null);
50
+ assert.equal(P.presetsView([]).current, null);
51
+ assert.equal(P.currentPreset([OWN]), null, 'a law of the project\'s own is no preset');
52
+ });
53
+
54
+ test('each preset applies its laws: from any book, a passed switch leaves exactly that preset in force', () => {
55
+ const starts = [[], [OWN]];
56
+ for (const from of [null, ...KEYS]) {
57
+ for (const base of starts) {
58
+ let book = base.slice();
59
+ if (from) book = pass(book, P.switchProposal(from, book).proposal);
60
+ for (const to of KEYS) {
61
+ if (to === from) { assert.equal(P.switchProposal(to, book).reason, 'preset_in_force'); continue; }
62
+ const sw = P.switchProposal(to, book);
63
+ assert.ok(sw.ok, `${from} → ${to}`);
64
+ assert.ok(laws.validateLawsProposal(sw.proposal).ok, `${from} → ${to}: a valid laws proposal`);
65
+ assert.ok(laws.checkAgainstInForce(sw.proposal, book).ok, `${from} → ${to}: accepted against the book it is filed on`);
66
+ const after = pass(book, sw.proposal);
67
+ assert.equal(P.currentPreset(after), to, `${from} → ${to}: ${to} is in force once passed`);
68
+ const own = base.length ? [OWN.text] : [];
69
+ assert.deepEqual(textsOf(after), presetTexts(to).concat(own).sort(), `${from} → ${to}: exactly its laws${own.length ? ', and the project\'s own kept' : ''}`);
70
+ assert.match(sw.rationale, new RegExp(`to ${P.PRESETS.find((p) => p.key === to).name}`));
71
+ }
72
+ }
73
+ }
74
+ assert.equal(P.switchProposal('chaos', []).reason, 'unknown_preset');
75
+ });
76
+
77
+ test('a law shared by two presets is kept across a switch; a changed lock is repealed and re-enacted', () => {
78
+ const guild = pass([], P.switchProposal('guild', []).proposal);
79
+ const sw = P.switchProposal('factory', guild);
80
+ const repealed = guild.filter((r) => sw.proposal.repeal.includes(String(r.id))).map((r) => r.text);
81
+ assert.deepEqual(repealed, ['Stay on the task; note side ideas'], 'only the off-task cap goes; land-only and stuck stay');
82
+ assert.ok(!sw.proposal.enact.some((l) => l.text === 'Land only. Ask before going live'), 'the shared law is not enacted twice');
83
+ // a book that has a preset law's words with other locks is not that preset
84
+ const drift = guild.map((r) => (r.text === 'Stay on the task; note side ideas' ? { ...r, locks: [{ setting: 'wandering', max: 'balanced' }] } : r));
85
+ assert.equal(P.currentPreset(drift), null);
86
+ const fix = P.switchProposal('guild', drift);
87
+ assert.equal(fix.proposal.repeal.length, 1);
88
+ assert.equal(fix.proposal.enact.length, 1);
89
+ assert.equal(P.currentPreset(pass(drift, fix.proposal)), 'guild');
90
+ });
91
+
92
+ test('every lock a preset carries is enforced on a real personal setting', () => {
93
+ for (const p of P.PRESETS) {
94
+ for (const law of p.laws) {
95
+ for (const lock of law.locks) {
96
+ assert.ok(lawLocks.settingSpec(lock.setting), `${p.key}: ${lock.setting} is a stored personal setting`);
97
+ }
98
+ }
99
+ const book = pass([], P.switchProposal(p.key, []).proposal);
100
+ const { locks } = lawLocks.lockedPrefs({}, laws.forSession(book).laws, { rank: 'archon', instanceModels: {} });
101
+ assert.ok(locks.every((l) => l.enforced), `${p.key}: every lock bites (${JSON.stringify(locks.filter((l) => !l.enforced))})`);
102
+ }
103
+ });
104
+
105
+ test('factory is strict: caps on every model, off-task never, the ask format and no unattended runs', () => {
106
+ const book = pass([], P.switchProposal('factory', []).proposal);
107
+ const texts = book.map((r) => r.text);
108
+ assert.ok(texts.includes('Ask like a human'));
109
+ assert.ok(texts.includes('No unattended runs'));
110
+ const { prefs, locks } = lawLocks.lockedPrefs({ model_allocation: { main: 'fable', subagentDefault: 'opus' }, wandering: 3 }, laws.forSession(book).laws, { rank: 'archon', instanceModels: {} });
111
+ const to = Object.fromEntries(locks.map((l) => [l.setting, l.to]));
112
+ assert.deepEqual(to, { wandering: 'locked', 'model.main': 'opus', 'model.subagentDefault': 'sonnet', 'model.subagentRoutine': 'haiku' });
113
+ assert.ok(prefs, 'the effective prefs obey it');
114
+ const open = pass([], P.switchProposal('open', []).proposal);
115
+ assert.equal(laws.forSession(open).laws.flatMap((l) => l.locks).length, 0, 'an open workshop locks nothing');
116
+ });
117
+
118
+ test('what each would lock: the locks after a switch, a law of the project\'s own included', () => {
119
+ const v = P.presetsView([OWN]);
120
+ const of = (k) => v.presets.find((p) => p.key === k).locks.map((l) => l.setting).sort();
121
+ assert.deepEqual(of('open'), ['verbosity']);
122
+ assert.deepEqual(of('guild'), ['verbosity', 'wandering']);
123
+ assert.deepEqual(of('factory'), ['model.main', 'model.subagentDefault', 'model.subagentRoutine', 'verbosity', 'wandering']);
124
+ assert.equal(v.presets.filter((p) => p.current).length, 0);
125
+ });
126
+
127
+ // ── the routes, through the real router ─────────────────────────────────────
128
+
129
+ const api = require(path.join(ROOT, 'src', 'module-api.js'));
130
+ const seams = require(path.join(ROOT, 'src', 'module-seams.js'));
131
+ const rrc = require(path.join(ROOT, 'src', 'bongos', 'route-rank-check.js'));
132
+ const resolver = require(path.join(GOV, 'resolver.js'));
133
+ const db = require(path.join(GOV, 'db.js'));
134
+ const board = require(path.join(GOV, 'board.js'));
135
+ const { attachFail } = require(path.join(ROOT, 'src', 'bongos', 'middleware', 'error-envelope.js'));
136
+ const express = require('express');
137
+
138
+ const realRequireBuilder = api.requireBuilder;
139
+ const realResolve = resolver.resolveBuilderPermissions;
140
+ const realList = db.listLaws;
141
+ const realPropose = board.proposeLaws;
142
+ let held = ['board.vote.cast', 'board.item.open'];
143
+ let book = [];
144
+ const filed = [];
145
+ api.requireBuilder = (req, res, next) => { req.builder = { id: 3, rank: 'archon' }; next(); };
146
+ resolver.resolveBuilderPermissions = async () => held;
147
+ db.listLaws = async () => book;
148
+ board.proposeLaws = async (a) => {
149
+ filed.push(a);
150
+ const c = laws.checkAgainstInForce(laws.validateLawsProposal({ enact: a.enact, repeal: a.repeal }).proposal, book);
151
+ if (!c.ok) return c;
152
+ return { ok: true, amendment: { id: '50', kind: 'laws' }, item: { id: '60' }, proposal: { enact: a.enact, repeal: a.repeal } };
153
+ };
154
+ seams._reset();
155
+ seams.registerProvider('government', { resolveBuilderPermissions: async () => held });
156
+ const buildGovernmentRouter = require(path.join(GOV, 'routes', 'government.js'));
157
+ const app = express();
158
+ app.use(express.json());
159
+ app.use(attachFail);
160
+ app.use(buildGovernmentRouter());
161
+ const server = http.createServer(app);
162
+ await new Promise((r) => server.listen(0, '127.0.0.1', r));
163
+ const BASE = `http://127.0.0.1:${server.address().port}`;
164
+ after(() => {
165
+ server.close(); api.requireBuilder = realRequireBuilder; resolver.resolveBuilderPermissions = realResolve;
166
+ db.listLaws = realList; board.proposeLaws = realPropose; seams._reset();
167
+ });
168
+ async function call(method, p, body) {
169
+ const res = await fetch(`${BASE}${p}`, { method, headers: body === undefined ? {} : { 'content-type': 'application/json' }, body: body === undefined ? undefined : JSON.stringify(body) });
170
+ let json = null; try { json = await res.json(); } catch { /* none */ }
171
+ return { status: res.status, json };
172
+ }
173
+
174
+ test('the preset routes are pinned where the law routes are', () => {
175
+ const pins = rrc.MODULE_EXPECTED_RANKS.government;
176
+ assert.equal(pins['GET /government/presets'], 'perm:board.vote.cast');
177
+ assert.equal(pins['POST /government/board/presets'], 'perm:board.item.open');
178
+ });
179
+
180
+ test('GET /government/presets: the three presets, none in force on a fresh project; the room\'s floor', async () => {
181
+ book = [];
182
+ const r = await call('GET', '/government/presets');
183
+ assert.equal(r.status, 200);
184
+ assert.equal(r.json.current, null);
185
+ assert.deepEqual(r.json.presets.map((p) => p.key), KEYS);
186
+ book = pass([], P.switchProposal('guild', []).proposal);
187
+ assert.equal((await call('GET', '/government/presets')).json.current, 'guild');
188
+ held = ['board.item.open'];
189
+ try { assert.equal((await call('GET', '/government/presets')).status, 403); } finally { held = ['board.vote.cast', 'board.item.open']; }
190
+ });
191
+
192
+ test('POST /government/board/presets: a switch is ONE laws vote through proposeLaws; it writes nothing itself', async () => {
193
+ book = [];
194
+ filed.length = 0;
195
+ const r = await call('POST', '/government/board/presets', { preset: 'factory' });
196
+ assert.equal(r.status, 201);
197
+ assert.equal(r.json.item.id, '60', 'the vote it filed');
198
+ assert.equal(filed.length, 1, 'one proposal');
199
+ assert.deepEqual(filed[0].enact.map((l) => l.text).sort(), presetTexts('factory'));
200
+ assert.equal(filed[0].proposedBy, 3);
201
+ assert.match(filed[0].rationaleMd, /Switch from no preset to Factory/);
202
+ assert.equal(book.length, 0, 'no law was written');
203
+
204
+ book = pass([], P.switchProposal('guild', []).proposal);
205
+ assert.equal((await call('POST', '/government/board/presets', { preset: 'guild' })).json.error.code, 'preset_in_force');
206
+ assert.equal((await call('POST', '/government/board/presets', { preset: 'chaos' })).status, 400);
207
+ assert.equal((await call('POST', '/government/board/presets', {})).status, 400);
208
+ // a law of the project's own holding a setting the preset locks: refused by name
209
+ book = [{ id: '9', text: 'Agents roam freely', effect_on_ai: null, locks: [{ setting: 'wandering', value: 'exploratory' }], repealed_at: null }];
210
+ const clash = await call('POST', '/government/board/presets', { preset: 'guild' });
211
+ assert.equal(clash.status, 409);
212
+ assert.equal(clash.json.error.code, 'setting_already_locked');
213
+ held = ['board.vote.cast'];
214
+ try { assert.equal((await call('POST', '/government/board/presets', { preset: 'open' })).status, 403); } finally { held = ['board.vote.cast', 'board.item.open']; }
215
+ });
@@ -97,7 +97,14 @@ test('every /governance route gates via REAL middleware on exactly its PINNED at
97
97
  // casting) — still gates on government.manage.
98
98
  // task 1004523: the docket reads the board family's atom too — opening the city
99
99
  // is part of casting, and each source inside is scoped to its own atom.
100
- const boardFamily = (key) => key.includes('/board/') || key.endsWith('/government/constitution') || key.endsWith('/government/docket');
100
+ // task 1004706: the law book is read where the room is — every builder's agents obey it.
101
+ // task 1004703: an ask is checked and written where the docket is read.
102
+ // task 1004709: who leads each building is read where the room is.
103
+ // task 1004711: a Lead grows their building where the room is (the holder is the wall).
104
+ // task 1004712: the treasury is read, and its pots and borrows decided, where the room is (the holder and the seat are the walls).
105
+ // task 1004715: the strictness presets are read where the law book is.
106
+ // task 1004716: the counts are read where the docket they count is.
107
+ const boardFamily = (key) => key.includes('/board/') || key.includes('/government/asks') || key.endsWith('/government/constitution') || key.endsWith('/government/docket') || key.endsWith('/government/counts') || key.endsWith('/government/laws') || key.endsWith('/government/leads') || key.endsWith('/government/buildouts') || key.includes('/government/treasury') || key.endsWith('/government/presets');
101
108
  for (const r of classified.filter((x) => !boardFamily(x.key))) {
102
109
  assert.deepEqual(r.perms, ['government.manage'], `${r.key}: the admin surface gates on the manage atom`);
103
110
  }
@@ -0,0 +1,118 @@
1
+ // tests/government_routing.mjs — THE DIRECTION DESK: every ask routed to a real
2
+ // seat (task 1004705, goal 1000111; ADR 0376).
3
+ //
4
+ // WHAT IT GUARDS. modules/government/routing.js, the one rule for who an open
5
+ // docket matter waits on:
6
+ // * every one of the nine buildings routes to its Lead when one is in office,
7
+ // and to the owner seat (every active Archon) when it is unseated;
8
+ // * a stored ask's seat wins over the building's; board:<b> goes to the
9
+ // members its decision rule seats, committee:<b>.<k> to the building's Lead;
10
+ // * a board sitting with no ask is its voters' (the docket's own `needs`);
11
+ // * an ask nobody can answer (past its deadline, or on an empty seat) is on
12
+ // nobody's "to do" and carries its own "if nobody answers" line;
13
+ // * passing an ask on: only whoever it waits on, only a stored ask, never a vote.
14
+ //
15
+ // Run: node --test --test-reporter=tap tests/government_routing.mjs
16
+
17
+ import { test } from 'node:test';
18
+ import assert from 'node:assert/strict';
19
+ import path from 'node:path';
20
+ import { createRequire } from 'node:module';
21
+ import { fileURLToPath } from 'node:url';
22
+
23
+ const require = createRequire(import.meta.url);
24
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
25
+ const routing = require(path.join(ROOT, 'modules', 'government', 'routing.js'));
26
+ const leads = require(path.join(ROOT, 'modules', 'government', 'leads.js'));
27
+ const { AREA_KEYS } = require(path.join(ROOT, 'modules', 'government', 'decision-rules.js'));
28
+
29
+ const NOW = Date.parse('2026-10-09T12:00:00Z');
30
+ const ARCHONS = [{ id: 30, github_login: 'builder-30' }, { id: 3, github_login: 'builder-3' }];
31
+ const owner = leads.ownerOf(ARCHONS);
32
+ const lead = (building, builderId) => ({ id: building.length, building, builder_id: builderId, removed_at: null });
33
+ const ASK = { question: 'Ship it?', options: [], recommend: { key: 'go', why: 'Tested.' }, waits_on: 'owner', if_nobody_answers: 'It stays where it is.' };
34
+ const matter = (over = {}) => ({ id: 'blockers:1', source: 'blockers', building: 'planning', state: 'needs', deadline: null, ...over });
35
+
36
+ test('every building routes to its Lead when seated, else to the owner seat', () => {
37
+ assert.deepEqual(Object.keys(routing.BUILDING_NAMES), AREA_KEYS, 'the nine buildings, in the docket\'s order');
38
+ const seated = AREA_KEYS.map((b, i) => lead(b, 100 + i));
39
+ for (const [i, b] of AREA_KEYS.entries()) {
40
+ const m = matter({ id: `x:${b}`, building: b });
41
+ // seated: its Lead, and only its Lead
42
+ const r = routing.routeMatter(m, String(100 + i), { leads: seated, owner }, NOW);
43
+ assert.equal(r.seat, `lead:${b}`, `${b}: its Lead's seat`);
44
+ assert.equal(r.label, `the ${routing.BUILDING_NAMES[b]} Lead`);
45
+ assert.equal(r.fell_to_owner, false);
46
+ assert.equal(r.yours, true, `${b}: the Lead's to answer`);
47
+ assert.equal(routing.routeMatter(m, '3', { leads: seated, owner }, NOW).yours, false, `${b}: not the owner's while it has a Lead`);
48
+ assert.equal(routing.routeMatter(m, String(100 + ((i + 1) % 9)), { leads: seated, owner }, NOW).yours, false, `${b}: not another building's Lead's`);
49
+ // unseated: the owner seat, every active Archon
50
+ const u = routing.routeMatter(m, '30', { leads: seated.filter((l) => l.building !== b), owner }, NOW);
51
+ assert.deepEqual([u.seat, u.label, u.fell_to_owner, u.yours], ['owner', 'the owner', true, true], `${b}: unseated, it falls to the owner`);
52
+ assert.equal(routing.routeMatter(m, '3', { leads: [], owner }, NOW).yours, true, `${b}: every Archon holds the owner seat`);
53
+ assert.equal(routing.routeMatter(m, String(100 + i), { leads: [], owner }, NOW).yours, false, `${b}: an unseated building is not a non-Archon's`);
54
+ }
55
+ // a removed Lead is not in office
56
+ const gone = { ...lead('planning', 7), removed_at: '2026-10-01T00:00:00Z' };
57
+ assert.equal(routing.routeMatter(matter(), '7', { leads: [gone], owner }, NOW).seat, 'owner');
58
+ });
59
+
60
+ test('a stored ask\'s seat wins; board and committee seats resolve to real people', () => {
61
+ const ctx = { leads: [lead('review', 50)], owner, holders: { charter: [{ id: 61 }, { builder_id: 62 }], people: [] } };
62
+ // the ask names the owner, though the building is seated
63
+ const toOwner = routing.routeMatter(matter({ building: 'review', ask: ASK }), '50', ctx, NOW);
64
+ assert.deepEqual([toOwner.seat, toOwner.yours], ['owner', false]);
65
+ assert.equal(routing.routeMatter(matter({ building: 'review', ask: ASK }), '30', ctx, NOW).yours, true);
66
+ // board:<b> — the members its decision rule seats
67
+ const board = routing.routeMatter(matter({ ask: { ...ASK, waits_on: 'board:charter' } }), '62', ctx, NOW);
68
+ assert.deepEqual([board.seat, board.label, board.yours], ['board:charter', 'the Charter board', true]);
69
+ assert.equal(routing.routeMatter(matter({ ask: { ...ASK, waits_on: 'board:charter' } }), '30', ctx, NOW).yours, false, 'the owner does not sit on it');
70
+ // nobody seated there → the owner
71
+ const empty = routing.routeMatter(matter({ ask: { ...ASK, waits_on: 'board:people' } }), '3', ctx, NOW);
72
+ assert.deepEqual([empty.seat, empty.fell_to_owner, empty.yours], ['owner', true, true]);
73
+ // committees are not held anywhere yet: the building's Lead answers, or the owner
74
+ const com = routing.routeMatter(matter({ ask: { ...ASK, waits_on: 'committee:review.testers' } }), '50', ctx, NOW);
75
+ assert.deepEqual([com.asked, com.seat, com.yours], ['committee:review.testers', 'lead:review', true]);
76
+ assert.equal(routing.routeMatter(matter({ ask: { ...ASK, waits_on: 'committee:rules.drafting' } }), '3', ctx, NOW).seat, 'owner');
77
+ // a seat that is not one is never trusted: the building's own seat
78
+ assert.equal(routing.seatOf(matter({ building: 'review', ask: { ...ASK, waits_on: 'everyone' } })), 'lead:review');
79
+ });
80
+
81
+ test('a board sitting with no ask is its voters\', as the docket\'s own needs says', () => {
82
+ const vote = matter({ id: 'board:9', source: 'board', building: 'charter' });
83
+ assert.equal(routing.seatOf(vote), 'board:charter');
84
+ // nobody is listed as holding the charter, yet a voter the sitting awaits has it
85
+ assert.equal(routing.routeMatter(vote, '77', { leads: [], owner, holders: {} }, NOW).yours, true);
86
+ assert.equal(routing.routeMatter({ ...vote, state: 'moving' }, '77', { leads: [], owner, holders: {} }, NOW).yours, false, 'voted already: not on them');
87
+ });
88
+
89
+ test('an ask nobody answers does what its "if nobody answers" line says, on nobody\'s to do', () => {
90
+ const lapsed = routing.routeMatter(matter({ deadline: '2026-10-08T00:00:00Z', ask: ASK }), '30', { leads: [], owner }, NOW);
91
+ assert.equal(lapsed.yours, false);
92
+ assert.deepEqual(lapsed.nobody, { why: 'lapsed', outcome: 'It stays where it is.' });
93
+ // before its deadline it is still the owner's
94
+ assert.equal(routing.routeMatter(matter({ deadline: '2026-10-10T00:00:00Z', ask: ASK }), '30', { leads: [], owner }, NOW).yours, true);
95
+ // a seat nobody holds (no Archon at all): nobody can answer
96
+ const vacant = routing.routeMatter(matter({ ask: ASK }), '30', { leads: [], owner: leads.ownerOf([]) }, NOW);
97
+ assert.deepEqual([vacant.yours, vacant.nobody], [false, { why: 'empty_seat', outcome: 'It stays where it is.' }]);
98
+ // a matter that needs nobody is never "nobody answered"
99
+ assert.equal(routing.routeMatter(matter({ state: 'moving', deadline: '2026-10-08T00:00:00Z' }), '30', { leads: [], owner }, NOW).nobody, null);
100
+ // routeAll marks each matter and counts the viewer's
101
+ const ms = [matter({ id: 'a:1' }), matter({ id: 'a:2', building: 'review' }), matter({ id: 'a:3', ask: ASK, deadline: '2026-10-01T00:00:00Z' })];
102
+ assert.equal(routing.routeAll(ms, '50', { leads: [lead('review', 50)], owner }, NOW), 1);
103
+ assert.deepEqual(ms.map((m) => m.route.yours), [false, true, false]);
104
+ });
105
+
106
+ test('passing an ask on: only whoever it waits on, a stored ask, never a vote, to another seat', () => {
107
+ const ctx = { leads: [lead('planning', 40)], owner };
108
+ const m = matter({ ask: { ...ASK, waits_on: 'lead:planning' } });
109
+ assert.equal(routing.passRefusal(m, '40', 'owner', ctx, NOW), null, 'the Lead hands it to the owner');
110
+ assert.equal(routing.passRefusal(m, '30', 'owner', ctx, NOW).code, 'not_yours', 'the owner cannot move what waits on the Lead');
111
+ assert.equal(routing.passRefusal(m, '40', 'lead:planning', ctx, NOW).code, 'same_seat');
112
+ assert.equal(routing.passRefusal(m, '40', 'the boss', ctx, NOW).code, 'unknown_seat');
113
+ assert.equal(routing.passRefusal(matter(), '40', 'owner', ctx, NOW).code, 'no_ask');
114
+ assert.equal(routing.passRefusal({ ...m, id: 'board:3', source: 'board' }, '40', 'owner', ctx, NOW).code, 'vote_not_passable');
115
+ // to an unseated building's Lead is to the owner: already there
116
+ assert.equal(routing.passRefusal(matter({ ask: ASK }), '30', 'lead:rules', ctx, NOW).code, 'same_seat');
117
+ assert.equal(routing.passRefusal(matter({ ask: ASK }), '30', 'lead:planning', ctx, NOW), null, 'the owner hands it to a seated Lead');
118
+ });
@@ -46,6 +46,11 @@ const NON_GRANT_MIGRATIONS = new Set([
46
46
  'government_020_genesis_stage_subject.sql', // BV2.PS04 genesis_stage board subject CHECK (task 1004429)
47
47
  'government_023_decision_rules.sql', // who decides what: the decision-rules table, matters (task 1004529)
48
48
  'government_026_board_outcome_withdrawn.sql', // board outcome CHECK widened: withdrawn, expired (task 1004564)
49
+ 'government_027_laws.sql', // standing orders: the laws table, amendment kind 'laws' (task 1004706)
50
+ 'government_028_asks.sql', // the ask: one stored shape per docket matter (task 1004703)
51
+ 'government_029_leads.sql', // the Lead of each building, seated by a passed People matter (task 1004709)
52
+ 'government_030_buildouts.sql', // a Lead's build-outs, on the record at once or by a passed vote (task 1004711)
53
+ 'government_031_treasury.sql', // the treasury: a pot per goal and the loans a dry pot asks the reserve for (task 1004712)
49
54
  ]);
50
55
 
51
56
  // Permission keys RENAMED after the grant migrations that first granted them.