@bongos/core 1.19.183 → 1.19.185
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 +102 -92
- package/clients/bongos-client/bongos-client.global.js +19 -19
- package/clients/bongos-client/index.cjs +19 -19
- package/clients/bongos-client/index.d.ts +29 -29
- package/clients/bongos-client/index.mjs +19 -19
- package/docs/adr/0157-archon-is-rank-and-identity-only.md +2 -2
- package/docs/api/openapi.json +117 -117
- package/docs/api-reference.md +10 -10
- package/docs/canonical-permissions.md +6 -6
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +19 -19
- package/modules/{governance → government}/catalog.js +8 -8
- package/modules/{governance → government}/config.js +6 -6
- package/modules/{governance → government}/db.js +44 -44
- package/modules/government/migrations/government_001_rename_from_governance.sql +213 -0
- package/modules/government/migrations/government_002_rename_permission_keys.sql +112 -0
- package/modules/government/module.json +22 -0
- package/modules/{governance → government}/protected-surfaces.json +0 -1
- package/modules/{governance/rank-roles.js → government/ranks.js} +18 -18
- package/modules/{governance/routes/governance.js → government/routes/government.js} +109 -84
- package/modules/{governance/routes/role-sync.js → government/routes/rank-sync.js} +7 -7
- package/modules/hall-ui/public/gate.js +1 -1
- package/modules/hall-ui/public/governance.js +38 -38
- package/modules/hall-ui/public/harbor.js +2 -2
- package/modules/hall-ui/public/shell.js +3 -3
- package/modules/hall-ui/public/watch.js +1 -1
- package/modules/lifecycle/db.js +6 -6
- package/modules/lifecycle/routes/gate-approvals.js +1 -1
- 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 +2 -3
- package/scripts/gds/seed-governance-tasks.js +3 -3
- package/src/bongos/auth.js +16 -16
- package/src/bongos/db.js +6 -6
- package/src/bongos/module-scope-map.js +7 -22
- package/src/bongos/permission-path-check.js +3 -3
- package/src/bongos/route-rank-check.js +15 -21
- package/src/bongos/serve-internal.js +2 -2
- package/src/module-api.js +7 -3
- package/tests/auth_page_gate.mjs +2 -2
- package/tests/criterion_authoring_gate.mjs +1 -1
- package/tests/governance_abuse_matrix.mjs +38 -38
- package/tests/governance_catalog.mjs +4 -4
- 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_fold_bench.mjs +2 -2
- package/tests/governance_live_proof.mjs +6 -6
- package/tests/governance_ownership_scope.mjs +2 -2
- package/tests/governance_page_gates.mjs +10 -10
- package/tests/governance_parity.mjs +3 -3
- package/tests/governance_principals.mjs +3 -3
- package/tests/governance_protected_surfaces.mjs +44 -19
- package/tests/governance_require_permission.mjs +11 -11
- package/tests/governance_resolver.mjs +4 -4
- package/tests/governance_role_sync.mjs +35 -35
- package/tests/governance_routes.mjs +98 -98
- package/tests/governance_seed.mjs +27 -4
- package/tests/governance_session_scope.mjs +5 -5
- package/tests/governance_vanilla_boot.mjs +4 -4
- package/tests/hall_page_gate_map.mjs +3 -3
- 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/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,30 +174,15 @@ 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,
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
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.
|
|
177
|
+
// RENAMED governance → government (goal 1000068, ADR 0174). The
|
|
178
|
+
// transitional `governance` alias key was removed in R05 (task 1003006)
|
|
179
|
+
// together with the /governance/* route aliases — the same compatibility
|
|
180
|
+
// window. The three live goals that named the old key (1000030, 1000057,
|
|
181
|
+
// 1000068) had it removed from goals.scope_modules in the same change, so
|
|
182
|
+
// no stored row references a key the map no longer defines.
|
|
187
183
|
government: [
|
|
188
|
-
'modules/government/',
|
|
184
|
+
'modules/government/',
|
|
189
185
|
'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.
|
|
197
|
-
governance: [
|
|
198
|
-
'modules/government/', 'modules/governance/',
|
|
199
|
-
'config/government.json', 'config/government.neutral.json',
|
|
200
|
-
'config/governance.json', 'config/governance.neutral.json',
|
|
201
186
|
],
|
|
202
187
|
// --- the two web surfaces ---
|
|
203
188
|
// BV1.R85: hall-UI graduated to modules/hall-ui/ — public-builders/ moved to
|
|
@@ -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,19 +231,18 @@ const EXPECTED_RANKS = {
|
|
|
231
231
|
'GET /security/adr': 'metic+archon',
|
|
232
232
|
};
|
|
233
233
|
|
|
234
|
-
// The authz-core route pins
|
|
235
|
-
//
|
|
236
|
-
// (ADR 0174, task 1003003). One object, two keys — never two literals.
|
|
234
|
+
// The authz-core route pins (ADR 0174). Paths and the gating atom were renamed
|
|
235
|
+
// governance→government in R05 (task 1003006); the module-key alias is gone.
|
|
237
236
|
const GOVERNMENT_PINS = {
|
|
238
|
-
'GET /
|
|
239
|
-
'GET /
|
|
240
|
-
'GET /
|
|
241
|
-
'POST /
|
|
242
|
-
'PUT /
|
|
243
|
-
'DELETE /
|
|
244
|
-
'POST /
|
|
245
|
-
'POST /
|
|
246
|
-
'DELETE /
|
|
237
|
+
'GET /government/ranks': 'perm:government.manage',
|
|
238
|
+
'GET /government/permissions': 'perm:government.manage',
|
|
239
|
+
'GET /government/assignments': 'perm:government.manage',
|
|
240
|
+
'POST /government/ranks': 'perm:government.manage',
|
|
241
|
+
'PUT /government/ranks/:rankKey/permissions/:permissionKey': 'perm:government.manage',
|
|
242
|
+
'DELETE /government/ranks/:rankKey/permissions/:permissionKey': 'perm:government.manage',
|
|
243
|
+
'POST /government/ranks/:rankKey/reset': 'perm:government.manage',
|
|
244
|
+
'POST /government/assignments': 'perm:government.manage',
|
|
245
|
+
'DELETE /government/assignments/:builderId/:rankKey': 'perm:government.manage',
|
|
247
246
|
};
|
|
248
247
|
|
|
249
248
|
// Module-scoped privileged-rank pins: a privileged route that lives in a FEATURE
|
|
@@ -417,17 +416,12 @@ const MODULE_EXPECTED_RANKS = {
|
|
|
417
416
|
},
|
|
418
417
|
// The government tab's server half (BV1.R110, ADR 0151; renamed by ADR 0174) —
|
|
419
418
|
// the routes that EDIT the rank/permission graph itself. Every route, the three
|
|
420
|
-
// reads included, is pinned to the `
|
|
419
|
+
// reads included, is pinned to the `government.manage` ATOM rather than a rank:
|
|
421
420
|
// on this surface the atom IS the authority (it is the catalog's meta-escalation
|
|
422
421
|
// key), and a downgrade here is the model's worst case — the routes that write
|
|
423
422
|
// authority drifting below the wall that guards authority. Reads are pinned too
|
|
424
423
|
// because the full rank/grant/assignment map is the authority model's blueprint.
|
|
425
424
|
//
|
|
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
425
|
// The same pins under the post-ADR-0174 module name (goal 1000068, task
|
|
432
426
|
// 1003003). Pins are applied only when the module directory is IN SCOPE — at
|
|
433
427
|
// least one route file was discovered under modules/<key>/ — so this entry is
|
|
@@ -749,7 +749,7 @@ function mountInternalSurfaces(app) {
|
|
|
749
749
|
});
|
|
750
750
|
|
|
751
751
|
// The /watch monitoring page and the /harbor dev-box management page (idea 257)
|
|
752
|
-
// and the /gate page (task 1179) all sit behind `page.view.
|
|
752
|
+
// and the /gate page (task 1179) all sit behind `page.view.government` — Metic
|
|
753
753
|
// and up since ADR 0157. Same server-side page gate, same posture: a builder
|
|
754
754
|
// without the permission never receives the shell (requireGovernancePage — named
|
|
755
755
|
// requireArchonPage before ADR 0157 — redirects them to the hall, and an
|
|
@@ -767,7 +767,7 @@ function mountInternalSurfaces(app) {
|
|
|
767
767
|
// group without a wall. Gated here so the group is uniform (task 1002710).
|
|
768
768
|
const SESSIONS_PAGE_RE = /^\/builders\/sessions(?:\.html)?\/?$/;
|
|
769
769
|
// The /governance roles-and-permissions tab (BV1.R113) sits one floor higher:
|
|
770
|
-
// the `
|
|
770
|
+
// the `government.manage` atom itself (archon), the same key its data routes
|
|
771
771
|
// check — requireGovernanceManagePage, not the metic-floored gate above.
|
|
772
772
|
const GOVERNANCE_PAGE_RE = /^\/builders\/governance(?:\.html)?\/?$/;
|
|
773
773
|
// The /people builder directory (task 1002577): gated on `project.curate`, the
|
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
|
|
@@ -390,7 +390,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
390
390
|
// ad7a262 and never packaged, so ~104 non-merge commits sat on main
|
|
391
391
|
// reachable by no pin; this is the artifact that ships them. Headline:
|
|
392
392
|
// the governance permission system's second half (R110 roles/perms/
|
|
393
|
-
// assignments CRUD behind
|
|
393
|
+
// assignments CRUD behind government.manage, R113 the Roles nav +
|
|
394
394
|
// page gate, R114 the C6 live proof, ADR 0157 delegating the
|
|
395
395
|
// operational block from Archon to Metic, and the permission resolve
|
|
396
396
|
// folded into the session load); the scan-before-install security
|
|
@@ -793,8 +793,12 @@ 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)
|
|
798
|
+
// 1.19.185 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
799
|
+
// landed since 1.19.184 with no explicit bump. run 31976564067. (task 1002620)
|
|
796
800
|
// ---------------------------------------------------------------------------
|
|
797
|
-
const CORE_VERSION = '1.19.
|
|
801
|
+
const CORE_VERSION = '1.19.185'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
798
802
|
|
|
799
803
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
800
804
|
// 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();
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// V3 HTTP: assign a seeded rank-role to a builder (rank is the only writer)
|
|
17
17
|
// V4 HTTP: mass-assignment on role create (is_system / permissions in body)
|
|
18
18
|
// V5 HTTP: every write vector 403s outright for a caller without
|
|
19
|
-
//
|
|
19
|
+
// government.manage (the low-trust builder's own attempt)
|
|
20
20
|
// V6 config: a role template proposing system keys is stripped; a seeded
|
|
21
21
|
// role's template grants are dropped wholesale; env overrides labels only
|
|
22
22
|
// V7 session-scope: an allow-list naming a system or principal key THROWS
|
|
@@ -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'));
|
|
@@ -109,9 +109,9 @@ test('every system permission floors at archon or is a principal (never seeded l
|
|
|
109
109
|
const calls = [];
|
|
110
110
|
const stub = {
|
|
111
111
|
getRoleByKey: async (k) => (SEEDED_ROLES.includes(k)
|
|
112
|
-
? {
|
|
113
|
-
: {
|
|
114
|
-
createRole: async (a) => ({
|
|
112
|
+
? { rank_key: k, label: k, is_system: true, seeded_rank: k }
|
|
113
|
+
: { rank_key: k, label: k, is_system: false, seeded_rank: null }),
|
|
114
|
+
createRole: async (a) => ({ rank_key: a.rankKey, label: a.label, description: a.description ?? null, is_system: false, seeded_rank: null, created_by: a.createdBy, created_at: 't' }),
|
|
115
115
|
grantRolePermission: async () => true,
|
|
116
116
|
revokeRolePermission: async () => true,
|
|
117
117
|
resetRoleGrants: async ({ permissionKeys }) => permissionKeys.length,
|
|
@@ -127,18 +127,18 @@ for (const k of Object.keys(stub)) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// The attacker holds the MOST privileged legitimate session — an Archon with
|
|
130
|
-
//
|
|
130
|
+
// government.manage. The matrix must hold even against the admin misusing the
|
|
131
131
|
// API, not just against a low-trust caller (V5 covers that one).
|
|
132
132
|
const realRequireBuilder = api.requireBuilder;
|
|
133
133
|
api.requireBuilder = (req, res, next) => { req.builder = { id: 42, rank: 'archon' }; next(); };
|
|
134
134
|
|
|
135
135
|
function grant(keys) {
|
|
136
136
|
seams._reset();
|
|
137
|
-
seams.registerProvider('
|
|
137
|
+
seams.registerProvider('government', { resolveBuilderPermissions: async () => keys });
|
|
138
138
|
}
|
|
139
|
-
grant(['
|
|
139
|
+
grant(['government.manage']);
|
|
140
140
|
|
|
141
|
-
const buildGovernanceRouter = require(path.join(ROOT, 'modules', '
|
|
141
|
+
const buildGovernanceRouter = require(path.join(ROOT, 'modules', 'government', 'routes', 'government.js'));
|
|
142
142
|
const app = express();
|
|
143
143
|
app.use(express.json());
|
|
144
144
|
app.use(attachFail);
|
|
@@ -168,7 +168,7 @@ async function call(method, p, body) {
|
|
|
168
168
|
|
|
169
169
|
test('V1: EVERY system permission is refused onto a custom role, before the db', async () => {
|
|
170
170
|
for (const k of SYSTEM_KEYS) {
|
|
171
|
-
const r = await call('PUT', `/
|
|
171
|
+
const r = await call('PUT', `/government/ranks/custom-role/permissions/${k}`);
|
|
172
172
|
assert.equal(r.status, 400, `${k} must be refused`);
|
|
173
173
|
assert.equal(r.json.error.code, 'permission_not_grantable', k);
|
|
174
174
|
assert.equal(calls.filter(([c]) => c === 'grantRolePermission').length, 0,
|
|
@@ -185,15 +185,15 @@ test('V1: EVERY system permission is refused onto a custom role, before the db',
|
|
|
185
185
|
test('V2a: the ARCHON rank-role refuses EVERY write — grant, revoke and reset alike', async () => {
|
|
186
186
|
for (const k of [...SYSTEM_KEYS, 'idea.triage']) {
|
|
187
187
|
for (const method of ['PUT', 'DELETE']) {
|
|
188
|
-
const r = await call(method, `/
|
|
188
|
+
const r = await call(method, `/government/ranks/archon/permissions/${k}`);
|
|
189
189
|
assert.equal(r.status, 403, `archon ← ${method} ${k} must be refused`);
|
|
190
|
-
assert.equal(r.json.error.code, '
|
|
190
|
+
assert.equal(r.json.error.code, 'archon_rank_immutable', `archon ← ${method} ${k}`);
|
|
191
191
|
assert.equal(calls.filter(([c]) => c === 'grantRolePermission' || c === 'revokeRolePermission').length, 0);
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
|
-
const r = await call('POST', '/
|
|
194
|
+
const r = await call('POST', '/government/ranks/archon/reset');
|
|
195
195
|
assert.equal(r.status, 403, 'reset must not be a back door into the archon role');
|
|
196
|
-
assert.equal(r.json.error.code, '
|
|
196
|
+
assert.equal(r.json.error.code, 'archon_rank_immutable');
|
|
197
197
|
assert.equal(calls.filter(([c]) => c === 'resetRoleGrants').length, 0);
|
|
198
198
|
});
|
|
199
199
|
|
|
@@ -201,7 +201,7 @@ test('V2b: EVERY editable rank-role still refuses every SUBSTRATE and PRINCIPAL
|
|
|
201
201
|
const neverGrantable = [...SUBSTRATE_KEYS, ...catalog.principalKeys()];
|
|
202
202
|
for (const role of EDITABLE_RANK_ROLES) {
|
|
203
203
|
for (const k of neverGrantable) {
|
|
204
|
-
const r = await call('PUT', `/
|
|
204
|
+
const r = await call('PUT', `/government/ranks/${role}/permissions/${k}`);
|
|
205
205
|
assert.equal(r.status, 400, `${role} ← ${k} must be refused`);
|
|
206
206
|
assert.equal(r.json.error.code, 'permission_not_grantable', `${role} ← ${k}`);
|
|
207
207
|
assert.equal(calls.filter(([c]) => c === 'grantRolePermission').length, 0,
|
|
@@ -213,7 +213,7 @@ test('V2b: EVERY editable rank-role still refuses every SUBSTRATE and PRINCIPAL
|
|
|
213
213
|
test('V2c: an editable rank-role ACCEPTS the in-app owner powers (the ADR 0172 lever)', async () => {
|
|
214
214
|
for (const role of EDITABLE_RANK_ROLES) {
|
|
215
215
|
for (const k of [...OWNER_POWERS, 'idea.triage']) {
|
|
216
|
-
const r = await call('PUT', `/
|
|
216
|
+
const r = await call('PUT', `/government/ranks/${role}/permissions/${k}`);
|
|
217
217
|
assert.equal(r.status, 200, `${role} ← ${k} must be ACCEPTED — this delegation is the owner's decision (ADR 0172)`);
|
|
218
218
|
assert.equal(calls.filter(([c]) => c === 'grantRolePermission').length, 1, `${role} ← ${k} must reach the db`);
|
|
219
219
|
}
|
|
@@ -222,7 +222,7 @@ test('V2c: an editable rank-role ACCEPTS the in-app owner powers (the ADR 0172 l
|
|
|
222
222
|
|
|
223
223
|
test('V2d: reset restores the CATALOG seed, never anything wider', async () => {
|
|
224
224
|
for (const role of EDITABLE_RANK_ROLES) {
|
|
225
|
-
const r = await call('POST', `/
|
|
225
|
+
const r = await call('POST', `/government/ranks/${role}/reset`);
|
|
226
226
|
assert.equal(r.status, 200, `${role} must be resettable`);
|
|
227
227
|
const [, args] = calls.find(([c]) => c === 'resetRoleGrants');
|
|
228
228
|
assert.deepEqual([...args.permissionKeys].sort(), [...catalog.seedForRank(role)].sort(),
|
|
@@ -233,24 +233,24 @@ test('V2d: reset restores the CATALOG seed, never anything wider', async () => {
|
|
|
233
233
|
}
|
|
234
234
|
// A custom role has no catalog default, so reset is refused rather than silently
|
|
235
235
|
// clearing its grants (which would be a destructive verb wearing a restore's name).
|
|
236
|
-
const r = await call('POST', '/
|
|
236
|
+
const r = await call('POST', '/government/ranks/custom-role/reset');
|
|
237
237
|
assert.equal(r.status, 400);
|
|
238
|
-
assert.equal(r.json.error.code, '
|
|
238
|
+
assert.equal(r.json.error.code, 'rank_has_no_default');
|
|
239
239
|
assert.equal(calls.filter(([c]) => c === 'resetRoleGrants').length, 0);
|
|
240
240
|
});
|
|
241
241
|
|
|
242
242
|
test('V3: EVERY seeded rank-role refuses assignment (rank is the only writer — R95b)', async () => {
|
|
243
243
|
for (const role of SEEDED_ROLES) {
|
|
244
|
-
const r = await call('POST', '/
|
|
244
|
+
const r = await call('POST', '/government/assignments', { builder_id: 7, rank_key: role });
|
|
245
245
|
assert.equal(r.status, 403, `assigning seeded role ${role} must be refused`);
|
|
246
|
-
assert.equal(r.json.error.code, '
|
|
246
|
+
assert.equal(r.json.error.code, 'seeded_rank_assignment_forbidden', role);
|
|
247
247
|
assert.equal(calls.filter(([c]) => c === 'assignRole').length, 0);
|
|
248
248
|
}
|
|
249
249
|
});
|
|
250
250
|
|
|
251
251
|
test('V4: mass-assignment on role create is refused outright (strict schema)', async () => {
|
|
252
|
-
const r = await call('POST', '/
|
|
253
|
-
|
|
252
|
+
const r = await call('POST', '/government/ranks', {
|
|
253
|
+
rank_key: 'sneaky-role',
|
|
254
254
|
label: 'Sneaky',
|
|
255
255
|
is_system: true,
|
|
256
256
|
seeded_rank: 'archon',
|
|
@@ -262,32 +262,32 @@ test('V4: mass-assignment on role create is refused outright (strict schema)', a
|
|
|
262
262
|
});
|
|
263
263
|
|
|
264
264
|
test('V4: a clean role create is never system and carries no grants', async () => {
|
|
265
|
-
const r = await call('POST', '/
|
|
265
|
+
const r = await call('POST', '/government/ranks', { rank_key: 'sneaky-role', label: 'Sneaky' });
|
|
266
266
|
assert.equal(r.status, 201);
|
|
267
267
|
assert.equal(r.json.role.is_system, false, 'a created role is never system');
|
|
268
268
|
assert.equal(r.json.role.seeded_rank, null);
|
|
269
269
|
assert.deepEqual(r.json.role.permissions, [], 'creation carries no grants');
|
|
270
270
|
const c = calls.find(([k]) => k === 'createRole');
|
|
271
|
-
assert.deepEqual(Object.keys(c[1]).sort(), ['createdBy', 'description', 'label', '
|
|
271
|
+
assert.deepEqual(Object.keys(c[1]).sort(), ['createdBy', 'description', 'label', 'rankKey'],
|
|
272
272
|
'nothing beyond the four whitelisted fields reaches the db');
|
|
273
273
|
assert.equal(calls.filter(([k]) => k === 'grantRolePermission').length, 0);
|
|
274
274
|
});
|
|
275
275
|
|
|
276
|
-
test('V5: a caller without
|
|
277
|
-
grant(catalog.grantableKeys().filter((k) => k !== '
|
|
276
|
+
test('V5: a caller without government.manage cannot reach ANY write vector at all', async () => {
|
|
277
|
+
grant(catalog.grantableKeys().filter((k) => k !== 'government.manage'));
|
|
278
278
|
for (const [method, p, body] of [
|
|
279
|
-
['POST', '/
|
|
280
|
-
['PUT', '/
|
|
281
|
-
['DELETE', '/
|
|
282
|
-
['POST', '/
|
|
283
|
-
['POST', '/
|
|
284
|
-
['DELETE', '/
|
|
279
|
+
['POST', '/government/ranks', { rank_key: 'x-role', label: 'X' }],
|
|
280
|
+
['PUT', '/government/ranks/custom-role/permissions/deploy.push_main', undefined],
|
|
281
|
+
['DELETE', '/government/ranks/custom-role/permissions/idea.triage', undefined],
|
|
282
|
+
['POST', '/government/ranks/metic/reset', undefined],
|
|
283
|
+
['POST', '/government/assignments', { builder_id: 7, rank_key: 'custom-role' }],
|
|
284
|
+
['DELETE', '/government/assignments/7/custom-role', undefined],
|
|
285
285
|
]) {
|
|
286
286
|
const r = await call(method, p, body);
|
|
287
287
|
assert.equal(r.status, 403, `${method} ${p} must deny — even holding every operational grant`);
|
|
288
288
|
assert.equal(r.json.error.code, 'permission_forbidden', `${method} ${p}`);
|
|
289
289
|
}
|
|
290
|
-
grant(['
|
|
290
|
+
grant(['government.manage']);
|
|
291
291
|
});
|
|
292
292
|
|
|
293
293
|
// ===========================================================================
|
|
@@ -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)', () => {
|
|
@@ -57,7 +57,7 @@ test('substrate keys are the out-of-band three, and each implies system + archon
|
|
|
57
57
|
assert.equal(byKey(k).floor, 'archon', `${k} must floor at archon`);
|
|
58
58
|
assert.equal(byKey(k).principal, null, `${k} must not also be a principal key`);
|
|
59
59
|
}
|
|
60
|
-
assert.equal(cat.isSubstrate('
|
|
60
|
+
assert.equal(cat.isSubstrate('government.manage'), false, 'an in-app archon power is not substrate');
|
|
61
61
|
assert.equal(cat.isSubstrate('nope.not.a.key'), false, 'unknown fails closed');
|
|
62
62
|
});
|
|
63
63
|
|
|
@@ -67,7 +67,7 @@ test('isGrantableTo: a sub-Archon rank-role takes owner powers, a custom role do
|
|
|
67
67
|
const custom = { is_system: false, seeded_rank: null };
|
|
68
68
|
|
|
69
69
|
// The widening: an in-app archon power reaches a rank-role, never a custom one.
|
|
70
|
-
for (const k of ['
|
|
70
|
+
for (const k of ['government.manage', 'builder.rank.set', 'version.close']) {
|
|
71
71
|
assert.equal(cat.isGrantableTo(k, metic), true, `${k} → metic rank-role`);
|
|
72
72
|
assert.equal(cat.isGrantableTo(k, custom), false, `${k} → custom role must stay refused`);
|
|
73
73
|
}
|
|
@@ -173,7 +173,7 @@ test('an unknown/reserved rank resolves to no permissions (fail-closed; ADR 0151
|
|
|
173
173
|
});
|
|
174
174
|
|
|
175
175
|
test('byKey returns a frozen record or null', () => {
|
|
176
|
-
const p = byKey('
|
|
176
|
+
const p = byKey('government.manage');
|
|
177
177
|
assert.ok(p && p.system === true && p.floor === 'archon');
|
|
178
178
|
assert.throws(() => { p.system = false; }, /Cannot assign|read only|frozen/i);
|
|
179
179
|
assert.equal(byKey('nope'), null);
|
|
@@ -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', 'government.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', 'government.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');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// tests/governance_fold_bench.mjs — the task-1002556 benchmark, re-runnable.
|
|
2
2
|
//
|
|
3
3
|
// Measures end-to-end latency of a requireBuilder + requirePermission route
|
|
4
|
-
// (GET /
|
|
4
|
+
// (GET /government/ranks) against a LIVE server, the way the fold was graded:
|
|
5
5
|
// point it at a stack before and after and compare. Asserts only route health
|
|
6
6
|
// (every request 200) — the numbers are the deliverable, printed for the
|
|
7
7
|
// operator to diff.
|
|
@@ -27,7 +27,7 @@ import { strict as assert } from 'node:assert';
|
|
|
27
27
|
const BASE = (process.env.GDS_BASE_URL || '').replace(/\/$/, '');
|
|
28
28
|
const TOKEN = process.env.BENCH_TOKEN || '';
|
|
29
29
|
if (!BASE || !TOKEN) {
|
|
30
|
-
console.log('governance_fold_bench.mjs SKIPPED — set GDS_BASE_URL and BENCH_TOKEN (a session token whose builder holds
|
|
30
|
+
console.log('governance_fold_bench.mjs SKIPPED — set GDS_BASE_URL and BENCH_TOKEN (a session token whose builder holds government.manage).');
|
|
31
31
|
process.exit(0);
|
|
32
32
|
}
|
|
33
33
|
const N = Number(process.env.BENCH_N) || 300;
|