@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,143 @@
1
+ // tests/government_counts.mjs — GET /government/counts, the government's counts in
2
+ // one server read (task 1004716, goal 1000111).
3
+ //
4
+ // What it pins:
5
+ // 1. the server's rule IS the docket's route: on you = route.yours (the docket's
6
+ // own counts.yours), on others = needs that are not yours and that somebody can
7
+ // still answer, an unrouted docket puts every need on you, an unread figure is
8
+ // null, never 0;
9
+ // 2. it agrees with the deck: the hall's one count rule (gov-living.js countsOf)
10
+ // over the docket alone and over the server's counts gives the same numbers,
11
+ // with and without answers, so no surface can drift from the server;
12
+ // 3. countsFor reads the viewer's own docket and the traffic port; the route is
13
+ // gated like the docket; the agents count is the open claims GET
14
+ // /tasks/claimable lists, and lifecycle hands both counts over its port;
15
+ // 4. the harness fixture is the rule run over the harness's own docket.
16
+ //
17
+ // Run: node --test --test-reporter=tap tests/government_counts.mjs
18
+
19
+ import { test } from 'node:test';
20
+ import assert from 'node:assert/strict';
21
+ import fs from 'node:fs';
22
+ import path from 'node:path';
23
+ import { createRequire } from 'node:module';
24
+ import { fileURLToPath } from 'node:url';
25
+
26
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
27
+ const require = createRequire(import.meta.url);
28
+ const C = require(path.join(ROOT, 'modules/government/counts.js'));
29
+ const L = require(path.join(ROOT, 'modules/hall-ui/public/gov-living.js'));
30
+ const FX = path.join(ROOT, 'scripts/hall-preview/fixtures');
31
+ const fx = (n) => JSON.parse(fs.readFileSync(path.join(FX, n + '.json'), 'utf8'));
32
+ const read = (f) => fs.readFileSync(path.join(ROOT, f), 'utf8');
33
+ const NOW = Date.parse('2026-10-09T15:00:00.000Z');
34
+ const TRAFFIC = { moving: 6, agents_at_work: 5 };
35
+
36
+ // the harness docket, with one ask on somebody else, one nobody can answer any
37
+ // more and one moving: every way a matter can leave "to do"
38
+ function mixedDocket() {
39
+ const d = fx('government__docket');
40
+ const needs = d.matters.filter((m) => m.state === 'needs');
41
+ needs[0].route = Object.assign({}, needs[0].route, { yours: false });
42
+ needs[1].route = Object.assign({}, needs[1].route, { yours: false, nobody: { why: 'lapsed', outcome: null } });
43
+ needs[2].state = 'moving';
44
+ needs[2].route = Object.assign({}, needs[2].route, { yours: false });
45
+ return d;
46
+ }
47
+
48
+ // ── 1. the rule ──────────────────────────────────────────────────────────────
49
+
50
+ test('on you is the docket\'s own route; on others is what somebody else can still answer', () => {
51
+ const d = fx('government__docket');
52
+ const c = C.countsOf(d, TRAFFIC);
53
+ assert.equal(c.on_you, d.counts.yours, 'the docket\'s own counts.yours');
54
+ assert.equal(c.on_you, d.matters.filter((m) => m.route && m.route.yours).length);
55
+ assert.deepEqual(c.yours, d.matters.filter((m) => m.route && m.route.yours).map((m) => m.id));
56
+ const x = C.countsOf(mixedDocket(), TRAFFIC);
57
+ assert.equal(x.on_you, c.on_you - 3);
58
+ assert.equal(x.on_others, 1, 'the lapsed ask and the moving matter are on nobody\'s list');
59
+ assert.equal(x.open, x.on_you + x.on_others);
60
+ assert.deepEqual([x.agents_at_work, x.moving], [5, 6]);
61
+ });
62
+
63
+ test('an unrouted docket puts every need on you; an unread figure is null, never 0', () => {
64
+ const d = mixedDocket();
65
+ d.routed = false;
66
+ const c = C.countsOf(d, {});
67
+ assert.equal(c.on_you, d.matters.filter((m) => m.state === 'needs').length);
68
+ assert.equal(c.on_others, 0);
69
+ assert.deepEqual([c.agents_at_work, c.moving], [null, null], 'no traffic read');
70
+ const none = C.countsOf(null, TRAFFIC);
71
+ assert.deepEqual([none.on_you, none.on_others, none.open, none.yours], [null, null, null, null]);
72
+ assert.deepEqual(C.countsOf({ matters: [], routed: true }, { moving: 0, agents_at_work: 0 }).on_you, 0, 'read and empty is a real zero');
73
+ });
74
+
75
+ // ── 2. it agrees with the deck ───────────────────────────────────────────────
76
+
77
+ test('the hall\'s count rule over the docket and over the server\'s counts give the same numbers', () => {
78
+ for (const d of [fx('government__docket'), mixedDocket()]) {
79
+ const srv = C.countsOf(d, TRAFFIC);
80
+ const claims = { active_claims: Array.from({ length: 5 }, (_, i) => ({ claim_id: String(i), task_id: String(i) })) };
81
+ const fromDocket = L.livingModel({ docket: d, claims, moving: { moving: 6 }, me: fx('me'), now: NOW });
82
+ const fromServer = L.livingModel({ counts: srv, me: fx('me'), now: NOW });
83
+ const strip = (c) => { const o = { ...c }; delete o._sources; return o; };
84
+ assert.deepEqual(strip(L.countsOf(fromServer)), strip(L.countsOf(fromDocket)));
85
+ assert.deepEqual(strip(L.countsOf(fromServer)), { on_you: srv.on_you, on_others: srv.on_others, open: srv.open, agents_at_work: 5, moving: 6 });
86
+ // an answer on the page leaves both the same way
87
+ const answered = [srv.yours[0], srv.yours[1]].concat(srv.others.slice(0, 1));
88
+ assert.deepEqual(strip(L.countsOf(fromServer, { answered })), strip(L.countsOf(fromDocket, { answered })));
89
+ }
90
+ });
91
+
92
+ test('the deck holds what the server counts', () => {
93
+ const K = (() => {
94
+ for (const f of ['street-draw.js', 'street-inside.js', 'street-depth.js', 'street-card.js', 'street-desk.js']) require(path.join(ROOT, 'modules/hall-ui/public', f));
95
+ return require(path.join(ROOT, 'modules/hall-ui/public/street-deck.js'));
96
+ })();
97
+ const d = mixedDocket();
98
+ const srv = C.countsOf(d, TRAFFIC);
99
+ const living = L.livingModel({ docket: d, counts: srv, me: fx('me'), now: NOW });
100
+ const dm = K.deckModel({
101
+ living, docket: d, goals: [], now: NOW, viewerId: String(d.viewer.id), scope: null,
102
+ session: { done: {}, delegated: {}, skipped: [], extra: [], batch: null, noBatch: {} },
103
+ });
104
+ assert.equal(dm.lead.n, srv.on_you);
105
+ assert.equal(dm.remaining, srv.open);
106
+ });
107
+
108
+ // ── 3. the read ──────────────────────────────────────────────────────────────
109
+
110
+ test('countsFor reads the viewer\'s own docket and the traffic', async () => {
111
+ const seen = [];
112
+ const docket = { docketFor: async (b) => { seen.push(b.id); return fx('government__docket'); } };
113
+ const c = await C.countsFor({ id: '1000025' }, { docket, readTraffic: async () => TRAFFIC });
114
+ assert.deepEqual(seen, ['1000025']);
115
+ assert.equal(c.viewer.id, '1000025');
116
+ assert.equal(c.on_you, fx('government__docket').counts.yours);
117
+ assert.deepEqual([c.agents_at_work, c.moving], [5, 6]);
118
+ });
119
+
120
+ test('the route is gated like the docket, and lifecycle hands both traffic counts over its port', async () => {
121
+ const route = read('modules/government/routes/government.js');
122
+ assert.match(route, /router\.get\('\/government\/counts', api\.requireBuilder, api\.requirePermission\('board\.vote\.cast'\)/);
123
+ assert.match(read('modules/lifecycle/routes/moving.js'), /registerProvider\('lifecycle\.traffic', \{ countMoving: moving\.countMoving, countAgentsAtWork: moving\.countAgentsAtWork \}\)/);
124
+ assert.ok(JSON.parse(read('modules/lifecycle/module.json')).provides.includes('lifecycle.traffic'));
125
+ const prev = process.env.NODE_ENV;
126
+ process.env.NODE_ENV = 'test';
127
+ try {
128
+ const moving = require(path.join(ROOT, 'modules/lifecycle/moving.js'));
129
+ let sql = '';
130
+ const n = await moving.countAgentsAtWork({ pool: { query: async (q) => { sql = q; return { rows: [{ n: 4 }] }; } } });
131
+ assert.equal(n, 4);
132
+ assert.match(sql, /FROM claims c/);
133
+ assert.match(sql, /released_at IS NULL/, 'one agent per open claim, as GET /tasks/claimable lists them');
134
+ } finally { process.env.NODE_ENV = prev; }
135
+ });
136
+
137
+ // ── 4. the harness fixture ──────────────────────────────────────────────────
138
+
139
+ test('the harness serves the rule run over its own docket', () => {
140
+ const d = fx('government__docket');
141
+ const want = Object.assign({ viewer: d.viewer, generated_at: d.generated_at }, C.countsOf(d, { moving: fx('moving').moving, agents_at_work: fx('tasks__claimable').active_claims.length }));
142
+ assert.deepEqual(fx('government__counts'), want);
143
+ });
@@ -315,7 +315,10 @@ test('a PASSED decision-rules amendment writes the rows inside the closing trans
315
315
  const log = [];
316
316
  let written = null;
317
317
  const origApply = config.applyBoardAmendment;
318
- config.applyBoardAmendment = () => { throw new Error('a rules amendment must not rewrite the board block'); };
318
+ // closeItem catches an apply failure and only logs it, so a throwing stub would
319
+ // pass silently: count the calls instead (the government_laws.mjs pattern).
320
+ let boardWrites = 0;
321
+ config.applyBoardAmendment = () => { boardWrites += 1; return {}; };
319
322
  try {
320
323
  await withDb({
321
324
  getVotesForItem: async () => [{ voter_id: '42', direction: 'yes' }],
@@ -331,6 +334,7 @@ test('a PASSED decision-rules amendment writes the rows inside the closing trans
331
334
  assert.equal(out.applied_constitution, undefined);
332
335
  });
333
336
  } finally { config.applyBoardAmendment = origApply; }
337
+ assert.equal(boardWrites, 0, 'the board block was not rewritten');
334
338
  assert.deepEqual(log, ['committed']);
335
339
  assert.equal(String(written.itemId), '21');
336
340
  assert.deepEqual(written.rules.map((r) => [r.area, r.decided_by, r.rule]), [['people', 'rank:council', 'consent']]);