@bongos/core 1.21.180 → 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 +18 -18
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/bongos/repo-metrics.js +50 -33
- package/src/module-api.js +1 -1
- package/tests/government_protected_surfaces.mjs +15 -96
- package/tests/repo_metrics_dead_exports.mjs +57 -0
- package/modules/government/protected-surfaces.js +0 -88
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/bongos/package-core.js",
|
|
5
|
-
"core_version": "1.21.
|
|
6
|
-
"core_contract": "1.21.
|
|
7
|
-
"source_commit": "
|
|
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-
|
|
9
|
+
"built_at": "2026-10-26T02:28:14.376Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 616,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 4184,
|
|
20
|
-
"tree_sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
10825
|
+
"sha256": "2bcd79989eda93d96e1e04728683589bd539bb6371f2c44229ad14eb2a6cc968"
|
|
10831
10826
|
},
|
|
10832
10827
|
{
|
|
10833
10828
|
"path": "package.json",
|
|
10834
10829
|
"mode": "0000644",
|
|
10835
|
-
"sha256": "
|
|
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": "
|
|
10850
|
+
"sha256": "ceca276036f7a58806471e1fa82c20b240be069a10eb6786b2c4d652cd5a5be8"
|
|
10856
10851
|
},
|
|
10857
10852
|
{
|
|
10858
10853
|
"path": "scripts/bongos/CLAUDE.md",
|
|
@@ -12052,7 +12047,7 @@
|
|
|
12052
12047
|
{
|
|
12053
12048
|
"path": "scripts/bongos/repo-metrics.js",
|
|
12054
12049
|
"mode": "0000644",
|
|
12055
|
-
"sha256": "
|
|
12050
|
+
"sha256": "15dee800bf6fc5aca8a9d332854825b1d0081f2269ccdf69c2b5d3e1010009cb"
|
|
12056
12051
|
},
|
|
12057
12052
|
{
|
|
12058
12053
|
"path": "scripts/bongos/retired-core-skills.json",
|
|
@@ -14677,7 +14672,7 @@
|
|
|
14677
14672
|
{
|
|
14678
14673
|
"path": "src/module-api.js",
|
|
14679
14674
|
"mode": "0000644",
|
|
14680
|
-
"sha256": "
|
|
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": "
|
|
16920
|
+
"sha256": "62343f456ba76291cfb1bebd13e6e8164b1734e79adf33ee71c1c3372522d9c7"
|
|
16926
16921
|
},
|
|
16927
16922
|
{
|
|
16928
16923
|
"path": "tests/government_rank_sync.mjs",
|
|
@@ -19604,6 +19599,11 @@
|
|
|
19604
19599
|
"mode": "0000644",
|
|
19605
19600
|
"sha256": "5c964c162b87f2e73f6dc3343b1c4be886b7d3e0d29c01d51280dc790fc218e7"
|
|
19606
19601
|
},
|
|
19602
|
+
{
|
|
19603
|
+
"path": "tests/repo_metrics_dead_exports.mjs",
|
|
19604
|
+
"mode": "0000644",
|
|
19605
|
+
"sha256": "f98a3d9635557d050f428c5ad3258ff1ef7652bf0f4fba87830cc09481b76e9b"
|
|
19606
|
+
},
|
|
19607
19607
|
{
|
|
19608
19608
|
"path": "tests/reward_gate.mjs",
|
|
19609
19609
|
"mode": "0000644",
|
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.
|
|
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.
|
|
@@ -3197,5 +3197,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3197
3197
|
landed since 1.21.178 with no explicit bump. run 40250732318. (task 1002620)
|
|
3198
3198
|
1.21.180 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3199
3199
|
landed since 1.21.179 with no explicit bump. run 40251859318. (task 1002620)
|
|
3200
|
+
1.21.181 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
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)
|
|
3200
3204
|
---------------------------------------------------------------------------
|
|
3201
3205
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
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.
|
|
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.
|
|
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",
|
package/release-notes.json
CHANGED
|
@@ -10356,5 +10356,17 @@
|
|
|
10356
10356
|
"id": "1004875",
|
|
10357
10357
|
"text": "The unused-code check now covers modules/, the largest part of the codebase, and fails the build when someone adds a dead file or an unused export there. Five dead exports were removed along the way."
|
|
10358
10358
|
}
|
|
10359
|
+
],
|
|
10360
|
+
"1.21.181": [
|
|
10361
|
+
{
|
|
10362
|
+
"id": "1004892",
|
|
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
|
+
}
|
|
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
|
+
}
|
|
10359
10371
|
]
|
|
10360
10372
|
}
|
|
@@ -37,6 +37,7 @@ const { execSync } = require('node:child_process');
|
|
|
37
37
|
const fs = require('node:fs');
|
|
38
38
|
const path = require('node:path');
|
|
39
39
|
const { hasFlag } = require('./cli-lib');
|
|
40
|
+
const { countKnipFindings } = require('./fitness-ratchets');
|
|
40
41
|
|
|
41
42
|
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
42
43
|
const TOP_N_LARGEST = 15;
|
|
@@ -151,36 +152,44 @@ function scanDeadCode() {
|
|
|
151
152
|
return { dead_exports: null, dead_files: null, knip_status: 'unparseable' };
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
const scored = scoreKnipReport(report);
|
|
156
|
+
if (!scored) return { dead_exports: null, dead_files: null, knip_status: 'unexpected_shape' };
|
|
157
|
+
return { ...scored, knip_status: 'ok' };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// knip JSON -> { dead_exports, dead_files }, or null on a shape we cannot read.
|
|
161
|
+
//
|
|
162
|
+
// dead_exports goes through the CI ratchet's own scorer, countKnipFindings, fed only
|
|
163
|
+
// the exports/types findings (task 1004892). Counting knip's raw list instead put
|
|
164
|
+
// ~1,800 exports under modules/ on the dashboard that ARE used: knip does not credit
|
|
165
|
+
// a CommonJS namespace use (`lib.x`), and modules/ is written that way. The scorer's
|
|
166
|
+
// name filter drops those, so this number and the ratchet's agree. `opts` passes
|
|
167
|
+
// through to the scorer (a test injects `wordIndex`).
|
|
168
|
+
//
|
|
169
|
+
// COST: the scorer builds its word index once per call (codeWordIndex walks the
|
|
170
|
+
// repo's .js/.mjs/.cjs/.html/.sh text) — measured ~2.3 s on this repo, beside a knip
|
|
171
|
+
// run that already takes far longer. It is deliberately NOT fed from
|
|
172
|
+
// gatherFileSizes()'s reads: that list is git-tracked .js/.mjs only, while the
|
|
173
|
+
// ratchet's index also reads .cjs/.html/.sh and skips docs/ — a different file set
|
|
174
|
+
// gives a different count, and matching CI's number is the whole point.
|
|
175
|
+
function scoreKnipReport(report, opts = {}) {
|
|
176
|
+
if (!report || typeof report !== 'object') return null;
|
|
177
|
+
const hasIssues = Array.isArray(report.issues);
|
|
178
|
+
if (!hasIssues && !Array.isArray(report.files)) return null;
|
|
159
179
|
let deadExports = null;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
(acc, issue) => acc + (issue.files === true || (Array.isArray(issue.files) && issue.files.length) ? 1 : 0),
|
|
170
|
-
0
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
// Some reporter modes additionally surface a flat unused-file list.
|
|
174
|
-
if (Array.isArray(report.files)) {
|
|
175
|
-
deadFiles = (deadFiles ?? 0) + report.files.length;
|
|
176
|
-
}
|
|
177
|
-
if (deadExports == null && deadFiles == null) {
|
|
178
|
-
return { dead_exports: null, dead_files: null, knip_status: 'unexpected_shape' };
|
|
179
|
-
}
|
|
180
|
-
} catch (_) {
|
|
181
|
-
return { dead_exports: null, dead_files: null, knip_status: 'unexpected_shape' };
|
|
180
|
+
let deadFiles = null;
|
|
181
|
+
if (hasIssues) {
|
|
182
|
+
const exportsOnly = report.issues.map((issue) => ({ file: issue.file, exports: issue.exports, types: issue.types }));
|
|
183
|
+
deadExports = countKnipFindings({ issues: exportsOnly }, opts);
|
|
184
|
+
deadFiles = report.issues.reduce(
|
|
185
|
+
// knip 6 gives an ARRAY here ([{ name }]), not a bool (task 1004875).
|
|
186
|
+
(acc, issue) => acc + (issue.files === true || (Array.isArray(issue.files) && issue.files.length) ? 1 : 0),
|
|
187
|
+
0
|
|
188
|
+
);
|
|
182
189
|
}
|
|
183
|
-
|
|
190
|
+
// Some reporter modes additionally surface a flat unused-file list.
|
|
191
|
+
if (Array.isArray(report.files)) deadFiles = (deadFiles ?? 0) + report.files.length;
|
|
192
|
+
return { dead_exports: deadExports, dead_files: deadFiles };
|
|
184
193
|
}
|
|
185
194
|
|
|
186
195
|
// ---------- branch hygiene ----------
|
|
@@ -287,7 +296,7 @@ async function upsertSnapshot(pool, snap) {
|
|
|
287
296
|
|
|
288
297
|
// ---------- main ----------
|
|
289
298
|
|
|
290
|
-
|
|
299
|
+
async function main() {
|
|
291
300
|
const args = process.argv.slice(2);
|
|
292
301
|
const asJson = hasFlag('--json', args);
|
|
293
302
|
const noWrite = hasFlag('--no-write', args);
|
|
@@ -360,7 +369,15 @@ async function upsertSnapshot(pool, snap) {
|
|
|
360
369
|
}
|
|
361
370
|
|
|
362
371
|
process.exit(0);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
module.exports = { scoreKnipReport };
|
|
375
|
+
|
|
376
|
+
// CLI only when run directly, so a test can require this file without it
|
|
377
|
+
// scanning the repo or writing to the DB (task 1004892).
|
|
378
|
+
if (require.main === module) {
|
|
379
|
+
main().catch((err) => {
|
|
380
|
+
console.error('fatal:', err);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
});
|
|
383
|
+
}
|
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.
|
|
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', () => {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// tests/repo_metrics_dead_exports.mjs — repo-metrics scores dead exports the way the
|
|
2
|
+
// CI knip ratchet does (task 1004892).
|
|
3
|
+
//
|
|
4
|
+
// Once knip scanned modules/ (task 1004875), repo-metrics' raw export count jumped by
|
|
5
|
+
// ~1,800: knip does not credit a CommonJS namespace use (`lib.x`), and modules/ is
|
|
6
|
+
// written that way. The ratchet's scorer (countKnipFindings) filters those by name;
|
|
7
|
+
// repo-metrics must use the same scorer, so the dashboard number matches CI's.
|
|
8
|
+
// Hermetic: synthetic knip JSON and an injected word index, no knip run.
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import { test } from 'node:test';
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const require = createRequire(import.meta.url);
|
|
16
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
17
|
+
const rm = require(path.join(ROOT, 'scripts/bongos/repo-metrics.js'));
|
|
18
|
+
const fr = require(path.join(ROOT, 'scripts/bongos/fitness-ratchets.js'));
|
|
19
|
+
|
|
20
|
+
const issue = (file, extra) => ({ file, files: [], exports: [], types: [], dependencies: [], unlisted: [], ...extra });
|
|
21
|
+
|
|
22
|
+
test('a modules/ export used through a namespace elsewhere is not a dead export', () => {
|
|
23
|
+
const wordIndex = new Map([
|
|
24
|
+
['usedViaNamespace', true], // spelled in 2+ files
|
|
25
|
+
['onlyHere', 'modules/x/lib.js'], // spelled only in its own file
|
|
26
|
+
]);
|
|
27
|
+
const report = {
|
|
28
|
+
issues: [
|
|
29
|
+
issue('modules/x/lib.js', {
|
|
30
|
+
exports: [{ name: 'usedViaNamespace' }, { name: 'onlyHere' }],
|
|
31
|
+
types: [{ name: 'neverSpelled' }],
|
|
32
|
+
}),
|
|
33
|
+
issue('src/y.js', { exports: [{ name: 'usedViaNamespace' }] }), // outside modules/: knip's verdict stands
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
const scored = rm.scoreKnipReport(report, { wordIndex });
|
|
37
|
+
assert.equal(scored.dead_exports, 3); // onlyHere + neverSpelled + the src/ one
|
|
38
|
+
// ...and that is exactly the ratchet scorer's count for the same export/type findings.
|
|
39
|
+
assert.equal(scored.dead_exports, fr.countKnipFindings(report, { wordIndex }));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('non-export findings (deps, unlisted) do not leak into dead_exports; dead files still count', () => {
|
|
43
|
+
const report = {
|
|
44
|
+
issues: [
|
|
45
|
+
issue('src/a.js', { files: [{ name: 'src/a.js' }], dependencies: [{ name: 'left-pad' }], exports: [{ name: 'x' }] }),
|
|
46
|
+
issue('src/b.js', { unlisted: [{ name: 'chalk' }] }),
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
const scored = rm.scoreKnipReport(report, { wordIndex: new Map() });
|
|
50
|
+
assert.equal(scored.dead_exports, 1);
|
|
51
|
+
assert.equal(scored.dead_files, 1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('a report with no issues list is an unexpected shape (null), not zero', () => {
|
|
55
|
+
assert.equal(rm.scoreKnipReport({}, { wordIndex: new Map() }), null);
|
|
56
|
+
assert.equal(rm.scoreKnipReport(null, { wordIndex: new Map() }), null);
|
|
57
|
+
});
|
|
@@ -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
|
-
};
|