@bongos/core 1.19.182 → 1.19.184

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 (64) hide show
  1. package/.bongos-core.json +81 -76
  2. package/docs/api/openapi.json +9 -9
  3. package/modules/{governance → government}/catalog.js +1 -1
  4. package/modules/{governance → government}/config.js +6 -6
  5. package/modules/{governance → government}/db.js +43 -43
  6. package/modules/government/migrations/government_001_rename_from_governance.sql +213 -0
  7. package/modules/government/module.json +22 -0
  8. package/modules/{governance → government}/protected-surfaces.json +2 -1
  9. package/modules/{governance/rank-roles.js → government/ranks.js} +9 -9
  10. package/modules/{governance → government}/routes/governance.js +5 -5
  11. package/modules/{governance/routes/role-sync.js → government/routes/rank-sync.js} +7 -7
  12. package/modules/lifecycle/db.js +6 -6
  13. package/package-lock.json +2 -2
  14. package/package.json +1 -1
  15. package/scripts/gds/fitness.js +11 -1
  16. package/scripts/gds/migration-namespace.js +15 -3
  17. package/scripts/gds/publish-manifest.js +5 -3
  18. package/scripts/gds/seed-governance-tasks.js +2 -2
  19. package/src/bongos/auth.js +9 -9
  20. package/src/bongos/db.js +6 -6
  21. package/src/bongos/module-scope-map.js +22 -1
  22. package/src/bongos/permission-path-check.js +3 -3
  23. package/src/bongos/route-rank-check.js +40 -21
  24. package/src/module-api.js +6 -2
  25. package/tests/auth_page_gate.mjs +2 -2
  26. package/tests/criterion_authoring_gate.mjs +1 -1
  27. package/tests/governance_abuse_matrix.mjs +7 -7
  28. package/tests/governance_catalog.mjs +1 -1
  29. package/tests/governance_claim_eligibility.mjs +1 -1
  30. package/tests/governance_config.mjs +7 -7
  31. package/tests/governance_fold.mjs +4 -4
  32. package/tests/governance_live_proof.mjs +2 -2
  33. package/tests/governance_ownership_scope.mjs +1 -1
  34. package/tests/governance_page_gates.mjs +6 -6
  35. package/tests/governance_parity.mjs +3 -3
  36. package/tests/governance_principals.mjs +1 -1
  37. package/tests/governance_protected_surfaces.mjs +63 -7
  38. package/tests/governance_require_permission.mjs +6 -6
  39. package/tests/governance_resolver.mjs +2 -2
  40. package/tests/governance_role_sync.mjs +14 -14
  41. package/tests/governance_routes.mjs +6 -6
  42. package/tests/governance_seed.mjs +11 -3
  43. package/tests/governance_session_scope.mjs +1 -1
  44. package/tests/governance_vanilla_boot.mjs +4 -4
  45. package/tests/hall_page_gate_map.mjs +1 -1
  46. package/tests/memory_isolation.mjs +2 -2
  47. package/tests/module_loader.mjs +1 -1
  48. package/tests/onboarding_observability.mjs +2 -2
  49. package/tests/rank_tier_single_source.mjs +2 -2
  50. package/tests/route_permission_check.mjs +2 -2
  51. package/modules/governance/module.json +0 -11
  52. /package/config/{governance.neutral.json → government.neutral.json} +0 -0
  53. /package/modules/{governance → government}/CLAUDE.md +0 -0
  54. /package/modules/{governance → government}/migrations/governance_001_tables.sql +0 -0
  55. /package/modules/{governance → government}/migrations/governance_002_seed_and_backfill.sql +0 -0
  56. /package/modules/{governance → government}/migrations/governance_003_task_abandon_metic.sql +0 -0
  57. /package/modules/{governance → government}/migrations/governance_004_criterion_create.sql +0 -0
  58. /package/modules/{governance → government}/migrations/governance_005_resync_builder_rank_roles.sql +0 -0
  59. /package/modules/{governance → government}/migrations/governance_006_version_create.sql +0 -0
  60. /package/modules/{governance → government}/migrations/governance_007_delegate_archon_ops_to_metic.sql +0 -0
  61. /package/modules/{governance → government}/protected-surfaces.js +0 -0
  62. /package/modules/{governance → government}/resolver.js +0 -0
  63. /package/modules/{governance → government}/session-scopes.js +0 -0
  64. /package/modules/{governance → government}/session-scopes.json +0 -0
