@bongos/core 1.19.755 → 1.19.756
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 +22 -12
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/fitness-checks-error-envelope.js +134 -0
- package/scripts/gds/fitness-checks-packaging.js +132 -0
- package/scripts/gds/fitness.js +2 -213
- package/src/module-api.js +1 -1
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.756",
|
|
6
|
+
"core_contract": "1.19.756",
|
|
7
|
+
"source_commit": "701a77495faed74b1cad4941c564f5a8a1a99158",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-16T02:
|
|
9
|
+
"built_at": "2026-09-16T02:21:20.653Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 483,
|
|
13
13
|
"agent_docs_stubbed": 25,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2114,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2622,
|
|
20
|
+
"tree_sha256": "0124e1a9cd241c6125c6a7a1351be80d37c9a40ba36c2f2def3ccb768417105c",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2512,7 +2512,7 @@
|
|
|
2512
2512
|
{
|
|
2513
2513
|
"path": "docs/module-api-changelog.md",
|
|
2514
2514
|
"mode": "0000644",
|
|
2515
|
-
"sha256": "
|
|
2515
|
+
"sha256": "cbc6427898e270a22852c496e1405b927c41e295a1a8808fb260b4f83f6f6429"
|
|
2516
2516
|
},
|
|
2517
2517
|
{
|
|
2518
2518
|
"path": "docs/modules-contract.md",
|
|
@@ -7567,12 +7567,12 @@
|
|
|
7567
7567
|
{
|
|
7568
7568
|
"path": "package-lock.json",
|
|
7569
7569
|
"mode": "0000644",
|
|
7570
|
-
"sha256": "
|
|
7570
|
+
"sha256": "cc3fcdaac4eeb204e96790fe0203978514bb550acc078a68d5577cd02d229f7c"
|
|
7571
7571
|
},
|
|
7572
7572
|
{
|
|
7573
7573
|
"path": "package.json",
|
|
7574
7574
|
"mode": "0000644",
|
|
7575
|
-
"sha256": "
|
|
7575
|
+
"sha256": "0e01d62b067f43577be10f62f417f36dbdcbadc0ba03d290ba902b9447d9ac34"
|
|
7576
7576
|
},
|
|
7577
7577
|
{
|
|
7578
7578
|
"path": "public-docs/index.html",
|
|
@@ -8024,11 +8024,21 @@
|
|
|
8024
8024
|
"mode": "0000644",
|
|
8025
8025
|
"sha256": "b8b1a7ed1d8dd217b24757a3ec9f4b3aa58b26a3e2a7b2cef9de217792e5ea43"
|
|
8026
8026
|
},
|
|
8027
|
+
{
|
|
8028
|
+
"path": "scripts/gds/fitness-checks-error-envelope.js",
|
|
8029
|
+
"mode": "0000644",
|
|
8030
|
+
"sha256": "ab11c75ae177397fdd507505732450fee3531d42b99ba0b34811d06563ebd21c"
|
|
8031
|
+
},
|
|
8027
8032
|
{
|
|
8028
8033
|
"path": "scripts/gds/fitness-checks-identity.js",
|
|
8029
8034
|
"mode": "0000644",
|
|
8030
8035
|
"sha256": "583b21bf6527b657058ebfab50aff8c51cec96a0b1baa4d01daea55e90d0ccf7"
|
|
8031
8036
|
},
|
|
8037
|
+
{
|
|
8038
|
+
"path": "scripts/gds/fitness-checks-packaging.js",
|
|
8039
|
+
"mode": "0000644",
|
|
8040
|
+
"sha256": "1f0611c9b8cb30e20119d50f67bc3a1148d6b5d869337361ae0ac58724aaca12"
|
|
8041
|
+
},
|
|
8032
8042
|
{
|
|
8033
8043
|
"path": "scripts/gds/fitness-checks-route-pins.js",
|
|
8034
8044
|
"mode": "0000644",
|
|
@@ -8057,7 +8067,7 @@
|
|
|
8057
8067
|
{
|
|
8058
8068
|
"path": "scripts/gds/fitness.js",
|
|
8059
8069
|
"mode": "0000644",
|
|
8060
|
-
"sha256": "
|
|
8070
|
+
"sha256": "4856485e5bb5bb03e47cd6953659c25214411d0b63287f6ab2faa4c34e1e27bc"
|
|
8061
8071
|
},
|
|
8062
8072
|
{
|
|
8063
8073
|
"path": "scripts/gds/gate-review.js",
|
|
@@ -9372,7 +9382,7 @@
|
|
|
9372
9382
|
{
|
|
9373
9383
|
"path": "src/module-api.js",
|
|
9374
9384
|
"mode": "0000644",
|
|
9375
|
-
"sha256": "
|
|
9385
|
+
"sha256": "a4071a82922c3333e62a1657f6ce674b19831dbf0152ab3c6c270904193d3f2c"
|
|
9376
9386
|
},
|
|
9377
9387
|
{
|
|
9378
9388
|
"path": "src/module-loader/catalog.js",
|
|
@@ -1984,5 +1984,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1984
1984
|
landed since 1.19.753 with no explicit bump. run 35039836268. (task 1002620)
|
|
1985
1985
|
1.19.755 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1986
1986
|
landed since 1.19.754 with no explicit bump. run 35047010813. (task 1002620)
|
|
1987
|
+
1.19.756 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1988
|
+
landed since 1.19.755 with no explicit bump. run 35047623582. (task 1002620)
|
|
1987
1989
|
---------------------------------------------------------------------------
|
|
1988
1990
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.756",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.756",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.756",
|
|
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",
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// scripts/gds/fitness-checks-error-envelope.js — Check 17: no route hand-rolls a
|
|
2
|
+
// FLAT error (BV1 R03 / task 1990 / ADR 0116).
|
|
3
|
+
//
|
|
4
|
+
// Carved out of scripts/gds/fitness.js by task 1003825 alongside the packaging pair,
|
|
5
|
+
// for the headroom reason documented there. Self-contained: the scan needs only
|
|
6
|
+
// CORE_ROOT / rel / walkJs / walkJsAllRoots from fitness-lib.js, and FLAT_ERROR_BASELINE
|
|
7
|
+
// travels WITH the check rather than staying behind — the baseline and the gate that
|
|
8
|
+
// reads it are one decision, and splitting them is how a ratchet quietly loses its floor.
|
|
9
|
+
//
|
|
10
|
+
// fitness.js requires the four public names straight back (checkErrorEnvelope,
|
|
11
|
+
// scanFlatErrorSites, countFlatErrors, FLAT_ERROR_BASELINE — the last three are read by
|
|
12
|
+
// the R04 sweep and its tests), so nothing outside this file changes shape.
|
|
13
|
+
// stripCommentsForFlat stays PRIVATE here: it was never on fitness.js's exports, and
|
|
14
|
+
// exporting it now to mirror the carve would be a new public name nothing consumes.
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
|
|
21
|
+
const { CORE_ROOT, rel, walkJs, walkJsAllRoots } = require('./fitness-lib.js');
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Check 17 — no route hand-rolls a FLAT error (BV1 R03 / task 1990 / ADR 0116)
|
|
24
|
+
// ============================================================================
|
|
25
|
+
//
|
|
26
|
+
// The error-envelope contract (ADR 0116) says every failure is the ONE envelope
|
|
27
|
+
// `{error:{code,message,details}}`, emitted via res.fail() or a thrown ApiError
|
|
28
|
+
// (the R02 middleware, task 1989). A hand-rolled flat error —
|
|
29
|
+
// `res.status(n).json({ error: 'x' })` or `.json({ error })` — breaks the
|
|
30
|
+
// contract: the caller gets a bare string where the envelope should be.
|
|
31
|
+
//
|
|
32
|
+
// RATCHET, not big-bang. The R04 sweep (task 1991) migrates the ~562 existing
|
|
33
|
+
// sites; until it finishes they'd all "fail" a strict check, so this HOLDS THE
|
|
34
|
+
// LINE: it fails only when the count rises ABOVE the committed baseline (a NEW
|
|
35
|
+
// hand-rolled flat error), and nudges the baseline DOWN as the sweep shrinks it.
|
|
36
|
+
// When the sweep reaches 0, FLAT_ERROR_BASELINE is 0 and any new flat error
|
|
37
|
+
// hard-fails immediately. Detector is shared with the sweep + its test.
|
|
38
|
+
const FLAT_ERROR_BASELINE = 0; // R04 sweep (task 1991) landed the surface at 0 — the gate is now hard-zero: any new hand-rolled flat error fails the build immediately.
|
|
39
|
+
// A flat error is a `.json({ … })` whose FIRST key is `error` with a value that
|
|
40
|
+
// is NOT an object literal (a string/var/shorthand). The envelope's nested
|
|
41
|
+
// `error: { … }` and `.json(envelope(…))` deliberately do NOT match.
|
|
42
|
+
// The `(?!\s*\{)` lookahead is whitespace-aware: without the inner `\s*` the
|
|
43
|
+
// preceding `\s*` backtracks to zero and the lookahead is tested at the space in
|
|
44
|
+
// `error: {`, so the compliant nested envelope was wrongly flagged (task 2029).
|
|
45
|
+
const FLAT_ERROR_VALUE_RE = /\.json\(\s*\{\s*error\s*:\s*(?!\s*\{)/g; // error: 'x' | error: code
|
|
46
|
+
const FLAT_ERROR_SHORTHAND_RE = /\.json\(\s*\{\s*error\s*[,}]/g; // { error } | { error, … }
|
|
47
|
+
|
|
48
|
+
// Strip // line comments + /* */ block comments AND blank out string/template
|
|
49
|
+
// contents (keeping the delimiters) so neither a DOC example of the anti-pattern
|
|
50
|
+
// — e.g. the res.fail() usage note in middleware/error-envelope.js — nor a
|
|
51
|
+
// pattern quoted inside a string literal is counted as a real flat error once
|
|
52
|
+
// the gate is hard-zero (task 1991). Real flat errors are unaffected: in
|
|
53
|
+
// `res.status(404).json({ error: 'x' })` the `.json({ error:` the detector keys
|
|
54
|
+
// on is CODE (outside the string), so blanking the `'x'` content still matches.
|
|
55
|
+
function stripCommentsForFlat(src) {
|
|
56
|
+
let out = '';
|
|
57
|
+
let i = 0;
|
|
58
|
+
let quote = null;
|
|
59
|
+
while (i < src.length) {
|
|
60
|
+
const c = src[i];
|
|
61
|
+
const d = src[i + 1];
|
|
62
|
+
if (quote) {
|
|
63
|
+
// inside a string/template: drop content, keep only the closing delimiter
|
|
64
|
+
if (c === '\\') { i += 2; continue; }
|
|
65
|
+
if (c === quote) { out += c; quote = null; }
|
|
66
|
+
i++; continue;
|
|
67
|
+
}
|
|
68
|
+
if (c === '"' || c === "'" || c === '`') { quote = c; out += c; i++; continue; }
|
|
69
|
+
if (c === '/' && d === '/') { while (i < src.length && src[i] !== '\n') i++; continue; }
|
|
70
|
+
if (c === '/' && d === '*') { i += 2; while (i < src.length && !(src[i] === '*' && src[i + 1] === '/')) i++; i += 2; continue; }
|
|
71
|
+
out += c; i++;
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function countFlatErrors(text) {
|
|
77
|
+
const code = stripCommentsForFlat(text);
|
|
78
|
+
return (code.match(FLAT_ERROR_VALUE_RE) || []).length
|
|
79
|
+
+ (code.match(FLAT_ERROR_SHORTHAND_RE) || []).length;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// scanFlatErrorSites() — per-file flat-error counts across the API surface
|
|
83
|
+
// (core src/bongos + every module), skipping tests. Shared by the check below, the
|
|
84
|
+
// R04 codemod, and tests/api_error_envelope_buildcheck.mjs.
|
|
85
|
+
function scanFlatErrorSites() {
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
const byFile = {};
|
|
88
|
+
let total = 0;
|
|
89
|
+
const files = [
|
|
90
|
+
...walkJs(path.join(CORE_ROOT, 'src', 'bongos')),
|
|
91
|
+
...walkJsAllRoots('modules'),
|
|
92
|
+
];
|
|
93
|
+
for (const abs of files) {
|
|
94
|
+
const relFile = rel(abs);
|
|
95
|
+
if (seen.has(relFile)) continue;
|
|
96
|
+
seen.add(relFile);
|
|
97
|
+
if (relFile.includes('/tests/') || relFile.endsWith('.test.js')) continue;
|
|
98
|
+
const n = countFlatErrors(fs.readFileSync(abs, 'utf8'));
|
|
99
|
+
if (n) { byFile[relFile] = n; total += n; }
|
|
100
|
+
}
|
|
101
|
+
return { total, byFile };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function checkErrorEnvelope() {
|
|
105
|
+
const { total, byFile } = scanFlatErrorSites();
|
|
106
|
+
const violations = [];
|
|
107
|
+
const warnings = [];
|
|
108
|
+
if (total > FLAT_ERROR_BASELINE) {
|
|
109
|
+
const top = Object.entries(byFile).sort((a, b) => b[1] - a[1]).slice(0, 8)
|
|
110
|
+
.map(([f, n]) => `${f} (${n})`).join(', ');
|
|
111
|
+
violations.push(
|
|
112
|
+
`${total} hand-rolled flat-error sites — ABOVE the ${FLAT_ERROR_BASELINE} baseline. A new route emitted a flat \`res.status(n).json({error:'x'})\` instead of \`res.fail('x', n)\` / \`throw new ApiError('x')\` (ADR 0116). Top files: ${top}. Use the envelope helper (src/bongos/middleware/error-envelope.js).`
|
|
113
|
+
);
|
|
114
|
+
} else if (total < FLAT_ERROR_BASELINE) {
|
|
115
|
+
warnings.push(
|
|
116
|
+
`${total} flat-error sites — BELOW the ${FLAT_ERROR_BASELINE} baseline. The sweep tightened; lower FLAT_ERROR_BASELINE to ${total} in fitness.js so the ratchet can't slip back (BV1 R04 / task 1991).`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
name: 'no route hand-rolls a flat error (ADR 0116 / R03)',
|
|
121
|
+
ok: violations.length === 0,
|
|
122
|
+
hardFail: violations.length > 0,
|
|
123
|
+
violations,
|
|
124
|
+
warnings,
|
|
125
|
+
note: `≤ ${FLAT_ERROR_BASELINE} flat \`.json({error:'…'})\` sites (ratchet → 0 as the R04 sweep lands); currently ${total}. New failures must use res.fail()/ApiError (the R02 envelope).`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
checkErrorEnvelope,
|
|
131
|
+
scanFlatErrorSites,
|
|
132
|
+
countFlatErrors,
|
|
133
|
+
FLAT_ERROR_BASELINE,
|
|
134
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// scripts/gds/fitness-checks-packaging.js — Checks 16 + 16b: core code must not
|
|
2
|
+
// HARD-require a boundary-EXCLUDED config or module.
|
|
3
|
+
//
|
|
4
|
+
// Carved out of scripts/gds/fitness.js by task 1003825, on the task-1003192
|
|
5
|
+
// precedent. That file had returned to the 1,500-line ratchet with ONE line of
|
|
6
|
+
// headroom (1,499 of 1,500, fitness-ratchets.js OVERSIZE_LINES), so the next check
|
|
7
|
+
// registered there would have tipped it over and redded the ratchet for whoever
|
|
8
|
+
// wrote it. These two checks left together because they are one subject — the same
|
|
9
|
+
// question asked of a config and of a module — and because the pair depends only on
|
|
10
|
+
// CORE_ROOT / rel / walkJs / walkJsAllRoots, all already public on fitness-lib.js,
|
|
11
|
+
// so the carve adds no import cycle.
|
|
12
|
+
//
|
|
13
|
+
// fitness.js requires both names straight back, so the CHECKS registry and the
|
|
14
|
+
// module surface the tests read are unchanged.
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
|
|
21
|
+
const { CORE_ROOT, rel, walkJs, walkJsAllRoots } = require('./fitness-lib.js');
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Check 16 — core code must not HARD-require an instance-excluded config/*.json
|
|
24
|
+
// ============================================================================
|
|
25
|
+
//
|
|
26
|
+
// A vanilla self-host boot ships only the PUBLISHABLE tree (publish-manifest.js). An
|
|
27
|
+
// instance-only config (config/devbox-app.json, config/bongos-cli.json, config/branding.json,
|
|
28
|
+
// config/modules.json, …) is boundary-EXCLUDED, so a bare `require('../../config/<excluded>.json')`
|
|
29
|
+
// in core code throws MODULE_NOT_FOUND and CRASHES the boot on a fresh instance — the exact
|
|
30
|
+
// class that shipped in R88 (two hard-requires in src/bongos/serve-internal.js; task 1824). The
|
|
31
|
+
// cure is to READ the config instead of requiring it, tolerating a missing file
|
|
32
|
+
// (tryLoadInstanceConfig in src/bongos/serve-internal.js / fs.existsSync → fail-to-vanilla), which
|
|
33
|
+
// also drops the require() literal so it no longer matches here. Check 1 (core↔host) scans
|
|
34
|
+
// code→code IMPORTS and misses this code→config class. Only an EXCLUDED config is a violation;
|
|
35
|
+
// a publishable neutral.*/schema config (which ships with the core) is fine.
|
|
36
|
+
const CONFIG_REQUIRE_RE = /require\(\s*['"]([^'"]*config\/[^'"]*\.json)['"]\s*\)/g;
|
|
37
|
+
|
|
38
|
+
function checkCoreRequiresExcludedConfig() {
|
|
39
|
+
const { isPublishable } = require('./publish-manifest');
|
|
40
|
+
const violations = [];
|
|
41
|
+
// src/ + scripts/ are CORE; modules ship from BOTH roots (ADR 0108 §Consequences #4). Dedup by
|
|
42
|
+
// repo-relative path so a shared checkout scans each file once.
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
const allAbs = [
|
|
45
|
+
...['src', 'scripts'].flatMap((d) => walkJs(path.join(CORE_ROOT, d))),
|
|
46
|
+
...walkJsAllRoots('modules'),
|
|
47
|
+
];
|
|
48
|
+
for (const abs of allAbs) {
|
|
49
|
+
const relFile = rel(abs);
|
|
50
|
+
if (seen.has(relFile)) continue;
|
|
51
|
+
seen.add(relFile);
|
|
52
|
+
if (!isPublishable(relFile)) continue; // only CORE (publishable) code can crash a vanilla boot
|
|
53
|
+
for (const raw of fs.readFileSync(abs, 'utf8').split('\n')) {
|
|
54
|
+
const trimmed = raw.trim();
|
|
55
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
|
|
56
|
+
let m;
|
|
57
|
+
CONFIG_REQUIRE_RE.lastIndex = 0;
|
|
58
|
+
while ((m = CONFIG_REQUIRE_RE.exec(raw))) {
|
|
59
|
+
const relCfg = rel(path.resolve(path.dirname(abs), m[1]));
|
|
60
|
+
if (!isPublishable(relCfg)) {
|
|
61
|
+
violations.push(`${relFile} → require('${m[1]}') hard-requires an instance-excluded config (${relCfg}) — guard the read (tryLoadInstanceConfig in src/bongos/serve-internal.js / fs.existsSync, fail-to-vanilla) so a fresh core boots (task 1824).`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
name: 'core never hard-requires an excluded config (task 1824)',
|
|
68
|
+
ok: violations.length === 0,
|
|
69
|
+
hardFail: violations.length > 0,
|
|
70
|
+
violations,
|
|
71
|
+
warnings: [],
|
|
72
|
+
note: "a publishable src/·scripts/·modules/ .js must not require('…config/X.json') when X.json is boundary-excluded — guard the read so a vanilla core boots.",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Check 16b — core code must not require an instance-EXCLUDED module (task 2135)
|
|
78
|
+
// ============================================================================
|
|
79
|
+
//
|
|
80
|
+
// Sibling of Check 16 for the code→MODULE class. A publishable core file that
|
|
81
|
+
// require()s a boundary-EXCLUDED module (modules/game, modules/art-pipeline,
|
|
82
|
+
// modules/character-anim, …) throws MODULE_NOT_FOUND once that module lives in the
|
|
83
|
+
// consumer's instance repo rather than the core package (R89 split). The cure is
|
|
84
|
+
// either to reclassify the module INTO core (if it belongs there — e.g. provisioning,
|
|
85
|
+
// sibling of dev-box) or to exclude the requiring file with the feature it serves
|
|
86
|
+
// (art tooling). Catches BOTH require forms: a direct literal
|
|
87
|
+
// require('../modules/<key>/…') AND a require(path.join(<root>, 'modules/<key>/…')).
|
|
88
|
+
// Only an EXCLUDED module required from a PUBLISHABLE file is a violation; requiring a
|
|
89
|
+
// publishable (core) module is fine, and the loader's DYNAMIC dispatch
|
|
90
|
+
// (path.join(dir,'routes',rk) — no literal modules/<key>/ string) is invisible here.
|
|
91
|
+
const MODULE_PATH_IN_STRING_RE = /['"](?:[^'"]*\/)?modules\/([a-z0-9][a-z0-9-]*)\/[^'"]*['"]/g;
|
|
92
|
+
|
|
93
|
+
function checkCoreRequiresExcludedModule() {
|
|
94
|
+
const { isPublishable } = require('./publish-manifest');
|
|
95
|
+
const violations = [];
|
|
96
|
+
const seen = new Set();
|
|
97
|
+
const allAbs = [
|
|
98
|
+
...['src', 'scripts'].flatMap((d) => walkJs(path.join(CORE_ROOT, d))),
|
|
99
|
+
...walkJsAllRoots('modules'),
|
|
100
|
+
];
|
|
101
|
+
for (const abs of allAbs) {
|
|
102
|
+
const relFile = rel(abs);
|
|
103
|
+
if (seen.has(relFile)) continue;
|
|
104
|
+
seen.add(relFile);
|
|
105
|
+
if (!isPublishable(relFile)) continue; // only CORE (publishable) code can crash a vanilla boot
|
|
106
|
+
for (const raw of fs.readFileSync(abs, 'utf8').split('\n')) {
|
|
107
|
+
const trimmed = raw.trim();
|
|
108
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
|
|
109
|
+
if (!/require\s*\(/.test(raw)) continue; // only lines that actually require()
|
|
110
|
+
let m;
|
|
111
|
+
MODULE_PATH_IN_STRING_RE.lastIndex = 0;
|
|
112
|
+
while ((m = MODULE_PATH_IN_STRING_RE.exec(raw))) {
|
|
113
|
+
const key = m[1];
|
|
114
|
+
if (isPublishable(`modules/${key}/module.json`)) continue; // requiring a CORE module is fine
|
|
115
|
+
violations.push(`${relFile} → require(...'modules/${key}/…') pulls in an instance-EXCLUDED module — reclassify it into core (if it belongs there) or exclude ${relFile} with the feature it serves, so a fresh core boots (task 2135).`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
name: 'core never requires an excluded module (task 2135)',
|
|
121
|
+
ok: violations.length === 0,
|
|
122
|
+
hardFail: violations.length > 0,
|
|
123
|
+
violations,
|
|
124
|
+
warnings: [],
|
|
125
|
+
note: "a publishable src/·scripts/·modules/ .js must not require('…modules/<key>/…') when that module is boundary-excluded — MODULE_NOT_FOUND post-split (R89).",
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
checkCoreRequiresExcludedConfig,
|
|
131
|
+
checkCoreRequiresExcludedModule,
|
|
132
|
+
};
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -37,6 +37,8 @@ const { CORE_ROOT, INSTANCE_ROOT, MODULE_ROOTS, REPO_ROOT, rel, walkJs, walkJsAl
|
|
|
37
37
|
const { IDENTITY_SCAN_ALLOWED, IDENTITY_SCAN_EXCLUDE, IDENTITY_SCAN_EXTRA_FILES, IDENTITY_SCAN_EXTS, IDENTITY_SCAN_ROOTS, IDENTITY_SCAN_SKIP_DIRS, PROMPT_IDENTITY_LITERALS, checkDesignContract, checkKernelPromptsNoHardcodedIdentity, checkNeutralConfigsNoHostIdentity, collectIdentityFiles, scanPromptIdentity, stripCommentsForIdentity } = require('./fitness-checks-identity.js');
|
|
38
38
|
const { WRITE_VALIDATION_BASELINE, WRITE_VALIDATION_EXEMPT, checkWriteRoutesValidated, scanUnvalidatedWriteRoutes } = require('./fitness-checks-write-validation.js');
|
|
39
39
|
const { GOAL_VOCAB_CITATION, GOAL_VOCAB_LINE_SCOPED, GOAL_VOCAB_SURFACE, LIFECYCLE_DB_FILES, PRELAUNCH_SURFACE, RENAME_IDENTITY_RE, VOCAB_ALLOW, VOCAB_BANNED, VOCAB_CITATION, VOCAB_EXTS, VOCAB_HISTORICAL_REF, VOCAB_SURFACE, checkGoalMembershipVocabulary, checkGovernmentRenameIdentity, checkPrelaunchVocabulary, checkVocabularyWall, vocabFilesToScan } = require('./fitness-checks-vocabulary.js');
|
|
40
|
+
const { checkCoreRequiresExcludedConfig, checkCoreRequiresExcludedModule } = require('./fitness-checks-packaging.js'); // Checks 16 + 16b — carved out by task 1003825 (the 1,500-line ratchet)
|
|
41
|
+
const { FLAT_ERROR_BASELINE, checkErrorEnvelope, countFlatErrors, scanFlatErrorSites } = require('./fitness-checks-error-envelope.js'); // Check 17 — carved out by task 1003825
|
|
40
42
|
// ============================================================================
|
|
41
43
|
// Check 1 — core ↔ host/game import boundary (ADR 0062 §1 cut-line)
|
|
42
44
|
// ============================================================================
|
|
@@ -1084,219 +1086,6 @@ function checkHallSectionRegistry() {
|
|
|
1084
1086
|
};
|
|
1085
1087
|
}
|
|
1086
1088
|
|
|
1087
|
-
// ============================================================================
|
|
1088
|
-
// Check 16 — core code must not HARD-require an instance-excluded config/*.json
|
|
1089
|
-
// ============================================================================
|
|
1090
|
-
//
|
|
1091
|
-
// A vanilla self-host boot ships only the PUBLISHABLE tree (publish-manifest.js). An
|
|
1092
|
-
// instance-only config (config/devbox-app.json, config/bongos-cli.json, config/branding.json,
|
|
1093
|
-
// config/modules.json, …) is boundary-EXCLUDED, so a bare `require('../../config/<excluded>.json')`
|
|
1094
|
-
// in core code throws MODULE_NOT_FOUND and CRASHES the boot on a fresh instance — the exact
|
|
1095
|
-
// class that shipped in R88 (two hard-requires in src/bongos/serve-internal.js; task 1824). The
|
|
1096
|
-
// cure is to READ the config instead of requiring it, tolerating a missing file
|
|
1097
|
-
// (tryLoadInstanceConfig in src/bongos/serve-internal.js / fs.existsSync → fail-to-vanilla), which
|
|
1098
|
-
// also drops the require() literal so it no longer matches here. Check 1 (core↔host) scans
|
|
1099
|
-
// code→code IMPORTS and misses this code→config class. Only an EXCLUDED config is a violation;
|
|
1100
|
-
// a publishable neutral.*/schema config (which ships with the core) is fine.
|
|
1101
|
-
const CONFIG_REQUIRE_RE = /require\(\s*['"]([^'"]*config\/[^'"]*\.json)['"]\s*\)/g;
|
|
1102
|
-
|
|
1103
|
-
function checkCoreRequiresExcludedConfig() {
|
|
1104
|
-
const { isPublishable } = require('./publish-manifest');
|
|
1105
|
-
const violations = [];
|
|
1106
|
-
// src/ + scripts/ are CORE; modules ship from BOTH roots (ADR 0108 §Consequences #4). Dedup by
|
|
1107
|
-
// repo-relative path so a shared checkout scans each file once.
|
|
1108
|
-
const seen = new Set();
|
|
1109
|
-
const allAbs = [
|
|
1110
|
-
...['src', 'scripts'].flatMap((d) => walkJs(path.join(CORE_ROOT, d))),
|
|
1111
|
-
...walkJsAllRoots('modules'),
|
|
1112
|
-
];
|
|
1113
|
-
for (const abs of allAbs) {
|
|
1114
|
-
const relFile = rel(abs);
|
|
1115
|
-
if (seen.has(relFile)) continue;
|
|
1116
|
-
seen.add(relFile);
|
|
1117
|
-
if (!isPublishable(relFile)) continue; // only CORE (publishable) code can crash a vanilla boot
|
|
1118
|
-
for (const raw of fs.readFileSync(abs, 'utf8').split('\n')) {
|
|
1119
|
-
const trimmed = raw.trim();
|
|
1120
|
-
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
|
|
1121
|
-
let m;
|
|
1122
|
-
CONFIG_REQUIRE_RE.lastIndex = 0;
|
|
1123
|
-
while ((m = CONFIG_REQUIRE_RE.exec(raw))) {
|
|
1124
|
-
const relCfg = rel(path.resolve(path.dirname(abs), m[1]));
|
|
1125
|
-
if (!isPublishable(relCfg)) {
|
|
1126
|
-
violations.push(`${relFile} → require('${m[1]}') hard-requires an instance-excluded config (${relCfg}) — guard the read (tryLoadInstanceConfig in src/bongos/serve-internal.js / fs.existsSync, fail-to-vanilla) so a fresh core boots (task 1824).`);
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
return {
|
|
1132
|
-
name: 'core never hard-requires an excluded config (task 1824)',
|
|
1133
|
-
ok: violations.length === 0,
|
|
1134
|
-
hardFail: violations.length > 0,
|
|
1135
|
-
violations,
|
|
1136
|
-
warnings: [],
|
|
1137
|
-
note: "a publishable src/·scripts/·modules/ .js must not require('…config/X.json') when X.json is boundary-excluded — guard the read so a vanilla core boots.",
|
|
1138
|
-
};
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
// ============================================================================
|
|
1142
|
-
// Check 16b — core code must not require an instance-EXCLUDED module (task 2135)
|
|
1143
|
-
// ============================================================================
|
|
1144
|
-
//
|
|
1145
|
-
// Sibling of Check 16 for the code→MODULE class. A publishable core file that
|
|
1146
|
-
// require()s a boundary-EXCLUDED module (modules/game, modules/art-pipeline,
|
|
1147
|
-
// modules/character-anim, …) throws MODULE_NOT_FOUND once that module lives in the
|
|
1148
|
-
// consumer's instance repo rather than the core package (R89 split). The cure is
|
|
1149
|
-
// either to reclassify the module INTO core (if it belongs there — e.g. provisioning,
|
|
1150
|
-
// sibling of dev-box) or to exclude the requiring file with the feature it serves
|
|
1151
|
-
// (art tooling). Catches BOTH require forms: a direct literal
|
|
1152
|
-
// require('../modules/<key>/…') AND a require(path.join(<root>, 'modules/<key>/…')).
|
|
1153
|
-
// Only an EXCLUDED module required from a PUBLISHABLE file is a violation; requiring a
|
|
1154
|
-
// publishable (core) module is fine, and the loader's DYNAMIC dispatch
|
|
1155
|
-
// (path.join(dir,'routes',rk) — no literal modules/<key>/ string) is invisible here.
|
|
1156
|
-
const MODULE_PATH_IN_STRING_RE = /['"](?:[^'"]*\/)?modules\/([a-z0-9][a-z0-9-]*)\/[^'"]*['"]/g;
|
|
1157
|
-
|
|
1158
|
-
function checkCoreRequiresExcludedModule() {
|
|
1159
|
-
const { isPublishable } = require('./publish-manifest');
|
|
1160
|
-
const violations = [];
|
|
1161
|
-
const seen = new Set();
|
|
1162
|
-
const allAbs = [
|
|
1163
|
-
...['src', 'scripts'].flatMap((d) => walkJs(path.join(CORE_ROOT, d))),
|
|
1164
|
-
...walkJsAllRoots('modules'),
|
|
1165
|
-
];
|
|
1166
|
-
for (const abs of allAbs) {
|
|
1167
|
-
const relFile = rel(abs);
|
|
1168
|
-
if (seen.has(relFile)) continue;
|
|
1169
|
-
seen.add(relFile);
|
|
1170
|
-
if (!isPublishable(relFile)) continue; // only CORE (publishable) code can crash a vanilla boot
|
|
1171
|
-
for (const raw of fs.readFileSync(abs, 'utf8').split('\n')) {
|
|
1172
|
-
const trimmed = raw.trim();
|
|
1173
|
-
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
|
|
1174
|
-
if (!/require\s*\(/.test(raw)) continue; // only lines that actually require()
|
|
1175
|
-
let m;
|
|
1176
|
-
MODULE_PATH_IN_STRING_RE.lastIndex = 0;
|
|
1177
|
-
while ((m = MODULE_PATH_IN_STRING_RE.exec(raw))) {
|
|
1178
|
-
const key = m[1];
|
|
1179
|
-
if (isPublishable(`modules/${key}/module.json`)) continue; // requiring a CORE module is fine
|
|
1180
|
-
violations.push(`${relFile} → require(...'modules/${key}/…') pulls in an instance-EXCLUDED module — reclassify it into core (if it belongs there) or exclude ${relFile} with the feature it serves, so a fresh core boots (task 2135).`);
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
return {
|
|
1185
|
-
name: 'core never requires an excluded module (task 2135)',
|
|
1186
|
-
ok: violations.length === 0,
|
|
1187
|
-
hardFail: violations.length > 0,
|
|
1188
|
-
violations,
|
|
1189
|
-
warnings: [],
|
|
1190
|
-
note: "a publishable src/·scripts/·modules/ .js must not require('…modules/<key>/…') when that module is boundary-excluded — MODULE_NOT_FOUND post-split (R89).",
|
|
1191
|
-
};
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
// ============================================================================
|
|
1195
|
-
// Check 17 — no route hand-rolls a FLAT error (BV1 R03 / task 1990 / ADR 0116)
|
|
1196
|
-
// ============================================================================
|
|
1197
|
-
//
|
|
1198
|
-
// The error-envelope contract (ADR 0116) says every failure is the ONE envelope
|
|
1199
|
-
// `{error:{code,message,details}}`, emitted via res.fail() or a thrown ApiError
|
|
1200
|
-
// (the R02 middleware, task 1989). A hand-rolled flat error —
|
|
1201
|
-
// `res.status(n).json({ error: 'x' })` or `.json({ error })` — breaks the
|
|
1202
|
-
// contract: the caller gets a bare string where the envelope should be.
|
|
1203
|
-
//
|
|
1204
|
-
// RATCHET, not big-bang. The R04 sweep (task 1991) migrates the ~562 existing
|
|
1205
|
-
// sites; until it finishes they'd all "fail" a strict check, so this HOLDS THE
|
|
1206
|
-
// LINE: it fails only when the count rises ABOVE the committed baseline (a NEW
|
|
1207
|
-
// hand-rolled flat error), and nudges the baseline DOWN as the sweep shrinks it.
|
|
1208
|
-
// When the sweep reaches 0, FLAT_ERROR_BASELINE is 0 and any new flat error
|
|
1209
|
-
// hard-fails immediately. Detector is shared with the sweep + its test.
|
|
1210
|
-
const FLAT_ERROR_BASELINE = 0; // R04 sweep (task 1991) landed the surface at 0 — the gate is now hard-zero: any new hand-rolled flat error fails the build immediately.
|
|
1211
|
-
// A flat error is a `.json({ … })` whose FIRST key is `error` with a value that
|
|
1212
|
-
// is NOT an object literal (a string/var/shorthand). The envelope's nested
|
|
1213
|
-
// `error: { … }` and `.json(envelope(…))` deliberately do NOT match.
|
|
1214
|
-
// The `(?!\s*\{)` lookahead is whitespace-aware: without the inner `\s*` the
|
|
1215
|
-
// preceding `\s*` backtracks to zero and the lookahead is tested at the space in
|
|
1216
|
-
// `error: {`, so the compliant nested envelope was wrongly flagged (task 2029).
|
|
1217
|
-
const FLAT_ERROR_VALUE_RE = /\.json\(\s*\{\s*error\s*:\s*(?!\s*\{)/g; // error: 'x' | error: code
|
|
1218
|
-
const FLAT_ERROR_SHORTHAND_RE = /\.json\(\s*\{\s*error\s*[,}]/g; // { error } | { error, … }
|
|
1219
|
-
|
|
1220
|
-
// Strip // line comments + /* */ block comments AND blank out string/template
|
|
1221
|
-
// contents (keeping the delimiters) so neither a DOC example of the anti-pattern
|
|
1222
|
-
// — e.g. the res.fail() usage note in middleware/error-envelope.js — nor a
|
|
1223
|
-
// pattern quoted inside a string literal is counted as a real flat error once
|
|
1224
|
-
// the gate is hard-zero (task 1991). Real flat errors are unaffected: in
|
|
1225
|
-
// `res.status(404).json({ error: 'x' })` the `.json({ error:` the detector keys
|
|
1226
|
-
// on is CODE (outside the string), so blanking the `'x'` content still matches.
|
|
1227
|
-
function stripCommentsForFlat(src) {
|
|
1228
|
-
let out = '';
|
|
1229
|
-
let i = 0;
|
|
1230
|
-
let quote = null;
|
|
1231
|
-
while (i < src.length) {
|
|
1232
|
-
const c = src[i];
|
|
1233
|
-
const d = src[i + 1];
|
|
1234
|
-
if (quote) {
|
|
1235
|
-
// inside a string/template: drop content, keep only the closing delimiter
|
|
1236
|
-
if (c === '\\') { i += 2; continue; }
|
|
1237
|
-
if (c === quote) { out += c; quote = null; }
|
|
1238
|
-
i++; continue;
|
|
1239
|
-
}
|
|
1240
|
-
if (c === '"' || c === "'" || c === '`') { quote = c; out += c; i++; continue; }
|
|
1241
|
-
if (c === '/' && d === '/') { while (i < src.length && src[i] !== '\n') i++; continue; }
|
|
1242
|
-
if (c === '/' && d === '*') { i += 2; while (i < src.length && !(src[i] === '*' && src[i + 1] === '/')) i++; i += 2; continue; }
|
|
1243
|
-
out += c; i++;
|
|
1244
|
-
}
|
|
1245
|
-
return out;
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
function countFlatErrors(text) {
|
|
1249
|
-
const code = stripCommentsForFlat(text);
|
|
1250
|
-
return (code.match(FLAT_ERROR_VALUE_RE) || []).length
|
|
1251
|
-
+ (code.match(FLAT_ERROR_SHORTHAND_RE) || []).length;
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
// scanFlatErrorSites() — per-file flat-error counts across the API surface
|
|
1255
|
-
// (core src/bongos + every module), skipping tests. Shared by the check below, the
|
|
1256
|
-
// R04 codemod, and tests/api_error_envelope_buildcheck.mjs.
|
|
1257
|
-
function scanFlatErrorSites() {
|
|
1258
|
-
const seen = new Set();
|
|
1259
|
-
const byFile = {};
|
|
1260
|
-
let total = 0;
|
|
1261
|
-
const files = [
|
|
1262
|
-
...walkJs(path.join(CORE_ROOT, 'src', 'bongos')),
|
|
1263
|
-
...walkJsAllRoots('modules'),
|
|
1264
|
-
];
|
|
1265
|
-
for (const abs of files) {
|
|
1266
|
-
const relFile = rel(abs);
|
|
1267
|
-
if (seen.has(relFile)) continue;
|
|
1268
|
-
seen.add(relFile);
|
|
1269
|
-
if (relFile.includes('/tests/') || relFile.endsWith('.test.js')) continue;
|
|
1270
|
-
const n = countFlatErrors(fs.readFileSync(abs, 'utf8'));
|
|
1271
|
-
if (n) { byFile[relFile] = n; total += n; }
|
|
1272
|
-
}
|
|
1273
|
-
return { total, byFile };
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
function checkErrorEnvelope() {
|
|
1277
|
-
const { total, byFile } = scanFlatErrorSites();
|
|
1278
|
-
const violations = [];
|
|
1279
|
-
const warnings = [];
|
|
1280
|
-
if (total > FLAT_ERROR_BASELINE) {
|
|
1281
|
-
const top = Object.entries(byFile).sort((a, b) => b[1] - a[1]).slice(0, 8)
|
|
1282
|
-
.map(([f, n]) => `${f} (${n})`).join(', ');
|
|
1283
|
-
violations.push(
|
|
1284
|
-
`${total} hand-rolled flat-error sites — ABOVE the ${FLAT_ERROR_BASELINE} baseline. A new route emitted a flat \`res.status(n).json({error:'x'})\` instead of \`res.fail('x', n)\` / \`throw new ApiError('x')\` (ADR 0116). Top files: ${top}. Use the envelope helper (src/bongos/middleware/error-envelope.js).`
|
|
1285
|
-
);
|
|
1286
|
-
} else if (total < FLAT_ERROR_BASELINE) {
|
|
1287
|
-
warnings.push(
|
|
1288
|
-
`${total} flat-error sites — BELOW the ${FLAT_ERROR_BASELINE} baseline. The sweep tightened; lower FLAT_ERROR_BASELINE to ${total} in fitness.js so the ratchet can't slip back (BV1 R04 / task 1991).`
|
|
1289
|
-
);
|
|
1290
|
-
}
|
|
1291
|
-
return {
|
|
1292
|
-
name: 'no route hand-rolls a flat error (ADR 0116 / R03)',
|
|
1293
|
-
ok: violations.length === 0,
|
|
1294
|
-
hardFail: violations.length > 0,
|
|
1295
|
-
violations,
|
|
1296
|
-
warnings,
|
|
1297
|
-
note: `≤ ${FLAT_ERROR_BASELINE} flat \`.json({error:'…'})\` sites (ratchet → 0 as the R04 sweep lands); currently ${total}. New failures must use res.fail()/ApiError (the R02 envelope).`,
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
1089
|
|
|
1301
1090
|
// ============================================================================
|
|
1302
1091
|
// Check 2c — every SKILL.md frontmatter parses the way Claude Code's YAML reader reads it (task 1003548)
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.756'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|