@bongos/core 1.21.181 → 1.21.182

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 CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/bongos/package-core.js",
5
- "core_version": "1.21.181",
6
- "core_contract": "1.21.181",
7
- "source_commit": "677ee2b818cf829dddd5434301b158be3408fbd5",
5
+ "core_version": "1.21.182",
6
+ "core_contract": "1.21.182",
7
+ "source_commit": "7c1587ddd1d73cdd824b50d8b2673acef202c345",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-10-26T01:16:25.738Z",
9
+ "built_at": "2026-10-26T02:28:14.376Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 616,
13
13
  "agent_docs_stubbed": 29,
14
- "functional_verbatim": 3539,
14
+ "functional_verbatim": 3538,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 4185,
20
- "tree_sha256": "e69b48d53a056c99114f3c5904e69828363e91f5f0df70b7b599e61e8e299d1d",
19
+ "file_count": 4184,
20
+ "tree_sha256": "92cf3493310173b9b2f207960210b2cf928d4da8e73e1810f0338412329118fa",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2927,7 +2927,7 @@
2927
2927
  {
2928
2928
  "path": "docs/file-map.md",
2929
2929
  "mode": "0000644",
2930
- "sha256": "4e0a9cf41330f62ec1d2bacdc07a7071b4081517e60f6446fe66a33e44175ce9"
2930
+ "sha256": "b0d4bd11b81295f36fec4c76d6b2f37f27834b31e101f6f753ef2e4f74f0a648"
2931
2931
  },
