@bongos/core 1.19.185 → 1.19.187
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 +90 -85
- package/clients/bongos-client/index.d.ts +2 -2
- package/config/government.neutral.json +6 -6
- package/docs/adr/0172-editable-rank-roles-substrate-axis.md +1 -1
- package/docs/api/openapi.json +12 -12
- package/docs/api-reference.md +7 -7
- package/modules/builder-settings/wandering-prefs.js +13 -13
- package/modules/government/catalog.js +79 -79
- package/modules/government/config.js +43 -33
- package/modules/government/db.js +58 -58
- package/modules/government/protected-surfaces.js +7 -7
- package/modules/government/protected-surfaces.json +4 -4
- package/modules/government/ranks.js +17 -17
- package/modules/government/resolver.js +24 -24
- package/modules/government/routes/government.js +88 -88
- package/modules/government/routes/rank-sync.js +8 -8
- package/modules/government/session-scopes.js +7 -7
- package/modules/government/session-scopes.json +5 -5
- package/modules/hall-ui/public/gate.js +2 -2
- package/modules/hall-ui/public/{governance.css → government.css} +11 -11
- package/modules/hall-ui/public/{governance.html → government.html} +14 -14
- package/modules/hall-ui/public/{governance.js → government.js} +108 -108
- package/modules/hall-ui/public/harbor.js +2 -2
- package/modules/hall-ui/public/shell.js +19 -15
- package/modules/hall-ui/public/watch.js +1 -1
- package/modules/lifecycle/claim-eligibility.js +1 -1
- package/modules/lifecycle/db.js +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/fitness.js +138 -0
- package/scripts/hall-preview/server.js +1 -1
- package/src/bongos/auth.js +11 -11
- package/src/bongos/serve-internal.js +8 -8
- package/src/module-api.js +5 -1
- package/tests/auth_page_gate.mjs +2 -2
- package/tests/auth_resolve_failure.mjs +4 -4
- package/tests/{governance_abuse_matrix.mjs → government_abuse_matrix.mjs} +39 -39
- package/tests/{governance_catalog.mjs → government_catalog.mjs} +11 -11
- package/tests/{governance_claim_eligibility.mjs → government_claim_eligibility.mjs} +2 -2
- package/tests/{governance_config.mjs → government_config.mjs} +54 -54
- package/tests/{governance_fold.mjs → government_fold.mjs} +6 -6
- package/tests/{governance_fold_bench.mjs → government_fold_bench.mjs} +3 -3
- package/tests/{governance_live_proof.mjs → government_live_proof.mjs} +5 -5
- package/tests/{governance_ownership_scope.mjs → government_ownership_scope.mjs} +16 -16
- package/tests/{governance_page_gates.mjs → government_page_gates.mjs} +14 -14
- package/tests/{governance_parity.mjs → government_parity.mjs} +3 -3
- package/tests/{governance_principals.mjs → government_principals.mjs} +14 -14
- package/tests/{governance_protected_surfaces.mjs → government_protected_surfaces.mjs} +1 -1
- package/tests/{governance_role_sync.mjs → government_rank_sync.mjs} +2 -2
- package/tests/{governance_require_permission.mjs → government_require_permission.mjs} +1 -1
- package/tests/{governance_resolver.mjs → government_resolver.mjs} +6 -6
- package/tests/{governance_routes.mjs → government_routes.mjs} +36 -36
- package/tests/{governance_seed.mjs → government_seed.mjs} +8 -8
- package/tests/{governance_session_scope.mjs → government_session_scope.mjs} +10 -10
- package/tests/{governance_vanilla_boot.mjs → government_vanilla_boot.mjs} +26 -26
- package/tests/government_vocabulary.mjs +94 -0
- package/tests/hall_nav.mjs +8 -7
- package/tests/hall_page_gate_map.mjs +9 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// tests/
|
|
1
|
+
// tests/government_config.mjs — the governance config contract (BV1.R96, ADR 0151).
|
|
2
2
|
//
|
|
3
3
|
// DB-free: exercises the PURE resolver (all inputs injected) plus the disk loader
|
|
4
4
|
// against the real neutral starter. The invariants that matter here are trust-boundary
|
|
@@ -18,7 +18,7 @@ const GOV = path.join(ROOT, 'modules', 'government');
|
|
|
18
18
|
const config = require(path.join(GOV, 'config.js'));
|
|
19
19
|
const catalog = require(path.join(GOV, 'catalog.js'));
|
|
20
20
|
const { envPrefix } = require(path.join(ROOT, 'src', 'instance-config'));
|
|
21
|
-
const {
|
|
21
|
+
const { resolveGovernmentConfig, loadGovernmentConfig, grantableOnly, NEUTRAL_PATH, INSTANCE_PATH } = config;
|
|
22
22
|
|
|
23
23
|
const NEUTRAL = JSON.parse(fs.readFileSync(NEUTRAL_PATH, 'utf8'));
|
|
24
24
|
// No env by default — every precedence test injects exactly the env it is asserting on.
|
|
@@ -27,73 +27,73 @@ const NO_ENV = {};
|
|
|
27
27
|
// ── precedence: neutral -> instance -> env ──────────────────────────────────────
|
|
28
28
|
|
|
29
29
|
test('neutral-only resolution yields sane defaults (the vanilla boot R102 proves)', () => {
|
|
30
|
-
const out =
|
|
31
|
-
assert.deepEqual(Object.keys(out.
|
|
32
|
-
assert.equal(out.
|
|
33
|
-
assert.equal(out.
|
|
30
|
+
const out = resolveGovernmentConfig({ neutral: NEUTRAL, env: NO_ENV });
|
|
31
|
+
assert.deepEqual(Object.keys(out.ranks).sort(), [...catalog.RANK_ORDER].sort());
|
|
32
|
+
assert.equal(out.ranks.metic.label, 'Metic');
|
|
33
|
+
assert.equal(out.ranks.archon.seeded, true);
|
|
34
34
|
for (const rank of catalog.RANK_ORDER) {
|
|
35
|
-
assert.ok(out.
|
|
35
|
+
assert.ok(out.ranks[rank].description, `${rank} template must carry a description`);
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
test('an empty config still resolves the four rank-roles to their canonical names', () => {
|
|
40
40
|
// governance_002 seeds them unconditionally, so a missing/mangled config must fall
|
|
41
41
|
// back rather than drop a role out of the set.
|
|
42
|
-
const out =
|
|
43
|
-
assert.deepEqual(Object.keys(out.
|
|
44
|
-
assert.equal(out.
|
|
42
|
+
const out = resolveGovernmentConfig({ env: NO_ENV });
|
|
43
|
+
assert.deepEqual(Object.keys(out.ranks).sort(), [...catalog.RANK_ORDER].sort());
|
|
44
|
+
assert.equal(out.ranks.xenos.label, 'xenos');
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
test('the instance file overrides the neutral starter, per role and per field', () => {
|
|
48
|
-
const out =
|
|
48
|
+
const out = resolveGovernmentConfig({
|
|
49
49
|
neutral: NEUTRAL,
|
|
50
|
-
instance: {
|
|
50
|
+
instance: { ranks: { metic: { label: 'Trusted' } } },
|
|
51
51
|
env: NO_ENV,
|
|
52
52
|
});
|
|
53
|
-
assert.equal(out.
|
|
53
|
+
assert.equal(out.ranks.metic.label, 'Trusted');
|
|
54
54
|
// The untouched field survives the merge, and so do the other three roles.
|
|
55
|
-
assert.equal(out.
|
|
56
|
-
assert.equal(out.
|
|
55
|
+
assert.equal(out.ranks.metic.description, NEUTRAL.ranks.metic.description);
|
|
56
|
+
assert.equal(out.ranks.archon.label, 'Archon');
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
test('the instance file may add a custom role template the neutral starter has not', () => {
|
|
60
|
-
const out =
|
|
60
|
+
const out = resolveGovernmentConfig({
|
|
61
61
|
neutral: NEUTRAL,
|
|
62
|
-
instance: {
|
|
62
|
+
instance: { ranks: { triager: { label: 'Triager', permissions: ['idea.triage', 'blocker.review'] } } },
|
|
63
63
|
env: NO_ENV,
|
|
64
64
|
});
|
|
65
|
-
assert.equal(out.
|
|
66
|
-
assert.deepEqual(out.
|
|
65
|
+
assert.equal(out.ranks.triager.seeded, false);
|
|
66
|
+
assert.deepEqual(out.ranks.triager.permissions, ['blocker.review', 'idea.triage']);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
test('an env override beats both the instance file and the neutral starter', () => {
|
|
70
70
|
const env = { [`${envPrefix()}_GOVERNANCE_ROLE_LABELS`]: 'metic=Crew,archon=Captain' };
|
|
71
|
-
const out =
|
|
71
|
+
const out = resolveGovernmentConfig({
|
|
72
72
|
neutral: NEUTRAL,
|
|
73
|
-
instance: {
|
|
73
|
+
instance: { ranks: { metic: { label: 'Trusted' } } },
|
|
74
74
|
env,
|
|
75
75
|
});
|
|
76
|
-
assert.equal(out.
|
|
77
|
-
assert.equal(out.
|
|
78
|
-
assert.equal(out.
|
|
76
|
+
assert.equal(out.ranks.metic.label, 'Crew');
|
|
77
|
+
assert.equal(out.ranks.archon.label, 'Captain');
|
|
78
|
+
assert.equal(out.ranks.xenos.label, 'Xenos', 'an unnamed role keeps its resolved label');
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
test('the env layer can relabel a role but can never grant it a permission', () => {
|
|
82
82
|
// The whole point of restricting env to labels: an env var must not widen authority.
|
|
83
83
|
const env = { [`${envPrefix()}_GOVERNANCE_ROLE_LABELS`]: 'triager=Renamed' };
|
|
84
|
-
const out =
|
|
84
|
+
const out = resolveGovernmentConfig({
|
|
85
85
|
neutral: NEUTRAL,
|
|
86
|
-
instance: {
|
|
86
|
+
instance: { ranks: { triager: { label: 'Triager', permissions: ['idea.triage'] } } },
|
|
87
87
|
env,
|
|
88
88
|
});
|
|
89
|
-
assert.equal(out.
|
|
90
|
-
assert.deepEqual(out.
|
|
89
|
+
assert.equal(out.ranks.triager.label, 'Renamed');
|
|
90
|
+
assert.deepEqual(out.ranks.triager.permissions, ['idea.triage']);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
test('an env label naming a role no config defines is inert (a typo cannot grow the role set)', () => {
|
|
94
94
|
const env = { [`${envPrefix()}_GOVERNANCE_ROLE_LABELS`]: 'metics=Typo' };
|
|
95
|
-
const out =
|
|
96
|
-
assert.deepEqual(Object.keys(out.
|
|
95
|
+
const out = resolveGovernmentConfig({ neutral: NEUTRAL, env });
|
|
96
|
+
assert.deepEqual(Object.keys(out.ranks).sort(), [...catalog.RANK_ORDER].sort());
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
// ── fail-closed: config is not a trust boundary ─────────────────────────────────
|
|
@@ -101,41 +101,41 @@ test('an env label naming a role no config defines is inert (a typo cannot grow
|
|
|
101
101
|
test('a template naming a system permission is stripped (config cannot grant system power)', () => {
|
|
102
102
|
const systemKeys = catalog.systemKeys();
|
|
103
103
|
assert.ok(systemKeys.includes('deploy.push_main'), 'fixture guard: the catalog still flags deploy.push_main');
|
|
104
|
-
const out =
|
|
104
|
+
const out = resolveGovernmentConfig({
|
|
105
105
|
neutral: NEUTRAL,
|
|
106
|
-
instance: {
|
|
106
|
+
instance: { ranks: { sneaky: { label: 'Sneaky', permissions: ['deploy.push_main', 'builder.rank.set', 'idea.triage'] } } },
|
|
107
107
|
env: NO_ENV,
|
|
108
108
|
});
|
|
109
|
-
assert.deepEqual(out.
|
|
109
|
+
assert.deepEqual(out.ranks.sneaky.permissions, ['idea.triage']);
|
|
110
110
|
// Belt and braces: NO system key survives a template, whichever one is named.
|
|
111
|
-
const all =
|
|
111
|
+
const all = resolveGovernmentConfig({
|
|
112
112
|
neutral: NEUTRAL,
|
|
113
|
-
instance: {
|
|
113
|
+
instance: { ranks: { everything: { permissions: catalog.permissionKeys() } } },
|
|
114
114
|
env: NO_ENV,
|
|
115
115
|
});
|
|
116
116
|
for (const k of systemKeys) {
|
|
117
|
-
assert.ok(!all.
|
|
117
|
+
assert.ok(!all.ranks.everything.permissions.includes(k), `config must not be able to grant system permission "${k}"`);
|
|
118
118
|
}
|
|
119
|
-
assert.deepEqual(all.
|
|
119
|
+
assert.deepEqual(all.ranks.everything.permissions, [...catalog.grantableKeys()].sort());
|
|
120
120
|
});
|
|
121
121
|
|
|
122
122
|
test('a template naming an unknown permission key is dropped (the catalog is the truth)', () => {
|
|
123
|
-
const out =
|
|
123
|
+
const out = resolveGovernmentConfig({
|
|
124
124
|
neutral: NEUTRAL,
|
|
125
|
-
instance: {
|
|
125
|
+
instance: { ranks: { odd: { permissions: ['idea.triage', 'not.a.real.permission', 'deploy.everything'] } } },
|
|
126
126
|
env: NO_ENV,
|
|
127
127
|
});
|
|
128
|
-
assert.deepEqual(out.
|
|
128
|
+
assert.deepEqual(out.ranks.odd.permissions, ['idea.triage']);
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
test('permissions on a SEEDED rank-role are dropped wholesale (grants derive from floors)', () => {
|
|
132
|
-
const out =
|
|
132
|
+
const out = resolveGovernmentConfig({
|
|
133
133
|
neutral: NEUTRAL,
|
|
134
|
-
instance: {
|
|
134
|
+
instance: { ranks: { xenos: { label: 'Newcomer', permissions: ['task.create', 'audit_log.read'] } } },
|
|
135
135
|
env: NO_ENV,
|
|
136
136
|
});
|
|
137
|
-
assert.equal(out.
|
|
138
|
-
assert.deepEqual(out.
|
|
137
|
+
assert.equal(out.ranks.xenos.label, 'Newcomer', 'a seeded role may be relabelled');
|
|
138
|
+
assert.deepEqual(out.ranks.xenos.permissions, [], 'a seeded role may never be re-granted from config');
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
test('grantableOnly de-duplicates, sorts, and rejects non-string entries', () => {
|
|
@@ -145,9 +145,9 @@ test('grantableOnly de-duplicates, sorts, and rejects non-string entries', () =>
|
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
test('the resolved config is frozen — a caller cannot mutate it into a grant', () => {
|
|
148
|
-
const out =
|
|
149
|
-
assert.ok(Object.isFrozen(out) && Object.isFrozen(out.
|
|
150
|
-
assert.throws(() => out.
|
|
148
|
+
const out = resolveGovernmentConfig({ neutral: NEUTRAL, env: NO_ENV });
|
|
149
|
+
assert.ok(Object.isFrozen(out) && Object.isFrozen(out.ranks) && Object.isFrozen(out.ranks.metic));
|
|
150
|
+
assert.throws(() => out.ranks.metic.permissions.push('deploy.push_main'), TypeError);
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
// ── the neutral starter itself ──────────────────────────────────────────────────
|
|
@@ -159,9 +159,9 @@ test('the neutral starter carries no host-specific identity (a vanilla instance
|
|
|
159
159
|
const raw = fs.readFileSync(NEUTRAL_PATH, 'utf8');
|
|
160
160
|
assert.deepEqual(scanPromptIdentity('config/government.neutral.json', raw, '.json'), []);
|
|
161
161
|
// And it carries only templates — never a live per-builder assignment.
|
|
162
|
-
assert.deepEqual(Object.keys(NEUTRAL).filter((k) => k !== '_note'), ['
|
|
163
|
-
for (const
|
|
164
|
-
assert.ok(!('permissions' in
|
|
162
|
+
assert.deepEqual(Object.keys(NEUTRAL).filter((k) => k !== '_note'), ['ranks']);
|
|
163
|
+
for (const rank of Object.values(NEUTRAL.ranks)) {
|
|
164
|
+
assert.ok(!('permissions' in rank), 'the neutral seeded ranks carry labels only, never grants');
|
|
165
165
|
}
|
|
166
166
|
});
|
|
167
167
|
|
|
@@ -171,13 +171,13 @@ test('the two config paths are the branding-shaped neutral/instance pair under c
|
|
|
171
171
|
assert.ok(fs.existsSync(NEUTRAL_PATH), 'the neutral starter must ship — it is the vanilla default');
|
|
172
172
|
});
|
|
173
173
|
|
|
174
|
-
test('
|
|
174
|
+
test('loadGovernmentConfig reads the real neutral starter and tolerates a missing instance pack', () => {
|
|
175
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
|
-
const out =
|
|
179
|
-
assert.equal(out.
|
|
180
|
-
assert.deepEqual(Object.keys(out.
|
|
178
|
+
const out = loadGovernmentConfig({ env: NO_ENV });
|
|
179
|
+
assert.equal(out.ranks.archon.label, 'Archon');
|
|
180
|
+
assert.deepEqual(Object.keys(out.ranks).sort(), [...catalog.RANK_ORDER].sort());
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
test('the neutral starter publishes with the core (a packaged vanilla instance needs it)', () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// tests/
|
|
1
|
+
// tests/government_fold.mjs — the session-load permission FOLD (task 1002556).
|
|
2
2
|
//
|
|
3
3
|
// DB-free. requirePermission used to cost a second round-trip on every gated
|
|
4
4
|
// request (requireBuilder loads the session, then resolveHeldPermissions hits
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// • fail-closed posture is untouched: a missing/malformed port still denies
|
|
16
16
|
// even when raw keys are present (authority must be ESTABLISHED, ADR 0016).
|
|
17
17
|
//
|
|
18
|
-
// The real-DB / real-HTTP complement is tests/
|
|
18
|
+
// The real-DB / real-HTTP complement is tests/government_live_proof.mjs, which
|
|
19
19
|
// exercises the folded lookupSession end-to-end on a live stack.
|
|
20
20
|
import assert from 'node:assert/strict';
|
|
21
21
|
import { test } from 'node:test';
|
|
@@ -121,8 +121,8 @@ 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
|
|
125
|
-
|
|
124
|
+
const buildGovernmentRouter = require(path.join(ROOT, 'modules', 'government', 'routes', 'government.js'));
|
|
125
|
+
buildGovernmentRouter();
|
|
126
126
|
const real = seams.resolveOptional('session-grants-sql').selectFragment;
|
|
127
127
|
assert.equal(dbKernel.sessionPermsSelect(), real, 'the shipped fragment embeds unchanged');
|
|
128
128
|
seams._reset();
|
|
@@ -133,8 +133,8 @@ 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
|
|
137
|
-
|
|
136
|
+
const buildGovernmentRouter = require(path.join(ROOT, 'modules', 'government', 'routes', 'government.js'));
|
|
137
|
+
buildGovernmentRouter();
|
|
138
138
|
const provider = seams.resolveOptional('session-grants-sql');
|
|
139
139
|
assert.ok(provider && typeof provider.selectFragment === 'string', 'fragment provider registered');
|
|
140
140
|
assert.match(provider.selectFragment, /AS raw_permission_keys/, 'yields the column auth.js reads');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// tests/
|
|
1
|
+
// tests/government_fold_bench.mjs — the task-1002556 benchmark, re-runnable.
|
|
2
2
|
//
|
|
3
3
|
// Measures end-to-end latency of a requireBuilder + requirePermission route
|
|
4
4
|
// (GET /government/ranks) against a LIVE server, the way the fold was graded:
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// set so the DB-free lane never runs it.
|
|
20
20
|
//
|
|
21
21
|
// Run: GDS_BASE_URL=http://localhost:3000 BENCH_TOKEN=<archon session token> \
|
|
22
|
-
// node tests/
|
|
22
|
+
// node tests/government_fold_bench.mjs
|
|
23
23
|
// BENCH_N (default 300) / BENCH_WARMUP (default 30) tune the sample.
|
|
24
24
|
|
|
25
25
|
import { strict as assert } from 'node:assert';
|
|
@@ -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('
|
|
30
|
+
console.log('government_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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// tests/
|
|
1
|
+
// tests/government_live_proof.mjs — the C6 governance LIVE proof (BV1.R114, ADR 0151).
|
|
2
2
|
//
|
|
3
3
|
// C6's live-demonstration clause: an Archon creates a CUSTOM role granting an
|
|
4
4
|
// OPERATIONAL permission, assigns it to a low-trust builder, and that builder can
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// substrate: the deploy/protected-path attempt is still blocked.
|
|
8
8
|
//
|
|
9
9
|
// This is the goal_wall_proof.mjs pattern: the abuse MATRIX
|
|
10
|
-
// (tests/
|
|
10
|
+
// (tests/government_abuse_matrix.mjs, R112) already proves every vector DB-free
|
|
11
11
|
// with stubs; THIS file proves the same story END-TO-END on the real stack —
|
|
12
12
|
// real Postgres rows, real sessions, real HTTP through the real router, the real
|
|
13
13
|
// resolver behind the real `governance` kernel port, no stubs, no monkeypatches.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// and is listed in run-unit-tests.js's INTEGRATION set so the DB-free lane
|
|
19
19
|
// never runs it.
|
|
20
20
|
//
|
|
21
|
-
// Run: DATABASE_URL=... node tests/
|
|
21
|
+
// Run: DATABASE_URL=... node tests/government_live_proof.mjs
|
|
22
22
|
// GDS_BASE_URL=http://localhost:3000 — optional: drive an already-running
|
|
23
23
|
// sandbox server (sharing that DATABASE_URL) instead of booting the router
|
|
24
24
|
// in-process. Same routes either way; this is how the live sandbox transcript
|
|
@@ -32,7 +32,7 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'test';
|
|
|
32
32
|
const require = createRequire(import.meta.url);
|
|
33
33
|
|
|
34
34
|
if (!process.env.DATABASE_URL) {
|
|
35
|
-
console.log('
|
|
35
|
+
console.log('government_live_proof.mjs SKIPPED — DATABASE_URL not set.');
|
|
36
36
|
process.exit(0);
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -41,7 +41,7 @@ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
|
41
41
|
try {
|
|
42
42
|
await pool.query('SELECT 1 FROM governance_roles LIMIT 1');
|
|
43
43
|
} catch (e) {
|
|
44
|
-
console.log(`
|
|
44
|
+
console.log(`government_live_proof.mjs SKIPPED — no migrated Postgres (${e.code || e.message}).`);
|
|
45
45
|
await pool.end().catch(() => {});
|
|
46
46
|
process.exit(0);
|
|
47
47
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// tests/
|
|
1
|
+
// tests/government_ownership_scope.mjs — ownership-scoped permissions
|
|
2
2
|
// ("act on OWN X") as a first-class axis (BV1.R100, ADR 0151 §3). DB-free.
|
|
3
3
|
//
|
|
4
4
|
// The thing being proved is a CONJUNCTION. `box.manage.own` is not a grant that
|
|
@@ -37,7 +37,7 @@ const db = require(path.join(GOV, 'db.js'));
|
|
|
37
37
|
const auth = require(path.join(ROOT, 'src', 'bongos', 'auth.js'));
|
|
38
38
|
|
|
39
39
|
const {
|
|
40
|
-
PERMISSIONS, RANK_ORDER,
|
|
40
|
+
PERMISSIONS, RANK_ORDER, RANK_SEED, RESOURCE_FAMILIES, permissionKeys, byKey,
|
|
41
41
|
scopeOf, resourceOf, isOwnershipScoped, isCrossOwner, ownershipScopedKeys, crossOwnerKeys,
|
|
42
42
|
crossOwnerKeysFor, isPrincipalKey, seedForRank,
|
|
43
43
|
} = catalog;
|
|
@@ -153,16 +153,16 @@ test('B1: for every paired family some rank reaches its OWN row but not everyone
|
|
|
153
153
|
test('B2: the seed grants the own key to ranks it withholds the cross-owner key from', () => {
|
|
154
154
|
for (const [resource, fam] of PAIRED) {
|
|
155
155
|
for (const ownKey of fam.own) {
|
|
156
|
-
const ownOnly = RANK_ORDER.filter((r) =>
|
|
156
|
+
const ownOnly = RANK_ORDER.filter((r) => RANK_SEED[r].includes(ownKey) && !fam.any.some((a) => RANK_SEED[r].includes(a)));
|
|
157
157
|
assert.ok(ownOnly.length > 0, `${resource}: no rank holds ${ownKey} without also holding ${fam.any.join('/')}`);
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
// The concrete claim, spelled out: a xenos may act on their own claim and their
|
|
161
161
|
// own box; they may not release another builder's claim or close another's box.
|
|
162
|
-
assert.ok(
|
|
163
|
-
assert.ok(!
|
|
164
|
-
assert.ok(
|
|
165
|
-
assert.ok(!
|
|
162
|
+
assert.ok(RANK_SEED.xenos.includes('task.act.own'));
|
|
163
|
+
assert.ok(!RANK_SEED.xenos.includes('claim.release.any'));
|
|
164
|
+
assert.ok(RANK_SEED.xenos.includes('box.manage.own'));
|
|
165
|
+
assert.ok(!RANK_SEED.xenos.includes('box.fleet.manage'));
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
test('B3: cross-owner authority is never CHEAPER than own-only authority', () => {
|
|
@@ -372,13 +372,13 @@ test('F4: a cross-owner key with no ownership-scoped counterpart throws at modul
|
|
|
372
372
|
|
|
373
373
|
test('G1: the seed is still floor-derived and cumulative — the ownership axis adds no grants', () => {
|
|
374
374
|
for (const rank of RANK_ORDER) {
|
|
375
|
-
const held = new Set(
|
|
375
|
+
const held = new Set(RANK_SEED[rank]);
|
|
376
376
|
const tier = RANK_ORDER.indexOf(rank);
|
|
377
377
|
for (const p of PERMISSIONS) {
|
|
378
378
|
assert.equal(held.has(p.key), p.floor !== null && RANK_ORDER.indexOf(p.floor) <= tier,
|
|
379
379
|
`${rank} vs ${p.key}: scope must not touch the floor derivation`);
|
|
380
380
|
}
|
|
381
|
-
assert.deepEqual([...seedForRank(rank)].sort(), [...
|
|
381
|
+
assert.deepEqual([...seedForRank(rank)].sort(), [...RANK_SEED[rank]].sort());
|
|
382
382
|
}
|
|
383
383
|
});
|
|
384
384
|
|
|
@@ -397,14 +397,14 @@ test('G2: the role resolve is untouched — own-scoped keys still resolve, princ
|
|
|
397
397
|
});
|
|
398
398
|
|
|
399
399
|
test('G3: the kernel port carries the ownership axis on the ONE governance port', () => {
|
|
400
|
-
const {
|
|
400
|
+
const { governmentPort } = require(path.join(GOV, 'routes', 'government.js'));
|
|
401
401
|
for (const name of ['authorizeOwned', 'authorizeOwnedOrAny', 'sameOwner', 'isOwnershipScoped', 'crossOwnerKeysFor']) {
|
|
402
|
-
assert.equal(typeof
|
|
402
|
+
assert.equal(typeof governmentPort[name], 'function', `the port must expose ${name}`);
|
|
403
403
|
}
|
|
404
404
|
// still the same one object port the earlier slices registered
|
|
405
|
-
assert.equal(typeof
|
|
406
|
-
assert.equal(typeof
|
|
407
|
-
assert.equal(
|
|
408
|
-
assert.equal(
|
|
409
|
-
assert.equal(
|
|
405
|
+
assert.equal(typeof governmentPort.resolveBuilderPermissions, 'function');
|
|
406
|
+
assert.equal(typeof governmentPort.resolvePrincipalPermissions, 'function');
|
|
407
|
+
assert.equal(governmentPort.isOwnershipScoped('box.manage.own'), true);
|
|
408
|
+
assert.equal(governmentPort.isOwnershipScoped('claim.release.any'), false);
|
|
409
|
+
assert.equal(governmentPort.isOwnershipScoped('nope.not.a.key'), false);
|
|
410
410
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// tests/
|
|
1
|
+
// tests/government_page_gates.mjs — the three HTML PAGE GATES + the wandering
|
|
2
2
|
// CEILING, re-expressed on the governance model (BV1.R107, ADR 0151).
|
|
3
3
|
//
|
|
4
4
|
// DB-free. Two doubles, both installed the way the existing suites do it:
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// auth.js destructures it at load time (tests/auth_page_gate.mjs precedent).
|
|
7
7
|
// • the `governance` KERNEL PORT is a seam stub, so the gates' authority half
|
|
8
8
|
// needs no Postgres, no router, no live module
|
|
9
|
-
// (tests/
|
|
9
|
+
// (tests/government_require_permission.mjs precedent).
|
|
10
10
|
//
|
|
11
11
|
// WHAT THIS FILE HAS TO PROVE, and why the oracles are split.
|
|
12
12
|
// A page gate that resolves its audience from catalog.floor cannot be checked
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// installed on the response double precisely so a regression to JSON shows up
|
|
27
27
|
// as an assertion rather than a TypeError.
|
|
28
28
|
//
|
|
29
|
-
// Run: node --test tests/
|
|
29
|
+
// Run: node --test tests/government_page_gates.mjs
|
|
30
30
|
|
|
31
31
|
import assert from 'node:assert/strict';
|
|
32
32
|
import { test } from 'node:test';
|
|
@@ -50,13 +50,13 @@ 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
|
|
|
53
|
-
const GATES = ['requireBuilderPage', 'requireNonXenosPage', '
|
|
53
|
+
const GATES = ['requireBuilderPage', 'requireNonXenosPage', 'requireGovernmentPage'];
|
|
54
54
|
const LIVE_RANKS = ['xenos', 'thetes', 'metic', 'archon'];
|
|
55
55
|
|
|
56
56
|
// ═══ the ORACLE — hand-written, from the rank tests R107 deleted ═════════════
|
|
57
57
|
// requireBuilderPage admitted any session at all → every live rank
|
|
58
58
|
// requireNonXenosPage admitted rank !== 'xenos' → thetes and up
|
|
59
|
-
//
|
|
59
|
+
// requireGovernmentPage admitted rank === 'archon' → archon only …until ADR 0157
|
|
60
60
|
//
|
|
61
61
|
// ADR 0157 (owner decision, 2026-08-05) lowered `page.view.government` archon→metic, so
|
|
62
62
|
// the monitoring pages (/watch, /harbor, /gate) now open to the working rank. That gate
|
|
@@ -71,7 +71,7 @@ const LIVE_RANKS = ['xenos', 'thetes', 'metic', 'archon'];
|
|
|
71
71
|
const PRE_R107_AUDIENCE = {
|
|
72
72
|
requireBuilderPage: { xenos: true, thetes: true, metic: true, archon: true },
|
|
73
73
|
requireNonXenosPage: { xenos: false, thetes: true, metic: true, archon: true },
|
|
74
|
-
|
|
74
|
+
requireGovernmentPage: { xenos: false, thetes: false, metic: true, archon: true },
|
|
75
75
|
};
|
|
76
76
|
// Where a SIGNED-IN builder who fails the gate is sent. Never the sign-in flow —
|
|
77
77
|
// they already have a session, so that would loop. Since task 1002613 (map N-1)
|
|
@@ -81,12 +81,12 @@ const PRE_R107_AUDIENCE = {
|
|
|
81
81
|
const DENIED_TO = {
|
|
82
82
|
requireBuilderPage: '/builders',
|
|
83
83
|
requireNonXenosPage: '/builders/not-ready',
|
|
84
|
-
|
|
84
|
+
requireGovernmentPage: '/builders',
|
|
85
85
|
};
|
|
86
86
|
const GATE_PERMISSION = {
|
|
87
87
|
requireBuilderPage: 'page.view.public',
|
|
88
88
|
requireNonXenosPage: 'page.view.builder',
|
|
89
|
-
|
|
89
|
+
requireGovernmentPage: 'page.view.government',
|
|
90
90
|
};
|
|
91
91
|
const deniedLoc = (gate, { unavailable = false } = {}) =>
|
|
92
92
|
`${DENIED_TO[gate]}?denied=${GATE_PERMISSION[gate]}${unavailable ? '&reason=unavailable' : ''}`;
|
|
@@ -95,7 +95,7 @@ const GATE_FLOOR = {
|
|
|
95
95
|
requireBuilderPage: 'xenos',
|
|
96
96
|
requireNonXenosPage: 'thetes',
|
|
97
97
|
// ADR 0157: `page.view.government` floor archon→metic.
|
|
98
|
-
|
|
98
|
+
requireGovernmentPage: 'metic',
|
|
99
99
|
};
|
|
100
100
|
const SIGN_IN = '/api/gds/auth/web/start';
|
|
101
101
|
|
|
@@ -249,16 +249,16 @@ test('B4: the page gates resolve per request — no memoised authority (ADR 0016
|
|
|
249
249
|
});
|
|
250
250
|
const req = { headers: { cookie: 'gds_session=validtoken' } };
|
|
251
251
|
let admitted = false;
|
|
252
|
-
await auth.
|
|
252
|
+
await auth.requireGovernmentPage(req, fakeRes(), () => { admitted = true; });
|
|
253
253
|
assert.equal(admitted, true, 'an Archon should reach the page');
|
|
254
254
|
keys = []; // the role is revoked between requests
|
|
255
255
|
const second = fakeRes();
|
|
256
256
|
let admittedAgain = false;
|
|
257
|
-
await auth.
|
|
257
|
+
await auth.requireGovernmentPage(req, second, () => { admittedAgain = true; });
|
|
258
258
|
seams._reset();
|
|
259
259
|
assert.equal(calls, 2, 'the resolver must be consulted on every request');
|
|
260
260
|
assert.equal(admittedAgain, false, 'a revoked role must deny on the very next request');
|
|
261
|
-
assert.equal(second._redirect.loc, deniedLoc('
|
|
261
|
+
assert.equal(second._redirect.loc, deniedLoc('requireGovernmentPage'));
|
|
262
262
|
});
|
|
263
263
|
|
|
264
264
|
test('B5: every permission a page gate names is a real catalog key', async () => {
|
|
@@ -286,7 +286,7 @@ test('C1: the per-role wandering default + ceiling are unchanged', () => {
|
|
|
286
286
|
assert.equal(wandering.defaultLevelForRank(role), expected.default, `${role} default`);
|
|
287
287
|
assert.equal(wandering.maxLevelForRank(role), expected.ceiling, `${role} ceiling`);
|
|
288
288
|
assert.deepEqual(
|
|
289
|
-
{ default: wandering.
|
|
289
|
+
{ default: wandering.rankWandering(role).default, ceiling: wandering.rankWandering(role).ceiling },
|
|
290
290
|
expected,
|
|
291
291
|
`${role} role metadata`,
|
|
292
292
|
);
|
|
@@ -339,7 +339,7 @@ test('C4: the ceiling is NOT answerable through the boolean permission axis', ()
|
|
|
339
339
|
});
|
|
340
340
|
|
|
341
341
|
test('C5: the derived rank projections cannot drift from the role metadata', () => {
|
|
342
|
-
for (const [role, meta] of Object.entries(wandering.
|
|
342
|
+
for (const [role, meta] of Object.entries(wandering.RANK_WANDERING)) {
|
|
343
343
|
assert.equal(wandering.RANK_DEFAULT[role], meta.default, `RANK_DEFAULT.${role}`);
|
|
344
344
|
assert.equal(wandering.RANK_MAX[role], meta.ceiling, `RANK_MAX.${role}`);
|
|
345
345
|
assert.ok(meta.default <= meta.ceiling, `${role}: default must not exceed the ceiling`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// tests/
|
|
1
|
+
// tests/government_parity.mjs — the compat-seed PARITY proof (BV1.R103, ADR 0151).
|
|
2
2
|
//
|
|
3
3
|
// The promise the whole governance cutover rests on: once governance_002 seeds the
|
|
4
4
|
// four rank-roles and backfills every builder the role matching their rank, each
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
// R104-R107 migration shifts coverage between oracles instead of erasing it.
|
|
25
25
|
// PART C — the two REAL middlewares, requirePermission vs requireRank, over the
|
|
26
26
|
// whole catalog rather than the four hand-picked keys in
|
|
27
|
-
// tests/
|
|
27
|
+
// tests/government_require_permission.mjs.
|
|
28
28
|
// PART D — the deliberate fail-closed divergence governance_002 documents.
|
|
29
29
|
//
|
|
30
|
-
// A third oracle already exists and is NOT duplicated here: tests/
|
|
30
|
+
// A third oracle already exists and is NOT duplicated here: tests/government_seed.mjs
|
|
31
31
|
// pins the catalog against the materialised migration SQL, so a floor edit that
|
|
32
32
|
// reaches no new migration reds there.
|
|
33
33
|
//
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// tests/
|
|
1
|
+
// tests/government_principals.mjs — machine principals as a first-class type
|
|
2
2
|
// (BV1.R98, ADR 0151 §3). DB-free.
|
|
3
3
|
//
|
|
4
4
|
// A builder's authority is the UNION of their ROLES. A machine principal's authority
|
|
@@ -26,7 +26,7 @@ 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
|
|
|
29
|
-
const { PERMISSIONS, RANK_ORDER,
|
|
29
|
+
const { PERMISSIONS, RANK_ORDER, RANK_SEED, PRINCIPALS, principalKeys, principalRoles, isPrincipalKey, principalByRole, seedForRank, isGrantable, isSystem, systemKeys, grantableKeys, permissionKeys } = catalog;
|
|
30
30
|
const { effectivePermissions, resolvePrincipalPermissions, principalHasPermission } = resolver;
|
|
31
31
|
|
|
32
32
|
// ── the catalog side: principals are a distinct, well-formed type ───────────────
|
|
@@ -71,7 +71,7 @@ test('isPrincipalKey separates the axes; an unknown key fails closed', () => {
|
|
|
71
71
|
|
|
72
72
|
test('no rank-role holds a principal permission — an Archon must not hold principal.bfg', () => {
|
|
73
73
|
for (const rank of RANK_ORDER) {
|
|
74
|
-
const held = new Set(
|
|
74
|
+
const held = new Set(RANK_SEED[rank]);
|
|
75
75
|
for (const key of principalKeys()) {
|
|
76
76
|
assert.ok(!held.has(key), `the ${rank} rank-role must not hold ${key}`);
|
|
77
77
|
}
|
|
@@ -81,7 +81,7 @@ test('no rank-role holds a principal permission — an Archon must not hold prin
|
|
|
81
81
|
assert.ok(!derived.has(key), `seedForRank('${rank}') must not derive ${key}`);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
-
assert.ok(!
|
|
84
|
+
assert.ok(!RANK_SEED.archon.includes('principal.bfg'),
|
|
85
85
|
'the top rank is the case that matters: identity is not seniority');
|
|
86
86
|
});
|
|
87
87
|
|
|
@@ -149,10 +149,10 @@ test('resolved principal sets are frozen — a caller cannot widen one', () => {
|
|
|
149
149
|
// ── show, never grant ───────────────────────────────────────────────────────────
|
|
150
150
|
|
|
151
151
|
test('the tab surface is a read-only projection: shown, flagged non-grantable', () => {
|
|
152
|
-
const {
|
|
153
|
-
assert.equal(typeof
|
|
154
|
-
assert.equal(typeof
|
|
155
|
-
const listed =
|
|
152
|
+
const { governmentPort } = require(path.join(GOV, 'routes', 'government.js'));
|
|
153
|
+
assert.equal(typeof governmentPort.listPrincipals, 'function');
|
|
154
|
+
assert.equal(typeof governmentPort.resolvePrincipalPermissions, 'function');
|
|
155
|
+
const listed = governmentPort.listPrincipals();
|
|
156
156
|
assert.deepEqual(listed.map((p) => p.system_role).sort(), ['bfg', 'discord_bot']);
|
|
157
157
|
for (const p of listed) {
|
|
158
158
|
assert.equal(p.grantable, false, `${p.system_role} must be rendered non-grantable`);
|
|
@@ -162,9 +162,9 @@ test('the tab surface is a read-only projection: shown, flagged non-grantable',
|
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
test('the governance port exposes no way to create, grant, or assign a principal', () => {
|
|
165
|
-
const {
|
|
165
|
+
const { governmentPort } = require(path.join(GOV, 'routes', 'government.js'));
|
|
166
166
|
const mutator = /(create|grant|assign|add|set|write|provision|revoke|delete|update)/i;
|
|
167
|
-
for (const name of Object.keys(
|
|
167
|
+
for (const name of Object.keys(governmentPort)) {
|
|
168
168
|
if (!/principal/i.test(name)) continue;
|
|
169
169
|
assert.ok(!mutator.test(name), `port method "${name}" looks like a principal mutator — principals are provisioned out-of-band only`);
|
|
170
170
|
}
|
|
@@ -208,14 +208,14 @@ test('the catalog models exactly the principals the substrate can provision', ()
|
|
|
208
208
|
|
|
209
209
|
test('the rank-role seed is still cumulative and floor-derived (R94 invariants hold)', () => {
|
|
210
210
|
for (let i = 1; i < RANK_ORDER.length; i++) {
|
|
211
|
-
const lower = new Set(
|
|
212
|
-
for (const k of
|
|
211
|
+
const lower = new Set(RANK_SEED[RANK_ORDER[i - 1]]);
|
|
212
|
+
for (const k of RANK_SEED[RANK_ORDER[i]]) assert.ok(typeof k === 'string');
|
|
213
213
|
for (const k of lower) {
|
|
214
|
-
assert.ok(
|
|
214
|
+
assert.ok(RANK_SEED[RANK_ORDER[i]].includes(k), `${RANK_ORDER[i]} lost ${k}`);
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
for (const rank of RANK_ORDER) {
|
|
218
|
-
const held = new Set(
|
|
218
|
+
const held = new Set(RANK_SEED[rank]);
|
|
219
219
|
const tier = RANK_ORDER.indexOf(rank);
|
|
220
220
|
for (const p of PERMISSIONS) {
|
|
221
221
|
assert.equal(held.has(p.key), p.floor !== null && RANK_ORDER.indexOf(p.floor) <= tier,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// tests/
|
|
1
|
+
// tests/government_rank_sync.mjs — rank→role assignment stays in sync (BV1.R95b, ADR 0151).
|
|
2
2
|
//
|
|
3
3
|
// DB-free. The decision the writer executes is a pure function (modules/government/
|
|
4
4
|
// ranks.js planRankSync), so these exercise PRODUCTION logic directly rather
|
|
@@ -214,7 +214,7 @@ test('the repair migration removes stale seeded roles and is idempotent', () =>
|
|
|
214
214
|
});
|
|
215
215
|
|
|
216
216
|
test('the repair migration touches ASSIGNMENTS, never GRANTS', () => {
|
|
217
|
-
// tests/
|
|
217
|
+
// tests/government_seed.mjs builds RANK_SEED as the UNION of every grant
|
|
218
218
|
// migration and fails loudly if one ever revokes. This migration must stay out of
|
|
219
219
|
// that model entirely: it never writes governance_role_permissions.
|
|
220
220
|
const code = stripSqlComments(repairSql);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// tests/
|
|
1
|
+
// tests/government_require_permission.mjs — the requirePermission gate (BV1.R97, ADR 0151).
|
|
2
2
|
//
|
|
3
3
|
// DB-free. The gate reaches the resolver through the `governance` KERNEL PORT
|
|
4
4
|
// (the one-way rule — core never requires modules/government/), so a stub
|