@bongos/core 1.21.87 → 1.21.89

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.
@@ -0,0 +1,220 @@
1
+ // tests/genesis_government.mjs — THE GOVERNMENT PICKED AT GENESIS IS THE ONE IN FORCE
2
+ // (task 1004656, goal 1000111 criterion gov-born-governed).
3
+ //
4
+ // WHAT IT GUARDS. A founder picks a starting government on the genesis decide screen.
5
+ // Before this task the pick was stored in the plan and never read again, so every
6
+ // project left genesis on the default monarchy it never chose. Now saving the plan
7
+ // files the pick as a charter amendment under the constitution in force, and under the
8
+ // monarchy a project is born with, the founder's own yes applies it. What can go wrong:
9
+ // * the pick saved but nothing filed (the old bug), or filed and never applied;
10
+ // * a pick that cannot be filed refusing the whole plan (the plan must always save);
11
+ // * Foundation filed with no council, which would seat nobody;
12
+ // * the hall's Charter picker filing a constitution the server would not build.
13
+ //
14
+ // The path RUNS end to end over the real code: genesis-home's savePlan with the REAL
15
+ // government port's proposeCharter, castVote and close (db stubbed, the board's own
16
+ // logic real), and the ratified text written through the stubbed apply. The hall
17
+ // picker runs the real street-amend.js over the server's real charter view.
18
+ //
19
+ // Run: node --test tests/genesis_government.mjs
20
+
21
+ import assert from 'node:assert/strict';
22
+ import { test } from 'node:test';
23
+ import { createRequire } from 'node:module';
24
+ import path from 'node:path';
25
+ import { fileURLToPath } from 'node:url';
26
+
27
+ const require = createRequire(import.meta.url);
28
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
29
+ const GOV = path.join(ROOT, 'modules', 'government');
30
+ const gh = require(path.join(ROOT, 'modules', 'onboarding', 'genesis-home.js'));
31
+ const board = require(path.join(GOV, 'board.js'));
32
+ const db = require(path.join(GOV, 'db.js'));
33
+ const config = require(path.join(GOV, 'config.js'));
34
+ const charter = require(path.join(GOV, 'charter.js'));
35
+ const DR = require(path.join(GOV, 'decision-rules.js'));
36
+ const A = require(path.join(ROOT, 'modules', 'hall-ui', 'public', 'street-amend.js'));
37
+
38
+ const MONARCHY = Object.freeze({ membership: 'rank:archon', pass_rule: 'first_ratifier', window_minutes: null, close_early_on_full_turnout: true });
39
+ const FOUNDER = '42';
40
+ const j = (v) => JSON.parse(JSON.stringify(v));
41
+
42
+ // A founding project in its decide stage: an in-memory project_settings store, a fake
43
+ // lifecycle seeder, and the REAL government port surface genesis-home calls.
44
+ function project() {
45
+ const store = new Map();
46
+ const deps = {
47
+ getSetting: async (k) => (store.has(k) ? { key: k, value: store.get(k) } : null),
48
+ setSetting: async (k, v) => { store.set(k, String(v)); return { key: k, value: String(v) }; },
49
+ lifecycle: { seedGenesis: async () => ({ seeded: 5 }), genesisTasks: async () => ({}) },
50
+ government: {
51
+ proposeCharter: board.proposeCharter,
52
+ genesisStageStatus: async () => ({ stages: [] }),
53
+ },
54
+ now: new Date('2026-10-08T00:00:00Z'),
55
+ };
56
+ return { store, deps };
57
+ }
58
+
59
+ // The board's db, stubbed to one founder under the monarchy a project is born with;
60
+ // everything that decides (validation, the self-removal guard, the tally, the close)
61
+ // is the real board.js. Returns what was filed and what was applied.
62
+ async function underMonarchy(fn) {
63
+ const seen = { filed: null, applied: [], votes: [] };
64
+ const originals = {};
65
+ const stubs = {
66
+ builderHoldsRankActive: async () => true,
67
+ getRankByKey: async () => null,
68
+ createAmendment: async (args) => { seen.filed = args; return { id: '5', proposed: args.proposed, proposed_by: args.proposedBy }; },
69
+ openBoardItem: async (args) => ({ id: '21', subject_type: args.subjectType, subject_id: '5', opened_by: args.openedBy, constitution: args.constitution, closes_at: null, closed_at: null }),
70
+ getBoardItemForVote: async () => ({ id: '21', subject_type: 'constitutional_amendment', subject_id: '5', opened_by: FOUNDER, constitution: MONARCHY, closed_at: null, expired: false }),
71
+ insertBoardVote: async (row) => { const v = { voter_id: row.voterId, direction: row.direction }; seen.votes.push(v); return v; },
72
+ insertVoteKarma: async () => {},
73
+ getVotesForItem: async () => seen.votes,
74
+ getActiveBuildersWithRank: async () => [{ builder_id: FOUNDER, github_login: 'founder', display_name: 'Founder' }],
75
+ getBoardItemAuthor: async () => FOUNDER,
76
+ closeBoardItem: async ({ outcome }) => ({ id: '21', subject_type: 'constitutional_amendment', subject_id: '5', opened_by: FOUNDER, closed_at: 'T', outcome }),
77
+ insertAuthorRatifyKarma: async () => true,
78
+ getAmendment: async () => ({ id: '5', proposed: seen.filed.proposed }),
79
+ };
80
+ for (const [k, v] of Object.entries(stubs)) { originals[k] = db[k]; db[k] = v; }
81
+ const originalApply = config.applyBoardAmendment;
82
+ config.applyBoardAmendment = (proposed) => { seen.applied.push(proposed); return proposed; };
83
+ try { await fn(seen); } finally {
84
+ for (const [k, v] of Object.entries(originals)) db[k] = v;
85
+ config.applyBoardAmendment = originalApply;
86
+ }
87
+ return seen;
88
+ }
89
+
90
+ async function decide(plan) {
91
+ const p = project();
92
+ assert.equal((await gh.startFounding(FOUNDER, p.deps)).ok, true);
93
+ const out = await gh.savePlan(plan, FOUNDER, p.deps);
94
+ return { p, out };
95
+ }
96
+
97
+ test('this checkout is born a monarchy, the state every genesis starts from', () => {
98
+ assert.equal(charter.readCharter(config.loadGovernmentConfig().board).form, 'monarchy');
99
+ });
100
+
101
+ for (const form of ['timocratic-republic', 'direct-democracy']) {
102
+ test(`picking ${form} at decide files it, the founder's one yes applies it, and the constitution reads as ${form}`, async () => {
103
+ const seen = await underMonarchy(async () => {
104
+ const { out } = await decide({ government: form });
105
+ assert.equal(out.ok, true, 'the plan saves');
106
+ assert.equal(out.charter.state, 'sitting', 'the pick is filed, not just recorded');
107
+ assert.equal(out.charter.one_vote_decides, true, 'under the monarchy the founder\'s yes carries it');
108
+ assert.equal(out.charter.item_id, '21');
109
+ // what the hall does next: the founder's yes, then the close
110
+ await board.castVote({ itemId: out.charter.item_id, voterId: FOUNDER, direction: 'yes' });
111
+ assert.equal((await board.maybeCloseAfterVote(out.charter.item_id)).outcome, 'passed');
112
+ });
113
+ assert.equal(seen.applied.length, 1, 'the ratified constitution is applied once');
114
+ assert.equal(charter.readCharter(seen.applied[0]).form, form, 'the constitution in force is the government the founder picked');
115
+ assert.match(seen.filed.rationaleMd, /^Charter: /, 'the record says in words what was chosen');
116
+ });
117
+ }
118
+
119
+ test('picking Monarchy files nothing: it is already the government in force', async () => {
120
+ const seen = await underMonarchy(async () => {
121
+ const { out } = await decide({ government: 'monarchy' });
122
+ assert.equal(out.ok, true);
123
+ assert.deepEqual(out.charter, { state: 'in_force', form: 'monarchy' });
124
+ });
125
+ assert.equal(seen.filed, null);
126
+ });
127
+
128
+ test('Foundation needs a council first: the plan saves, nothing is filed, and the reason is named', async () => {
129
+ const seen = await underMonarchy(async () => {
130
+ const { p, out } = await decide({ government: 'foundation' });
131
+ assert.equal(out.ok, true, 'the plan is never refused over the government');
132
+ assert.deepEqual(out.charter, { state: 'needs_council', form: 'foundation' });
133
+ assert.equal(JSON.parse(p.store.get(gh.PLAN_KEY)).government, 'foundation', 'the pick is still recorded');
134
+ });
135
+ assert.equal(seen.filed, null, 'a council nobody sits on is never filed');
136
+ });
137
+
138
+ test('no pick, no government module, a refusal, a throw: the plan still saves and says what happened', async () => {
139
+ let r = await decide({});
140
+ assert.deepEqual(r.out.charter, { state: 'none' });
141
+ const p = project();
142
+ await gh.startFounding(FOUNDER, p.deps);
143
+ p.deps.government = { genesisStageStatus: async () => ({ stages: [] }) };
144
+ r = { out: await gh.savePlan({ government: 'direct-democracy' }, FOUNDER, p.deps) };
145
+ assert.equal(r.out.ok, true);
146
+ assert.deepEqual(r.out.charter, { state: 'not_filed', form: 'direct-democracy', reason: 'no_government' });
147
+ for (const [proposeCharter, reason] of [
148
+ [async () => ({ ok: false, reason: 'unknown_form' }), 'unknown_form'],
149
+ [async () => { throw new Error('board down'); }, 'charter_failed'],
150
+ ]) {
151
+ const q = project();
152
+ await gh.startFounding(FOUNDER, q.deps);
153
+ q.deps.government = { proposeCharter, genesisStageStatus: async () => ({ stages: [] }) };
154
+ const out = await gh.savePlan({ government: 'direct-democracy' }, FOUNDER, q.deps);
155
+ assert.equal(out.ok, true, `${reason}: the plan saves`);
156
+ assert.equal(out.charter.state, 'not_filed');
157
+ assert.equal(out.charter.reason, reason);
158
+ }
159
+ });
160
+
161
+ test('a demo stays the monarchy it is born with (owner ruling 2026-10-01): nothing is filed', () => {
162
+ assert.equal(gh.demoPlan({ people: 3, hours_each: 4 }).government, 'monarchy');
163
+ });
164
+
165
+ // ── the hall's Charter picker ───────────────────────────────────────────────
166
+
167
+ function constitutionView(boardBlock = MONARCHY) {
168
+ return j({
169
+ board: boardBlock,
170
+ charter: charter.charterView(boardBlock),
171
+ decision_rules: {
172
+ areas: DR.resolveAll([], { board: boardBlock }),
173
+ rules: DR.DECISION_RULES.map((r) => ({ key: r.key, words: r.words })),
174
+ changed_by: 'a passed amendment only',
175
+ },
176
+ });
177
+ }
178
+
179
+ test('the Charter offers the forms that can be adopted in one tap; Foundation and the later forms are named, not offered', () => {
180
+ const d = A.draftFor('charter', constitutionView());
181
+ assert.deepEqual(d.forms.pick.map((f) => f.words), ['Monarchy / BDFL', 'Timocratic Republic', 'Direct Democracy']);
182
+ assert.deepEqual(d.forms.council, ['Foundation']);
183
+ assert.deepEqual(d.forms.later, ['Presidential', 'Cooperative', 'Federation']);
184
+ assert.equal(A.draftFor('review', constitutionView()).forms.pick.length, 0, 'only the Charter carries forms');
185
+ });
186
+
187
+ test('a tapped form sets who and how together, files through the charter route, and the server builds the same constitution', () => {
188
+ for (const key of ['timocratic-republic', 'direct-democracy']) {
189
+ let d = A.draftFor('charter', constitutionView());
190
+ d = A.pick(d, 'form', key);
191
+ assert.equal(A.formOf(d).value, key, 'the lit chip is the form the settings make');
192
+ d = A.pick(d, 'reason', 'More of us should decide.');
193
+ const req = A.request(d);
194
+ assert.equal(req.url, '/api/bongos/government/board/charter');
195
+ assert.deepEqual(req.body, { form: key, rationale_md: 'More of us should decide.' });
196
+ // what the server files for that body
197
+ const built = charter.buildCharterProposal(MONARCHY, { form: key });
198
+ assert.equal(built.ok, true);
199
+ assert.equal(built.proposal.membership, d.decided_by, `${key}: the seat the hall showed is the seat the server files`);
200
+ assert.equal(charter.readCharter(built.proposal).form, key);
201
+ assert.match(A.answer(d, { item: { id: 7 } }).toast, new RegExp(A.formOf(d).words.replace(/[/]/g, '\\/')));
202
+ }
203
+ });
204
+
205
+ test('picking who or how by hand after a form lights no form, and files as a plain amendment', () => {
206
+ let d = A.pick(A.draftFor('charter', constitutionView()), 'form', 'direct-democracy');
207
+ d = A.pick(d, 'rule', 'majority');
208
+ assert.equal(A.formOf(d), null, 'the name follows the settings: a mix is no named form');
209
+ d = A.pick(d, 'reason', 'A majority, not consent.');
210
+ assert.equal(A.request(d).url, '/api/bongos/government/board/items');
211
+ assert.equal(A.pick(d, 'form', 'presidential'), d, 'a form outside the offered set changes nothing');
212
+ });
213
+
214
+ test('the charter route\'s refusals are said in words', () => {
215
+ const d = A.draftFor('charter', constitutionView());
216
+ const say = (reason) => A.refusal(400, { error: { code: 'invalid_charter', details: { reason } } });
217
+ assert.match(say('council_needs_a_rank'), /council rank/);
218
+ assert.match(say('form_not_available'), /not available yet/);
219
+ assert.ok(d.ok);
220
+ });