@@ -220,7 +220,7 @@ function xenosClaimAllowed(rank, newcomerFriendly) {
220
220
  // 0016 — resolved server-side, uncached), and taking a second pool connection while
221
221
  // a tx holds FOR UPDATE locks is a needless deadlock/exhaustion hazard.
222
222
  async function claimPermissionsFor(builderId) {
223
- const governance = seams.resolveOptional('governance');
223
+ const governance = seams.resolveOptional('government');
224
224
  if (!governance || typeof governance.resolveBuilderPermissions !== 'function') return null;
225
225
  try {
226
226
  const keys = await governance.resolveBuilderPermissions(builderId);
@@ -408,12 +408,12 @@ function disambiguateWorktreeName(desired, taken) {
408
408
  //
409
409
  // Returns the updated builder row, or throws { code:'BUILDER_NOT_FOUND' } /
410
410
  // { code:'RANK_UNCHANGED' }.
411
- // syncGovernanceRankRole — keep governance_builder_roles in step with builders.rank
411
+ // syncGovernanceRankRole — keep government_builder_ranks in step with builders.rank
412
412
  // (BV1.R95b, ADR 0151). This module's rank writer backs the automatic xenos→thetes
413
413
  // graduation, which had the same hole as the Archon-driven path: the rank moved and
414
414
  // the role did not, so a freshly-graduated Thetes kept the xenos role.
415
415
  //
416
- // Resolved through the `governance.roleSync` kernel port (never a sibling require —
416
+ // Resolved through the `government.rankSync` kernel port (never a sibling require —
417
417
  // ADR 0083) and run on the CALLER's transaction client, so the rotation commits
418
418
  // atomically with the ship + graduation. A missing port (governance disabled) skips:
419
419
  // no role ⇒ zero permissions, fail-closed.
@@ -421,9 +421,9 @@ function disambiguateWorktreeName(desired, taken) {
421
421
  // TWINNED with src/bongos/db.js (the boundary-forced _applyRankChangeTx duplication,
422
422
  // ADR 0091 §2) — keep the two in sync.
423
423
  async function syncGovernanceRankRole(client, builderId, rank) {
424
- const roleSync = seams.resolveOptional('governance.roleSync');
425
- if (!roleSync || typeof roleSync.syncRankRole !== 'function') return null;
426
- return roleSync.syncRankRole(client, { builderId, rank });
424
+ const roleSync = seams.resolveOptional('government.rankSync');
425
+ if (!roleSync || typeof roleSync.syncRank !== 'function') return null;
426
+ return roleSync.syncRank(client, { builderId, rank });
427
427
  }
428
428
 
429
429
  // _applyRankChangeTx — the in-transaction CORE of a rank change, shared by the
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.182",
3
+ "version": "1.19.184",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.182",
9
+ "version": "1.19.184",
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.182",
3
+ "version": "1.19.184",
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",
@@ -732,6 +732,16 @@ const LEGACY_NAMESPACED = {
732
732
  // game module: world_events + players were created before the module system (V1 schema). The
733
733
  // schema_migrations table is a core tracking table that every migration may re-create idempotently.
734
734
  game: new Set(['world_events', 'players', 'schema_migrations']),
735
+ // government module: the module KEY was renamed governance→government (ADR 0174, goal 1000068),
736
+ // so its own historical migrations now read as "outside the namespace" purely because the key
737
+ // moved underneath them. These three tables ARE this module's — under the name it used to have.
738
+ // They are not renamed in place because schema_migrations is keyed by filename STEM: renaming an
739
+ // already-applied migration file makes it look unapplied and re-runs it, and governance_002 is a
740
+ // seed/backfill. government_001_rename_from_governance.sql renames the live tables forward; these
741
+ // entries let the historical files that created them keep describing what actually happened.
742
+ government: new Set([
743
+ 'governance_roles', 'governance_builder_roles', 'governance_role_permissions',
744
+ ]),
735
745
  };
736
746
  const DESTRUCTIVE_DDL = [
737
747
  { re: /\bdrop\s+table\b/i, label: 'DROP TABLE' },
@@ -1141,7 +1151,7 @@ function checkKernelPromptsNoHardcodedIdentity() {
1141
1151
  // this check nothing in CI could see it.
1142
1152
  //
1143
1153
  // Only the NEUTRAL packs are scanned, deliberately: an instance pack (config/
1144
- // branding.json, config/governance.json) is host content that legitimately names
1154
+ // branding.json, config/government.json) is host content that legitimately names
1145
1155
  // its host, and is boundary-excluded from the published core anyway.
1146
1156
  function checkNeutralConfigsNoHostIdentity() {
1147
1157
  const violations = [];
@@ -57,6 +57,15 @@ const LEGACY_BARE_CORE_MAX = 187;
57
57
  // module migrations must be `<key>_`-prefixed; these stay bare (already applied on prod).
58
58
  const LEGACY_BARE_MODULES = new Set(['game', 'demo']);
59
59
 
60
+ // RENAMED_MODULE_PREFIXES: a module whose KEY was renamed keeps its historical
61
+ // migration-filename prefix for files that were already applied. schema_migrations
62
+ // is keyed by filename STEM, so renaming an applied migration makes it look
63
+ // unapplied and RE-RUNS it — and a re-run seed/backfill is a data event, not a
64
+ // no-op. The already-applied files therefore keep the old prefix forever; only
65
+ // NEW migrations must carry the module's current namespace.
66
+ // government <- governance (ADR 0174, goal 1000068)
67
+ const RENAMED_MODULE_PREFIXES = { government: new Set(['governance']) };
68
+
60
69
  // ---- parsing ---------------------------------------------------------------
61
70
 
62
71
  const BARE_RE = /^(\d{3,})_(.+)\.sql$/;
@@ -116,9 +125,11 @@ function filenameViolation(source, basename, { moduleKey } = {}) {
116
125
  ? null // legacy bare module — grandfathered
117
126
  : `module "${moduleKey}" migration "${basename}" is bare — a NEW module migration must be ${ns}_-prefixed (ADR 0083 §Decision #5 / ADR 0108 §5): use ${ns}_${pad3(p.number)}_${p.slug}.sql`;
118
127
  }
119
- return p.prefix === ns
120
- ? null
121
- : `module "${moduleKey}" migration "${basename}" carries the "${p.prefix}_" prefix — it must be ${ns}_-prefixed to its own module (ADR 0083 §Decision #5).`;
128
+ if (p.prefix === ns) return null;
129
+ // A renamed module's already-applied files keep their historical prefix (see
130
+ // RENAMED_MODULE_PREFIXES) — the stem is the schema_migrations key, so it cannot move.
131
+ if ((RENAMED_MODULE_PREFIXES[moduleKey] || new Set()).has(p.prefix)) return null;
132
+ return `module "${moduleKey}" migration "${basename}" carries the "${p.prefix}_" prefix — it must be ${ns}_-prefixed to its own module (ADR 0083 §Decision #5).`;
122
133
  }
123
134
 
124
135
  // ---- cross-root stem-collision detector ------------------------------------
@@ -186,6 +197,7 @@ function lineageReport({ core = [], instance = [], modulesByKey = {} } = {}) {
186
197
 
187
198
  module.exports = {
188
199
  CORE_NS,
200
+ RENAMED_MODULE_PREFIXES,
189
201
  INSTANCE_NS,
190
202
  RESERVED_NS,
191
203
  LEGACY_BARE_CORE_MAX,
@@ -61,7 +61,8 @@ const PUBLISH_ALLOWLIST = [
61
61
  'modules/sessions/',
62
62
  'modules/autonomy/',
63
63
  'modules/security/',
64
- 'modules/governance/', // configurable roles & permissions (BV1 goal 1000030, ADR 0151) — the rank/permission machinery publishes AS the open-source methodology (ADR 0065 §4 / ADR 0098); it is in PROTECTED_GLOBS for write-protection, NOT publish-exclusion. default:true.
64
+ 'modules/governance/', // configurable roles & permissions (BV1 goal 1000030, ADR 0151) — the rank/permission machinery publishes AS the open-source methodology (ADR 0065 §4 / ADR 0098); it is in PROTECTED_GLOBS for write-protection, NOT publish-exclusion. default:true. Renamed → modules/government/ (ADR 0174); dropped with the folder in goal 1000068 R04.
65
+ 'modules/government/', // the same machinery under its post-ADR-0174 name (goal 1000068, task 1003003). Added in lockstep with the protected-surfaces.json entry: publish_manifest asserts every PROTECTED_GLOB is either topology-excluded or publishable, so registering the glob as protected WITHOUT this line default-denies the module from the published core — the open-source methodology would silently lose its permission machinery at the next release.
65
66
  'modules/hall-ui/',
66
67
  'modules/status-ui/',
67
68
  'modules/dev-box/',
@@ -74,7 +75,7 @@ const PUBLISH_ALLOWLIST = [
74
75
  'config/design-tokens.neutral.json',
75
76
  'config/design-tokens.schema.json', // the design-token contract (generic schema, no instance values)
76
77
  'config/hierarchy.neutral.json',
77
- 'config/governance.neutral.json', // the starting role templates a fresh instance inherits (BV1.R96)
78
+ 'config/government.neutral.json', // the starting rank templates a fresh instance inherits (BV1.R96; renamed from governance.neutral.json by ADR 0174)
78
79
  // --- the portable methodology core, governance, and sanitized docs ---
79
80
  'CLAUDE.md',
80
81
  'README.md',
@@ -211,7 +212,8 @@ const INSTANCE_EXCLUDE = [
211
212
  'config/modules.json',
212
213
  'config/devbox-app.json',
213
214
  'config/hierarchy.json',
214
- 'config/governance.json',
215
+ 'config/government.json',
216
+ 'config/governance.json', // pre-ADR-0174 spelling — a live instance may still carry one; it is host content and stays denied
215
217
  'CODEOWNERS', // names the owner (a protected surface, excluded as identity)
216
218
  // instance SEED migrations — this host's scope-truth SQL (planning seeds, world data).
217
219
  // ADR 0105: a consumer's core must not carry them; migrate.sh reads instance seeds from
@@ -195,10 +195,10 @@ const TASKS = [
195
195
  {
196
196
  ref: 'R96', kind: 'feature', priority: 4, est_minutes: 100, manual_degree: 2, credits_reward: 40,
197
197
  criterion_ids: [C4], depends_on_refs: ['R93', 'R94'],
198
- title: 'config/governance.json + neutral + resolve-over-neutral',
198
+ title: 'config/government.json + neutral + resolve-over-neutral',
199
199
  description: `**Why:** role templates/defaults are per-instance config (the branding pattern); live data is DB.
200
200
 
201
- **Done when:** \`config/governance.json\` (instance) resolves over \`config/governance.neutral.json\` (neutral) with env override on top, mirroring src/branding.js precedence; it seeds the starting role templates a fresh instance inherits; live assignments stay in the DB (R93). Depends on R93/R94.`,
201
+ **Done when:** \`config/government.json\` (instance) resolves over \`config/government.neutral.json\` (neutral) with env override on top, mirroring src/branding.js precedence; it seeds the starting role templates a fresh instance inherits; live assignments stay in the DB (R93). Depends on R93/R94.`,
202
202
  },
203
203
  {
204
204
  ref: 'R102', kind: 'verify', priority: 3, est_minutes: 70, manual_degree: 2, credits_reward: 35,
@@ -367,7 +367,7 @@ async function maybeSeatFirstAdmin(builder) {
367
367
  // flips xenos→archon outside any transaction), and it quietly repairs a builder whose
368
368
  // assignment drifted for any other reason.
369
369
  //
370
- // Resolved through the `governance.roleSync` kernel port — auth.js is a KERNEL_FILE
370
+ // Resolved through the `government.rankSync` kernel port — auth.js is a KERNEL_FILE
371
371
  // and may not require a module (ADR 0083 / ADR 0091 §1). Runs on the pool: there is
372
372
  // no surrounding transaction here, unlike the two rank-change paths, which pass their
373
373
  // own client so the rotation is atomic with the rank write.
@@ -377,8 +377,8 @@ async function maybeSeatFirstAdmin(builder) {
377
377
  async function safeSyncRankRole(builder) {
378
378
  if (!builder || builder.id == null) return;
379
379
  try {
380
- await seams.resolveOptional('governance.roleSync')
381
- ?.syncRankRole(null, { builderId: builder.id, rank: builder.rank });
380
+ await seams.resolveOptional('government.rankSync')
381
+ ?.syncRank(null, { builderId: builder.id, rank: builder.rank });
382
382
  } catch (err) {
383
383
  console.error('[auth] rank-role sync failed (non-fatal):', err.message);
384
384
  }
@@ -1195,8 +1195,8 @@ async function resolveSession(req) {
1195
1195
  // a behaviour-identical remodelling of the #919 gate, not a re-gating of it.
1196
1196
  //
1197
1197
  // GOVERNANCE OWNS THE POLICY, THE KERNEL STAYS THE ENFORCER. The declaration is
1198
- // governance-owned data in modules/governance/session-scopes.json, validated
1199
- // against the permission catalog at load by modules/governance/session-scopes.js.
1198
+ // governance-owned data in modules/government/session-scopes.json, validated
1199
+ // against the permission catalog at load by modules/government/session-scopes.js.
1200
1200
  // Core reads it as DATA with fs — never `require` (ADR 0083's one-way rule: a
1201
1201
  // module's code and enable/disable state must never be something the kernel
1202
1202
  // depends on). The sibling trust primitive permission-path-check.js reads the
@@ -1211,7 +1211,7 @@ async function resolveSession(req) {
1211
1211
  // registry does not declare is DENIED rather than defaulting to full authority. A
1212
1212
  // null/absent source is a DECLARED row (rows minted before migration 090 added the
1213
1213
  // column, which predate the 'box' source existing), not an implicit default.
1214
- const SESSION_SCOPE_REGISTRY_PATH = path.join(__dirname, '..', '..', 'modules', 'governance', 'session-scopes.json');
1214
+ const SESSION_SCOPE_REGISTRY_PATH = path.join(__dirname, '..', '..', 'modules', 'government', 'session-scopes.json');
1215
1215
 
1216
1216
  // Pure: validate + normalize the parsed registry. Split from the file read so the
1217
1217
  // shipped assertions are what the tests exercise. `label` identifies the file in
@@ -1440,7 +1440,7 @@ function signInWithReturn(req) {
1440
1440
  // requireGovernancePage was rank === 'archon' floor archon → page.view.governance
1441
1441
  // That third gate was spelled `requireArchonPage` until ADR 0157 lowered
1442
1442
  // page.view.governance's floor archon→metic and the name stopped being true; the
1443
- // floor now lives in modules/governance/catalog.js, not in the function's name.
1443
+ // floor now lives in modules/government/catalog.js, not in the function's name.
1444
1444
  // The one accepted divergence is the one governance_002 already documents
1445
1445
  // (tests/governance_parity.mjs Part D): a builder on a reserved/divine rank holds
1446
1446
  // no seeded role, so they now fail a page gate a bare rank test would have
@@ -1476,7 +1476,7 @@ const PAGE_GATES = Object.freeze({
1476
1476
  // through to the DB resolve, so the fold can only ever be an optimization.
1477
1477
  async function resolveHeldPermissions(builderId, rawKeys) {
1478
1478
  if (builderId === null || builderId === undefined || builderId === '') return null;
1479
- const governance = seams.resolveOptional('governance');
1479
+ const governance = seams.resolveOptional('government');
1480
1480
  if (!governance || typeof governance.resolveBuilderPermissions !== 'function') return null;
1481
1481
  if (Array.isArray(rawKeys) && typeof governance.effectivePermissions === 'function') {
1482
1482
  try {
@@ -1723,7 +1723,7 @@ function requirePermission(...perms) {
1723
1723
  const deny = (held) => {
1724
1724
  const missing = held === null ? [...required] : required.filter((key) => !held.includes(key));
1725
1725
  const floors = {};
1726
- const governance = seams.resolveOptional('governance');
1726
+ const governance = seams.resolveOptional('government');
1727
1727
  if (governance && typeof governance.permissionFloor === 'function') {
1728
1728
  for (const key of missing) {
1729
1729
  const floor = governance.permissionFloor(key);
package/src/bongos/db.js CHANGED
@@ -338,10 +338,10 @@ function singleRungPromotionError(oldRank, newRank) {
338
338
  // has nothing to collide on, so it's returned unchanged. Pure + exported so
339
339
  // tests can exercise it without a DB; claimTask calls it inside the txn.
340
340
 
341
- // syncGovernanceRankRole — keep governance_builder_roles in step with builders.rank
342
- // (BV1.R95b, ADR 0151). Resolved through the `governance.roleSync` kernel port and
341
+ // syncGovernanceRankRole — keep government_builder_ranks in step with builders.rank
342
+ // (BV1.R95b, ADR 0151). Resolved through the `government.rankSync` kernel port and
343
343
  // run on the CALLER's transaction client, so the role rotation is atomic with the
344
- // rank write beside it. Core never requires modules/governance/ (ADR 0083).
344
+ // rank write beside it. Core never requires modules/government/ (ADR 0083).
345
345
  //
346
346
  // A missing port means the governance module is disabled, and skipping is the
347
347
  // fail-closed answer: the builder then holds no role and resolves to ZERO
@@ -351,9 +351,9 @@ function singleRungPromotionError(oldRank, newRank) {
351
351
  // TWINNED with modules/lifecycle/db.js (the boundary-forced _applyRankChangeTx
352
352
  // duplication, ADR 0091 §2) — keep the two in sync.
353
353
  async function syncGovernanceRankRole(client, builderId, rank) {
354
- const roleSync = seams.resolveOptional('governance.roleSync');
355
- if (!roleSync || typeof roleSync.syncRankRole !== 'function') return null;
356
- return roleSync.syncRankRole(client, { builderId, rank });
354
+ const roleSync = seams.resolveOptional('government.rankSync');
355
+ if (!roleSync || typeof roleSync.syncRank !== 'function') return null;
356
+ return roleSync.syncRank(client, { builderId, rank });
357
357
  }
358
358
 
359
359
  async function _applyRankChangeTx(client, { builderId, newRank, actorBuilderId, reasonNote = null, enforceSingleRungPromotion = false }) {
@@ -174,8 +174,29 @@ const MODULE_GLOBS = {
174
174
  // role templates (R96), listed here as goal territory; the module code lands in
175
175
  // R93+ (the carve precedes the feature — ADR 0086 §4). Written as exact paths,
176
176
  // not 'config/governance*.json' — the touches convention has no wildcards. ---
177
+ // RENAMED governance → government (goal 1000068, task 1003003, ADR 0174).
178
+ // Both keys live through the transition and BOTH carry BOTH sets of globs,
179
+ // so the wall is identical whichever key a goal names and whichever name
180
+ // the folder currently has on disk. The `government` key must land BEFORE
181
+ // R04 moves the folder: goals carry scope_modules by KEY, so moving the
182
+ // folder first would leave goal 1000068's own wall not matching the files
183
+ // its remaining tasks need to touch, blocking the rename mid-goal.
184
+ // `modules/government/` is registered in protected-surfaces.json in the
185
+ // same change — `protected` DERIVES from that registry, so without it this
186
+ // key would compute protected:false and silently drop the Archon gate.
187
+ government: [
188
+ 'modules/government/', 'modules/governance/',
189
+ 'config/government.json', 'config/government.neutral.json',
190
+ 'config/governance.json', 'config/governance.neutral.json',
191
+ ],
192
+ // DEPRECATED ALIAS — kept for one release so goals and tasks already
193
+ // scoped to `governance` keep resolving. Removed by goal 1000068 R05
194
+ // (task 1003006), the release that drops the /governance/* route aliases;
195
+ // the two removals are the same compatibility window and should land
196
+ // together.
177
197
  governance: [
178
- 'modules/governance/',
198
+ 'modules/government/', 'modules/governance/',
199
+ 'config/government.json', 'config/government.neutral.json',
179
200
  'config/governance.json', 'config/governance.neutral.json',
180
201
  ],
181
202
  // --- the two web surfaces ---
@@ -40,7 +40,7 @@ const RANK_TIER = { xenos: 1, thetes: 2, metic: 3, archon: 4 };
40
40
  // GOVERNANCE OWNS THE DATA; THE SUBSTRATE STAYS THE ENFORCER. Which paths are
41
41
  // protected, at what rank floor, mapped to which `system: true` catalog
42
42
  // permission, is governance-owned data in
43
- // modules/governance/protected-surfaces.json — no longer a constant baked into
43
+ // modules/government/protected-surfaces.json — no longer a constant baked into
44
44
  // this file. Re-mapping a floor there is a POLICY edit. It cannot move
45
45
  // enforcement into the app: the three layers above (git pre-push hook, grader
46
46
  // pre-pass, post-push main audit) plus gate-review.js and the goal-scope check
@@ -61,7 +61,7 @@ const RANK_TIER = { xenos: 1, thetes: 2, metic: 3, archon: 4 };
61
61
  // file that ships inside the core package next to this file (modules/ ships with
62
62
  // @bongos/core, ADR 0108 §1). The sibling trust primitive route-rank-check.js
63
63
  // already reads modules/*/routes/*.js off disk the same way, to rank-gate them.
64
- // Code still flows ONE way only: modules/governance/protected-surfaces.js →
64
+ // Code still flows ONE way only: modules/government/protected-surfaces.js →
65
65
  // src/module-api.js → this matcher. Nothing in core requires the module.
66
66
  //
67
67
  // FAIL-CLOSED: a missing/empty/malformed registry THROWS. It never degrades to
@@ -71,7 +71,7 @@ const RANK_TIER = { xenos: 1, thetes: 2, metic: 3, archon: 4 };
71
71
  // Resolved from __dirname (this file is <coreRoot>/src/bongos/) rather than via
72
72
  // instance-config so the load keeps zero repo imports and works in a bare git
73
73
  // hook.
74
- const REGISTRY_PATH = path.join(__dirname, '..', '..', 'modules', 'governance', 'protected-surfaces.json');
74
+ const REGISTRY_PATH = path.join(__dirname, '..', '..', 'modules', 'government', 'protected-surfaces.json');
75
75
 
76
76
  function loadRegistry(file) {
77
77
  let raw;
@@ -52,7 +52,7 @@ const SPECIALTY = 'deterministic: every src/bongos/routes/ route must classify t
52
52
  // classified 'any-builder': a downgrade that reads as legitimate. To classify it
53
53
  // honestly the auditor needs each atom's rank FLOOR (the rank that holds it under
54
54
  // governance's compat seed), which is declared exactly once — in
55
- // modules/governance/catalog.js. There is deliberately NO second permission→rank table
55
+ // modules/government/catalog.js. There is deliberately NO second permission→rank table
56
56
  // here; a second list is a list that drifts.
57
57
  //
58
58
  // READ AS DATA, NOT require()d. ADR 0083's one-way rule forbids core from importing a
@@ -60,14 +60,14 @@ const SPECIALTY = 'deterministic: every src/bongos/routes/ route must classify t
60
60
  // core, so we parse the catalog's `floor:` declarations out of its SOURCE — the same
61
61
  // technique this file already applies to modules/*/routes/*.js, and the posture the
62
62
  // sibling trust primitive permission-path-check.js takes with
63
- // modules/governance/protected-surfaces.json. Like those, it runs with no DB, no
63
+ // modules/government/protected-surfaces.json. Like those, it runs with no DB, no
64
64
  // server and no module loader, so it works inside a git hook.
65
65
  //
66
66
  // LAZY + FAIL-CLOSED. Loaded on first use, so an instance whose routes carry no
67
67
  // permission gate and no permission pin never reads it. An unreadable or empty catalog
68
68
  // THROWS rather than degrading to "every atom is unknown"; an atom the catalog does not
69
69
  // define classifies 'unknown', which hard-fails a write route.
70
- const CATALOG_PATH = path.join(MODULES_DIR, 'governance', 'catalog.js');
70
+ const CATALOG_PATH = path.join(MODULES_DIR, 'government', 'catalog.js');
71
71
  // low → high authority. Mirrors catalog.js RANK_ORDER; the parse below rejects any
72
72
  // floor outside it, so a divergence surfaces as 'unknown' (loud) rather than as trust.
73
73
  const PERMISSION_RANK_ORDER = ['xenos', 'thetes', 'metic', 'archon'];
@@ -231,6 +231,21 @@ const EXPECTED_RANKS = {
231
231
  'GET /security/adr': 'metic+archon',
232
232
  };
233
233
 
234
+ // The authz-core route pins, referenced by BOTH the `governance` and the
235
+ // `government` module keys below while goal 1000068's rename is in flight
236
+ // (ADR 0174, task 1003003). One object, two keys — never two literals.
237
+ const GOVERNMENT_PINS = {
238
+ 'GET /governance/roles': 'perm:governance.manage',
239
+ 'GET /governance/permissions': 'perm:governance.manage',
240
+ 'GET /governance/assignments': 'perm:governance.manage',
241
+ 'POST /governance/roles': 'perm:governance.manage',
242
+ 'PUT /governance/roles/:roleKey/permissions/:permissionKey': 'perm:governance.manage',
243
+ 'DELETE /governance/roles/:roleKey/permissions/:permissionKey': 'perm:governance.manage',
244
+ 'POST /governance/roles/:roleKey/reset': 'perm:governance.manage',
245
+ 'POST /governance/assignments': 'perm:governance.manage',
246
+ 'DELETE /governance/assignments/:builderId/:roleKey': 'perm:governance.manage',
247
+ };
248
+
234
249
  // Module-scoped privileged-rank pins: a privileged route that lives in a FEATURE
235
250
  // MODULE (modules/<key>/routes/…), pinned like EXPECTED_RANKS but verified ONLY
236
251
  // when that module is in scope (its routes were discovered in this scan). So an
@@ -400,24 +415,28 @@ const MODULE_EXPECTED_RANKS = {
400
415
  'POST /security/reports/:id/fix': 'perm:security.report.adjudicate',
401
416
  'POST /security/reports/:id/link-fix': 'perm:security.report.adjudicate',
402
417
  },
403
- // The governance tab's server half (BV1.R110, ADR 0151) — the routes that EDIT
404
- // the role/permission graph itself. Every route, the three reads included, is
405
- // pinned to the `governance.manage` ATOM rather than a rank: on this surface
406
- // the atom IS the authority (it is the catalog's meta-escalation key), and a
407
- // downgrade here is the model's worst case — the routes that write authority
408
- // drifting below the wall that guards authority. Reads are pinned too because
409
- // the full role/grant/assignment map is the authority model's blueprint.
410
- governance: {
411
- 'GET /governance/roles': 'perm:governance.manage',
412
- 'GET /governance/permissions': 'perm:governance.manage',
413
- 'GET /governance/assignments': 'perm:governance.manage',
414
- 'POST /governance/roles': 'perm:governance.manage',
415
- 'PUT /governance/roles/:roleKey/permissions/:permissionKey': 'perm:governance.manage',
416
- 'DELETE /governance/roles/:roleKey/permissions/:permissionKey': 'perm:governance.manage',
417
- 'POST /governance/roles/:roleKey/reset': 'perm:governance.manage',
418
- 'POST /governance/assignments': 'perm:governance.manage',
419
- 'DELETE /governance/assignments/:builderId/:roleKey': 'perm:governance.manage',
420
- },
418
+ // The government tab's server half (BV1.R110, ADR 0151; renamed by ADR 0174) —
419
+ // the routes that EDIT the rank/permission graph itself. Every route, the three
420
+ // reads included, is pinned to the `governance.manage` ATOM rather than a rank:
421
+ // on this surface the atom IS the authority (it is the catalog's meta-escalation
422
+ // key), and a downgrade here is the model's worst case — the routes that write
423
+ // authority drifting below the wall that guards authority. Reads are pinned too
424
+ // because the full rank/grant/assignment map is the authority model's blueprint.
425
+ //
426
+ // The ROUTE PATHS and the `governance.manage` key are renamed by R05 (task
427
+ // 1003006), not here: R02 only makes the pins reachable under both module keys.
428
+ // Renaming the paths before the routes move would pin routes that do not exist
429
+ // and red the build for the whole window.
430
+ governance: GOVERNMENT_PINS,
431
+ // The same pins under the post-ADR-0174 module name (goal 1000068, task
432
+ // 1003003). Pins are applied only when the module directory is IN SCOPE — at
433
+ // least one route file was discovered under modules/<key>/ — so this entry is
434
+ // inert until R04 (task 1003005) moves the folder, and becomes load-bearing
435
+ // the moment it does, with no second edit needed. Both keys reference the SAME
436
+ // object rather than a copy: two literals here would be the "second list that
437
+ // drifts" failure mode this file exists to prevent, on the surface where a
438
+ // silent drift is worst.
439
+ government: GOVERNMENT_PINS,
421
440
  };
422
441
 
423
442
  // The rank a route's RANK middleware implies, or null when it carries none.
package/src/module-api.js CHANGED
@@ -336,7 +336,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
336
336
  // and 2026-08-03 with no bump behind them, so no vendored-tarball instance
337
337
  // could take any of them. Headline: the governance permission system
338
338
  // (BV1.R95–R109 — resolver + kernel port, first-class principals, session
339
- // scopes, requirePermission(), ownership-scoped grants, config/governance.json)
339
+ // scopes, requirePermission(), ownership-scoped grants, config/government.json)
340
340
  // and the route-rank/validation hardening that followed it; the ship.js
341
341
  // Windows project-path fix so Windows sessions actually get paid (89f291c);
342
342
  // the sequential chain runner (task 1002484); Metic task abandon/un-abandon
@@ -791,8 +791,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
791
791
  // landed since 1.19.180 with no explicit bump. run 31972686846. (task 1002620)
792
792
  // 1.19.182 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
793
793
  // landed since 1.19.181 with no explicit bump. run 31972846686. (task 1002620)
794
+ // 1.19.183 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
795
+ // landed since 1.19.182 with no explicit bump. run 31973728179. (task 1002620)
796
+ // 1.19.184 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
797
+ // landed since 1.19.183 with no explicit bump. run 31974847472. (task 1002620)
794
798
  // ---------------------------------------------------------------------------
795
- const CORE_VERSION = '1.19.182'; // CI auto-patch carrier (ADR 0161); see changelog above
799
+ const CORE_VERSION = '1.19.184'; // CI auto-patch carrier (ADR 0161); see changelog above
796
800
 
797
801
  // A namespaced logger so a module's log lines are attributable + consistent.
798
802
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -34,9 +34,9 @@ const auth = require('../src/bongos/auth.js');
34
34
  // exactly as tests/governance_require_permission.mjs stubs it for requirePermission.
35
35
  // The audience is unchanged; tests/governance_page_gates.mjs is the parity proof.
36
36
  const seams = require('../src/module-seams.js');
37
- const catalog = require('../modules/governance/catalog.js');
37
+ const catalog = require('../modules/government/catalog.js');
38
38
  seams._reset();
39
- seams.registerProvider('governance', {
39
+ seams.registerProvider('government', {
40
40
  resolveBuilderPermissions: async () => catalog.seedForRank('xenos'),
41
41
  });
42
42
 
@@ -29,7 +29,7 @@ const buildDoneWhenRouter = require('../modules/lifecycle/routes/done-when.js');
29
29
  const { authorizeCriterionCreate } = buildDoneWhenRouter;
30
30
  const { authorizeGoalManagement, isGoalOwnerOrManager } = require('../modules/lifecycle/goal-authz.js');
31
31
  const { MODULE_EXPECTED_RANKS } = require('../src/bongos/route-rank-check.js');
32
- const catalog = require('../modules/governance/catalog.js');
32
+ const catalog = require('../modules/government/catalog.js');
33
33
 
34
34
  const doneWhenSrc = readFileSync(new URL('../modules/lifecycle/routes/done-when.js', import.meta.url), 'utf8');
35
35
  const { test, summary } = makeRunner();
@@ -49,11 +49,11 @@ const require = createRequire(import.meta.url);
49
49
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
50
50
  const api = require(path.join(ROOT, 'src', 'module-api.js'));
51
51
  const seams = require(path.join(ROOT, 'src', 'module-seams.js'));
52
- const catalog = require(path.join(ROOT, 'modules', 'governance', 'catalog.js'));
53
- const govConfig = require(path.join(ROOT, 'modules', 'governance', 'config.js'));
54
- const sessionScopes = require(path.join(ROOT, 'modules', 'governance', 'session-scopes.js'));
55
- const resolver = require(path.join(ROOT, 'modules', 'governance', 'resolver.js'));
56
- const govDb = require(path.join(ROOT, 'modules', 'governance', 'db.js'));
52
+ const catalog = require(path.join(ROOT, 'modules', 'government', 'catalog.js'));
53
+ const govConfig = require(path.join(ROOT, 'modules', 'government', 'config.js'));
54
+ const sessionScopes = require(path.join(ROOT, 'modules', 'government', 'session-scopes.js'));
55
+ const resolver = require(path.join(ROOT, 'modules', 'government', 'resolver.js'));
56
+ const govDb = require(path.join(ROOT, 'modules', 'government', 'db.js'));
57
57
  const ppc = require(path.join(ROOT, 'src', 'bongos', 'permission-path-check.js'));
58
58
  const rrc = require(path.join(ROOT, 'src', 'bongos', 'route-rank-check.js'));
59
59
  const { attachFail } = require(path.join(ROOT, 'src', 'bongos', 'middleware', 'error-envelope.js'));
@@ -134,11 +134,11 @@ api.requireBuilder = (req, res, next) => { req.builder = { id: 42, rank: 'archon
134
134
 
135
135
  function grant(keys) {
136
136
  seams._reset();
137
- seams.registerProvider('governance', { resolveBuilderPermissions: async () => keys });
137
+ seams.registerProvider('government', { resolveBuilderPermissions: async () => keys });
138
138
  }
139
139
  grant(['governance.manage']);
140
140
 
141
- const buildGovernanceRouter = require(path.join(ROOT, 'modules', 'governance', 'routes', 'governance.js'));
141
+ const buildGovernanceRouter = require(path.join(ROOT, 'modules', 'government', 'routes', 'governance.js'));
142
142
  const app = express();
143
143
  app.use(express.json());
144
144
  app.use(attachFail);
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
11
11
 
12
12
  const require = createRequire(import.meta.url);
13
13
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
14
- const cat = require(path.join(ROOT, 'modules', 'governance', 'catalog.js'));
14
+ const cat = require(path.join(ROOT, 'modules', 'government', 'catalog.js'));
15
15
  const { PERMISSIONS, RANK_ORDER, RANK_ROLE_SEED, permissionKeys, byKey, isSystem, isGrantable, systemKeys, grantableKeys, seedForRank } = cat;
16
16
 
17
17
  test('every permission is well-formed (key, boolean system, valid floor)', () => {
@@ -36,7 +36,7 @@ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
36
36
  const LIFECYCLE = path.join(ROOT, 'modules', 'lifecycle');
37
37
  const eligibility = require(path.join(LIFECYCLE, 'claim-eligibility.js'));
38
38
  const db = require(path.join(LIFECYCLE, 'db.js'));
39
- const catalog = require(path.join(ROOT, 'modules', 'governance', 'catalog.js'));
39
+ const catalog = require(path.join(ROOT, 'modules', 'government', 'catalog.js'));
40
40
 
41
41
  const { CLAIM_ANY, CLAIM_NEWCOMER, permissionAllowsClaim, claimEligibilityAllows } = eligibility;
42
42
 
@@ -14,7 +14,7 @@ import { fileURLToPath } from 'node:url';
14
14
 
15
15
  const require = createRequire(import.meta.url);
16
16
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
17
- const GOV = path.join(ROOT, 'modules', 'governance');
17
+ 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'));
@@ -157,7 +157,7 @@ test('the neutral starter carries no host-specific identity (a vanilla instance
157
157
  // the CI gate (which does not scan .json itself — IDENTITY_SCAN_EXTS is js/html/css).
158
158
  const { scanPromptIdentity } = require(path.join(ROOT, 'scripts', 'gds', 'fitness.js'));
159
159
  const raw = fs.readFileSync(NEUTRAL_PATH, 'utf8');
160
- assert.deepEqual(scanPromptIdentity('config/governance.neutral.json', raw, '.json'), []);
160
+ assert.deepEqual(scanPromptIdentity('config/government.neutral.json', raw, '.json'), []);
161
161
  // And it carries only templates — never a live per-builder assignment.
162
162
  assert.deepEqual(Object.keys(NEUTRAL).filter((k) => k !== '_note'), ['roles']);
163
163
  for (const role of Object.values(NEUTRAL.roles)) {
@@ -166,13 +166,13 @@ test('the neutral starter carries no host-specific identity (a vanilla instance
166
166
  });
167
167
 
168
168
  test('the two config paths are the branding-shaped neutral/instance pair under config/', () => {
169
- assert.equal(path.relative(ROOT, NEUTRAL_PATH).split(path.sep).join('/'), 'config/governance.neutral.json');
170
- assert.equal(path.relative(ROOT, INSTANCE_PATH).split(path.sep).join('/'), 'config/governance.json');
169
+ assert.equal(path.relative(ROOT, NEUTRAL_PATH).split(path.sep).join('/'), 'config/government.neutral.json');
170
+ assert.equal(path.relative(ROOT, INSTANCE_PATH).split(path.sep).join('/'), 'config/government.json');
171
171
  assert.ok(fs.existsSync(NEUTRAL_PATH), 'the neutral starter must ship — it is the vanilla default');
172
172
  });
173
173
 
174
174
  test('loadGovernanceConfig reads the real neutral starter and tolerates a missing instance pack', () => {
175
- // A vanilla instance has no config/governance.json at all — the default INSTANCE_PATH
175
+ // A vanilla instance has no config/government.json at all — the default INSTANCE_PATH
176
176
  // is genuinely absent in this checkout, so the default call exercises that path.
177
177
  assert.equal(fs.existsSync(INSTANCE_PATH), false, 'fixture guard: this checkout is the vanilla case');
178
178
  const out = loadGovernanceConfig({ env: NO_ENV });
@@ -182,6 +182,6 @@ test('loadGovernanceConfig reads the real neutral starter and tolerates a missin
182
182
 
183
183
  test('the neutral starter publishes with the core (a packaged vanilla instance needs it)', () => {
184
184
  const { isPublishable } = require(path.join(ROOT, 'scripts', 'gds', 'publish-manifest.js'));
185
- assert.equal(isPublishable('config/governance.neutral.json'), true);
186
- assert.equal(isPublishable('config/governance.json'), false, "a host's filled-in pack is instance content");
185
+ assert.equal(isPublishable('config/government.neutral.json'), true);
186
+ assert.equal(isPublishable('config/government.json'), false, "a host's filled-in pack is instance content");
187
187
  });
@@ -27,7 +27,7 @@ const require = createRequire(import.meta.url);
27
27
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
28
28
  const auth = require(path.join(ROOT, 'src', 'bongos', 'auth.js'));
29
29
  const seams = require(path.join(ROOT, 'src', 'module-seams.js'));
30
- const resolver = require(path.join(ROOT, 'modules', 'governance', 'resolver.js'));
30
+ const resolver = require(path.join(ROOT, 'modules', 'government', 'resolver.js'));
31
31
  const { attachFail } = require(path.join(ROOT, 'src', 'bongos', 'middleware', 'error-envelope.js'));
32
32
 
33
33
  function fakeRes() {
@@ -44,7 +44,7 @@ function fakeRes() {
44
44
  // One gate run against a stubbed port. `port` null = unregistered.
45
45
  async function run(req, port) {
46
46
  seams._reset();
47
- if (port !== null) seams.registerProvider('governance', port);
47
+ if (port !== null) seams.registerProvider('government', port);
48
48
  const res = fakeRes();
49
49
  let nexted = false;
50
50
  await auth.requirePermission('idea.triage')(req, res, () => { nexted = true; });
@@ -121,7 +121,7 @@ test('the kernel shape guard: the real fragment embeds; a malformed one degrades
121
121
  assert.equal(dbKernel.sessionPermsSelect(), '', 'a fragment with a statement separator is refused');
122
122
  // The REAL registered fragment passes the guard verbatim.
123
123
  seams._reset();
124
- const buildGovernanceRouter = require(path.join(ROOT, 'modules', 'governance', 'routes', 'governance.js'));
124
+ const buildGovernanceRouter = require(path.join(ROOT, 'modules', 'government', 'routes', 'governance.js'));
125
125
  buildGovernanceRouter();
126
126
  const real = seams.resolveOptional('session-grants-sql').selectFragment;
127
127
  assert.equal(dbKernel.sessionPermsSelect(), real, 'the shipped fragment embeds unchanged');
@@ -133,7 +133,7 @@ test('the module registers the capability-named SQL seam the kernel embeds', ()
133
133
  // §Decision #4); the governance route factory is what registers it. Pin the
134
134
  // registration and the column contract the kernel query depends on.
135
135
  seams._reset();
136
- const buildGovernanceRouter = require(path.join(ROOT, 'modules', 'governance', 'routes', 'governance.js'));
136
+ const buildGovernanceRouter = require(path.join(ROOT, 'modules', 'government', 'routes', 'governance.js'));
137
137
  buildGovernanceRouter();
138
138
  const provider = seams.resolveOptional('session-grants-sql');
139
139
  assert.ok(provider && typeof provider.selectFragment === 'string', 'fragment provider registered');