@bongos/core 1.19.221 → 1.19.222

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.
package/.bongos-core.json CHANGED
@@ -2,11 +2,11 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.221",
6
- "core_contract": "1.19.221",
7
- "source_commit": "30b2efb16495a941d6a4b70fd03b772cd69bce23",
5
+ "core_version": "1.19.222",
6
+ "core_contract": "1.19.222",
7
+ "source_commit": "4425d4ace9a3b996b50be1b59002e4cf85f027db",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-18T03:23:47.936Z",
9
+ "built_at": "2026-08-18T03:36:13.104Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 311,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 1635,
20
- "tree_sha256": "aa35386f9d066c748b4374cf27b4dc5fbf48ccfef32dfb8ef8c60cf44fdb8c96",
20
+ "tree_sha256": "6bf87d2a8bb013b102f3ae8287a56fd6fe7ed99ed49b48f4607abcb047f59f74",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -397,7 +397,7 @@
397
397
  {
398
398
  "path": "config/government.neutral.json",
399
399
  "mode": "0000644",
400
- "sha256": "70fe731d06dd9744178a27ec4379ee8c769f9a45d9a585f095b1765433767afc"
400
+ "sha256": "7ab2f7aff4bc52d09ee1a4aa3b76a7646ff2ee462bbc459941a468a9cc1246eb"
401
401
  },
402
402
  {
403
403
  "path": "config/modules.neutral.json",
@@ -2902,7 +2902,7 @@
2902
2902
  {
2903
2903
  "path": "modules/government/config.js",
2904
2904
  "mode": "0000644",
2905
- "sha256": "183bca92e8ba897871287aeb58e6f9cfebcf9510b15d6cd9701066ce7c108edd"
2905
+ "sha256": "dc4784b8d1f39c10c62ee935c6ff7d2d85db42f80479750e2ca2b8ca2877ebc6"
2906
2906
  },
2907
2907
  {
2908
2908
  "path": "modules/government/db.js",
@@ -4292,12 +4292,12 @@
4292
4292
  {
4293
4293
  "path": "package-lock.json",
4294
4294
  "mode": "0000644",
4295
- "sha256": "5b55ea236b725452ef02e308616d3827927f68e82a483022a856e4066864dc99"
4295
+ "sha256": "44f9bf150c24b1bed1d68bcd0a9661b4c659a32e5087a268a21b7b4370d7099a"
4296
4296
  },
4297
4297
  {
4298
4298
  "path": "package.json",
4299
4299
  "mode": "0000644",
4300
- "sha256": "ec221145b014962cce2d2c9fc9e231a0b296bb65153ba8079e30f2f4e80b9df7"
4300
+ "sha256": "231179c4f9a0fffdd0ecdb48ceb161e99e7aab4e3058296762561d510f15281a"
4301
4301
  },
4302
4302
  {
4303
4303
  "path": "public-docs/index.html",
@@ -5912,7 +5912,7 @@
5912
5912
  {
5913
5913
  "path": "src/module-api.js",
5914
5914
  "mode": "0000644",
5915
- "sha256": "4aa9331871262decd3e4b2a16e1cd9ca58b4e4000228cde02086ad209a1bb4b7"
5915
+ "sha256": "e999662a04f1981cbf01b2a9b5d18d33e71034311702fac6e5a6e0a0a209dcc0"
5916
5916
  },
5917
5917
  {
5918
5918
  "path": "src/module-loader/catalog.js",
@@ -6847,7 +6847,7 @@
6847
6847
  {
6848
6848
  "path": "tests/government_config.mjs",
6849
6849
  "mode": "0000644",
6850
- "sha256": "979deef4473243dc86ab13b9284104bce71f6a1ee839acc6c5a6db04770cd62c"
6850
+ "sha256": "330372412c80d8d33698eba68a5c19e79daff17b0d820a9a797c883071a5641f"
6851
6851
  },
6852
6852
  {
6853
6853
  "path": "tests/government_fold.mjs",
@@ -17,5 +17,12 @@
17
17
  "label": "Archon",
18
18
  "description": "Seeded rank \u2014 top authority. Adds the admin + system (non-grantable) powers."
19
19
  }
20
+ },
21
+ "_board_note": "The CONSTITUTION \u2014 the Board Room's rule set (BV1.R05 of goal 1000069, ADR 0175). Day one runs the real machinery configured to MONARCHY: membership 'rank:archon', pass_rule 'first_ratifier', no clock. That is a set of VALUES, never a separate code path \u2014 the same resolver, tally and window run whatever the numbers say, so flipping to a full board later changes configuration and not a branch that has never executed. RECOMMENDED once a project has a board worth sitting: all Archons, one vote each, pass_rule 'consent', window_minutes 90 \u2014 documented here, deliberately not the default. membership is the predicate 'rank:<key>'; it may name a CUSTOM rank, and WHO HOLDS that rank stays a DB row resolved per request (this file grants nobody anything). pass_rule is one of first_ratifier | consent. window_minutes is null for no clock, else whole minutes up to 7 days. Every unreadable field falls back to ITS OWN default, and the fallbacks are the STRICT end of both axes, so a malformed constitution can never resolve to a board that is wider or easier to pass than this one. To customise, copy to config/government.json and edit there \u2014 not by env var: a label is cosmetic, a constitution is not.",
22
+ "board": {
23
+ "membership": "rank:archon",
24
+ "pass_rule": "first_ratifier",
25
+ "window_minutes": null,
26
+ "close_early_on_full_turnout": true
20
27
  }
21
28
  }
@@ -126,6 +126,78 @@ function sanitizeRole(key, raw) {
126
126
  });
127
127
  }
