@bongos/core 1.19.183 → 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 +80 -75
- 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 +1 -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 +3 -2
- 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/permission-path-check.js +3 -3
- package/src/bongos/route-rank-check.js +3 -3
- package/src/module-api.js +4 -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 +3 -3
- 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,
|
|
@@ -75,7 +75,7 @@ const PUBLISH_ALLOWLIST = [
|
|
|
75
75
|
'config/design-tokens.neutral.json',
|
|
76
76
|
'config/design-tokens.schema.json', // the design-token contract (generic schema, no instance values)
|
|
77
77
|
'config/hierarchy.neutral.json',
|
|
78
|
-
'config/
|
|
78
|
+
'config/government.neutral.json', // the starting rank templates a fresh instance inherits (BV1.R96; renamed from governance.neutral.json by ADR 0174)
|
|
79
79
|
// --- the portable methodology core, governance, and sanitized docs ---
|
|
80
80
|
'CLAUDE.md',
|
|
81
81
|
'README.md',
|
|
@@ -212,7 +212,8 @@ const INSTANCE_EXCLUDE = [
|
|
|
212
212
|
'config/modules.json',
|
|
213
213
|
'config/devbox-app.json',
|
|
214
214
|
'config/hierarchy.json',
|
|
215
|
-
'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
|
|
216
217
|
'CODEOWNERS', // names the owner (a protected surface, excluded as identity)
|
|
217
218
|
// instance SEED migrations — this host's scope-truth SQL (planning seeds, world data).
|
|
218
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 }) {
|
|
@@ -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'];
|
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
|
|
@@ -793,8 +793,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
793
793
|
// landed since 1.19.181 with no explicit bump. run 31972846686. (task 1002620)
|
|
794
794
|
// 1.19.183 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
795
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)
|
|
796
798
|
// ---------------------------------------------------------------------------
|
|
797
|
-
const CORE_VERSION = '1.19.
|
|
799
|
+
const CORE_VERSION = '1.19.184'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
798
800
|
|
|
799
801
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
800
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');
|
|
@@ -176,7 +176,7 @@ try {
|
|
|
176
176
|
// …the resolver reports it honestly (it is a defined catalog key; matrix V10
|
|
177
177
|
// proves no route consumes it via requirePermission, so it admits nothing
|
|
178
178
|
// in-app)…
|
|
179
|
-
const resolver = require('../modules/
|
|
179
|
+
const resolver = require('../modules/government/resolver.js');
|
|
180
180
|
const perms = await resolver.resolveBuilderPermissions(THETES);
|
|
181
181
|
ok(perms.includes(OPERATIONAL_KEY), 'the resolved set includes the operational grant');
|
|
182
182
|
console.log(` (resolved set ${perms.includes(SYSTEM_KEY) ? 'includes' : 'omits'} the forced '${SYSTEM_KEY}' — in-app it guards no route either way)`);
|
|
@@ -187,7 +187,7 @@ try {
|
|
|
187
187
|
// protected-path diff while the forced grant row sits in the database.
|
|
188
188
|
const { checkPermissionPaths } = require('../src/bongos/permission-path-check.js');
|
|
189
189
|
const sub = checkPermissionPaths({
|
|
190
|
-
changedFiles: ['migrations/999_glp_demo_evil.sql', 'modules/
|
|
190
|
+
changedFiles: ['migrations/999_glp_demo_evil.sql', 'modules/government/catalog.js'],
|
|
191
191
|
builderRank: 'thetes',
|
|
192
192
|
});
|
|
193
193
|
ok(sub.hardFail === true && sub.findings.length === 2,
|
|
@@ -30,7 +30,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
30
30
|
|
|
31
31
|
const require = createRequire(import.meta.url);
|
|
32
32
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
33
|
-
const GOV = path.join(ROOT, 'modules', '
|
|
33
|
+
const GOV = path.join(ROOT, 'modules', 'government');
|
|
34
34
|
const catalog = require(path.join(GOV, 'catalog.js'));
|
|
35
35
|
const resolver = require(path.join(GOV, 'resolver.js'));
|
|
36
36
|
const db = require(path.join(GOV, 'db.js'));
|
|
@@ -45,8 +45,8 @@ dbKernel.lookupSession = async () => {
|
|
|
45
45
|
|
|
46
46
|
const auth = require('../src/bongos/auth.js');
|
|
47
47
|
const seams = require('../src/module-seams.js');
|
|
48
|
-
const catalog = require('../modules/
|
|
49
|
-
const resolver = require('../modules/
|
|
48
|
+
const catalog = require('../modules/government/catalog.js');
|
|
49
|
+
const resolver = require('../modules/government/resolver.js');
|
|
50
50
|
const wandering = require('../modules/builder-settings/wandering-prefs.js');
|
|
51
51
|
const { attachFail } = require('../src/bongos/middleware/error-envelope.js');
|
|
52
52
|
|
|
@@ -126,7 +126,7 @@ async function runGate(name, { rank = null, resolve, port = true } = {}) {
|
|
|
126
126
|
lookupThrows = false;
|
|
127
127
|
seams._reset();
|
|
128
128
|
if (port) {
|
|
129
|
-
seams.registerProvider('
|
|
129
|
+
seams.registerProvider('government', {
|
|
130
130
|
// The real seed for that rank — the rows governance_002 gives the one
|
|
131
131
|
// rank-role a builder holds. The INPUT, not the oracle.
|
|
132
132
|
resolveBuilderPermissions: resolve || (async () => catalog.seedForRank(rank)),
|
|
@@ -144,7 +144,7 @@ async function runGate(name, { rank = null, resolve, port = true } = {}) {
|
|
|
144
144
|
|
|
145
145
|
test('A0: the hand-written audience table agrees with the real threshold function', () => {
|
|
146
146
|
// A second, catalog-free oracle for the literal above: auth.rankMeetsThreshold
|
|
147
|
-
// is the production rank predicate and reads nothing from modules/
|
|
147
|
+
// is the production rank predicate and reads nothing from modules/government/.
|
|
148
148
|
for (const gate of GATES) {
|
|
149
149
|
for (const rank of LIVE_RANKS) {
|
|
150
150
|
assert.equal(
|
|
@@ -203,7 +203,7 @@ test('B1: a session-lookup error fails CLOSED (redirect to sign-in, no page)', a
|
|
|
203
203
|
for (const gate of GATES) {
|
|
204
204
|
nextSession = null;
|
|
205
205
|
seams._reset();
|
|
206
|
-
seams.registerProvider('
|
|
206
|
+
seams.registerProvider('government', { resolveBuilderPermissions: async () => catalog.seedForRank('archon') });
|
|
207
207
|
lookupThrows = true;
|
|
208
208
|
const res = fakeRes();
|
|
209
209
|
let nexted = false;
|
|
@@ -244,7 +244,7 @@ test('B4: the page gates resolve per request — no memoised authority (ADR 0016
|
|
|
244
244
|
nextSession = { builder_id: 7, rank: 'archon' };
|
|
245
245
|
lookupThrows = false;
|
|
246
246
|
seams._reset();
|
|
247
|
-
seams.registerProvider('
|
|
247
|
+
seams.registerProvider('government', {
|
|
248
248
|
resolveBuilderPermissions: async () => { calls += 1; return keys; },
|
|
249
249
|
});
|
|
250
250
|
const req = { headers: { cookie: 'gds_session=validtoken' } };
|
|
@@ -57,8 +57,8 @@ const { RANKS } = require(path.join(ROOT, 'src', 'bongos', 'db-kernel.js'));
|
|
|
57
57
|
const routeRank = require(path.join(ROOT, 'src', 'bongos', 'route-rank-check.js'));
|
|
58
58
|
const seams = require(path.join(ROOT, 'src', 'module-seams.js'));
|
|
59
59
|
const { attachFail } = require(path.join(ROOT, 'src', 'bongos', 'middleware', 'error-envelope.js'));
|
|
60
|
-
const catalog = require(path.join(ROOT, 'modules', '
|
|
61
|
-
const resolver = require(path.join(ROOT, 'modules', '
|
|
60
|
+
const catalog = require(path.join(ROOT, 'modules', 'government', 'catalog.js'));
|
|
61
|
+
const resolver = require(path.join(ROOT, 'modules', 'government', 'resolver.js'));
|
|
62
62
|
|
|
63
63
|
const { PERMISSIONS, RANK_ORDER, seedForRank } = catalog;
|
|
64
64
|
const FLOORED = PERMISSIONS.filter((p) => p.floor !== null);
|
|
@@ -287,7 +287,7 @@ function fakeRes() {
|
|
|
287
287
|
// not the logic — resolvedFor() is the real resolver over the real seed.
|
|
288
288
|
async function permissionAdmits(key, rank) {
|
|
289
289
|
seams._reset();
|
|
290
|
-
seams.registerProvider('
|
|
290
|
+
seams.registerProvider('government', { resolveBuilderPermissions: async () => resolvedFor(rank) });
|
|
291
291
|
let nexted = false;
|
|
292
292
|
await auth.requirePermission(key)({ builder: { id: 7, rank } }, fakeRes(), () => { nexted = true; });
|
|
293
293
|
seams._reset();
|
|
@@ -22,7 +22,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
22
22
|
|
|
23
23
|
const require = createRequire(import.meta.url);
|
|
24
24
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
25
|
-
const GOV = path.join(ROOT, 'modules', '
|
|
25
|
+
const GOV = path.join(ROOT, 'modules', 'government');
|
|
26
26
|
const catalog = require(path.join(GOV, 'catalog.js'));
|
|
27
27
|
const resolver = require(path.join(GOV, 'resolver.js'));
|
|
28
28
|
|