@bongos/core 1.19.732 → 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 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.732",
6
- "core_contract": "1.19.732",
7
- "source_commit": "05f95d3811b50b7da27f822578bed1bdaa6c678d",
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:41:18.185Z",
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": 2102,
14
+ "functional_verbatim": 2104,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2603,
20
- "tree_sha256": "ff43e4e917e23173177aea01ebf8266b85bab2ff188f67ab26f5577a47c3ffe3",
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": "6a3d0cfd51a5e932d65a8634f52f4a61e62f3501453b104e2ab5d2086860d9e1"
2485
+ "sha256": "05bbf0c615af2c986b0bad8963870a0420b3f0dee092bbbcbcaf9794c1f607a1"
2486
2486
  },
2487
2487
  {
2488
2488
  "path": "docs/modules-contract.md",
@@ -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": "387d518becd0cce38ecb15cb525084bcabfef04c138d6cae3eabaf3d928ddb5d"
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": "9c3abb069cfdee124b4fd5c521fe1c11b44177f7af941c9ab7bbb2a4a3718bab"
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": "dc33512a9aebd07ee5a82a6aff3d969080aa4c7f465a2f64b65f534502043f25"
7520
+ "sha256": "d991c248b006d2066c11468c5749e58951463ecab027b50991ff4ca28279f294"
7516
7521
  },
7517
7522
  {
7518
7523
  "path": "package.json",
7519
7524
  "mode": "0000644",
7520
- "sha256": "05b4e628ace671cc392fad7a333da1d0eea15d7f33350dd08f6bf4d21b48e972"
7525
+ "sha256": "ed94a0e7dc7bdd1e48ebd1f9521b9aeb93a8e0289e9928393cf49461fd46dafa"
7521
7526
  },
7522
7527
  {
7523
7528
  "path": "public-docs/index.html",
@@ -9317,7 +9322,7 @@
9317
9322
  {
9318
9323
  "path": "src/module-api.js",
9319
9324
  "mode": "0000644",
9320
- "sha256": "bd79c2971dd8196fe95345a663188120141c97b5b797147c81a48bad1c9ccc0e"
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",
@@ -10887,7 +10897,7 @@
10887
10897
  {
10888
10898
  "path": "tests/grade_server_authoritative.mjs",
10889
10899
  "mode": "0000644",
10890
- "sha256": "9482e5e458023b691f8e5971e232c6492d5e3e2772e927e1f6639f95808c8149"
10900
+ "sha256": "f27ea217e74d880296db9f78aa8ed3b97d1a4a7a71496015af5f5d3e3ad69f05"
10891
10901
  },
10892
10902
  {
10893
10903
  "path": "tests/grade_unavailable_display.mjs",
@@ -1938,5 +1938,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1938
1938
  landed since 1.19.730 with no explicit bump. run 34766029928. (task 1002620)
1939
1939
  1.19.732 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1940
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)
1941
1943
  ---------------------------------------------------------------------------
1942
1944
  ```
@@ -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.732",
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.732",
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.732",
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",
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.732'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
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);
@@ -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`);