128
128
 
129
+ // ── the constitution: who governs, how an item passes, how long a sitting runs ──
130
+ // BV1.R05 of goal 1000069 (ADR 0175). The Board Room's rule set is CONFIGURATION on
131
+ // the real machinery, never a separate code path: day one runs the same resolver,
132
+ // the same tally and the same window as a full consent board, with the numbers set
133
+ // to monarchy (owner decision 1). Flipping the switch later changes values, not a
134
+ // branch that has never executed.
135
+ //
136
+ // It lives here, beside the rank templates, for the same reason ADR 0175 put the
137
+ // board tables inside modules/government/: this is an AUTHORITY question, and
138
+ // authority is this module's territory.
139
+ //
140
+ // STILL NOT A SOURCE OF AUTHORITY TRUTH. Same wall as the rest of this file — the
141
+ // predicate says which RANK governs; who actually holds that rank stays a DB row,
142
+ // resolved per request by R06's membership resolver. Nothing here names a builder,
143
+ // and a config edit cannot make anyone a board member.
144
+ const BOARD_DEFAULTS = Object.freeze({
145
+ membership: 'rank:archon',
146
+ pass_rule: 'first_ratifier',
147
+ window_minutes: null, // null = no clock; monarchy does not sit
148
+ close_early_on_full_turnout: true,
149
+ });
150
+
151
+ // The pass rules that EXIST, not the ones a project should pick. `consent` is the
152
+ // documented recommendation once a project has a board worth sitting (all Archons,
153
+ // one vote each, consent, a 90-minute window); `first_ratifier` is the day-one
154
+ // default and what an unreadable value falls back to.
155
+ const PASS_RULES = Object.freeze(['first_ratifier', 'consent']);
156
+
157
+ // A membership predicate is `rank:<key>` — the only grammar v1 defines. It is NOT
158
+ // validated against catalog.RANK_ORDER on purpose: a project may govern by a CUSTOM
159
+ // rank, and custom ranks are DB rows this file cannot see (checking here would make
160
+ // config authoritative over a live rank list, which is exactly the inversion the
161
+ // header forbids). R06 resolves the predicate against the live ranks and yields an
162
+ // empty board if it names nothing — fail-closed there, shape-checked here.
163
+ const MEMBERSHIP_RE = /^rank:[a-z][a-z0-9_-]*$/;
164
+
165
+ // A sitting longer than a week is indistinguishable from no clock at all, and a
166
+ // zero/negative window would close every item the instant it opened. Both are far
167
+ // likelier to be a typo than an intent, so both fall back to the default.
168
+ const MAX_WINDOW_MINUTES = 7 * 24 * 60;
169
+
170
+ // Sanitize the board block. Every unreadable field falls back to ITS OWN default
171
+ // independently, so one typo costs one setting rather than the whole constitution.
172
+ //
173
+ // THE INVARIANT THAT MATTERS: a malformed constitution must never resolve to a board
174
+ // that is WIDER or EASIER to pass than the platform default. `rank:archon` is the
175
+ // narrowest membership this project can express, and `first_ratifier` demands an
176
+ // affirmative act by a member where `consent` lets an empty window pass unopposed
177
+ // (owner decision 6). So the fallbacks are the strict end of both axes — a broken
178
+ // config cannot quietly enfranchise anyone or lower the bar.
179
+ function sanitizeBoard(raw) {
180
+ const r = isObj(raw) ? raw : {};
181
+ const membership = typeof r.membership === 'string' && MEMBERSHIP_RE.test(r.membership)
182
+ ? r.membership
183
+ : BOARD_DEFAULTS.membership;
184
+ const passRule = PASS_RULES.includes(r.pass_rule) ? r.pass_rule : BOARD_DEFAULTS.pass_rule;
185
+ // null is a MEANINGFUL value (no clock), so it is accepted rather than defaulted.
186
+ // Anything else must be a whole number of minutes inside the sane band.
187
+ const w = r.window_minutes;
188
+ const windowMinutes = w === null ? null
189
+ : (Number.isInteger(w) && w > 0 && w <= MAX_WINDOW_MINUTES ? w : BOARD_DEFAULTS.window_minutes);
190
+ const closeEarly = typeof r.close_early_on_full_turnout === 'boolean'
191
+ ? r.close_early_on_full_turnout
192
+ : BOARD_DEFAULTS.close_early_on_full_turnout;
193
+ return Object.freeze({
194
+ membership,
195
+ pass_rule: passRule,
196
+ window_minutes: windowMinutes,
197
+ close_early_on_full_turnout: closeEarly,
198
+ });
199
+ }
200
+
129
201
  // Pure resolver — all inputs injected, so tests drive it with zero I/O (the
