@bongos/core 1.19.731 → 1.19.733
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 +32 -17
- package/docs/module-api-changelog.md +4 -0
- package/docs/onboarding/diagrams/04-architecture.mmd +1 -1
- package/modules/grading/dead-feature-claim.js +121 -0
- package/modules/grading/grader-score.js +32 -2
- package/modules/grading/grader-workers/worker-verdict.js +12 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/diagram-facts.js +31 -21
- package/scripts/gds/gen-diagrams.js +1 -1
- package/src/module-api.js +1 -1
- package/tests/dead_feature_claim.mjs +176 -0
- package/tests/diagram_repo_slug.mjs +142 -0
- package/tests/grade_server_authoritative.mjs +57 -0
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.733",
|
|
6
|
+
"core_contract": "1.19.733",
|
|
7
|
+
"source_commit": "1cf8536fa296cc05a9c0fc2a1cff4667ab628843",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-13T15:
|
|
9
|
+
"built_at": "2026-09-13T15:46:24.724Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 477,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2104,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2605,
|
|
20
|
+
"tree_sha256": "df29652d8de78cc03652254e561648215caacb4e49b7c6a73a7eb502d9c2e049",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2482,7 +2482,7 @@
|
|
|
2482
2482
|
{
|
|
2483
2483
|
"path": "docs/module-api-changelog.md",
|
|
2484
2484
|
"mode": "0000644",
|
|
2485
|
-
"sha256": "
|
|
2485
|
+
"sha256": "05bbf0c615af2c986b0bad8963870a0420b3f0dee092bbbcbcaf9794c1f607a1"
|
|
2486
2486
|
},
|
|
2487
2487
|
{
|
|
2488
2488
|
"path": "docs/modules-contract.md",
|
|
@@ -2512,7 +2512,7 @@
|
|
|
2512
2512
|
{
|
|
2513
2513
|
"path": "docs/onboarding/diagrams/04-architecture.mmd",
|
|
2514
2514
|
"mode": "0000644",
|
|
2515
|
-
"sha256": "
|
|
2515
|
+
"sha256": "ee949090b1df91be015dabbcaa175bd2f81f3089714cf5f712ad2b0b053f4714"
|
|
2516
2516
|
},
|
|
2517
2517
|
{
|
|
2518
2518
|
"path": "docs/onboarding/diagrams/05-versioning.mmd",
|
|
@@ -4404,6 +4404,11 @@
|
|
|
4404
4404
|
"mode": "0000644",
|
|
4405
4405
|
"sha256": "b651b73c65b0da33284430b430638dbc39b34db43846a05ab431deaabcc44b3b"
|
|
4406
4406
|
},
|
|
4407
|
+
{
|
|
4408
|
+
"path": "modules/grading/dead-feature-claim.js",
|
|
4409
|
+
"mode": "0000644",
|
|
4410
|
+
"sha256": "907d02d33fd6e593d4bfcc0d9a00a9622ceaf49dd88b0f81b29ff4718d63a3d5"
|
|
4411
|
+
},
|
|
4407
4412
|
{
|
|
4408
4413
|
"path": "modules/grading/generated-provenance.js",
|
|
4409
4414
|
"mode": "0000644",
|
|
@@ -4432,7 +4437,7 @@
|
|
|
4432
4437
|
{
|
|
4433
4438
|
"path": "modules/grading/grader-score.js",
|
|
4434
4439
|
"mode": "0000644",
|
|
4435
|
-
"sha256": "
|
|
4440
|
+
"sha256": "e361b57dd292492abf8d40ad8935dab8ad619717b9c439d70808b9325d2fcb5e"
|
|
4436
4441
|
},
|
|
4437
4442
|
{
|
|
4438
4443
|
"path": "modules/grading/grader-subagent.js",
|
|
@@ -4487,7 +4492,7 @@
|
|
|
4487
4492
|
{
|
|
4488
4493
|
"path": "modules/grading/grader-workers/worker-verdict.js",
|
|
4489
4494
|
"mode": "0000644",
|
|
4490
|
-
"sha256": "
|
|
4495
|
+
"sha256": "52b04b281ddde638cf5a46172ad9d05d7caa8ba57a2d01c2b907b82b8524d4d2"
|
|
4491
4496
|
},
|
|
4492
4497
|
{
|
|
4493
4498
|
"path": "modules/grading/grader.js",
|
|
@@ -7512,12 +7517,12 @@
|
|
|
7512
7517
|
{
|
|
7513
7518
|
"path": "package-lock.json",
|
|
7514
7519
|
"mode": "0000644",
|
|
7515
|
-
"sha256": "
|
|
7520
|
+
"sha256": "d991c248b006d2066c11468c5749e58951463ecab027b50991ff4ca28279f294"
|
|
7516
7521
|
},
|
|
7517
7522
|
{
|
|
7518
7523
|
"path": "package.json",
|
|
7519
7524
|
"mode": "0000644",
|
|
7520
|
-
"sha256": "
|
|
7525
|
+
"sha256": "ed94a0e7dc7bdd1e48ebd1f9521b9aeb93a8e0289e9928393cf49461fd46dafa"
|
|
7521
7526
|
},
|
|
7522
7527
|
{
|
|
7523
7528
|
"path": "public-docs/index.html",
|
|
@@ -7907,7 +7912,7 @@
|
|
|
7907
7912
|
{
|
|
7908
7913
|
"path": "scripts/gds/diagram-facts.js",
|
|
7909
7914
|
"mode": "0000644",
|
|
7910
|
-
"sha256": "
|
|
7915
|
+
"sha256": "3ba594a6d475bb94be7edf5277cd60d5537bf1ab66f92f9d47eeb5138c56341f"
|
|
7911
7916
|
},
|
|
7912
7917
|
{
|
|
7913
7918
|
"path": "scripts/gds/discipline-modes.json",
|
|
@@ -8057,7 +8062,7 @@
|
|
|
8057
8062
|
{
|
|
8058
8063
|
"path": "scripts/gds/gen-diagrams.js",
|
|
8059
8064
|
"mode": "0000644",
|
|
8060
|
-
"sha256": "
|
|
8065
|
+
"sha256": "7cb78cd9796df68b6d7e1e4677cd778690f60583ba3ddc2fc23048f2ff75e4ba"
|
|
8061
8066
|
},
|
|
8062
8067
|
{
|
|
8063
8068
|
"path": "scripts/gds/gen-file-map.js",
|
|
@@ -9317,7 +9322,7 @@
|
|
|
9317
9322
|
{
|
|
9318
9323
|
"path": "src/module-api.js",
|
|
9319
9324
|
"mode": "0000644",
|
|
9320
|
-
"sha256": "
|
|
9325
|
+
"sha256": "83f3cb9f3f8992a2b8accb6b66190e82095bfcbe1ca2fb9bb40a471a194b380a"
|
|
9321
9326
|
},
|
|
9322
9327
|
{
|
|
9323
9328
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10174,6 +10179,11 @@
|
|
|
10174
10179
|
"mode": "0000644",
|
|
10175
10180
|
"sha256": "07d2825c812a10cd1d39182883652dec15d360d3645dd38023a00a0593d36ddc"
|
|
10176
10181
|
},
|
|
10182
|
+
{
|
|
10183
|
+
"path": "tests/dead_feature_claim.mjs",
|
|
10184
|
+
"mode": "0000644",
|
|
10185
|
+
"sha256": "af5f2cbeb241470d4a819a8531aeb4069cbb4fe913bc479f3542661c39ce5ba7"
|
|
10186
|
+
},
|
|
10177
10187
|
{
|
|
10178
10188
|
"path": "tests/dependencies_migration.mjs",
|
|
10179
10189
|
"mode": "0000644",
|
|
@@ -10249,6 +10259,11 @@
|
|
|
10249
10259
|
"mode": "0000644",
|
|
10250
10260
|
"sha256": "36a3bad951c6bfb4ee7b9230a5e6114095bb3b94f1f0019ec5c61c5168dee537"
|
|
10251
10261
|
},
|
|
10262
|
+
{
|
|
10263
|
+
"path": "tests/diagram_repo_slug.mjs",
|
|
10264
|
+
"mode": "0000644",
|
|
10265
|
+
"sha256": "5132322b103e6ea5a3a04b1509eba987e4988cd749751b17b5d31d813ec508d5"
|
|
10266
|
+
},
|
|
10252
10267
|
{
|
|
10253
10268
|
"path": "tests/diff_derived_signals.mjs",
|
|
10254
10269
|
"mode": "0000644",
|
|
@@ -10882,7 +10897,7 @@
|
|
|
10882
10897
|
{
|
|
10883
10898
|
"path": "tests/grade_server_authoritative.mjs",
|
|
10884
10899
|
"mode": "0000644",
|
|
10885
|
-
"sha256": "
|
|
10900
|
+
"sha256": "f27ea217e74d880296db9f78aa8ed3b97d1a4a7a71496015af5f5d3e3ad69f05"
|
|
10886
10901
|
},
|
|
10887
10902
|
{
|
|
10888
10903
|
"path": "tests/grade_unavailable_display.mjs",
|
|
@@ -1936,5 +1936,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1936
1936
|
landed since 1.19.729 with no explicit bump. run 34765502139. (task 1002620)
|
|
1937
1937
|
1.19.731 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1938
1938
|
landed since 1.19.730 with no explicit bump. run 34766029928. (task 1002620)
|
|
1939
|
+
1.19.732 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1940
|
+
landed since 1.19.731 with no explicit bump. run 34766314699. (task 1002620)
|
|
1941
|
+
1.19.733 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1942
|
+
landed since 1.19.732 with no explicit bump. run 34766569496. (task 1002620)
|
|
1939
1943
|
---------------------------------------------------------------------------
|
|
1940
1944
|
```
|
|
@@ -28,7 +28,7 @@ flowchart TB
|
|
|
28
28
|
DEPLOY["auto-pull deploy (ADR 0129)\na timer pulls THIS instance repo every ~2 min →\nnpm ci if the pin changed → migrate → regenerate docs → restart → healthz"]
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
subgraph GH["GITHUB — the instance's own repo
|
|
31
|
+
subgraph GH["GITHUB — the instance's own repo"]
|
|
32
32
|
MAIN["branch-protected main\n(no direct push; PR + required checks only)\nholds config + the pinned @bongos/core — NOT the platform source"]
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// modules/grading/dead-feature-claim.js — does a panel finding say, in its own
|
|
2
|
+
// words, that the shipped code does not run at all? (task 1002665)
|
|
3
|
+
//
|
|
4
|
+
// WHY. On the task 1002648 ship the Quality worker found that the new PATCH
|
|
5
|
+
// needs_migration path threw at runtime — a TASK_SETTER_COLUMNS allowlist gap —
|
|
6
|
+
// and wrote "the feature is entirely non-operational". It then filed that at
|
|
7
|
+
// severity `nit`, the grade PASSed, and the dead feature merged to main. The
|
|
8
|
+
// finding TEXT was right; the SEVERITY was miscalibrated, and severity is the
|
|
9
|
+
// only channel a reader's attention follows.
|
|
10
|
+
//
|
|
11
|
+
// Two things had to be true for that to merge, and this module addresses the
|
|
12
|
+
// first. (1) Nothing re-read the finding's own claim against its own severity.
|
|
13
|
+
// (2) Severity has no vote in the gate at all: `passed` in grader-score.js is
|
|
14
|
+
// `bothPass && !lowConfidence && …`, computed from the rubric SCORES — a major
|
|
15
|
+
// or blocker issue does not by itself stop a passing grade. So a severity floor
|
|
16
|
+
// alone would not have gated this ship, which is why grader-score.js pairs this
|
|
17
|
+
// classifier with a server-side guard rather than relying on the raised severity
|
|
18
|
+
// to do the work. The row that asked for this proposed the floor on its own; the
|
|
19
|
+
// floor on its own is decoration.
|
|
20
|
+
//
|
|
21
|
+
// SCOPE, deliberately narrow. This matches only ABSOLUTE claims about the
|
|
22
|
+
// submitted change: always-throws, never-runs, non-operational. It does NOT
|
|
23
|
+
// match conditional defects ("would throw on a null id") — those are ordinary
|
|
24
|
+
// findings the scores already judge — and it does not match the word
|
|
25
|
+
// "unreachable", which the originating row suggested: a reviewer writes "the old
|
|
26
|
+
// branch is now unreachable" to PRAISE a dead-code deletion, and that reading is
|
|
27
|
+
// at least as common as the damning one. A false positive here costs a builder a
|
|
28
|
+
// manual-confirm round, so the phrase list stays at claims that have no innocent
|
|
29
|
+
// reading, and the suppressors below drop the two framings that do.
|
|
30
|
+
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
// Absolute claims of runtime-deadness. Each must be unambiguous ON ITS OWN — if
|
|
34
|
+
// a phrase has an innocent reading in ordinary review prose, it does not belong
|
|
35
|
+
// here, it belongs in the scores.
|
|
36
|
+
const DEAD_CLAIM_PATTERNS = [
|
|
37
|
+
/\bnon-?operational\b/i,
|
|
38
|
+
/\b(?:always|unconditionally) throws?\b/i,
|
|
39
|
+
/\bthrows? (?:at runtime|on every (?:call|invocation|request))\b/i,
|
|
40
|
+
/\bnever (?:executes|runs|fires|gets called)\b/i,
|
|
41
|
+
/\b(?:can|will) never (?:run|execute|be (?:called|reached))\b/i,
|
|
42
|
+
/\bdoes ?n[o']?t work at all\b/i,
|
|
43
|
+
/\b(?:completely|entirely|totally) (?:broken|dead|inoperable)\b/i,
|
|
44
|
+
/\bdead on arrival\b/i,
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
// A claim about code the change REMOVED or FIXED, or a hypothetical, is the
|
|
48
|
+
// opposite of a dead-feature report — it is the reviewer describing what the
|
|
49
|
+
// diff cured. These read within a short window BEFORE the matched phrase,
|
|
50
|
+
// because that is where the framing sits in English ("this used to always
|
|
51
|
+
// throw", "no longer non-operational", "would always throw if the id were null").
|
|
52
|
+
const SUPPRESSORS = [
|
|
53
|
+
/\bused to\b/i,
|
|
54
|
+
/\bno longer\b/i,
|
|
55
|
+
/\bpreviously\b/i,
|
|
56
|
+
/\bbefore this (?:change|diff|fix|patch)\b/i,
|
|
57
|
+
/\bwas\b/i,
|
|
58
|
+
/\bwould\b/i,
|
|
59
|
+
/\bif\b/i,
|
|
60
|
+
/\bprevents?\b/i,
|
|
61
|
+
/\bavoids?\b/i,
|
|
62
|
+
/\bfix(?:e[sd])?\b/i,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
// How far back a suppressor counts. One clause, not one paragraph: read too far
|
|
66
|
+
// and an unrelated "fixed" earlier in a multi-sentence summary silences a real
|
|
67
|
+
// claim; read too little and "this would always throw" trips.
|
|
68
|
+
const SUPPRESSOR_WINDOW = 40;
|
|
69
|
+
|
|
70
|
+
// Does `text` assert that the submitted code does not run at all? Returns the
|
|
71
|
+
// matched phrase (truthy, and useful in the audit trail) or null. Pure.
|
|
72
|
+
function deadFeatureClaim(text) {
|
|
73
|
+
const s = typeof text === 'string' ? text : '';
|
|
74
|
+
if (!s) return null;
|
|
75
|
+
for (const re of DEAD_CLAIM_PATTERNS) {
|
|
76
|
+
const m = re.exec(s);
|
|
77
|
+
if (!m) continue;
|
|
78
|
+
const before = s.slice(Math.max(0, m.index - SUPPRESSOR_WINDOW), m.index);
|
|
79
|
+
if (SUPPRESSORS.some((sup) => sup.test(before))) continue;
|
|
80
|
+
return m[0];
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The same question asked of one issues[] entry. Only `summary` carries the
|
|
86
|
+
// worker's prose — `severity`/`file` are enumerated fields — so this reads that
|
|
87
|
+
// and nothing else, which keeps a file PATH containing an unlucky word (say,
|
|
88
|
+
// `always-throws.test.js`) from raising a severity.
|
|
89
|
+
function issueClaimsRuntimeDead(issue) {
|
|
90
|
+
if (!issue || typeof issue !== 'object') return null;
|
|
91
|
+
return deadFeatureClaim(issue.summary);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Severities in ascending order of gravity; anything at or above `major` needs no
|
|
95
|
+
// floor, and an unrecognised severity is treated as below it (fail-closed: an
|
|
96
|
+
// unknown label must not buy an exemption).
|
|
97
|
+
const AT_LEAST_MAJOR = new Set(['major', 'blocker']);
|
|
98
|
+
|
|
99
|
+
// Raise a runtime-dead finding to `major`, preserving what the worker reported.
|
|
100
|
+
// Returns a NEW array when anything changed, the input array otherwise, so a
|
|
101
|
+
// caller can cheaply tell whether the floor fired.
|
|
102
|
+
function applyDeadFeatureFloor(issues) {
|
|
103
|
+
if (!Array.isArray(issues)) return issues;
|
|
104
|
+
let changed = false;
|
|
105
|
+
const out = issues.map((it) => {
|
|
106
|
+
const claim = issueClaimsRuntimeDead(it);
|
|
107
|
+
if (!claim) return it;
|
|
108
|
+
if (AT_LEAST_MAJOR.has(String(it.severity || '').toLowerCase())) return it;
|
|
109
|
+
changed = true;
|
|
110
|
+
return {
|
|
111
|
+
...it,
|
|
112
|
+
severity: 'major',
|
|
113
|
+
severity_reported: it.severity,
|
|
114
|
+
severity_floor: 'runtime_dead',
|
|
115
|
+
severity_floor_claim: claim,
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
return changed ? out : issues;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = { deadFeatureClaim, issueClaimsRuntimeDead, applyDeadFeatureFloor };
|
|
@@ -14,6 +14,7 @@ const fs = require('node:fs');
|
|
|
14
14
|
const { DEFAULT_GRADER_MODEL, FALLTHROUGH_CONFIDENCE, LOW_CONFIDENCE, THRESHOLD, VERSION, permissionPathCheck, routeRankCheck } = require('./grader-config.js');
|
|
15
15
|
const { TRIVIAL_MAX_FILES, TRIVIAL_MAX_LINES, clamp, median, r1 } = require('./grader-model.js');
|
|
16
16
|
const { extractJson } = require('./grader-prompt.js');
|
|
17
|
+
const { issueClaimsRuntimeDead } = require('./dead-feature-claim.js');
|
|
17
18
|
|
|
18
19
|
function isNum(n) {
|
|
19
20
|
return Number.isFinite(n);
|
|
@@ -480,6 +481,35 @@ function serverSideGuards(gradeResult, { committedFiles, builderRank, diffStats,
|
|
|
480
481
|
implausiblePassWaived = 'panel_evidence';
|
|
481
482
|
}
|
|
482
483
|
|
|
484
|
+
// --- Guard 2b: a finding that says the change does not run (task 1002665) ---
|
|
485
|
+
// On the task 1002648 ship the Quality worker wrote that the new code path was
|
|
486
|
+
// "entirely non-operational", filed it at `nit`, and the grade PASSed — the
|
|
487
|
+
// dead feature merged to main and was caught only because the builder happened
|
|
488
|
+
// to read the finding prose. Severity alone cannot fix that, because severity
|
|
489
|
+
// has no vote here: `passed` is computed from the rubric SCORES, so a major or
|
|
490
|
+
// blocker issue does not stop a passing grade by itself. This is the vote.
|
|
491
|
+
//
|
|
492
|
+
// Re-derived server-side from the submitted summaries rather than trusting the
|
|
493
|
+
// client's `severity_floor` marker — same posture as Guard 1, and the worker's
|
|
494
|
+
// floor is the honest-panel half of the same rule.
|
|
495
|
+
//
|
|
496
|
+
// It downgrades to CONCERNS, not fail: the classifier reads a model's prose, so
|
|
497
|
+
// a false positive should cost a human glance through manual-confirm rather
|
|
498
|
+
// than a hard FAIL and a paid re-grade. That is still enough to close the hole
|
|
499
|
+
// — 1002648 merged automatically, and a concerns verdict does not.
|
|
500
|
+
const deadClaims = (base.issues || [])
|
|
501
|
+
.map((it) => ({ it, claim: issueClaimsRuntimeDead(it) }))
|
|
502
|
+
.filter((x) => x.claim);
|
|
503
|
+
if (clientPassed && deadClaims.length) {
|
|
504
|
+
const { it, claim } = deadClaims[0];
|
|
505
|
+
const where = it.file ? ` in ${it.file}` : '';
|
|
506
|
+
guardsTripped.push({ guard: 'runtime_dead_finding', severity: 'major',
|
|
507
|
+
summary: `a ${it.severity_reported || it.severity || '?'}-severity finding${where} asserts the change does not run ("${claim}") — a passing grade over a self-reported dead feature must be human-confirmed` });
|
|
508
|
+
extraIssues.push({ severity: 'major', file: it.file || '(grade)', worker: 'server-runtime-dead', summary:
|
|
509
|
+
`[server runtime-dead] the panel's own words say this does not run ("${claim}"); severity was reported as ${it.severity_reported || it.severity || '?'}` });
|
|
510
|
+
if (downgradeTo !== 'fail') downgradeTo = 'concerns';
|
|
511
|
+
}
|
|
512
|
+
|
|
483
513
|
// --- Guard 3: bound the credit/achievement signals (SR-18) ---
|
|
484
514
|
// The net-negative "Subtractor" bonus + shipper badges key off client-supplied
|
|
485
515
|
// signals.diff_stats + committed_files the server can't re-derive. We can't
|
|
@@ -517,7 +547,7 @@ function serverSideGuards(gradeResult, { committedFiles, builderRank, diffStats,
|
|
|
517
547
|
signals: {
|
|
518
548
|
...(base.signals || {}),
|
|
519
549
|
server_guard: {
|
|
520
|
-
applied: ['route_rank', 'permission_path', 'permission_path_unprovable', 'implausible_pass', 'forged_net_negative'],
|
|
550
|
+
applied: ['route_rank', 'permission_path', 'permission_path_unprovable', 'implausible_pass', 'runtime_dead_finding', 'forged_net_negative'],
|
|
521
551
|
ok: true,
|
|
522
552
|
tripped: [],
|
|
523
553
|
// task 1002658 / ADR 0164: the audit trail records WHY a zero-issue
|
|
@@ -556,7 +586,7 @@ function serverSideGuards(gradeResult, { committedFiles, builderRank, diffStats,
|
|
|
556
586
|
// body already ran; this is the authoritative post-hoc adjustment).
|
|
557
587
|
confidence: forwardedConfidence,
|
|
558
588
|
server_guard: {
|
|
559
|
-
applied: ['route_rank', 'permission_path', 'permission_path_unprovable', 'implausible_pass', 'forged_net_negative'],
|
|
589
|
+
applied: ['route_rank', 'permission_path', 'permission_path_unprovable', 'implausible_pass', 'runtime_dead_finding', 'forged_net_negative'],
|
|
560
590
|
ok: guardsTripped.length === 0,
|
|
561
591
|
downgraded_to: downgradeTo,
|
|
562
592
|
client_passed: clientPassed,
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
// (the audit's explicit warning). Behavior-neutral: the objects produced here
|
|
10
10
|
// are identical (same keys, same order, same values) to the inlined copies.
|
|
11
11
|
|
|
12
|
+
const { applyDeadFeatureFloor } = require('../dead-feature-claim.js');
|
|
13
|
+
|
|
12
14
|
// The { verdict:'fail', scores:{}, confidence:0, ... } shape every error
|
|
13
15
|
// short-circuit returns. `issues` defaults to [] (the subprocess_error and
|
|
14
16
|
// parse_error paths) and is the parsed issues array on the schema_error path
|
|
@@ -19,13 +21,21 @@ function failResult({ notesMd, error, issues = [] }) {
|
|
|
19
21
|
|
|
20
22
|
// The three identical extractions every worker does after the parse guard:
|
|
21
23
|
// confidence (null-tolerant), the bounded issues array, and the subagent notes.
|
|
24
|
+
//
|
|
25
|
+
// The issues array also passes the runtime-dead floor (task 1002665): a worker
|
|
26
|
+
// that writes "the feature is entirely non-operational" and files it at `nit`
|
|
27
|
+
// has contradicted itself, and on the task 1002648 ship that contradiction is
|
|
28
|
+
// what let a dead feature merge. Raising it HERE, at the one place all four
|
|
29
|
+
// workers parse their own output, means the worker's own downstream logic —
|
|
30
|
+
// applySeverityGatePolicy below, which asks whether a fail is backed by a
|
|
31
|
+
// major/blocker — sees the corrected severity rather than the reported one.
|
|
22
32
|
function parseCommon(parsed) {
|
|
23
33
|
const conf = parsed.confidence == null
|
|
24
34
|
? null
|
|
25
35
|
: Number.isFinite(Number(parsed.confidence)) ? Number(parsed.confidence) : null;
|
|
26
|
-
const issues = Array.isArray(parsed.issues)
|
|
36
|
+
const issues = applyDeadFeatureFloor(Array.isArray(parsed.issues)
|
|
27
37
|
? parsed.issues.filter((i) => i && typeof i === 'object').slice(0, 50)
|
|
28
|
-
: [];
|
|
38
|
+
: []);
|
|
29
39
|
const notesFromSubagent = typeof parsed.notes_md === 'string' ? parsed.notes_md : '';
|
|
30
40
|
return { conf, issues, notesFromSubagent };
|
|
31
41
|
}
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.733",
|
|
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.733",
|
|
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.733",
|
|
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",
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
|
|
21
21
|
const fs = require('node:fs');
|
|
22
22
|
const path = require('node:path');
|
|
23
|
-
const { execFileSync } = require('node:child_process');
|
|
24
23
|
|
|
25
24
|
// --- comment stripping (so rank/status literals in prose don't read as wiring) ---
|
|
26
25
|
// Line-based on purpose: a regex block-comment strip over concatenated files can
|
|
@@ -232,25 +231,36 @@ function seededAchievementCount(migs) {
|
|
|
232
231
|
return ids.size;
|
|
233
232
|
}
|
|
234
233
|
|
|
235
|
-
// Repo slug ("
|
|
236
|
-
//
|
|
237
|
-
// (
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
234
|
+
// Repo slug ("github.com/owner/repo") for 04's REPO node.
|
|
235
|
+
//
|
|
236
|
+
// Sourced from the BRANDING CONTRACT (`repo: { owner, name }`), never from the
|
|
237
|
+
// git `origin` remote. Reading git was the original design (task #357) and it
|
|
238
|
+
// did satisfy the rule it was written for — keep builder identity out of
|
|
239
|
+
// implementation code (audit-authorship.sh) — but it only moved the identity
|
|
240
|
+
// from the source into the GENERATED file, which is committed AND published.
|
|
241
|
+
// The portable core carries no branding.json, so `origin` there is always some
|
|
242
|
+
// real person's fork and every regeneration wrote that login into
|
|
243
|
+
// docs/onboarding/diagrams/04-architecture.mmd and shipped it in the core
|
|
244
|
+
// package. That is task 1003911: the one leak the task 1003869 sweep could not
|
|
245
|
+
// hold, because a generator puts it back the moment anyone regenerates.
|
|
246
|
+
//
|
|
247
|
+
// Branding fails to neutral by construction, which is the whole point: an unset
|
|
248
|
+
// `repo` yields the placeholder, so the portable core renders a placeholder and
|
|
249
|
+
// a host instance that declares its own repo renders its own. Every other
|
|
250
|
+
// identity string in these diagrams already follows exactly this rule — see
|
|
251
|
+
// `brand()` in gen-diagrams.js, whose header states it for the currency, the
|
|
252
|
+
// world/product name and the public host.
|
|
253
|
+
function repoSlug() {
|
|
254
|
+
let b = {};
|
|
255
|
+
try { b = require('../../src/branding').branding() || {}; } catch (_) { /* fail-to-neutral */ }
|
|
256
|
+
const r = (b && b.repo) || {};
|
|
257
|
+
const owner = typeof r.owner === 'string' ? r.owner.trim() : '';
|
|
258
|
+
const name = typeof r.name === 'string' ? r.name.trim() : '';
|
|
259
|
+
// Both halves or neither — a half-filled pack must not render "owner/".
|
|
260
|
+
// null, not a placeholder string: the CALLER owns the prose, so a neutral
|
|
261
|
+
// instance can drop the clause entirely instead of rendering a parenthetical
|
|
262
|
+
// that restates the label it sits inside.
|
|
263
|
+
return owner && name ? `github.com/${owner}/${name}` : null;
|
|
254
264
|
}
|
|
255
265
|
|
|
256
266
|
// An instance that IMPORTED the origin backlog renumbers the origin tasks by a
|
|
@@ -315,7 +325,7 @@ async function conceptualFacts({ repo, migs, api }) {
|
|
|
315
325
|
taskStatus: status,
|
|
316
326
|
taskLabels: { ...ALL_TASK_IDS },
|
|
317
327
|
achievementsSeeded: seededAchievementCount(migs),
|
|
318
|
-
repoSlug: repoSlug(
|
|
328
|
+
repoSlug: repoSlug(),
|
|
319
329
|
};
|
|
320
330
|
}
|
|
321
331
|
|
|
@@ -444,7 +444,7 @@ function architectureMmd(f) {
|
|
|
444
444
|
' DEPLOY["auto-pull deploy (ADR 0129)\\na timer pulls THIS instance repo every ~2 min →\\nnpm ci if the pin changed → migrate → regenerate docs → restart → healthz"]',
|
|
445
445
|
' end',
|
|
446
446
|
'',
|
|
447
|
-
` subgraph GH["GITHUB — the instance's own repo (${c.repoSlug})"]`,
|
|
447
|
+
` subgraph GH["GITHUB — the instance's own repo${c.repoSlug ? ` (${c.repoSlug})` : ''}"]`,
|
|
448
448
|
' MAIN["branch-protected main\\n(no direct push; PR + required checks only)\\nholds config + the pinned @bongos/core — NOT the platform source"]',
|
|
449
449
|
' end',
|
|
450
450
|
'',
|
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.733'; // 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');
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// tests/dead_feature_claim.mjs — the runtime-dead severity floor (task 1002665).
|
|
2
|
+
//
|
|
3
|
+
// Evidence: on the task 1002648 ship the Quality worker found that the new PATCH
|
|
4
|
+
// needs_migration path threw at runtime, wrote "the feature is entirely
|
|
5
|
+
// non-operational", filed it at severity `nit`, and the grade PASSed. The dead
|
|
6
|
+
// feature merged to main; it was caught only because the builder read the prose.
|
|
7
|
+
//
|
|
8
|
+
// Two halves are proved here. The FLOOR raises such a finding to `major` so the
|
|
9
|
+
// worker's own logic and every reader see the severity its own words imply. The
|
|
10
|
+
// CLASSIFIER is deliberately narrow, and most of these cases exist to prove what
|
|
11
|
+
// it does NOT match — a false positive costs a builder a manual-confirm round,
|
|
12
|
+
// so conditional defects, praise for deleted code, and the ambiguous word
|
|
13
|
+
// "unreachable" all have to stay out.
|
|
14
|
+
//
|
|
15
|
+
// Run: node tests/dead_feature_claim.mjs
|
|
16
|
+
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
const { deadFeatureClaim, issueClaimsRuntimeDead, applyDeadFeatureFloor } = require('../modules/grading/dead-feature-claim.js');
|
|
21
|
+
const { parseCommon, applySeverityGatePolicy } = require('../modules/grading/grader-workers/worker-verdict.js');
|
|
22
|
+
|
|
23
|
+
let passed = 0;
|
|
24
|
+
let failed = 0;
|
|
25
|
+
function test(name, fn) {
|
|
26
|
+
try { fn(); passed++; console.log(` ok ${name}`); }
|
|
27
|
+
catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.message}`); }
|
|
28
|
+
}
|
|
29
|
+
function eq(actual, expected, msg) {
|
|
30
|
+
if (actual !== expected) throw new Error(`${msg || 'mismatch'}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
31
|
+
}
|
|
32
|
+
function truthy(v, msg) { if (!v) throw new Error(msg || `expected truthy, got ${JSON.stringify(v)}`); }
|
|
33
|
+
function falsy(v, msg) { if (v) throw new Error(msg || `expected falsy, got ${JSON.stringify(v)}`); }
|
|
34
|
+
|
|
35
|
+
// ---- the claim classifier: what it MUST catch ----------------------------
|
|
36
|
+
|
|
37
|
+
test('the exact 1002648 wording is a dead-feature claim', () => {
|
|
38
|
+
truthy(deadFeatureClaim('TASK_SETTER_COLUMNS lacks needs_migration, so the feature is entirely non-operational'));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('non-operational, with or without the hyphen', () => {
|
|
42
|
+
truthy(deadFeatureClaim('this path is nonoperational'));
|
|
43
|
+
truthy(deadFeatureClaim('this path is non-operational'));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('always-throws and never-runs claims', () => {
|
|
47
|
+
truthy(deadFeatureClaim('the new helper always throws because the key is absent'));
|
|
48
|
+
truthy(deadFeatureClaim('this branch never executes'));
|
|
49
|
+
truthy(deadFeatureClaim('the handler can never run — nothing dispatches to it'));
|
|
50
|
+
truthy(deadFeatureClaim('the route throws at runtime on the first call'));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('completely/entirely broken and dead on arrival', () => {
|
|
54
|
+
truthy(deadFeatureClaim('the migration is completely broken'));
|
|
55
|
+
truthy(deadFeatureClaim('this endpoint is dead on arrival'));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('the claim is returned, not just a boolean, so the audit trail can quote it', () => {
|
|
59
|
+
eq(deadFeatureClaim('the feature is entirely non-operational'), 'non-operational');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ---- the claim classifier: what it MUST NOT catch ------------------------
|
|
63
|
+
|
|
64
|
+
test('a CONDITIONAL defect is not a dead-feature claim — the scores judge those', () => {
|
|
65
|
+
falsy(deadFeatureClaim('this would always throw if the id were null'));
|
|
66
|
+
falsy(deadFeatureClaim('if the cache is cold the loader never runs'));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('praise for what the diff REMOVED is not a dead-feature claim', () => {
|
|
70
|
+
falsy(deadFeatureClaim('the old branch used to always throw; this fixes it'));
|
|
71
|
+
falsy(deadFeatureClaim('this path is no longer non-operational'));
|
|
72
|
+
falsy(deadFeatureClaim('previously the handler never executed'));
|
|
73
|
+
falsy(deadFeatureClaim('before this change the route was completely broken'));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('"unreachable" is NOT matched — a reviewer writes it to praise a dead-code deletion', () => {
|
|
77
|
+
falsy(deadFeatureClaim('the old fallback is now unreachable and was deleted'));
|
|
78
|
+
falsy(deadFeatureClaim('this line is unreachable'));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('empty and non-string input are safe', () => {
|
|
82
|
+
falsy(deadFeatureClaim(''));
|
|
83
|
+
falsy(deadFeatureClaim(null));
|
|
84
|
+
falsy(deadFeatureClaim(undefined));
|
|
85
|
+
falsy(deadFeatureClaim({ summary: 'always throws' }));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ---- issueClaimsRuntimeDead reads the SUMMARY only -----------------------
|
|
89
|
+
|
|
90
|
+
test('only the summary is read — a file path with an unlucky name never raises a severity', () => {
|
|
91
|
+
falsy(issueClaimsRuntimeDead({ severity: 'nit', file: 'tests/always-throws.test.js', summary: 'rename this' }));
|
|
92
|
+
truthy(issueClaimsRuntimeDead({ severity: 'nit', file: 'x.js', summary: 'the setter always throws' }));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('a malformed issue entry is not a claim', () => {
|
|
96
|
+
falsy(issueClaimsRuntimeDead(null));
|
|
97
|
+
falsy(issueClaimsRuntimeDead('always throws'));
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ---- the floor -----------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
test('a nit that asserts the feature is dead is raised to major', () => {
|
|
103
|
+
const out = applyDeadFeatureFloor([{ severity: 'nit', file: 'db.js', summary: 'the feature is entirely non-operational' }]);
|
|
104
|
+
eq(out[0].severity, 'major');
|
|
105
|
+
eq(out[0].severity_reported, 'nit', 'what the worker actually said is preserved');
|
|
106
|
+
eq(out[0].severity_floor, 'runtime_dead');
|
|
107
|
+
eq(out[0].severity_floor_claim, 'non-operational');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('minor is raised too; major and blocker are left exactly as reported', () => {
|
|
111
|
+
const out = applyDeadFeatureFloor([
|
|
112
|
+
{ severity: 'minor', summary: 'always throws' },
|
|
113
|
+
{ severity: 'major', summary: 'always throws' },
|
|
114
|
+
{ severity: 'blocker', summary: 'always throws' },
|
|
115
|
+
]);
|
|
116
|
+
eq(out[0].severity, 'major');
|
|
117
|
+
eq(out[0].severity_floor, 'runtime_dead');
|
|
118
|
+
eq(out[1].severity_floor, undefined, 'an already-major finding is not re-stamped');
|
|
119
|
+
eq(out[2].severity, 'blocker', 'a blocker is never lowered to major');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('an UNKNOWN severity is raised — an unrecognised label must not buy an exemption', () => {
|
|
123
|
+
const out = applyDeadFeatureFloor([{ severity: 'trivial', summary: 'always throws' }]);
|
|
124
|
+
eq(out[0].severity, 'major');
|
|
125
|
+
eq(out[0].severity_reported, 'trivial');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('findings with no dead-feature claim pass through untouched, same array', () => {
|
|
129
|
+
const input = [{ severity: 'nit', summary: 'comment style' }];
|
|
130
|
+
eq(applyDeadFeatureFloor(input), input, 'the input array itself is returned when nothing changed');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('the floor does not mutate the caller\'s objects', () => {
|
|
134
|
+
const it = { severity: 'nit', summary: 'always throws' };
|
|
135
|
+
applyDeadFeatureFloor([it]);
|
|
136
|
+
eq(it.severity, 'nit', 'the original entry is untouched');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('a non-array is returned as-is rather than throwing', () => {
|
|
140
|
+
eq(applyDeadFeatureFloor(null), null);
|
|
141
|
+
eq(applyDeadFeatureFloor(undefined), undefined);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ---- the wiring: every worker parses through parseCommon -----------------
|
|
145
|
+
//
|
|
146
|
+
// parseCommon is the one place all four workers turn their own JSON into issues,
|
|
147
|
+
// so the floor belongs there. Without this test the floor could be unwired from
|
|
148
|
+
// parseCommon and every unit above would still pass.
|
|
149
|
+
|
|
150
|
+
test('parseCommon applies the floor, so all four workers get it', () => {
|
|
151
|
+
const { issues } = parseCommon({ issues: [{ severity: 'nit', file: 'db.js', summary: 'the feature is entirely non-operational' }] });
|
|
152
|
+
eq(issues[0].severity, 'major');
|
|
153
|
+
eq(issues[0].severity_floor, 'runtime_dead');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('parseCommon still bounds and filters issues[] exactly as before', () => {
|
|
157
|
+
const { issues } = parseCommon({ issues: [null, 'junk', { severity: 'nit', summary: 'fine' }] });
|
|
158
|
+
eq(issues.length, 1, 'non-object entries are dropped');
|
|
159
|
+
eq(parseCommon({ issues: Array.from({ length: 80 }, () => ({ severity: 'nit', summary: 'x' })) }).issues.length, 50,
|
|
160
|
+
'the 50-entry bound is unchanged');
|
|
161
|
+
eq(parseCommon({ issues: 'not an array' }).issues.length, 0);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('the raised severity reaches the worker\'s own severity gate: a dead-feature fail is no longer downgraded', () => {
|
|
165
|
+
const { issues } = parseCommon({ issues: [{ severity: 'nit', file: 'db.js', summary: 'the new setter always throws' }] });
|
|
166
|
+
const result = applySeverityGatePolicy(
|
|
167
|
+
{ verdict: 'fail', scores: { code_quality: 4 }, issues, notes_md: '' },
|
|
168
|
+
{ threshold: 6 }
|
|
169
|
+
);
|
|
170
|
+
eq(result.verdict, 'fail', 'a fail backed by a floor-raised finding stays a fail');
|
|
171
|
+
eq(result.policy_downgrade, undefined);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
console.log('');
|
|
175
|
+
console.log(`dead_feature_claim: ${passed} passed, ${failed} failed`);
|
|
176
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// tests/diagram_repo_slug.mjs
|
|
2
|
+
//
|
|
3
|
+
// Task 1003911 — the onboarding architecture diagram must not carry the live
|
|
4
|
+
// instance's GitHub owner into published output.
|
|
5
|
+
//
|
|
6
|
+
// The bug: diagram-facts.repoSlug() derived the slug from the git `origin`
|
|
7
|
+
// remote. That satisfied the rule it was written for (no builder identity
|
|
8
|
+
// hardcoded in implementation code — audit-authorship.sh) but only moved the
|
|
9
|
+
// identity from the SOURCE into the GENERATED file, which is committed and
|
|
10
|
+
// shipped in the core package. Because it is a generator, hand-sweeping the
|
|
11
|
+
// .mmd achieved nothing: the next regeneration put the owner login straight
|
|
12
|
+
// back, which is how it survived the task 1003869 sweep and failed the leak
|
|
13
|
+
// scan on landed main.
|
|
14
|
+
//
|
|
15
|
+
// The fix routes the slug through the branding contract (`repo: {owner,name}`),
|
|
16
|
+
// which fails to neutral by construction — the same rule the currency, the
|
|
17
|
+
// world/product name and the public host already follow.
|
|
18
|
+
//
|
|
19
|
+
// NOTE ON METHOD: this file never writes a real owner login down. It reads the
|
|
20
|
+
// live `origin` at run time and asserts the output does not contain it. That is
|
|
21
|
+
// the self-leak invariant mirror-redact.js holds about itself, and the trap this
|
|
22
|
+
// sweep fell into twice — a test that pins "X must not leak" by spelling X out
|
|
23
|
+
// puts X back into the very tree it is guarding.
|
|
24
|
+
//
|
|
25
|
+
// Run: node tests/diagram_repo_slug.mjs
|
|
26
|
+
|
|
27
|
+
import { strict as assert } from 'node:assert';
|
|
28
|
+
import { execFileSync } from 'node:child_process';
|
|
29
|
+
import fs from 'node:fs';
|
|
30
|
+
import os from 'node:os';
|
|
31
|
+
import path from 'node:path';
|
|
32
|
+
|
|
33
|
+
let passed = 0;
|
|
34
|
+
let failed = 0;
|
|
35
|
+
function test(name, fn) {
|
|
36
|
+
try {
|
|
37
|
+
fn();
|
|
38
|
+
passed++;
|
|
39
|
+
console.log(` ok ${name}`);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
failed++;
|
|
42
|
+
console.log(` FAIL ${name}`);
|
|
43
|
+
console.log(` ${err.message}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const REPO = process.cwd();
|
|
48
|
+
|
|
49
|
+
// branding() memoizes on first call, so each pack needs its own process.
|
|
50
|
+
function slugWith(pack) {
|
|
51
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'repo-slug-'));
|
|
52
|
+
const file = path.join(tmp, 'branding.json');
|
|
53
|
+
fs.writeFileSync(file, JSON.stringify(pack));
|
|
54
|
+
try {
|
|
55
|
+
const out = execFileSync(
|
|
56
|
+
process.execPath,
|
|
57
|
+
['-e', 'process.stdout.write(JSON.stringify(require("./scripts/gds/diagram-facts.js").repoSlug()))'],
|
|
58
|
+
{ cwd: REPO, encoding: 'utf8', env: { ...process.env, BONGOS_BRANDING_FILE: file } },
|
|
59
|
+
);
|
|
60
|
+
return JSON.parse(out);
|
|
61
|
+
} finally {
|
|
62
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The live owner, read at run time and never written down. If there is no
|
|
67
|
+
// origin remote (a CI checkout without one), the guards that need it skip.
|
|
68
|
+
function liveOwner() {
|
|
69
|
+
try {
|
|
70
|
+
const url = execFileSync('git', ['-C', REPO, 'remote', 'get-url', 'origin'], {
|
|
71
|
+
encoding: 'utf8',
|
|
72
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
73
|
+
}).trim();
|
|
74
|
+
const m = url.match(/[:/]([^/:]+)\/[^/]+?(?:\.git)?$/);
|
|
75
|
+
return m ? m[1] : null;
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---- the branding contract drives it, and fails to neutral ----
|
|
82
|
+
|
|
83
|
+
test('an unset repo yields null, so the caller can drop the clause entirely', () => {
|
|
84
|
+
assert.equal(slugWith({ repo: { owner: '', name: '' } }), null);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('a pack with no repo key at all is still neutral, not a crash', () => {
|
|
88
|
+
assert.equal(slugWith({}), null);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('a declared repo renders that instance own slug', () => {
|
|
92
|
+
assert.equal(slugWith({ repo: { owner: 'octocat', name: 'hello-world' } }), 'github.com/octocat/hello-world');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('a half-filled pack renders nothing, never a dangling owner/', () => {
|
|
96
|
+
assert.equal(slugWith({ repo: { owner: 'octocat', name: '' } }), null);
|
|
97
|
+
assert.equal(slugWith({ repo: { owner: '', name: 'hello-world' } }), null);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('whitespace is not a declaration', () => {
|
|
101
|
+
assert.equal(slugWith({ repo: { owner: ' ', name: ' ' } }), null);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ---- the regression guard: reverting to git-derivation must FAIL here ----
|
|
105
|
+
//
|
|
106
|
+
// Every assertion above passes if repoSlug() goes back to reading `origin`,
|
|
107
|
+
// because those packs are neutral and a git fallback would only fire when the
|
|
108
|
+
// pack is empty. This is the one that catches it.
|
|
109
|
+
|
|
110
|
+
test('a neutral pack does NOT fall back to the git origin owner', () => {
|
|
111
|
+
const owner = liveOwner();
|
|
112
|
+
if (!owner) return; // no remote in this checkout; nothing to leak
|
|
113
|
+
const got = slugWith({ repo: { owner: '', name: '' } });
|
|
114
|
+
assert.equal(got, null, 'neutral branding must render no slug at all');
|
|
115
|
+
assert.doesNotMatch(String(got), new RegExp(owner, 'i'), 'the git origin owner reached the diagram facts');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('a declared repo wins over the git origin, it is not merely a fallback', () => {
|
|
119
|
+
const owner = liveOwner();
|
|
120
|
+
if (!owner) return;
|
|
121
|
+
const got = slugWith({ repo: { owner: 'octocat', name: 'hello-world' } });
|
|
122
|
+
assert.equal(got, 'github.com/octocat/hello-world');
|
|
123
|
+
assert.doesNotMatch(got, new RegExp(owner, 'i'));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ---- the committed artifact, which is what actually ships ----
|
|
127
|
+
|
|
128
|
+
test('the committed 04-architecture.mmd carries no owner login', () => {
|
|
129
|
+
const owner = liveOwner();
|
|
130
|
+
if (!owner) return;
|
|
131
|
+
const mmd = path.join(REPO, 'docs/onboarding/diagrams/04-architecture.mmd');
|
|
132
|
+
if (!fs.existsSync(mmd)) return;
|
|
133
|
+
const text = fs.readFileSync(mmd, 'utf8');
|
|
134
|
+
assert.doesNotMatch(
|
|
135
|
+
text,
|
|
136
|
+
new RegExp(owner, 'i'),
|
|
137
|
+
'the published diagram names the live owner — regenerate it after fixing the generator',
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
console.log(`\ndiagram_repo_slug: ${passed} passed, ${failed} failed`);
|
|
142
|
+
process.exit(failed ? 1 : 0);
|
|
@@ -478,6 +478,63 @@ await test('applyGrade on a server-downgraded (passed=false) grade awards no cre
|
|
|
478
478
|
assert.ok(!queries.some((q) => /INSERT INTO credit_log/.test(q.sql)), 'no credit_log insert');
|
|
479
479
|
});
|
|
480
480
|
|
|
481
|
+
// ========================================================================
|
|
482
|
+
// Guard 2b — a finding whose own words say the change does not run (task 1002665).
|
|
483
|
+
//
|
|
484
|
+
// The 1002648 regression, reconstructed: the Quality worker wrote "the feature is
|
|
485
|
+
// entirely non-operational", filed it at `nit`, the rubric scores were high, and
|
|
486
|
+
// the grade PASSed straight to a merge. Note WHY a severity floor alone could not
|
|
487
|
+
// have stopped it — `passed` is computed from the scores, so even at `major` the
|
|
488
|
+
// grade still passes. This guard is the vote severity never had.
|
|
489
|
+
// ========================================================================
|
|
490
|
+
|
|
491
|
+
await test('a nit-severity finding that says the change does not run downgrades a PASS', () => {
|
|
492
|
+
const g = grader.serverSideGuards(
|
|
493
|
+
clientGrade({ issues: [{ severity: 'nit', file: 'src/bongos/db.js', summary: 'TASK_SETTER_COLUMNS lacks needs_migration, so the feature is entirely non-operational' }] }),
|
|
494
|
+
{ committedFiles: ['src/bongos/db.js'], builderRank: 'metic', diffStats: { files_changed: 2, lines_added: 40, lines_removed: 3 } }
|
|
495
|
+
);
|
|
496
|
+
assert.equal(g.passed, false, 'the 1002648 grade must no longer pass');
|
|
497
|
+
assert.equal(g.signals.server_guard.downgraded_to, 'concerns',
|
|
498
|
+
'a prose heuristic downgrades to manual-confirm, not a hard FAIL');
|
|
499
|
+
assert.ok(g.signals.server_guard.tripped.some((t) => t.guard === 'runtime_dead_finding'),
|
|
500
|
+
'the guard names itself in the audit trail');
|
|
501
|
+
assert.ok(g.issues.some((i) => i.worker === 'server-runtime-dead'),
|
|
502
|
+
'the finding is surfaced so the builder sees why');
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
await test('high rubric scores do NOT rescue it — that is the hole severity alone left open', () => {
|
|
506
|
+
const g = grader.serverSideGuards(
|
|
507
|
+
clientGrade({ ff: 10, cq: 10, issues: [{ severity: 'nit', file: 'x.js', summary: 'the new handler always throws' }] }),
|
|
508
|
+
{ committedFiles: ['x.js'], builderRank: 'metic', diffStats: { files_changed: 1, lines_added: 20, lines_removed: 0 } }
|
|
509
|
+
);
|
|
510
|
+
assert.equal(g.passed, false, 'a 10/10 grade over a self-reported dead feature must still stop');
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
await test('an ordinary conditional finding is untouched — no false block', () => {
|
|
514
|
+
const g = grader.serverSideGuards(
|
|
515
|
+
clientGrade({ issues: [{ severity: 'nit', file: 'x.js', summary: 'this would throw if the id were null' }] }),
|
|
516
|
+
{ committedFiles: ['x.js'], builderRank: 'metic', diffStats: { files_changed: 1, lines_added: 20, lines_removed: 0 } }
|
|
517
|
+
);
|
|
518
|
+
assert.equal(g.passed, true, 'a conditional defect is ordinary review prose, not a dead-feature claim');
|
|
519
|
+
assert.equal(g.signals.server_guard.ok, true);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
await test('praise for deleted dead code is untouched — no false block', () => {
|
|
523
|
+
const g = grader.serverSideGuards(
|
|
524
|
+
clientGrade({ issues: [{ severity: 'nit', file: 'x.js', summary: 'the old fallback used to always throw; this removes it' }] }),
|
|
525
|
+
{ committedFiles: ['x.js'], builderRank: 'metic', diffStats: { files_changed: 1, lines_added: 5, lines_removed: 30 } }
|
|
526
|
+
);
|
|
527
|
+
assert.equal(g.passed, true, 'describing what the diff CURED must not be read as a dead feature');
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
await test('a grade that was ALREADY failing is not resurrected by the guard', () => {
|
|
531
|
+
const g = grader.serverSideGuards(
|
|
532
|
+
clientGrade({ ff: 2, cq: 2, issues: [{ severity: 'nit', file: 'x.js', summary: 'always throws' }] }),
|
|
533
|
+
{ committedFiles: ['x.js'], builderRank: 'metic', diffStats: { files_changed: 1, lines_added: 20, lines_removed: 0 } }
|
|
534
|
+
);
|
|
535
|
+
assert.equal(g.passed, false);
|
|
536
|
+
});
|
|
537
|
+
|
|
481
538
|
// ------------------------------------------------------------------------
|
|
482
539
|
process.on('exit', () => {
|
|
483
540
|
console.log(`\ngrade_server_authoritative: ${passed} passed, ${failed} failed`);
|