2932
2932
  {
2933
2933
  "path": "docs/handoff-template.md",
@@ -2937,7 +2937,7 @@
2937
2937
  {
2938
2938
  "path": "docs/module-api-changelog.md",
2939
2939
  "mode": "0000644",
2940
- "sha256": "4835f21d9d6cf7eaa82821e1bb5eb4c79493267300ef852ad1ca1c05b4a41e65"
2940
+ "sha256": "e2197d9aa0029cc2fe02353ecac558c7a051df54999b694fefa41335a415cdb1"
2941
2941
  },
2942
2942
  {
2943
2943
  "path": "docs/modules-contract.md",
@@ -5379,11 +5379,6 @@
5379
5379
  "mode": "0000644",
5380
5380
  "sha256": "a07001ad8854abc7b997eb900fe435990f90392ac6808688496f022d81219cf8"
5381
5381
  },
5382
- {
5383
- "path": "modules/government/protected-surfaces.js",
5384
- "mode": "0000644",
5385
- "sha256": "527ae1c53369eb0c6878584d311afa08d9e5d6ea4c8f9ce60406e57dcdf692aa"
5386
- },
5387
5382
  {
5388
5383
  "path": "modules/government/protected-surfaces.json",
5389
5384
  "mode": "0000644",
@@ -10827,12 +10822,12 @@
10827
10822
  {
10828
10823
  "path": "package-lock.json",
10829
10824
  "mode": "0000644",
10830
- "sha256": "4d3ecd3d1a0b7e6602da53e83e88c204ab82a45ca83ae3d24979746970f15ac9"
10825
+ "sha256": "2bcd79989eda93d96e1e04728683589bd539bb6371f2c44229ad14eb2a6cc968"
10831
10826
  },
10832
10827
  {
10833
10828
  "path": "package.json",
10834
10829
  "mode": "0000644",
10835
- "sha256": "2a01fd05d918fc38ad18d1cc6305681129b6115a4eab33f7d1fa4d34b8da2e44"
10830
+ "sha256": "18e16f23126637566ce4f667c6dd6b5f81a6d43034f2beef8cad0b2184d9a885"
10836
10831
  },
10837
10832
  {
10838
10833
  "path": "public-docs/index.html",
@@ -10852,7 +10847,7 @@
10852
10847
  {
10853
10848
  "path": "release-notes.json",
10854
10849
  "mode": "0000644",
10855
- "sha256": "2a6e6fa89b09fd51aa8fae09c92eec30b6d0823dcdf3eaeda63826e7269a46ea"
10850
+ "sha256": "ceca276036f7a58806471e1fa82c20b240be069a10eb6786b2c4d652cd5a5be8"
10856
10851
  },
10857
10852
  {
10858
10853
  "path": "scripts/bongos/CLAUDE.md",
@@ -14677,7 +14672,7 @@
14677
14672
  {
14678
14673
  "path": "src/module-api.js",
14679
14674
  "mode": "0000644",
14680
- "sha256": "c48862d3a5d62afff6c50b8de356b68ec2ef4f22ced0c0caa36a99d5f135747a"
14675
+ "sha256": "1a1c9489fe25fb1f38547caad2c8f56196c02183d75f94a223ca92ce44e8c3b4"
14681
14676
  },
14682
14677
  {
14683
14678
  "path": "src/module-loader/catalog.js",
@@ -16922,7 +16917,7 @@
16922
16917
  {
16923
16918
  "path": "tests/government_protected_surfaces.mjs",
16924
16919
  "mode": "0000644",
16925
- "sha256": "6c974754ccdf93e12d6a9154e15642dcd952733c836ae5016b94eddff80232af"
16920
+ "sha256": "62343f456ba76291cfb1bebd13e6e8164b1734e79adf33ee71c1c3372522d9c7"
16926
16921
  },
16927
16922
  {
16928
16923
  "path": "tests/government_rank_sync.mjs",
package/docs/file-map.md CHANGED
@@ -124,7 +124,7 @@
124
124
  │ ├── pixel-art/ ← the opt-in pixel-art skills (task 1004470, default-OFF): otb-tile-generate, otb-design-review, otb-character-review, otb-feedback-capture, otb-figma-sync under skills/<name>/SKILL.md — moved out of .claude/skills/, where they were orphans no manifest owned. Skill TEXT only: the pipeline, palette, rubric and the copyrighted reference maps stay in the instance's never-published art-pipeline (ADR 0098). module.json, CLAUDE.md, skills/.
125
125
  │ ├── character-anim/ ← consistent-character generative animation (ADR 0088) — graded painterly frame sets + manifest (its otb-character-review skill is the pixel-art module's since task 1004470). CLAUDE.md, module.json.
126
126
  │ ├── provisioning/ ← stands up + operates a Cloud Bongos instance's own infra (server/DB/domain/DNS/TLS) via the API (ADR 0111); web-tier request+state only, no cloud tokens — a separate control-plane runner drains the intents. Distinct from the `hosting` module (ADR 0092), which brokers compute workloads. CLAUDE.md, module.json.
127
- │ ├── government/ ← CORE-DOMAIN module (ADR 0151 / BV1 goal 1000030, renamed governance→government by ADR 0174, default-on): Discord-style configurable ranks + permissions. The PERMISSION is the atomic authority unit and the four ranks (xenos/thetes/metic/archon) are the positions in the government; the module owns the code-defined permission catalog — each key flagged `system` (trust-boundary), `substrate` (enforced out of band) or operational (delegable) — plus the compatibility seed that reproduces the old rank thresholds exactly. ADR 0016 is preserved: authority is still resolved server-side, per request, uncached. The word `role` is deliberately absent — it is reserved for the crafts. CLAUDE.md, module.json, catalog.js, ranks.js, resolver.js, config.js, db.js, board.js + board-membership.js + membership-predicate.js, session-scopes.{js,json}, protected-surfaces.{js,json}, routes/ (government + rank-sync), pollers/ (board-expiry), migrations/.
127
+ │ ├── government/ ← CORE-DOMAIN module (ADR 0151 / BV1 goal 1000030, renamed governance→government by ADR 0174, default-on): Discord-style configurable ranks + permissions. The PERMISSION is the atomic authority unit and the four ranks (xenos/thetes/metic/archon) are the positions in the government; the module owns the code-defined permission catalog — each key flagged `system` (trust-boundary), `substrate` (enforced out of band) or operational (delegable) — plus the compatibility seed that reproduces the old rank thresholds exactly. ADR 0016 is preserved: authority is still resolved server-side, per request, uncached. The word `role` is deliberately absent — it is reserved for the crafts. CLAUDE.md, module.json, catalog.js, ranks.js, resolver.js, config.js, db.js, board.js + board-membership.js + membership-predicate.js, session-scopes.{js,json}, protected-surfaces.json, routes/ (government + rank-sync), pollers/ (board-expiry), migrations/.
128
128
  │ ├── specialities/ ← CORE-DOMAIN module (task 1003855 / ADR 0296, default-on): role SUB-SPECIALITIES — a named bundle of knowledge, skills and an interaction contract sitting UNDER a craft. A craft says HOW you work (engineer/ideator/artist/ui); a speciality says WHAT you work on, and how a subject-matter expert wants to be engaged inside it. Deliberately thin over three surfaces that already exist — discipline-modes.json for craft→playbook, behavior_prefs.interaction for the per-session contract (a speciality contract rides the SAME GET /me → Conductor path rather than adding a second one), and the ADR 0083 manifest for declared skills; the only new machinery is the knowledge bundle. Training is two-fold: documents the builder supplied, plus learnings captured from sessions worked in the speciality (pointing at existing `learnings` rows, never a second corpus). Owned per-builder OR per-project, each carrying its owner's sharing decision (private/project/public) and a sellable flag that records INTENT only — ADR 0107 defers the marketplace, so there is nothing to list on. CLAUDE.md, module.json, specialities.js, db.js, routes/, migrations/.
129
129
  │ ├── agents/ ← declarative agent registry (BV1 goal 1000038 Phase 1) — the one module that ships `default:false`, so shipped is not enabled and an instance opts in per ADR 0083. `.claude/agents/*.md` are reconciled at deploy into `agents_definitions` plus the per-fire `agents_runs` ledger; files-are-source (an instance-authored DB row is never clobbered), and `author_rank` is stamped from the author's LIVE DB rank because a committed file cannot grant authority (ADR 0016). Spawns consume the EXISTING read-only `grade` port, so v1 agents are observe-and-report only — no write principals — and every fire bills through the shared LLM cost cache. `spend.requiresPayer` (task 1003884): a fire spends real money for whoever called it, so the core REFUSES to enable the module on an instance whose join door is `open` with no payer configured — an account created seconds ago would otherwise spend the instance's money with nobody to bill. lib/spend-ceiling.js is the per-payer monthly ceiling, failing closed when it is exhausted OR unreadable; lib/fire-budget.js is the separate RATE ceiling. CLAUDE.md, module.json, spawn.js, lib/, routes/, pollers/ (event-router), migrations/.
130
130
  │ └── demo/ ← BV1.R52 acceptance-proof module: one rank-gated write route + one namespaced migration proving the loader mounts a scaffolded module with zero core edits. CLAUDE.md, module.json.
@@ -3199,5 +3199,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
3199
3199
  landed since 1.21.179 with no explicit bump. run 40251859318. (task 1002620)
3200
3200
  1.21.181 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3201
3201
  landed since 1.21.180 with no explicit bump. run 40253320267. (task 1002620)
3202
+ 1.21.182 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3203
+ landed since 1.21.181 with no explicit bump. run 40259167777. (task 1002620)
3202
3204
  ---------------------------------------------------------------------------
3203
3205
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.181",
3
+ "version": "1.21.182",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.181",
9
+ "version": "1.21.182",
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.21.181",
3
+ "version": "1.21.182",
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",
@@ -10362,5 +10362,11 @@
10362
10362
  "id": "1004892",
10363
10363
  "text": "The repo health dashboard's dead-code number now counts the same way the CI check does, so it no longer jumps by about 1,800 false alarms from modules/."
10364
10364
  }
10365
+ ],
10366
+ "1.21.182": [
10367
+ {
10368
+ "id": "1004890",
10369
+ "text": "Removed a leftover permission-list checker that nothing in the product used; the real protections and their tests are unchanged."
10370
+ }
10365
10371
  ]
10366
10372
  }
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.21.181'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.182'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -26,9 +26,23 @@ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
26
26
  const GOV = path.join(ROOT, 'modules', 'government');
27
27
 
28
28
  const ppc = require(path.join(ROOT, 'src', 'bongos', 'permission-path-check.js'));
29
- const registry = require(path.join(GOV, 'protected-surfaces.js'));
30
29
  const catalog = require(path.join(GOV, 'catalog.js'));
31
30
 
31
+ // The registry exactly as the live kernel matcher loads it — the one loader the
32
+ // enforcers (pre-push hook, grader pre-pass, main audit, goal-scope check) use.
33
+ // Task 1004890 deleted modules/government/protected-surfaces.js, a wrapper over
34
+ // this same matcher that nothing but this test ever required; FLOOR and
35
+ // SYSTEM_PERMISSIONS are derived here the way it derived them.
36
+ const registry = {
37
+ SURFACES: ppc.PROTECTED_SURFACES,
38
+ PROTECTED_GLOBS: ppc.PROTECTED_GLOBS,
39
+ FLOOR: catalog.RANK_ORDER[Math.min(...ppc.PROTECTED_SURFACES.map((s) => catalog.RANK_ORDER.indexOf(s.floor)))],
40
+ SYSTEM_PERMISSIONS: [...new Set(ppc.PROTECTED_SURFACES.map((s) => s.permission))].sort(),
41
+ surfaceFor: (file) => ppc.surfaceFor(file),
42
+ floorFor: (file) => ppc.floorFor(file),
43
+ isProtected: (file) => ppc.surfaceFor(file) !== null,
44
+ };
45
+
32
46
  // The verbatim PROTECTED_GLOBS array as it stood immediately before R101.
33
47
  const EXPECTED_GLOBS_BEFORE_R101 = [
34
48
  'src/bongos/auth.js',
@@ -979,91 +993,6 @@ test('every entry maps to a permission the catalog defines as system:true', () =
979
993
  assert.deepEqual(registry.SYSTEM_PERMISSIONS, ['path.protected.modify']);
980
994
  });
981
995
 
982
- // Feed a BAD registry through the real loader in a cold subprocess and assert the
983
- // module refuses to load. The pattern is the sibling probe below ("a missing
984
- // registry fails CLOSED"): patch fs.readFileSync for the registry path only, then
985
- // require the module under test and let its load-time throw be the result.
986
- //
987
- // WHY A SUBPROCESS AND NOT AN IN-PROCESS ASSERTION (task 1003196, finding 9). The
988
- // version of this test before that task read the catalog and checked that a
989
- // grantable key exists and an unknown key returns null — both true regardless of
990
- // whether the guard exists. Demonstrated: deleting the whole validation loop from
991
- // modules/government/protected-surfaces.js left the entire suite green. The guard
992
- // is load-time, so only a fresh module load can exercise it, and the registry is
993
- // read from disk, so only a patched read can make it bad.
994
- //
995
- // This matters because the sibling test above ("every entry maps to a permission
996
- // the catalog defines as system:true") only checks the COMMITTED file. The
997
- // load-time throw is what protects an out-of-band registry write — the Government
998
- // tab editing an instance file at runtime — and nothing was watching it.
999
- function loadWithRegistry(registryJson) {
1000
- execFileSync(process.execPath, ['-e',
1001
- "const fs=require('node:fs');const real=fs.readFileSync;" +
1002
- 'const fake=' + JSON.stringify(JSON.stringify(registryJson)) + ';' +
1003
- "fs.readFileSync=(p,...r)=>(String(p).includes('protected-surfaces.json') ? fake : real(p,...r));" +
1004
- "require('./modules/government/protected-surfaces.js');",
1005
- ], { cwd: ROOT, stdio: 'pipe' });
1006
- }
1007
-
1008
- test('a surface mapped to a GRANTABLE permission fails loud at load', () => {
1009
- // A grantable (system:false) key is the dangerous case ADR 0151 §4 names: a
1010
- // custom rank could be handed the very power the substrate reserves.
1011
- const grantable = catalog.grantableKeys()[0];
1012
- assert.ok(grantable, 'the catalog must define at least one grantable key for this probe');
1013
- assert.equal(catalog.isSystem(grantable), false, 'fixture sanity: the probe key must be grantable');
1014
- assert.throws(
1015
- () => loadWithRegistry({
1016
- floor: 'metic',
1017
- permission: grantable,
1018
- surfaces: [{ glob: 'src/probe/grantable-surface.js' }],
1019
- }),
1020
- /which is not system:true/,
1021
- 'a surface mapping to a grantable permission must refuse to load',
1022
- );
1023
- });
1024
-
1025
- test('a surface mapped to an UNKNOWN permission fails loud at load', () => {
1026
- assert.equal(catalog.byKey('not.a.real.permission'), null, 'fixture sanity: the key must be unknown');
1027
- assert.throws(
1028
- () => loadWithRegistry({
1029
- floor: 'metic',
1030
- permission: 'not.a.real.permission',
1031
- surfaces: [{ glob: 'src/probe/unknown-surface.js' }],
1032
- }),
1033
- /which the permission catalog does not define/,
1034
- 'a surface mapping to a permission the catalog never defines must refuse to load',
1035
- );
1036
- });
1037
-
1038
- test('the probe itself is honest — a GOOD registry loads clean through the same path', () => {
1039
- // Without this, both assertions above would pass if the subprocess threw for an
1040
- // unrelated reason (a bad patch, a missing cwd, a syntax error in the -e script).
1041
- assert.doesNotThrow(() => loadWithRegistry({
1042
- floor: 'metic',
1043
- permission: registry.SYSTEM_PERMISSIONS[0],
1044
- surfaces: [{ glob: 'src/probe/good-surface.js' }],
1045
- }), 'a well-formed registry must still load — otherwise the probes above prove nothing');
1046
- });
1047
-
1048
- test('an unknown FLOOR is refused before the catalog check ever sees it', () => {
1049
- // The validation loop's third branch (`!catalog.RANK_ORDER.includes(s.floor)`) is
1050
- // a DRIFT guard, not dead code, and this test records why it cannot be provoked
1051
- // through the registry: the kernel loader keeps its own local RANK_TIER map
1052
- // (permission-path-check.js) and rejects an unknown floor first. The government
1053
- // branch only becomes reachable if those two rank lists ever disagree — which is
1054
- // exactly the drift it exists to catch, and is not craftable from JSON alone.
1055
- assert.throws(
1056
- () => loadWithRegistry({
1057
- floor: 'emperor',
1058
- permission: registry.SYSTEM_PERMISSIONS[0],
1059
- surfaces: [{ glob: 'src/probe/bad-floor.js' }],
1060
- }),
1061
- /unknown floor/,
1062
- );
1063
- assert.deepEqual(catalog.RANK_ORDER, ['xenos', 'thetes', 'metic', 'archon'],
1064
- 'if this list and the kernel RANK_TIER drift, the government floor branch wakes up');
1065
- });
1066
-
1067
996
  // ---------- synchronous, no DB, no server ----------
1068
997
 
1069
998
  test('the registry loads synchronously from the repo with no DB and no server', () => {
@@ -1080,12 +1009,6 @@ test('the registry loads synchronously from the repo with no DB and no server',
1080
1009
  test('the registry data file carries no DB/network dependency', () => {
1081
1010
  const json = fs.readFileSync(path.join(GOV, 'protected-surfaces.json'), 'utf8');
1082
1011
  assert.doesNotThrow(() => JSON.parse(json), 'the canonical form must be plain parseable data');
1083
- const code = fs.readFileSync(path.join(GOV, 'protected-surfaces.js'), 'utf8')
1084
- .split('\n').filter((l) => !l.trim().startsWith('//')).join('\n');
1085
- for (const banned of ['./db', 'pool', 'fetch(', 'await ']) {
1086
- assert.ok(!code.includes(banned),
1087
- `protected-surfaces.js must not introduce "${banned}" — the substrate reads this with no DB and no server (ADR 0151 §4)`);
1088
- }
1089
1012
  });
1090
1013
 
1091
1014
  // ---------- policy owns / substrate enforces ----------
@@ -1095,10 +1018,6 @@ test('the kernel does not require the governance module (ADR 0083 one-way rule)'
1095
1018
  const requires = [...kernel.matchAll(/require\(\s*['"]([^'"]+)['"]\s*\)/g)].map((m) => m[1]);
1096
1019
  assert.deepEqual(requires.filter((r) => r.includes('modules/')), [],
1097
1020
  'core must never require a module — it reads the registry as data');
1098
- // …and the flow runs the legal way: governance reaches the matcher through the doorway.
1099
- const gov = fs.readFileSync(path.join(GOV, 'protected-surfaces.js'), 'utf8');
1100
- assert.ok(/require\(\s*['"]\.\.\/\.\.\/src\/module-api['"]\s*\)/.test(gov),
1101
- 'governance must reach the kernel matcher through the published doorway');
1102
1021
  });
1103
1022
 
1104
1023
  test('the hardcoded protected list is gone from the kernel — one source, no drift', () => {
@@ -1,88 +0,0 @@
1
- 'use strict';
2
-
3
- // modules/government/protected-surfaces.js — the protected-surface registry's
4
- // GOVERNMENT-side API (ADR 0151 §4, BV1.R101).
5
- //
6
- // POLICY OWNS / SUBSTRATE ENFORCES — the whole point of this slice.
7
- //
8
- // GOVERNMENT OWNS THE DATA. protected-surfaces.json (next to this file) says
9
- // WHICH repo paths are protected, at WHAT rank floor, mapped to WHICH
10
- // `system: true` catalog permission. That file is the source of truth; the
11
- // Archon Government tab (R110/R111) edits it as policy. Before R101 the same
12
- // information was a hardcoded PROTECTED_GLOBS constant inside the kernel.
13
- //
14
- // THE OUT-OF-BAND SUBSTRATE STAYS THE ENFORCER. Nothing here enforces
15
- // anything. The enforcers are the git pre-push hook, the grader pre-pass, the
16
- // post-push main audit and the goal-scope check — they read
17
- // the registry with no DB, no server and no HTTP, and they are what actually
18
- // blocks a sub-floor change (ADR 0043). Re-mapping a floor here changes what
19
- // the substrate protects; it can never make the request path the authority.
20
- // Moving enforcement into the app is the regression R101 exists to prevent
21
- // (ADR 0151 §"Alternatives considered").
22
- //
23
- // NO DB DEPENDENCY. A DB-backed override is a government-tab concern layered
24
- // on top and must never become a dependency of the hook — the hook runs on a
25
- // laptop with no database and no network. This module is therefore
26
- // synchronous, DB-free and requires nothing but the catalog and the doorway.
27
- //
28
- // DEPENDENCY DIRECTION (ADR 0083's one-way rule). Code flows exactly one way:
29
- // this module → src/module-api.js (the published doorway) → the kernel matcher
30
- // in src/bongos/permission-path-check.js. Core never requires this module. The
31
- // kernel loads the JSON data file directly (a static data read, the same way the
32
- // sibling trust primitive route-rank-check.js reads modules/*/routes/*.js off
33
- // disk to rank-gate them), and we read the parsed result back through the
34
- // doorway so there is exactly ONE loader and ONE matcher — no second list to
35
- // drift (the SEC #863 failure mode).
36
-
37
- const api = require('../../src/module-api');
38
- const catalog = require('./catalog');
39
-
40
- const ppc = api.permissionPathCheck;
41
-
42
- // The registry, as parsed by the single kernel loader.
43
- const SURFACES = ppc.PROTECTED_SURFACES;
44
-
45
- // ── invariants asserted at module load (fail loud on an authoring typo) ───────
46
- // The government module is where the registry meets the permission catalog, so
47
- // this is where "every protected surface maps to a real, non-grantable
48
- // permission" is checked. A surface mapping to an unknown key, or to a
49
- // `system: false` (grantable) key, would mean an Archon could hand a custom rank
50
- // the power the substrate is supposed to reserve — exactly the wall ADR 0151 §4
51
- // builds. Fail loud rather than silently protecting nothing.
52
- for (const s of SURFACES) {
53
- const perm = catalog.byKey(s.permission);
54
- if (!perm) {
55
- throw new Error(`protected-surface registry: "${s.glob}" maps to "${s.permission}", which the permission catalog does not define`);
56
- }
57
- if (perm.system !== true) {
58
- throw new Error(`protected-surface registry: "${s.glob}" maps to "${s.permission}", which is not system:true — a protected surface must map to a NON-GRANTABLE permission (ADR 0151 §4)`);
59
- }
60
- if (!catalog.RANK_ORDER.includes(s.floor)) {
61
- throw new Error(`protected-surface registry: "${s.glob}" declares floor "${s.floor}", which is not a rank`);
62
- }
63
- }
64
-
65
- // The lowest floor any surface declares — 'metic' today (ADR 0043's Metic floor,
66
- // now DERIVED from registry data rather than hardcoded).
67
- const FLOOR = catalog.RANK_ORDER[Math.min(...SURFACES.map((s) => catalog.RANK_ORDER.indexOf(s.floor)))];
68
-
69
- // The distinct system permissions the registry maps onto.
70
- const SYSTEM_PERMISSIONS = Object.freeze([...new Set(SURFACES.map((s) => s.permission))].sort());
71
-
72
- // Per-path lookups — the single matcher, reached through the doorway.
73
- const surfaceFor = (file) => ppc.surfaceFor(file);
74
- const floorFor = (file) => ppc.floorFor(file);
75
- const permissionFor = (file) => ppc.permissionFor(file);
76
- const isProtected = (file) => ppc.surfaceFor(file) !== null;
77
-
78
- module.exports = {
79
- SURFACES,
80
- PROTECTED_GLOBS: ppc.PROTECTED_GLOBS,
81
- REGISTRY_PATH: ppc.REGISTRY_PATH,
82
- FLOOR,
83
- SYSTEM_PERMISSIONS,
84
- surfaceFor,
85
- floorFor,
86
- permissionFor,
87
- isProtected,
88
- };