130
202
  // resolveBranding pattern).
131
203
  function resolveGovernmentConfig({ neutral, instance, env = process.env } = {}) {
@@ -146,7 +218,16 @@ function resolveGovernmentConfig({ neutral, instance, env = process.env } = {})
146
218
  for (const key of catalog.RANK_ORDER) {
147
219
  if (!ranks[key]) ranks[key] = sanitizeRole(key, {});
148
220
  }
149
- return Object.freeze({ ranks: Object.freeze(ranks) });
221
+ // The board block merges FIELD BY FIELD, neutral under instance, so a project that
222
+ // wants only a different window keeps the platform's answer for everything else
223
+ // rather than having to restate the whole constitution to change one number.
224
+ //
225
+ // Deliberately NOT env-overridable. applyLabelOverrides exists because a label is
226
+ // cosmetic; the constitution is not, and an env var that could change who governs
227
+ // would be a way to alter authority from outside the checkout AND outside the DB.
228
+ // Changing the constitution is a board act (owner decision 11), not a deploy flag.
229
+ const board = sanitizeBoard({ ...((neutral || {}).board || {}), ...((instance || {}).board || {}) });
230
+ return Object.freeze({ ranks: Object.freeze(ranks), board });
150
231
  }
151
232
 
152
233
  // READ, never require(): a bare require('../../config/X.json') of a boundary-excluded
@@ -179,6 +260,10 @@ module.exports = {
179
260
  resolveGovernmentConfig,
180
261
  loadGovernmentConfig,
181
262
  grantableOnly,
263
+ sanitizeBoard,
264
+ BOARD_DEFAULTS,
265
+ PASS_RULES,
266
+ MAX_WINDOW_MINUTES,
182
267
  NEUTRAL_PATH,
183
268
  INSTANCE_PATH,
184
269
  };
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.221",
3
+ "version": "1.19.222",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.221",
9
+ "version": "1.19.222",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.221",
3
+ "version": "1.19.222",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
package/src/module-api.js CHANGED
@@ -869,8 +869,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
869
869
  // landed since 1.19.219 with no explicit bump. run 32094701831. (task 1002620)
