@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.
- package/.bongos-core.json +81 -76
- package/docs/api/openapi.json +9 -9
- package/modules/{governance → government}/catalog.js +1 -1
- package/modules/{governance → government}/config.js +6 -6
- package/modules/{governance → government}/db.js +43 -43
- package/modules/government/migrations/government_001_rename_from_governance.sql +213 -0
- package/modules/government/module.json +22 -0
- package/modules/{governance → government}/protected-surfaces.json +2 -1
- package/modules/{governance/rank-roles.js → government/ranks.js} +9 -9
- package/modules/{governance → government}/routes/governance.js +5 -5
- package/modules/{governance/routes/role-sync.js → government/routes/rank-sync.js} +7 -7
- package/modules/lifecycle/db.js +6 -6
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/fitness.js +11 -1
- package/scripts/gds/migration-namespace.js +15 -3
- package/scripts/gds/publish-manifest.js +5 -3
- package/scripts/gds/seed-governance-tasks.js +2 -2
- package/src/bongos/auth.js +9 -9
- package/src/bongos/db.js +6 -6
- package/src/bongos/module-scope-map.js +22 -1
- package/src/bongos/permission-path-check.js +3 -3
- package/src/bongos/route-rank-check.js +40 -21
- package/src/module-api.js +6 -2
- package/tests/auth_page_gate.mjs +2 -2
- package/tests/criterion_authoring_gate.mjs +1 -1
- package/tests/governance_abuse_matrix.mjs +7 -7
- package/tests/governance_catalog.mjs +1 -1
- package/tests/governance_claim_eligibility.mjs +1 -1
- package/tests/governance_config.mjs +7 -7
- package/tests/governance_fold.mjs +4 -4
- package/tests/governance_live_proof.mjs +2 -2
- package/tests/governance_ownership_scope.mjs +1 -1
- package/tests/governance_page_gates.mjs +6 -6
- package/tests/governance_parity.mjs +3 -3
- package/tests/governance_principals.mjs +1 -1
- package/tests/governance_protected_surfaces.mjs +63 -7
- package/tests/governance_require_permission.mjs +6 -6
- package/tests/governance_resolver.mjs +2 -2
- package/tests/governance_role_sync.mjs +14 -14
- package/tests/governance_routes.mjs +6 -6
- package/tests/governance_seed.mjs +11 -3
- package/tests/governance_session_scope.mjs +1 -1
- package/tests/governance_vanilla_boot.mjs +4 -4
- package/tests/hall_page_gate_map.mjs +1 -1
- package/tests/memory_isolation.mjs +2 -2
- package/tests/module_loader.mjs +1 -1
- package/tests/onboarding_observability.mjs +2 -2
- package/tests/rank_tier_single_source.mjs +2 -2
- package/tests/route_permission_check.mjs +2 -2
- package/modules/governance/module.json +0 -11
- /package/config/{governance.neutral.json → government.neutral.json} +0 -0
- /package/modules/{governance → government}/CLAUDE.md +0 -0
- /package/modules/{governance → government}/migrations/governance_001_tables.sql +0 -0
- /package/modules/{governance → government}/migrations/governance_002_seed_and_backfill.sql +0 -0
- /package/modules/{governance → government}/migrations/governance_003_task_abandon_metic.sql +0 -0
- /package/modules/{governance → government}/migrations/governance_004_criterion_create.sql +0 -0
- /package/modules/{governance → government}/migrations/governance_005_resync_builder_rank_roles.sql +0 -0
- /package/modules/{governance → government}/migrations/governance_006_version_create.sql +0 -0
- /package/modules/{governance → government}/migrations/governance_007_delegate_archon_ops_to_metic.sql +0 -0
- /package/modules/{governance → government}/protected-surfaces.js +0 -0
- /package/modules/{governance → government}/resolver.js +0 -0
- /package/modules/{governance → government}/session-scopes.js +0 -0
- /package/modules/{governance → government}/session-scopes.json +0 -0
package/modules/lifecycle/db.js
CHANGED
|
@@ -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('
|
|
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
|
|
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 `
|
|
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('
|
|
425
|
-
if (!roleSync || typeof roleSync.
|
|
426
|
-
return roleSync.
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -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/
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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,
|
package/src/bongos/auth.js
CHANGED
|
@@ -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 `
|
|
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('
|
|
381
|
-
?.
|
|
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/
|
|
1199
|
-
// against the permission catalog at load by modules/
|
|
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', '
|
|
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/
|
|
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('
|
|
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('
|
|
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
|
|
342
|
-
// (BV1.R95b, ADR 0151). Resolved through the `
|
|
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/
|
|
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('
|
|
355
|
-
if (!roleSync || typeof roleSync.
|
|
356
|
-
return roleSync.
|
|
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/
|
|
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/
|
|
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', '
|
|
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/
|
|
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/
|
|
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, '
|
|
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
|
|
404
|
-
// the
|
|
405
|
-
// pinned to the `governance.manage` ATOM rather than a rank:
|
|
406
|
-
// the atom IS the authority (it is the catalog's meta-escalation
|
|
407
|
-
// downgrade here is the model's worst case — the routes that write
|
|
408
|
-
// drifting below the wall that guards authority. Reads are pinned too
|
|
409
|
-
// the full
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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/
|
|
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.
|
|
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');
|
package/tests/auth_page_gate.mjs
CHANGED
|
@@ -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/
|
|
37
|
+
const catalog = require('../modules/government/catalog.js');
|
|
38
38
|
seams._reset();
|
|
39
|
-
seams.registerProvider('
|
|
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/
|
|
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', '
|
|
53
|
-
const govConfig = require(path.join(ROOT, 'modules', '
|
|
54
|
-
const sessionScopes = require(path.join(ROOT, 'modules', '
|
|
55
|
-
const resolver = require(path.join(ROOT, 'modules', '
|
|
56
|
-
const govDb = require(path.join(ROOT, 'modules', '
|
|
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('
|
|
137
|
+
seams.registerProvider('government', { resolveBuilderPermissions: async () => keys });
|
|
138
138
|
}
|
|
139
139
|
grant(['governance.manage']);
|
|
140
140
|
|
|
141
|
-
const buildGovernanceRouter = require(path.join(ROOT, 'modules', '
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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/
|
|
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/
|
|
170
|
-
assert.equal(path.relative(ROOT, INSTANCE_PATH).split(path.sep).join('/'), 'config/
|
|
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/
|
|
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/
|
|
186
|
-
assert.equal(isPublishable('config/
|
|
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', '
|
|
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('
|
|
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', '
|
|
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', '
|
|
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');
|