870
870
  // 1.19.221 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
871
871
  // landed since 1.19.220 with no explicit bump. run 32095276039. (task 1002620)
872
+ // 1.19.222 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
873
+ // landed since 1.19.221 with no explicit bump. run 32096034408. (task 1002620)
872
874
  // ---------------------------------------------------------------------------
873
- const CORE_VERSION = '1.19.221'; // CI auto-patch carrier (ADR 0161); see changelog above
875
+ const CORE_VERSION = '1.19.222'; // CI auto-patch carrier (ADR 0161); see changelog above
874
876
 
875
877
  // A namespaced logger so a module's log lines are attributable + consistent.
876
878
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -18,7 +18,8 @@ const GOV = path.join(ROOT, 'modules', 'government');
18
18
  const config = require(path.join(GOV, 'config.js'));
19
19
  const catalog = require(path.join(GOV, 'catalog.js'));
20
20
  const { envPrefix } = require(path.join(ROOT, 'src', 'instance-config'));
21
- const { resolveGovernmentConfig, loadGovernmentConfig, grantableOnly, NEUTRAL_PATH, INSTANCE_PATH } = config;
21
+ const { resolveGovernmentConfig, loadGovernmentConfig, grantableOnly, sanitizeBoard,
22
+ BOARD_DEFAULTS, PASS_RULES, MAX_WINDOW_MINUTES, NEUTRAL_PATH, INSTANCE_PATH } = config;
22
23
 
23
24
  const NEUTRAL = JSON.parse(fs.readFileSync(NEUTRAL_PATH, 'utf8'));
24
25
  // No env by default — every precedence test injects exactly the env it is asserting on.
@@ -158,11 +159,20 @@ test('the neutral starter carries no host-specific identity (a vanilla instance
158
159
  const { scanPromptIdentity } = require(path.join(ROOT, 'scripts', 'gds', 'fitness.js'));
159
160
  const raw = fs.readFileSync(NEUTRAL_PATH, 'utf8');
160
161
  assert.deepEqual(scanPromptIdentity('config/government.neutral.json', raw, '.json'), []);
161
- // And it carries only templates — never a live per-builder assignment.
162
- assert.deepEqual(Object.keys(NEUTRAL).filter((k) => k !== '_note'), ['ranks']);
162
+ // And it carries only templates — never a live per-builder assignment. The key list
163
+ // is an ALLOWLIST on purpose: a new top-level key has to be justified here before it
164
+ // can ship, which is what stops a live grant or assignment arriving unnoticed.
165
+ // `board` joined it in R05 (goal 1000069) — it is the CONSTITUTION, a rule shape, and
166
+ // the assertion below pins that it names no builder. `_`-prefixed keys are prose notes.
167
+ assert.deepEqual(Object.keys(NEUTRAL).filter((k) => !k.startsWith('_')).sort(), ['board', 'ranks']);
163
168
  for (const rank of Object.values(NEUTRAL.ranks)) {
164
169
  assert.ok(!('permissions' in rank), 'the neutral seeded ranks carry labels only, never grants');
165
170
  }
171
+ // The constitution says which RANK governs, never WHO. A builder id, login or handle
172
+ // here would make config authoritative over membership, which is R06's DB question.
173
+ assert.match(NEUTRAL.board.membership, /^rank:/, 'membership is a rank predicate, not a person');
174
+ assert.equal(JSON.stringify(NEUTRAL.board).includes('builder'), false,
175
+ 'the neutral constitution must not reference any builder');
166
176
  });
167
177
 
168
178
  test('the two config paths are the branding-shaped neutral/instance pair under config/', () => {
@@ -185,3 +195,137 @@ test('the neutral starter publishes with the core (a packaged vanilla instance n
185
195
  assert.equal(isPublishable('config/government.neutral.json'), true);
186
196
  assert.equal(isPublishable('config/government.json'), false, "a host's filled-in pack is instance content");
187
197
  });
198
+
199
+ // ── the constitution (BV1.R05 of goal 1000069, ADR 0175) ────────────────────────
200
+ //
201
+ // The board block is CONFIGURATION on the real machinery, not a second code path.
202
+ // These tests pin the two things that make that safe: the platform default really is
203
+ // monarchy, and a malformed constitution can never resolve to a board that is WIDER
204
+ // or EASIER to pass than that default. A config file is editable by anyone who can
205
+ // write the checkout — the same trust argument the permission tests above make.
206
+
207
+ test('day one is monarchy: the neutral starter ships the Archon-ratifies constitution', () => {
208
+ const out = resolveGovernmentConfig({ neutral: NEUTRAL, env: NO_ENV });
209
+ assert.deepEqual(out.board, {
210
+ membership: 'rank:archon',
211
+ pass_rule: 'first_ratifier',
212
+ window_minutes: null, // no clock — monarchy does not sit
213
+ close_early_on_full_turnout: true,
214
+ }, 'owner decision 1: the real machinery, configured to monarchy');
215
+ assert.deepEqual(NEUTRAL.board, out.board, 'the shipped file and the resolved value must agree');
216
+ });
217
+
218
+ test('a project with no board block at all still gets the full default constitution', () => {
219
+ // The vanilla-boot case, and the upgrade case: an instance whose config predates
220
+ // R05 has no board key. It must resolve to monarchy, not to undefined fields that
221
+ // R06 would then have to guess at.
222
+ const out = resolveGovernmentConfig({ neutral: {}, instance: {}, env: NO_ENV });
223
+ assert.deepEqual(out.board, BOARD_DEFAULTS);
224
+ });
225
+
226
+ test('the instance pack overrides the constitution FIELD BY FIELD', () => {
227
+ // Changing one number must not require restating the whole constitution.
228
+ const out = resolveGovernmentConfig({
229
+ neutral: NEUTRAL,
230
+ instance: { board: { pass_rule: 'consent', window_minutes: 90 } },
231
+ env: NO_ENV,
232
+ });
233
+ assert.equal(out.board.pass_rule, 'consent');
234
+ assert.equal(out.board.window_minutes, 90);
235
+ assert.equal(out.board.membership, 'rank:archon', 'untouched fields keep the neutral answer');
236
+ assert.equal(out.board.close_early_on_full_turnout, true);
237
+ });
238
+
239
+ test('the documented recommendation is expressible as pure configuration', () => {
240
+ // "all Archons, one vote each, consent, a 90-minute window" — the spec's recommended
241
+ // starting point. If this could not be reached by config alone, the "values not code
242
+ // paths" claim would be false.
243
+ const out = resolveGovernmentConfig({
244
+ neutral: NEUTRAL,
245
+ instance: { board: { membership: 'rank:archon', pass_rule: 'consent', window_minutes: 90 } },
246
+ env: NO_ENV,
247
+ });
248
+ assert.deepEqual(out.board, {
249
+ membership: 'rank:archon',
250
+ pass_rule: 'consent',
251
+ window_minutes: 90,
252
+ close_early_on_full_turnout: true,
253
+ });
254
+ });
255
+
256
+ test('a malformed constitution can never widen the franchise or lower the bar', () => {
257
+ // The invariant, enumerated over every junk shape a hand-edited file can take.
258
+ // rank:archon is the narrowest membership expressible; first_ratifier demands an
259
+ // affirmative act by a member where consent lets an empty window pass (decision 6).
260
+ const junk = [
261
+ undefined, null, 42, 'consent', [],
262
+ { membership: 'everyone' },
263
+ { membership: 'rank:' },
264
+ { membership: 'RANK:ARCHON' }, // grammar is lowercase
265
+ { membership: '' },
266
+ { membership: { rank: 'archon' } },
267
+ { pass_rule: 'unanimous' },
268
+ { pass_rule: 'majority' },
269
+ { pass_rule: null },
270
+ { membership: 'rank:xenos; DROP TABLE government_ranks' },
271
+ ];
272
+ for (const raw of junk) {
273
+ const b = sanitizeBoard(raw);
274
+ assert.equal(b.membership, BOARD_DEFAULTS.membership, `${JSON.stringify(raw)} must not widen membership`);
275
+ assert.equal(b.pass_rule, BOARD_DEFAULTS.pass_rule, `${JSON.stringify(raw)} must not lower the pass bar`);
276
+ }
277
+ });
278
+
279
+ test('one unreadable field costs one setting, not the whole constitution', () => {
280
+ const b = sanitizeBoard({ membership: 'rank:metic', pass_rule: 'nonsense', window_minutes: 30 });
281
+ assert.equal(b.membership, 'rank:metic', 'a VALID field survives its neighbour being junk');
282
+ assert.equal(b.window_minutes, 30);
283
+ assert.equal(b.pass_rule, BOARD_DEFAULTS.pass_rule, 'only the broken field falls back');
284
+ });
285
+
286
+ test('membership accepts a CUSTOM rank — config cannot be authoritative over the live rank list', () => {
287
+ // Deliberately NOT validated against catalog.RANK_ORDER: custom ranks are DB rows
288
+ // this file cannot see, and checking here would invert the config/DB boundary.
289
+ // R06 resolves the predicate against live ranks and yields an empty board if it
290
+ // names nothing, so the fail-closed step happens where the truth lives.
291
+ assert.equal(sanitizeBoard({ membership: 'rank:triage-helper' }).membership, 'rank:triage-helper');
292
+ assert.equal(sanitizeBoard({ membership: 'rank:board_member' }).membership, 'rank:board_member');
293
+ });
294
+
295
+ test('window_minutes: null means no clock, and only a sane whole-minute band is accepted', () => {
296
+ assert.equal(sanitizeBoard({ window_minutes: null }).window_minutes, null, 'null is MEANINGFUL, not missing');
297
+ assert.equal(sanitizeBoard({ window_minutes: 90 }).window_minutes, 90);
298
+ assert.equal(sanitizeBoard({ window_minutes: MAX_WINDOW_MINUTES }).window_minutes, MAX_WINDOW_MINUTES);
299
+ for (const bad of [0, -5, 1.5, '90', NaN, Infinity, MAX_WINDOW_MINUTES + 1]) {
300
+ assert.equal(sanitizeBoard({ window_minutes: bad }).window_minutes, BOARD_DEFAULTS.window_minutes,
301
+ `window_minutes ${String(bad)} must fall back — a zero window closes on open, a huge one is not a sitting`);
302
+ }
303
+ });
304
+
305
+ test('no env var can change the constitution', () => {
306
+ // applyLabelOverrides exists because a label is cosmetic. Authority is not: an env
307
+ // var that could move the board would be a way to change who governs from outside
308
+ // both the checkout and the DB. Changing it is a board act (owner decision 11).
309
+ const p = envPrefix();
310
+ const hostile = {
311
+ [`${p}_GOVERNMENT_BOARD_MEMBERSHIP`]: 'rank:xenos',
312
+ [`${p}_GOVERNMENT_BOARD_PASS_RULE`]: 'consent',
313
+ [`${p}_GOVERNMENT_BOARD`]: JSON.stringify({ membership: 'rank:xenos' }),
314
+ [`${p}_GOVERNMENT_RANK_LABELS`]: 'archon=Owner',
315
+ };
316
+ const out = resolveGovernmentConfig({ neutral: NEUTRAL, env: hostile });
317
+ assert.deepEqual(out.board, NEUTRAL.board, 'the constitution is untouched by env');
318
+ assert.equal(out.ranks.archon.label, 'Owner', 'while the LABEL override still works — proving env was read');
319
+ });
320
+
321
+ test('the resolved constitution is frozen, like the rest of the config contract', () => {
322
+ const out = resolveGovernmentConfig({ neutral: NEUTRAL, env: NO_ENV });
323
+ assert.ok(Object.isFrozen(out.board));
324
+ assert.throws(() => { 'use strict'; out.board.membership = 'rank:xenos'; });
325
+ });
326
+
327
+ test('PASS_RULES is the closed set the rest of the goal codes against', () => {
328
+ assert.deepEqual([...PASS_RULES].sort(), ['consent', 'first_ratifier']);
329
+ assert.ok(Object.isFrozen(PASS_RULES));
330
+ assert.ok(PASS_RULES.includes(BOARD_DEFAULTS.pass_rule), 'the default must be a member of the set');
331
+ });