@bongos/core 1.19.388 → 1.19.389

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.388",
6
- "core_contract": "1.19.388",
7
- "source_commit": "4458912ebde20d0f871c49406b22f782bb091d55",
5
+ "core_version": "1.19.389",
6
+ "core_contract": "1.19.389",
7
+ "source_commit": "6025dfdf6c2020fdf22152cb9edf610790cc83b0",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-27T23:15:30.043Z",
9
+ "built_at": "2026-08-27T23:26:50.061Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 339,
13
13
  "agent_docs_stubbed": 22,
14
- "functional_verbatim": 1619,
14
+ "functional_verbatim": 1620,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1980,
20
- "tree_sha256": "b54f206e331f024b81b8e2625069e9ba55b966cb845e06547fc11ac3e8faba85",
19
+ "file_count": 1981,
20
+ "tree_sha256": "1cb4d393dd04fb49bb9f7aaaea5ae98dc3b4d9ce595c3a00b3aa0d86c7db7c95",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -162,12 +162,12 @@
162
162
  {
163
163
  "path": ".claude/skills/grade-recover/SKILL.md",
164
164
  "mode": "0000644",
165
- "sha256": "e0bf90fa006499aee0a7d75f10fe4b5972c9501129b1e432e7d6e192b367a54c"
165
+ "sha256": "db66b9abfd36829c37885e14644a9f0c2e7025e32975a1346a1adb9816df681a"
166
166
  },
167
167
  {
168
168
  "path": ".claude/skills/grader-health/SKILL.md",
169
169
  "mode": "0000644",
170
- "sha256": "3268e1138e02245ddf16d11b072133168cf0f59ca0ea4656631babba5df7158c"
170
+ "sha256": "dcff580f864ed98f6f037d011d290a758aa76500214835df76bcf4e041445417"
171
171
  },
172
172
  {
173
173
  "path": ".claude/skills/idea-triage/SKILL.md",
@@ -1517,12 +1517,12 @@
1517
1517
  {
1518
1518
  "path": "docs/copy-inventory.md",
1519
1519
  "mode": "0000644",
1520
- "sha256": "17ead5156e550c0c2f1754a6296d578e05c4185ea3eb27c4a5c7900f757e2688"
1520
+ "sha256": "6b2b21a3b5812c9d344165441ffd7397f39cabe4a72f652bb977ad31edb9a65a"
1521
1521
  },
1522
1522
  {
1523
1523
  "path": "docs/copy-registry.json",
1524
1524
  "mode": "0000644",
1525
- "sha256": "a612166dc9f945ad0a7f32d8d13c86b1cc080f2214305d1408790f1a1ddbbe4d"
1525
+ "sha256": "460230106e7c700485a3e9f797eb96e78dca54365e9dcaf56d60b111a08c1008"
1526
1526
  },
1527
1527
  {
1528
1528
  "path": "docs/design-contract.md",
@@ -1792,7 +1792,7 @@
1792
1792
  {
1793
1793
  "path": "docs/file-map.md",
1794
1794
  "mode": "0000644",
1795
- "sha256": "d68a8b0fd3177afadb529f65e1953efecb66c4649161858b7d62a287bb1e1483"
1795
+ "sha256": "c717435fcd8d12a1073419666cdeeec1ae40d988789f2980413d2c22e1c45784"
1796
1796
  },
1797
1797
  {
1798
1798
  "path": "docs/handoff-template.md",
@@ -3492,7 +3492,7 @@
3492
3492
  {
3493
3493
  "path": "modules/grading/grader-config.js",
3494
3494
  "mode": "0000644",
3495
- "sha256": "125d5fab993fd4276eaf3df731bdf03afe25ef1032a8bec7c1bf5a16f781b73a"
3495
+ "sha256": "9f06f2521e00f675c74a09538d086f6cdf362dfbb08bbc96464b6db10c2d5d2b"
3496
3496
  },
3497
3497
  {
3498
3498
  "path": "modules/grading/grader-model.js",
@@ -3507,12 +3507,12 @@
3507
3507
  {
3508
3508
  "path": "modules/grading/grader-rubric.json",
3509
3509
  "mode": "0000644",
3510
- "sha256": "6a0a2205fee2a4b0efc422c884826a9fd2af3d8785b393d5f7123e1758f1ebfc"
3510
+ "sha256": "a1aad082fe64bd44eeb3c80a46f750861d078e03b29f1a0544aa18a069e532e5"
3511
3511
  },
3512
3512
  {
3513
3513
  "path": "modules/grading/grader-score.js",
3514
3514
  "mode": "0000644",
3515
- "sha256": "fe36685ad575e49d30b9251c9e781668473ca609ebc6be627c979adde76a2896"
3515
+ "sha256": "387d518becd0cce38ecb15cb525084bcabfef04c138d6cae3eabaf3d928ddb5d"
3516
3516
  },
3517
3517
  {
3518
3518
  "path": "modules/grading/grader-subagent.js",
@@ -3542,7 +3542,7 @@
3542
3542
  {
3543
3543
  "path": "modules/grading/grader-workers/index.js",
3544
3544
  "mode": "0000644",
3545
- "sha256": "186ebc1d6456f3d9d0435766744fd767005b667fc4f6020bb9041f6b66388c4b"
3545
+ "sha256": "c527d3dd996b4064ae14836e305de097edea2b1a3c46776d8ec7da8c2c6afe71"
3546
3546
  },
3547
3547
  {
3548
3548
  "path": "modules/grading/grader-workers/narc.js",
@@ -3627,7 +3627,7 @@
3627
3627
  {
3628
3628
  "path": "modules/hall-ui/public/builders.js",
3629
3629
  "mode": "0000644",
3630
- "sha256": "22f1d7714524e07397354507df61ccd4bcaf7c1779317945d1ba4cb45fca7042"
3630
+ "sha256": "e8a0e98de75cab1f595f7541c0732b032ea0060e1b56526c52f352fcbd4a26a3"
3631
3631
  },
3632
3632
  {
3633
3633
  "path": "modules/hall-ui/public/chime.wav",
@@ -5122,12 +5122,12 @@
5122
5122
  {
5123
5123
  "path": "package-lock.json",
5124
5124
  "mode": "0000644",
5125
- "sha256": "11102606096b81deac5e3d59392a487905e92010e257cbaf7136a0fe059dbaa8"
5125
+ "sha256": "eea9d3bbfd5303579b774edd548952fb8faa1b88acc2d21a1091409a247ec3f0"
5126
5126
  },
5127
5127
  {
5128
5128
  "path": "package.json",
5129
5129
  "mode": "0000644",
5130
- "sha256": "0c4a59120f7f038cf1e01f2bc30cb083e5ac7e445a51c83961caf4c551c0c7fa"
5130
+ "sha256": "f012bfc1226d3d1d3dbe511bdb4c734f2e6731c37f220126f773b88ef3303a8c"
5131
5131
  },
5132
5132
  {
5133
5133
  "path": "public-docs/index.html",
@@ -6547,7 +6547,7 @@
6547
6547
  {
6548
6548
  "path": "scripts/gds/ship-grade-steps.js",
6549
6549
  "mode": "0000644",
6550
- "sha256": "e68f4c2848cb59878cd0b96b48fdd075db4a035354d51afef52aa6225f7895e5"
6550
+ "sha256": "f47c4a0d6a6dfdc386e3b7e1da456c0c983b727672646cb1d033d8b11c03f5f0"
6551
6551
  },
6552
6552
  {
6553
6553
  "path": "scripts/gds/ship-grade.js",
@@ -7107,7 +7107,7 @@
7107
7107
  {
7108
7108
  "path": "src/module-api.js",
7109
7109
  "mode": "0000644",
7110
- "sha256": "46c4b0ff4b30df23292a087eb1c4678da2706bb8f89ff0c1025b86b6ff35aadc"
7110
+ "sha256": "6daab593739c8b3f37576399b8ba4cdcb6fdf82f8ce914ce1cdb4322ac8d1f24"
7111
7111
  },
7112
7112
  {
7113
7113
  "path": "src/module-loader/catalog.js",
@@ -8387,7 +8387,7 @@
8387
8387
  {
8388
8388
  "path": "tests/grader.mjs",
8389
8389
  "mode": "0000644",
8390
- "sha256": "e97bbe1f1d9bc1b602adbae26e5144675a9324c5b729d0046ffa43407a88922c"
8390
+ "sha256": "fd1ba73b702d61774275e8a7d4af7a8f14173505d98e966744d17a17321c49e0"
8391
8391
  },
8392
8392
  {
8393
8393
  "path": "tests/grader_bypass.mjs",
@@ -8409,6 +8409,11 @@
8409
8409
  "mode": "0000644",
8410
8410
  "sha256": "fa155f3c5faa3763d1a51d56b7746ad870e826e3249f5e9b08108a3cfbfcbd72"
8411
8411
  },
8412
+ {
8413
+ "path": "tests/grader_quality_window_overflow.mjs",
8414
+ "mode": "0000644",
8415
+ "sha256": "cbe81b9176d6ffdd59ea8d6955e30aab9bf6d13bd15144fd79d126455aa0e1e3"
8416
+ },
8412
8417
  {
8413
8418
  "path": "tests/grader_regrade_delta.mjs",
8414
8419
  "mode": "0000644",
@@ -40,7 +40,7 @@ The panel could not run (worker infra outage or a pre-spawn bypass). This is **n
40
40
  Two known sub-shapes, both recognizable because the "fail" carries **no substantive findings**:
41
41
 
42
42
  - **Server-guard trip** (`signals.server_guard.tripped` non-empty, e.g. `implausible_pass`): the server downgraded a client-asserted pass it found implausible — historically this guard has fired on genuinely clean, unanimous zero-issue passes. Explain the downgrade honestly, then re-grade: `node scripts/gds/ship.js <id> --regrade`.
43
- - **A FAIL scored around 4.5/10 with zero issues and a null/zero axis** (the errored-worker class): a worker error was forwarded as a real axis score. Same response — re-grade; the panel rerun replaces the forged shape with a real verdict.
43
+ - **A FAIL scored around 4.5/10 with zero issues and a null/zero axis** (the errored-worker class): a worker error was forwarded as a real axis score. Same response — re-grade; the panel rerun replaces the forged shape with a real verdict. Since task 1003318 a Quality worker that never ran (a `Prompt is too long` window overflow, `error: PROMPT_TOO_LONG`) no longer forges a 0: the server records `signals.cq_lens_unavailable` with a json-null axis and the card reads `QUALITY LENS UNAVAILABLE` — re-grade or manual-confirm; a 4.5 with a 0 axis now only comes from a server predating it.
44
44
 
45
45
  If an honest re-grade re-trips the guard on a diff you can defend, use the **auditable override path** — file an override-request with a real rationale (≥20 chars; only the task's most-recent claim holder or an Archon may file; an Archon then decides it):
46
46
 
@@ -31,7 +31,7 @@ Baselines: the 14-agent evaluation window measured a **90.6%** pass rate, alread
31
31
 
32
32
  ### 3 — Cost anomalies
33
33
 
34
- Benchmark: mean panel cost **$2.14** per grade, and ~90% of it is **fixed** re-exploration cost, independent of diff size. Read grader spend and divide by the grade count from axis 2's window:
34
+ Benchmark: mean panel cost **$2.14** per grade, and ~90% of it is **fixed** re-exploration cost, independent of diff size. (Measured with Quality on the haiku tier; since task 1003318 Quality runs on claude-sonnet-5, so a mean sitting somewhat above $2.14 is the new normal until re-baselined — a rise alone is not an anomaly.) Read grader spend and divide by the grade count from axis 2's window:
35
35
 
36
36
  ```
37
37
  node scripts/gds/api.js GET "/api/gds/public/cost-summary?source=grader&days=30" --quiet \
@@ -4,13 +4,13 @@
4
4
  > Deterministic on a given tree — regenerate and commit rather than hand-editing.
5
5
  > Machine-readable twin: [`copy-registry.json`](copy-registry.json).
6
6
 
7
- **1519 strings** across 3 surfaces · 1332 distinct pieces of copy · 1519 certain, 0 heuristic · 144 shown in more than one place.
7
+ **1520 strings** across 3 surfaces · 1333 distinct pieces of copy · 1520 certain, 0 heuristic · 144 shown in more than one place.
8
8
 
9
9
  ## Surfaces
10
10
 
11
11
  | surface | lives at | files | strings |
12
12
  |---|---|---|---|
13
- | `builders-hall` | `modules/hall-ui/public` | 71 | 1127 |
13
+ | `builders-hall` | `modules/hall-ui/public` | 71 | 1128 |
14
14
  | `status-dashboard` | `modules/status-ui/public` | 4 | 98 |
15
15
  | `landing` | `public-landing` | 5 | 294 |
16
16
 
@@ -78,7 +78,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
78
78
  | `cfe0f4c4fcc2` | 2x | Closing your box… (a few seconds) | modules/hall-ui/public/box-panel.js:265, modules/hall-ui/public/box-panel.js:370 |
79
79
  | … | | _104 more in the JSON registry_ | |
80
80
 
81
- ## `builders-hall` — 1127 strings
81
+ ## `builders-hall` — 1128 strings
82
82
 
83
83
  | id | conf | text | where |
84
84
  |---|---|---|---|
@@ -131,7 +131,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
131
131
  | `daf73127afff` | certain | A word on why | `modules/hall-ui/public/hall-render.js:275` |
132
132
  | `996edffc1966` | certain | Access requests | `modules/hall-ui/public/watch.html:38` |
133
133
  | `996edffc1966` | certain | Access requests | `modules/hall-ui/public/watch.html:53` |
134
- | `303f8d90266f` | certain | Achievements could not be loaded. | `modules/hall-ui/public/builders.js:1138` |
134
+ | `303f8d90266f` | certain | Achievements could not be loaded. | `modules/hall-ui/public/builders.js:1143` |
135
135
  | `6ca31dd5eb91` | certain | Acknowledge the primer | `modules/hall-ui/public/primer.html:65` |
136
136
  | `4461b13f95c2` | certain | Actions apply at once. Closing stops and deletes the box; you can start a fresh one anytime. | `modules/hall-ui/public/settings.html:162` |
137
137
  | `f6036360c90c` | certain | Activity, by craft | `modules/hall-ui/public/profile.html:84` |
@@ -370,7 +370,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
370
370
  | `541127601291` | certain | Download for Mac | `modules/hall-ui/public/settings.html:177` |
371
371
  | `2ed2fd8376c0` | certain | Download for Windows | `modules/hall-ui/public/settings.html:178` |
372
372
  | `c7af57097d94` | certain | Download the Dev Box app: | `modules/hall-ui/public/hall-render.js:650` |
373
- | `9cf07334a5a9` | certain | Downvote (−1) | `modules/hall-ui/public/builders.js:725` |
373
+ | `9cf07334a5a9` | certain | Downvote (−1) | `modules/hall-ui/public/builders.js:730` |
374
374
  | `9c728558d6e7` | certain | Drachmae &amp; karma | `modules/hall-ui/public/drachmae.html:15` |
375
375
  | `9c728558d6e7` | certain | Drachmae &amp; karma | `modules/hall-ui/public/drachmae.html:15` |
376
376
  | `9d9cc4ef8557` | certain | Drachmae &amp; karma — {{worldName}} | `modules/hall-ui/public/drachmae.html:6` |
@@ -463,7 +463,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
463
463
  | `a4ff1e9c34e3` | certain | Idea {…} | `modules/hall-ui/public/idea.js:252` |
464
464
  | `ba82ec56f1e3` | certain | Idea — {{worldName}} | `modules/hall-ui/public/idea.html:6` |
465
465
  | `ba82ec56f1e3` | certain | Idea — {{worldName}} | `modules/hall-ui/public/idea.html:6` |
466
- | `e181637c5a5e` | certain | Ideas awaiting triage | `modules/hall-ui/public/builders.js:845` |
466
+ | `e181637c5a5e` | certain | Ideas awaiting triage | `modules/hall-ui/public/builders.js:850` |
467
467
  | `d4e21769ce1c` | certain | Ideas filed, and what became of them | `modules/hall-ui/public/profile.html:103` |
468
468
  | `379b20d548ad` | certain | Ideas whose author has said "I had the thought but not the full picture". Anyone can develop one — the author | `modules/hall-ui/public/ideas.html:33` |
469
469
  | `ef224c9de7c8` | certain | Ideas — {{worldName}} | `modules/hall-ui/public/ideas.html:6` |
@@ -497,7 +497,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
497
497
  | `2a3c9e501f8e` | certain | Key storage isn't set up yet. As an Archon you can turn it on now — the server generates and safely stores the | `modules/hall-ui/public/settings.js:963` |
498
498
  | `7d6eaa8d4514` | certain | Kinds of work shipped | `modules/hall-ui/public/hall-render.js:1089` |
499
499
  | `d7b4392e0ea6` | certain | Largest files | `modules/hall-ui/public/watch.js:848` |
500
- | `81f62e066dbf` | certain | Last 14 days: {…} / 10 average across {…} graded work{…} ({…} passed). | `modules/hall-ui/public/builders.js:581` |
500
+ | `81f62e066dbf` | certain | Last 14 days: {…} / 10 average across {…} graded work{…} ({…} passed). | `modules/hall-ui/public/builders.js:586` |
501
501
  | `35a15eb0a117` | certain | Last 30 days | `modules/hall-ui/public/hall-widgets.js:129` |
502
502
  | `9be8b839eff8` | certain | Last 7 days | `modules/hall-ui/public/hall-widgets.js:129` |
503
503
  | `59f0bc78a28f` | certain | Last 90 days | `modules/hall-ui/public/hall-widgets.js:130` |
@@ -540,7 +540,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
540
540
  | `df335a1224ae` | certain | Member since {…} | `modules/hall-ui/public/hall-render.js:544` |
541
541
  | `df335a1224ae` | certain | Member since {…} | `modules/hall-ui/public/profile.js:58` |
542
542
  | `c7a670e9552d` | certain | Merged into | `modules/hall-ui/public/profile.js:322` |
543
- | `7637613c231b` | certain | Metic rank and above may vote | `modules/hall-ui/public/builders.js:729` |
543
+ | `7637613c231b` | certain | Metic rank and above may vote | `modules/hall-ui/public/builders.js:734` |
544
544
  | `d7ce4a3bb745` | certain | Minimize the welcome panel | `modules/hall-ui/public/hall-widgets.js:52` |
545
545
  | `19b0ed5781ae` | certain | Minimum rank required to claim this task | `modules/hall-ui/public/task-lib.js:65` |
546
546
  | `64e91044df43` | certain | Mint a fresh CLI token using this browser session and paste it into your terminal — the quick way back in when | `modules/hall-ui/public/settings.html:117` |
@@ -845,7 +845,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
845
845
  | `381f29be7497` | certain | The most recently shipped tasks. | `modules/hall-ui/public/work.html:97` |
846
846
  | `82479c539fca` | certain | The name shown beside your work — in the roster, the leaderboard, and the work feed. Your sign-in and the cred | `modules/hall-ui/public/settings.html:67` |
847
847
  | `b88dd8b96b72` | certain | The next person to notice this sentence reads your note instead of re-litigating it. | `modules/hall-ui/public/copy-desk.html:122` |
848
- | `aa716f3dd551` | certain | The page could not be loaded. Try again. | `modules/hall-ui/public/builders.js:1393` |
848
+ | `aa716f3dd551` | certain | The page could not be loaded. Try again. | `modules/hall-ui/public/builders.js:1398` |
849
849
  | `3bc5ec5f35ec` | certain | The ranks, lowest to highest | `modules/hall-ui/public/ranks.html:39` |
850
850
  | `3b5260b8780a` | certain | The reserved 16-rank ladder, which ranks are live today (archon, metic, thetes, xenos), and what each may do. | `modules/hall-ui/public/diagrams.html:69` |
851
851
  | `cfa55899240b` | certain | The roadmap could not be loaded right now. Reload to try again. | `modules/hall-ui/public/roadmap.js:407` |
@@ -896,14 +896,14 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
896
896
  | `1a64679fbb42` | certain | Typo or grammar | `modules/hall-ui/public/copy-desk.html:96` |
897
897
  | `2c1c20785a6e` | certain | Unclear — a person would not know what to do | `modules/hall-ui/public/copy-desk.html:93` |
898
898
  | `aa445f06cc5e` | certain | Unlink failed: {…} | `modules/hall-ui/public/settings.js:843` |
899
- | `67fbc770b3b3` | certain | Upvote (+1) | `modules/hall-ui/public/builders.js:725` |
899
+ | `67fbc770b3b3` | certain | Upvote (+1) | `modules/hall-ui/public/builders.js:730` |
900
900
  | `e8d747720e47` | certain | View all sessions → | `modules/hall-ui/public/watch.html:96` |
901
901
  | `9b92aa01516d` | certain | View all {…} {…} → | `modules/hall-ui/public/goals-render.js:530` |
902
902
  | `4babdfeaf27f` | certain | View full standing → | `modules/hall-ui/public/hall-widgets.js:86` |
903
903
  | `f8546b3e285e` | certain | View tasks {…} | `modules/hall-ui/public/roadmap.js:134` |
904
904
  | `71c09cda8246` | certain | Voice for {…} | `modules/hall-ui/public/settings.js:76` |
905
905
  | `2940b7828d26` | certain | Volume {…}%. | `modules/hall-ui/public/settings.js:211` |
906
- | `6905f2ee5702` | certain | Vote on this work | `modules/hall-ui/public/builders.js:725` |
906
+ | `6905f2ee5702` | certain | Vote on this work | `modules/hall-ui/public/builders.js:730` |
907
907
  | `b2937e14ee9b` | certain | Vote yes | `modules/hall-ui/public/government.js:885` |
908
908
  | `8cc425582984` | certain | Vulnerability reports | `modules/hall-ui/public/watch.html:117` |
909
909
  | `b1e63e33f739` | certain | Vulnerability reports, engagement reports, override requests, and the audit log. | `modules/hall-ui/public/watch.html:115` |
@@ -1021,7 +1021,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1021
1021
  | `77b0b2b446a1` | certain | e.g. octocat | `modules/hall-ui/public/watch.js:207` |
1022
1022
  | `89cc9754d633` | certain | e.g. runaway cost — paused pending review | `modules/hall-ui/public/harbor.js:146` |
1023
1023
  | `5321f9da0841` | certain | exists in schema, no behavior yet | `modules/hall-ui/public/diagrams.html:53` |
1024
- | `df941da50c7c` | certain | fell short | `modules/hall-ui/public/builders.js:558` |
1024
+ | `df941da50c7c` | certain | fell short | `modules/hall-ui/public/builders.js:563` |
1025
1025
  | `31735e119a04` | certain | first vote | `modules/hall-ui/public/government.js:537` |
1026
1026
  | `9563bd9f4971` | certain | first — your username is all we need. | `modules/hall-ui/public/hall-render.js:275` |
1027
1027
  | `1874087e6d87` | certain | for Mac or Windows, sign in with your | `modules/hall-ui/public/hall-render.js:650` |
@@ -1066,7 +1066,8 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1066
1066
  | `e02979266baf` | certain | no data | `modules/hall-ui/public/hall-render.js:990` |
1067
1067
  | `739a4bd6bd83` | certain | node scripts/setup-builder.js | `modules/hall-ui/public/hall-render.js:717` |
1068
1068
  | `61f0f4b2f4ab` | certain | not categorised yet | `modules/hall-ui/public/goals-page.js:277` |
1069
- | `01a3d68d80c1` | certain | not graded (grader outage) | `modules/hall-ui/public/builders.js:555` |
1069
+ | `01a3d68d80c1` | certain | not graded (grader outage) | `modules/hall-ui/public/builders.js:558` |
1070
+ | `d43b9bbb8d4d` | certain | not graded (quality lens unavailable) | `modules/hall-ui/public/builders.js:560` |
1070
1071
  | `3916a3dc8e98` | certain | not started | `modules/hall-ui/public/roadmap.js:108` |
1071
1072
  | `3916a3dc8e98` | certain | not started | `modules/hall-ui/public/roadmap.js:263` |
1072
1073
  | `e2c643fbf4d6` | certain | npm install | `modules/hall-ui/public/hall-render.js:691` |
@@ -1174,7 +1175,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1174
1175
  | `893901f11e81` | certain | · {…} permission{…} | `modules/hall-ui/public/government.js:147` |
1175
1176
  | `4a5ff4f662df` | certain | — ${…} of ${…} this month ({…}%). {…} | `modules/hall-ui/public/hall-render.js:452` |
1176
1177
  | `cbd220dd9184` | certain | — a later push must be re-approved. | `modules/hall-ui/public/gate.js:87` |
1177
- | `a6ae9cbdee30` | certain | — all builders — | `modules/hall-ui/public/builders.js:661` |
1178
+ | `a6ae9cbdee30` | certain | — all builders — | `modules/hall-ui/public/builders.js:666` |
1178
1179
  | `729d4ce22b99` | certain | — any — | `modules/hall-ui/public/hall-widgets.js:132` |
1179
1180
  | `9ac518dfa95e` | certain | — none (the version's general bucket) — | `modules/hall-ui/public/task-detail.js:315` |
1180
1181
  | `da9962dc1729` | certain | — or open any diagram fullscreen. | `modules/hall-ui/public/diagrams.html:42` |
@@ -1186,7 +1187,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1186
1187
  | `ff6b84565e10` | certain | — {…}{…} | `modules/hall-ui/public/government.js:652` |
1187
1188
  | `5b9fa7cb4fb0` | certain | …and {…} more waiting — | `modules/hall-ui/public/goal-inbox.js:121` |
1188
1189
  | `b2a04abfead0` | certain | …{…} more awaiting a nod. | `modules/hall-ui/public/goal-inbox.js:251` |
1189
- | `e35f11bdc5d7` | certain | …{…} more open ideas — triage clears them one at a time. | `modules/hall-ui/public/builders.js:843` |
1190
+ | `e35f11bdc5d7` | certain | …{…} more open ideas — triage clears them one at a time. | `modules/hall-ui/public/builders.js:848` |
1190
1191
  | `de8fa5223aee` | certain | ‹ Prev | `modules/hall-ui/public/hall-kit.js:71` |
1191
1192
  | `d4b84b7fbdeb` | certain | ← All goals | `modules/hall-ui/public/goals.html:56` |
1192
1193
  | `7d29dc9e0391` | certain | ← Back to Home | `modules/hall-ui/public/not-ready.html:35` |
@@ -3,9 +3,9 @@
3
3
  "generator": "scripts/gds/copy-inventory.js",
4
4
  "goal": 1000074,
5
5
  "counts": {
6
- "strings": 1519,
7
- "unique_ids": 1332,
8
- "certain": 1519,
6
+ "strings": 1520,
7
+ "unique_ids": 1333,
8
+ "certain": 1520,
9
9
  "heuristic": 0,
10
10
  "duplicate_ids": 144
11
11
  },
@@ -13,7 +13,7 @@
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
15
  "files": 71,
16
- "strings": 1127
16
+ "strings": 1128
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -1969,7 +1969,7 @@
1969
1969
  "surface": "builders-hall",
1970
1970
  "text": "Achievements could not be loaded.",
1971
1971
  "file": "modules/hall-ui/public/builders.js",
1972
- "line": 1138,
1972
+ "line": 1143,
1973
1973
  "origin": "js-markup",
1974
1974
  "confidence": "certain"
1975
1975
  },
@@ -4120,7 +4120,7 @@
4120
4120
  "surface": "builders-hall",
4121
4121
  "text": "Downvote (−1)",
4122
4122
  "file": "modules/hall-ui/public/builders.js",
4123
- "line": 725,
4123
+ "line": 730,
4124
4124
  "origin": "js-markup",
4125
4125
  "confidence": "certain"
4126
4126
  },
@@ -4957,7 +4957,7 @@
4957
4957
  "surface": "builders-hall",
4958
4958
  "text": "Ideas awaiting triage",
4959
4959
  "file": "modules/hall-ui/public/builders.js",
4960
- "line": 845,
4960
+ "line": 850,
4961
4961
  "origin": "js-markup",
4962
4962
  "confidence": "certain"
4963
4963
  },
@@ -5263,7 +5263,7 @@
5263
5263
  "surface": "builders-hall",
5264
5264
  "text": "Last 14 days: {…} / 10 average across {…} graded work{…} ({…} passed).",
5265
5265
  "file": "modules/hall-ui/public/builders.js",
5266
- "line": 581,
5266
+ "line": 586,
5267
5267
  "origin": "js-text-assign",
5268
5268
  "confidence": "certain"
5269
5269
  },
@@ -5650,7 +5650,7 @@
5650
5650
  "surface": "builders-hall",
5651
5651
  "text": "Metic rank and above may vote",
5652
5652
  "file": "modules/hall-ui/public/builders.js",
5653
- "line": 729,
5653
+ "line": 734,
5654
5654
  "origin": "js-markup",
5655
5655
  "confidence": "certain"
5656
5656
  },
@@ -8395,7 +8395,7 @@
8395
8395
  "surface": "builders-hall",
8396
8396
  "text": "The page could not be loaded. Try again.",
8397
8397
  "file": "modules/hall-ui/public/builders.js",
8398
- "line": 1393,
8398
+ "line": 1398,
8399
8399
  "origin": "js-notice",
8400
8400
  "confidence": "certain"
8401
8401
  },
@@ -8854,7 +8854,7 @@
8854
8854
  "surface": "builders-hall",
8855
8855
  "text": "Upvote (+1)",
8856
8856
  "file": "modules/hall-ui/public/builders.js",
8857
- "line": 725,
8857
+ "line": 730,
8858
8858
  "origin": "js-markup",
8859
8859
  "confidence": "certain"
8860
8860
  },
@@ -8917,7 +8917,7 @@
8917
8917
  "surface": "builders-hall",
8918
8918
  "text": "Vote on this work",
8919
8919
  "file": "modules/hall-ui/public/builders.js",
8920
- "line": 725,
8920
+ "line": 730,
8921
8921
  "origin": "js-markup",
8922
8922
  "confidence": "certain"
8923
8923
  },
@@ -9979,7 +9979,7 @@
9979
9979
  "surface": "builders-hall",
9980
9980
  "text": "fell short",
9981
9981
  "file": "modules/hall-ui/public/builders.js",
9982
- "line": 558,
9982
+ "line": 563,
9983
9983
  "origin": "js-markup",
9984
9984
  "confidence": "certain"
9985
9985
  },
@@ -10384,7 +10384,16 @@
10384
10384
  "surface": "builders-hall",
10385
10385
  "text": "not graded (grader outage)",
10386
10386
  "file": "modules/hall-ui/public/builders.js",
10387
- "line": 555,
10387
+ "line": 558,
10388
+ "origin": "js-markup",
10389
+ "confidence": "certain"
10390
+ },
10391
+ {
10392
+ "id": "d43b9bbb8d4d",
10393
+ "surface": "builders-hall",
10394
+ "text": "not graded (quality lens unavailable)",
10395
+ "file": "modules/hall-ui/public/builders.js",
10396
+ "line": 560,
10388
10397
  "origin": "js-markup",
10389
10398
  "confidence": "certain"
10390
10399
  },
@@ -11356,7 +11365,7 @@
11356
11365
  "surface": "builders-hall",
11357
11366
  "text": "— all builders —",
11358
11367
  "file": "modules/hall-ui/public/builders.js",
11359
- "line": 661,
11368
+ "line": 666,
11360
11369
  "origin": "js-markup",
11361
11370
  "confidence": "certain"
11362
11371
  },
@@ -11464,7 +11473,7 @@
11464
11473
  "surface": "builders-hall",
11465
11474
  "text": "…{…} more open ideas — triage clears them one at a time.",
11466
11475
  "file": "modules/hall-ui/public/builders.js",
11467
- "line": 843,
11476
+ "line": 848,
11468
11477
  "origin": "js-markup",
11469
11478
  "confidence": "certain"
11470
11479
  },
package/docs/file-map.md CHANGED
@@ -335,6 +335,7 @@ tests/
335
335
  ├── terrain_picker.mjs ← (ADR 0012): picker dispatch shape + GROUND-ONLY invariant (objects must never affect tile selection).
336
336
  ├── walkability.mjs ← (ADR 0012): isBlocked over the actual INITIAL_MAP for sea / sand / grass / rock-on-grass / all 8 container cells / vendor cell / cell south of vendor / out-of-bounds. Loads server-side src/world/{map,terrain}.js directly so server logic is exercised.
337
337
  ├── grader.mjs ← (task 197): pure scoring functions + weight blending + end-to-end grade() per kind. Run with `node tests/grader.mjs`.
338
+ ├── grader_quality_window_overflow.mjs ← (task 1003318): a Quality worker that dies on a context-window overflow is PROMPT_TOO_LONG (no retry), a null axis, and a server-side lens outage → manual-confirm, never a fabricated 0/10; the card, the forged-flag and blocker-beside-outage shapes.
338
339
  ├── session_records.mjs ← (6D.1, ADR 0027): scrubSecrets (ADR 0022 shapes + JWT/DSN/assignments) + buildSessionDigest + resolveTranscriptPath (worktree-session lookup, #551). Fixtures built at runtime so no literal secret hits the scanner.
339
340
  ├── session_search.mjs ← (6D.2): the session-search ORDER BY whitelist (injection guard) + LIMIT clamp.
340
341
  ├── session_eval.mjs ← (6D.3 + 6D.4): classifyTurn / computeOverhead / detectFootguns / summarizeSystemicFindings, incl. a #506-shaped fixture.
@@ -33,9 +33,11 @@ const ASSUMED_BUILDER_MODEL = V4.assumed_builder_model || 'claude-fable-5';
33
33
  const DEFAULT_GRADER_MODEL = V4.default_model || 'claude-sonnet-5';
34
34
  const FALLTHROUGH_CONFIDENCE = V4.fallthrough_confidence ?? 0.3;
35
35
  // task 1002660: per-worker model tier overrides (rubric-configured). The
36
- // Quality worker was 41% of panel spend with 75% nit findings — its cq/economy
37
- // judgment doesn't need the full cross-model tier. Keys are worker kinds;
38
- // values are model ids. workerModelFor guards the cross-model invariant.
36
+ // Quality worker was 41% of panel spend with 75% nit findings, so it ran on
37
+ // the haiku tier — until its 200k window overflowed on any big diff and the
38
+ // lens went dark (task 1003318: now claude-sonnet-5, a floor that coincides
39
+ // with default_model for top-tier builders). Keys are worker kinds; values are
40
+ // model ids. workerModelFor guards the cross-model invariant.
39
41
  const WORKER_MODEL_TIERS = V4.worker_model_tiers || {};
40
42
  const workers = require('./grader-workers');
41
43
  const generatedProvenance = require('./generated-provenance'); // task 1002795
@@ -12,8 +12,8 @@
12
12
  "assumed_builder_model": "claude-fable-5",
13
13
  "model_refresh_note": "task 1002659 (2026-08-11): pins refreshed to the Claude 5 family. Assumed builder = claude-fable-5 (the owner-top-model default per CLAUDE.md §2); default grader = claude-sonnet-5 (cross-model per ADR 0024, cost-parity with the prior sonnet-4-6 posture — opus-5 grades the sonnet/haiku builders where the grader should be the stronger model). selectGraderModel no longer throws on an unknown builder id: it warns loudly and falls back to default_model (the #117 throw stranded ships whose builder id lacked an opus/sonnet/haiku substring — exactly what claude-fable-5 is).",
14
14
  "active_panel": ["narc", "quality", "hacker", "efficiency"],
15
- "worker_model_tiers": { "quality": "claude-haiku-4-5-20251001" },
16
- "worker_model_tiers_note": "task 1002660: per-worker cost tiering. Quality was 41% of panel spend with 75% nit findings — its cq/economy lens runs on the haiku tier. workerModelFor refuses an override that lands on the builder's own model family (the ADR 0024 cross-model guarantee outranks the cost lever); gate semantics unchanged — Quality's verdict still gates identically.",
15
+ "worker_model_tiers": { "quality": "claude-sonnet-5" },
16
+ "worker_model_tiers_note": "task 1002660: per-worker cost tiering. Quality was 41% of panel spend with 75% nit findings — its cq/economy lens ran on the haiku tier. task 1003318: moved to claude-sonnet-5 — haiku 4.5's 200k window overflows on any diff over ~150 KB (`Prompt is too long`, deterministic; tasks 1003297 and 1003298 each lost three ~$5 panel runs to it) and a Quality worker that never starts is missing signal on a GATING lens. For fable/opus builders the panel model is already claude-sonnet-5, so the tier coincides with default_model and acts as a floor — a future default_model bump must not pin Quality back onto a smaller window; it stays a live cost lever only for haiku builders (whose panel runs on opus-5). workerModelFor refuses an override that lands on the builder's own model family (the ADR 0024 cross-model guarantee outranks the cost lever); gate semantics unchanged — Quality's verdict still gates identically.",
17
17
  "fallthrough_confidence": 0.3,
18
18
  "low_confidence_threshold": 0.4,
19
19
  "rationale": "GDS-V3 task #410 (MAS Phase 2.0), 2026-05-29. Supersedes v3 (single-prompt 3-shot generalist). Phase 1 calibration showed minimal between-shot disagreement on the same prompt — the strict gate fired correctly but the diversity signal was weak. v4 trades sampling-variance for lens-specialization: replace 3 identical-prompt shots with N workers (each a distinct prompt focused on one axis). Phase 2.0 ships 2 of the 3 planned workers (narc for completion, quality for cq + advisory economy) — keeps the gate at the same strictness while the framework calibrates. Phase 2.1 adds hacker (security; wraps /builder-redteam as a CI gate). Phase 2.2 adds efficiency_monger.",
@@ -19,18 +19,23 @@ function isNum(n) {
19
19
  return Number.isFinite(n);
20
20
  }
21
21
 
22
- // #1002653 corroboration helpers for validateAndScore's null-ff lens branch.
23
- // A worker entry is untrusted input — w.issues may be any shape. Absent issues
24
- // on an errored worker is the normal outage shape (treated as none); a truthy
25
- // NON-ARRAY issues is malformed and must DISQUALIFY corroboration (fail-closed
26
- // schema_error downstream), never TypeError into a 500 on grade submission.
27
- const isNarcErroredClean = (w) => {
28
- if (!w || w.kind !== 'narc' || !w.error) return false;
22
+ // #1002653 / task 1003318 corroboration helpers for validateAndScore's null-axis
23
+ // lens branches (Narc → ff, Quality → cq). A worker entry is untrusted input —
24
+ // w.issues may be any shape. Absent issues on an errored worker is the normal
25
+ // outage shape (treated as none); a truthy NON-ARRAY issues is malformed and
26
+ // must DISQUALIFY corroboration (fail-closed schema_error downstream), never
27
+ // TypeError into a 500 on grade submission.
28
+ const isWorkerErroredClean = (kind) => (w) => {
29
+ if (!w || w.kind !== kind || !w.error) return false;
29
30
  const issues = w.issues == null ? [] : w.issues;
30
31
  if (!Array.isArray(issues)) return false;
31
32
  return !issues.some((i) => i && (i.severity === 'blocker' || i.severity === 'major'));
32
33
  };
33
- const isNarcHealthy = (w) => !!w && w.kind === 'narc' && !w.error;
34
+ const isWorkerHealthy = (kind) => (w) => !!w && w.kind === kind && !w.error;
35
+ const isNarcErroredClean = isWorkerErroredClean('narc');
36
+ const isNarcHealthy = isWorkerHealthy('narc');
37
+ const isQualityErroredClean = isWorkerErroredClean('quality');
38
+ const isQualityHealthy = isWorkerHealthy('quality');
34
39
 
35
40
  // task 1002658 (ADR 0164): a SUBSTANTIVE issue is one a real panel emits — a
36
41
  // known severity + a non-empty summary. The old plausibility guard counted raw
@@ -95,8 +100,8 @@ function validateAndScore(rawSubagent, meta = {}) {
95
100
  // needs POSITIVE corroboration: workers[] carries an errored
96
101
  // no-substantive-finding Narc (#473 missing-signal semantics) and no healthy
97
102
  // Narc. Empty/narc-less/absent workers, ABSENT ff, and every other shape
98
- // fall through to the fail-closed schema_error below. Quality has no such
99
- // branch — gating lens, absence stays fail-closed.
103
+ // fall through to the fail-closed schema_error below. Quality's twin branch
104
+ // (task 1003318, below) is gating, so it never passes.
100
105
  // Trust note: workers[] is client-supplied like every SR-9 grade input — a
101
106
  // forger gains nothing here they cannot get by sending ff:9 directly; the
102
107
  // server-derivable-signals redesign is tracked separately (SR-9 residual).
@@ -105,8 +110,23 @@ function validateAndScore(rawSubagent, meta = {}) {
105
110
  metaWorkers.some(isNarcErroredClean) && !metaWorkers.some(isNarcHealthy);
106
111
  const ffExplicitNull = parsed.functional_fidelity === null;
107
112
  const ffLensUnavailable = ffExplicitNull && narcMissingSignal;
113
+ // task 1003318: the same shape for the GATING Quality lens. The haiku-tier
114
+ // Quality worker died on a context-window overflow (`Prompt is too long`)
115
+ // before it started; aggregatePanel forwarded code_quality: null and
116
+ // Number(null) === 0 fabricated "FAIL — code_quality below 6" at (9+0)/2.
117
+ // Unlike the advisory ff branch this NEVER passes: a gating lens that did not
118
+ // run is recorded as unavailable (json-null axis, score on the axis that ran,
119
+ // signals.cq_lens_unavailable) and the task parks at completed for
120
+ // manual-confirm — fail-closed, and a forger gains nothing they could not get
121
+ // by sending cq:9 outright. Same corroboration: an errored no-finding Quality
122
+ // in workers[] and no healthy one; every other null shape is schema_error.
123
+ const qualityMissingSignal = !!metaWorkers &&
124
+ metaWorkers.some(isQualityErroredClean) && !metaWorkers.some(isQualityHealthy);
125
+ const cqExplicitNull = parsed.code_quality === null;
126
+ const cqLensUnavailable = cqExplicitNull && qualityMissingSignal;
127
+ const qualityErrorTag = cqLensUnavailable ? String(metaWorkers.find(isQualityErroredClean).error) : null;
108
128
  const ff = ffExplicitNull ? null : Number(parsed.functional_fidelity);
109
- const cq = Number(parsed.code_quality);
129
+ const cq = cqExplicitNull ? null : Number(parsed.code_quality);
110
130
  const economyRaw = Number(parsed.economy);
111
131
  const economy =
112
132
  isNum(economyRaw) && economyRaw >= 0 && economyRaw <= 10 ? economyRaw : null;
@@ -138,9 +158,13 @@ function validateAndScore(rawSubagent, meta = {}) {
138
158
 
139
159
  signals.confidence = confidence;
140
160
  signals.issue_count = issues.length;
141
- if (ffLensUnavailable) signals.ff_lens_unavailable = true;
161
+ // Set AND cleared: `signals` spreads the client's own blob, so a forged
162
+ // `*_lens_unavailable: true` on a judged grade must not survive into the row
163
+ // (the ship card keys its outage reading on these).
164
+ if (ffLensUnavailable) signals.ff_lens_unavailable = true; else delete signals.ff_lens_unavailable;
165
+ if (cqLensUnavailable) signals.cq_lens_unavailable = true; else delete signals.cq_lens_unavailable;
142
166
 
143
- if ((!ffLensUnavailable && (!isNum(ff) || ff < 0 || ff > 10)) || !isNum(cq) || cq < 0 || cq > 10) {
167
+ if ((!ffLensUnavailable && (!isNum(ff) || ff < 0 || ff > 10)) || (!cqLensUnavailable && (!isNum(cq) || cq < 0 || cq > 10))) {
144
168
  return {
145
169
  score: 0,
146
170
  passed: false,
@@ -152,22 +176,34 @@ function validateAndScore(rawSubagent, meta = {}) {
152
176
  notes_md: `Subagent grader ${VERSION}: subagent returned scores outside [0,10] or non-numeric. Got functional_fidelity=${parsed.functional_fidelity}, code_quality=${parsed.code_quality}.`
153
177
  + (ffExplicitNull && !narcMissingSignal
154
178
  ? ' Explicit null functional_fidelity without a corroborated Narc outage in signals.workers is fail-closed.'
179
+ : '')
180
+ + (cqExplicitNull && !qualityMissingSignal
181
+ ? ' Explicit null code_quality without a corroborated Quality outage in signals.workers is fail-closed.'
155
182
  : ''),
156
183
  issues,
157
184
  };
158
185
  }
159
186
 
160
187
  const ffR = ffLensUnavailable ? null : r1(clamp(ff, 0, 10));
161
- const cqR = r1(clamp(cq, 0, 10));
188
+ const cqR = cqLensUnavailable ? null : r1(clamp(cq, 0, 10));
162
189
  const economyR = economy != null ? r1(clamp(economy, 0, 10)) : null;
163
190
  const securityR = securityScore != null ? r1(clamp(securityScore, 0, 10)) : null;
164
191
  const efficiencyR = efficiencyScore != null ? r1(clamp(efficiencyScore, 0, 10)) : null;
165
192
  const economyPenalty =
166
193
  economyR != null ? Math.max(0, (THRESHOLD - economyR) / 2) : 0;
167
- const cqEffective = economyR != null ? r1(clamp(cqR - economyPenalty, 0, 10)) : cqR;
168
- // With the ff lens out, the score reflects only the axis that ran — never a
169
- // blend with a synthetic 0 (the fabricated 4.5/10) or a synthetic threshold.
170
- const blended = ffLensUnavailable ? cqR : r1((ffR + cqR) / 2);
194
+ const cqEffective = cqLensUnavailable
195
+ ? null
196
+ : economyR != null ? r1(clamp(cqR - economyPenalty, 0, 10)) : cqR;
197
+ // With a lens out, the score reflects only the axis that ran — never a blend
198
+ // with a synthetic 0 (the fabricated 4.5/10) or a synthetic threshold. Both
199
+ // out → 0: nothing was judged.
200
+ const blended = ffLensUnavailable && cqLensUnavailable
201
+ ? 0
202
+ : ffLensUnavailable
203
+ ? cqR
204
+ : cqLensUnavailable
205
+ ? ffR
206
+ : r1((ffR + cqR) / 2);
171
207
 
172
208
  // confidence must be present (not null) for a PASS. A subagent that omits
173
209
  // confidence entirely silently bypassed the low-confidence gate under the
@@ -182,26 +218,40 @@ function validateAndScore(rawSubagent, meta = {}) {
182
218
  // (null < THRESHOLD is true via JS coercion), and must not appear in the
183
219
  // verdict's below-threshold branches.
184
220
  const ffBelow = !ffLensUnavailable && ffR < THRESHOLD;
185
- const bothPass = !ffBelow && cqR >= THRESHOLD;
186
- const passed = bothPass && !lowConfidence && !confidenceAbsent;
187
-
221
+ const cqBelow = !cqLensUnavailable && cqR < THRESHOLD;
222
+ const bothPass = !ffBelow && !cqBelow;
223
+ // A GATING lens that did not run is never a PASS (ff is advisory since #481;
224
+ // cq is not) — manual-confirm is the only way past it (task 1003318).
225
+ const passed = bothPass && !lowConfidence && !confidenceAbsent && !cqLensUnavailable;
226
+
227
+ // The UNAVAILABLE reading is only honest when the lenses that DID run found
228
+ // nothing substantive: a Hacker blocker beside a dead Quality is a rejection
229
+ // with a missing axis, not an outage, and must keep the ordinary FAIL chain
230
+ // (the #574 posture — a dark lens hardens the outcome, never softens it).
231
+ const hasSubstantiveFinding = issues.some((i) => i.severity === 'blocker' || i.severity === 'major');
188
232
  const verdict = passed
189
233
  ? 'PASS'
190
- : confidenceAbsent
191
- ? 'FAIL — confidence absent'
192
- : lowConfidence
193
- ? 'FAIL — low confidence'
194
- : bothPass
195
- ? 'FAIL'
196
- : ffBelow && cqR < THRESHOLD
197
- ? `FAIL — both axes below ${THRESHOLD}`
198
- : ffBelow
199
- ? `FAIL — functional_fidelity below ${THRESHOLD}`
200
- : `FAIL — code_quality below ${THRESHOLD}`;
234
+ : cqLensUnavailable && !hasSubstantiveFinding
235
+ ? `UNAVAILABLE — the code_quality lens did not run (Quality worker errored: ${qualityErrorTag}); not a quality rejection — manual-confirm available`
236
+ + (ffBelow ? ` (functional_fidelity below ${THRESHOLD})` : '')
237
+ // the confidence reasons the chain below would have named — kept as a
238
+ // tail so the #489 gate-walk signal is never hidden by the outage line
239
+ + (confidenceAbsent ? '; confidence absent' : lowConfidence ? `; low confidence ${confidence}` : '')
240
+ : confidenceAbsent
241
+ ? 'FAIL — confidence absent'
242
+ : lowConfidence
243
+ ? 'FAIL — low confidence'
244
+ : bothPass
245
+ ? 'FAIL'
246
+ : ffBelow && cqBelow
247
+ ? `FAIL — both axes below ${THRESHOLD}`
248
+ : ffBelow
249
+ ? `FAIL — functional_fidelity below ${THRESHOLD}`
250
+ : `FAIL — code_quality below ${THRESHOLD}`;
201
251
 
202
252
  const notesLines = [];
203
253
  notesLines.push(`Shot ${signals.shot_index != null ? signals.shot_index : '?'} (${signals.model}) — ${verdict}`);
204
- notesLines.push(` ff=${ffLensUnavailable ? 'n/a (lens unavailable)' : `${ffR}/10`} cq=${cqR}/10${economyR != null ? ` econ=${economyR}/10 (advisory)` : ''} conf=${confidence ?? '?'}`);
254
+ notesLines.push(` ff=${ffLensUnavailable ? 'n/a (lens unavailable)' : `${ffR}/10`} cq=${cqLensUnavailable ? 'n/a (lens unavailable)' : `${cqR}/10`}${economyR != null ? ` econ=${economyR}/10 (advisory)` : ''} conf=${confidence ?? '?'}`);
205
255
  if (notesFromSubagent) {
206
256
  notesLines.push('');
207
257
  notesLines.push(notesFromSubagent.trim());
@@ -559,6 +609,9 @@ function aggregateShots(shotResults, meta = {}) {
559
609
  const cq = median(verdicts.map((v) => v.cq));
560
610
  const economy = median(verdicts.map((v) => v.economy));
561
611
  const cqEffective = median(verdicts.map((v) => v.cq_effective));
612
+ // NB (task 1003318): this v3 multi-shot path still collapses a null axis to
613
+ // 0 — it serves the trivial-diff single shot, which cannot overflow a window;
614
+ // the panel path's lens-outage handling lives in validateAndScore.
562
615
  const blended = ff != null && cq != null ? r1((ff + cq) / 2) : 0;
563
616
  // Top-level aggregated confidence = median of per-shot confidences. Without
564
617
  // this, ship.js forwards a null top-level confidence to /tasks/:id/grade,
@@ -45,6 +45,17 @@ const TRANSIENT_WORKER_ERRORS = new Set([
45
45
  'SUBAGENT_NONZERO_EXIT',
46
46
  ]);
47
47
 
48
+ // task 1003318: a context-window overflow is NOT transient. The CLI refuses the
49
+ // request before the worker starts (`Prompt is too long: the request is ~251248
50
+ // tokens (limit 200000)`) and refuses the retry identically, so the #465 retry
51
+ // only burns another round. Its own tag names the cause on the grade row and
52
+ // in the server's verdict line (grader-score.js keys the lens outage on ANY
53
+ // errored no-finding Quality — the tag is the diagnosis, not the trigger).
54
+ // The pattern is the API's exact sentence, so a diff that merely mentions the
55
+ // phrase cannot trip it.
56
+ const PROMPT_TOO_LONG_RE = /prompt is too long: the request is ~?\d+ tokens/i;
57
+ const PROMPT_TOO_LONG = 'PROMPT_TOO_LONG';
58
+
48
59
  // #467: per-worker subagent timeout. The global 240s applied to EVERY worker,
49
60
  // which timed out the Read-heavy ones (Narc walks ACs + reads files — hence
50
61
  // Phase 2.0.5's 5-read workaround; Quality is next-heaviest). Per-worker budgets
@@ -176,6 +187,14 @@ async function runWorker(worker, { task, valueSummary, diff, changedFiles, diffS
176
187
  // (`claude` prints its "not logged in" line there) — fall back so the tail
177
188
  // is never empty when the subprocess did explain itself.
178
189
  stderrTail = redactStderrTail(err.stderr) ?? redactStderrTail(err.stdout);
190
+ // Matched on the RAW stream (the redacted tail keeps only the last 400
191
+ // chars), stderr first and stdout only when stderr is silent — stdout
192
+ // carries the model's own text. A SUBAGENT_TIMEOUT keeps its tag: it is
193
+ // the #467 budget signal, never a refusal.
194
+ if (err.code !== 'SUBAGENT_TIMEOUT') {
195
+ const said = String(err.stderr || '').trim() || String(err.stdout || '');
196
+ if (PROMPT_TOO_LONG_RE.test(said)) subprocessError = PROMPT_TOO_LONG;
197
+ }
179
198
  }
180
199
  const parsed = subprocessError
181
200
  ? null
@@ -550,17 +550,22 @@
550
550
  // A grader OUTAGE row (panel_outcome 'unavailable') carries a placeholder
551
551
  // score of 0 — label it, never render it as a 0.0 fail (task 1002652).
552
552
  const isOutage = g.panel_outcome === 'unavailable';
553
- const score = isOutage ? '—' : Number(g.score).toFixed(1);
553
+ // A json-null gating axis (task 1003318 — the Quality worker never ran)
554
+ // is likewise not a judgment: the score is the surviving axis, not a fail.
555
+ const isLensOut = !isOutage && !!g.rubric_scores && g.rubric_scores.code_quality === null;
556
+ const score = isOutage || isLensOut ? '—' : Number(g.score).toFixed(1);
554
557
  const verdict = isOutage
555
558
  ? `<span style="color:var(--ink-soft,#8a8577)">not graded (grader outage)</span>`
556
- : g.passed
559
+ : isLensOut
560
+ ? `<span style="color:var(--ink-soft,#8a8577)">not graded (quality lens unavailable)</span>`
561
+ : g.passed
557
562
  ? `<span style="color:var(--olive,#5b7a4d);font-weight:600">passed</span>`
558
563
  : `<span style="color:#a04040;font-weight:600">fell short</span>`;
559
564
  return `
560
565
  <tr>
561
566
  <td class="ledger__num">#${g.task_id}</td>
562
567
  <td>${escapeHtml(g.title || '')}</td>
563
- <td class="ledger__drachmae">${isOutage ? '—' : `${score} / 10`}</td>
568
+ <td class="ledger__drachmae">${isOutage || isLensOut ? '—' : `${score} / 10`}</td>
564
569
  <td>${verdict}</td>
565
570
  <td class="ledger__date">${fmtDate(g.graded_at)}</td>
566
571
  </tr>
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.388",
3
+ "version": "1.19.389",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.388",
9
+ "version": "1.19.389",
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.388",
3
+ "version": "1.19.389",
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",
@@ -306,6 +306,33 @@ function formatGradeOutcome(g, taskId, cliFinding = null) {
306
306
  return out.join('\n');
307
307
  }
308
308
 
309
+ // task 1003318: ONE gating lens went dark (the Quality worker died on a
310
+ // context-window overflow before it started) while the rest of the panel
311
+ // judged the diff and found nothing substantive. The server records that axis
312
+ // as unavailable instead of fabricating a 0 (validateAndScore
313
+ // cq_lens_unavailable); present it the way #943 presents a total outage —
314
+ // there are no findings to fix, and the paths forward are re-grade or
315
+ // manual-confirm. Sits BELOW the authorization wall (a refusal is never an
316
+ // outage) and steps aside when any lens raised a blocker/major (then the
317
+ // ordinary FAIL block prints the findings). The task stays at completed.
318
+ const cqLensOutage = !g.passed
319
+ && g.signals?.cq_lens_unavailable === true
320
+ && g.rubric_scores?.code_quality === null
321
+ && !(g.issues || []).some((i) => i && (i.severity === 'blocker' || i.severity === 'major'));
322
+ if (cqLensOutage) {
323
+ out.push('');
324
+ out.push('Subagent grade: QUALITY LENS UNAVAILABLE · the Quality worker did not run — NOT a quality rejection');
325
+ if (g.notes_md) out.push(g.notes_md.split('\n').map((l) => ' ' + l).join('\n'));
326
+ out.push(...formatOutageDiagnostic(g, null));
327
+ out.push('');
328
+ out.push('The other lenses judged the diff and found nothing substantive; code_quality is recorded as unavailable, not 0. Task stays at completed.');
329
+ out.push('When the grader is healthy again, re-grade:');
330
+ out.push(` node scripts/gds/ship.js ${taskId} --regrade`);
331
+ out.push('Or land it now via manual-confirm:');
332
+ out.push(` curl -X POST -H "Authorization: Bearer <token>" ${resolveInstanceBase() || '<instance-origin>'}/api/gds/tasks/${taskId}/confirm`);
333
+ return out.join('\n');
334
+ }
335
+
309
336
  const verdict = g.passed ? 'PASS' : 'FAIL';
310
337
  out.push('');
311
338
  out.push(`Subagent grade: ${verdict} · blended ${g.score}/10 · threshold ${g.threshold} (both axes)`);
package/src/module-api.js CHANGED
@@ -1216,8 +1216,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
1216
1216
  // landed since 1.19.386 with no explicit bump. run 33121066592. (task 1002620)
1217
1217
  // 1.19.388 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1218
1218
  // landed since 1.19.387 with no explicit bump. run 33125589814. (task 1002620)
1219
+ // 1.19.389 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1220
+ // landed since 1.19.388 with no explicit bump. run 33126279278. (task 1002620)
1219
1221
  // ---------------------------------------------------------------------------
1220
- const CORE_VERSION = '1.19.388'; // CI auto-patch carrier (ADR 0161); see changelog above
1222
+ const CORE_VERSION = '1.19.389'; // CI auto-patch carrier (ADR 0161); see changelog above
1221
1223
 
1222
1224
  // A namespaced logger so a module's log lines are attributable + consistent.
1223
1225
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
package/tests/grader.mjs CHANGED
@@ -842,20 +842,20 @@ test('selectGraderModel: builder Haiku → grader Opus', () => {
842
842
 
843
843
  // --- task 1002660: per-worker model tiering (cross-model-guarded) ---
844
844
 
845
- test('workerModelFor: Quality tiers down to haiku; other workers keep the panel model', () => {
845
+ test('workerModelFor: Quality runs on its sonnet tier (task 1003318 — haiku\'s window overflowed); other workers keep the panel model', () => {
846
846
  assert.equal(
847
- grader.workerModelFor('quality', 'claude-sonnet-5', 'claude-fable-5'),
848
- 'claude-haiku-4-5-20251001'
847
+ grader.workerModelFor('quality', 'claude-opus-5', 'claude-fable-5'),
848
+ 'claude-sonnet-5'
849
849
  );
850
850
  for (const kind of ['narc', 'hacker', 'efficiency']) {
851
- assert.equal(grader.workerModelFor(kind, 'claude-sonnet-5', 'claude-fable-5'), 'claude-sonnet-5');
851
+ assert.equal(grader.workerModelFor(kind, 'claude-opus-5', 'claude-fable-5'), 'claude-opus-5');
852
852
  }
853
853
  });
854
854
 
855
855
  test('workerModelFor: a tier override landing on the BUILDER family is refused (cross-model guarantee)', () => {
856
- // A haiku builder must not be quality-judged by haiku — fall back to the panel model.
856
+ // A sonnet builder must not be quality-judged by sonnet — fall back to the panel model.
857
857
  assert.equal(
858
- grader.workerModelFor('quality', 'claude-opus-5', 'claude-haiku-4-5-20251001'),
858
+ grader.workerModelFor('quality', 'claude-opus-5', 'claude-sonnet-5'),
859
859
  'claude-opus-5'
860
860
  );
861
861
  });
@@ -1795,6 +1795,7 @@ test('v4 #465/#467: TRANSIENT_WORKER_ERRORS covers infra noise but NOT timeout',
1795
1795
  assert.ok(t.has(code), `${code} should be retryable`);
1796
1796
  }
1797
1797
  assert.ok(!t.has('SUBAGENT_TIMEOUT'), 'timeout is a real budget signal, never retried');
1798
+ assert.ok(!t.has('PROMPT_TOO_LONG'), 'a window overflow is deterministic, never retried (task 1003318)');
1798
1799
  });
1799
1800
 
1800
1801
  test('v4 #467: audit-exports classifyExports — consumed / test-only / DEAD', () => {
@@ -0,0 +1,324 @@
1
+ // tests/grader_quality_window_overflow.mjs
2
+ //
3
+ // task 1003318: the grader's Quality worker ran on haiku 4.5, whose 200k context
4
+ // window overflows on any diff over ~150 KB. The CLI exits before the worker
5
+ // starts (`Prompt is too long: the request is ~251248 tokens (limit 200000)`),
6
+ // the exit was retried once as if transient, the dead axis was forwarded to the
7
+ // server as code_quality: null → Number(null) === 0 → a fabricated
8
+ // "FAIL — code_quality below 6" at (9+0)/2 = 4.5. Deterministic, not flaky:
9
+ // tasks 1003297 and 1003298 each burned three ~$5 panel runs on it.
10
+ //
11
+ // Three things must hold now:
12
+ // 1. the Quality tier is a model whose window covers the diffs the panel sees
13
+ // (claude-sonnet-5), still behind the ADR 0024 cross-model guard;
14
+ // 2. a window overflow is recorded as its own non-transient worker error
15
+ // (PROMPT_TOO_LONG) — no retry, stderr tail kept — and the panel treats it
16
+ // as missing signal (CONCERNS), never a 0/10 axis;
17
+ // 3. the server, seeing an explicit code_quality: null corroborated by an
18
+ // errored no-finding Quality worker, records the lens as UNAVAILABLE
19
+ // (json-null axis, score on the axis that ran, cq_lens_unavailable) and
20
+ // parks the task for manual-confirm — fail-closed, never an auto-pass, and
21
+ // never the fabricated 4.5. An uncorroborated null stays schema_error.
22
+ //
23
+ // Run: node tests/grader_quality_window_overflow.mjs
24
+
25
+ import { strict as assert } from 'node:assert';
26
+ import { createRequire } from 'node:module';
27
+
28
+ const require = createRequire(import.meta.url);
29
+ const rubric = require('../modules/grading/grader-rubric.json');
30
+ const grader = require('../modules/grading/grader.js');
31
+ const workersMod = require('../modules/grading/grader-workers/index.js');
32
+ const ship = require('../scripts/gds/ship.js');
33
+
34
+ const { aggregatePanel, validateAndScore, workerModelFor } = grader;
35
+ const { runWorker, TRANSIENT_WORKER_ERRORS } = workersMod;
36
+ const { formatGradeOutcome } = ship;
37
+
38
+ let passed = 0;
39
+ let failed = 0;
40
+ function test(name, fn) {
41
+ try { fn(); passed++; console.log(` ok ${name}`); }
42
+ catch (err) { failed++; console.error(` FAIL ${name}`); console.error(` ${err.message}`); }
43
+ }
44
+ async function atest(name, fn) {
45
+ try { await fn(); passed++; console.log(` ok ${name}`); }
46
+ catch (err) { failed++; console.error(` FAIL ${name}`); console.error(` ${err.message}`); }
47
+ }
48
+
49
+ const OVERFLOW_LINE = 'Prompt is too long: the request is ~251248 tokens (limit 200000)';
50
+
51
+ // The exact error runSubagent throws on a nonzero exit (grader-subagent.js).
52
+ const nonzeroExit = ({ exitCode = 1, stderr = '', stdout = '' } = {}) =>
53
+ Object.assign(new Error(`subagent exited with code ${exitCode}`), {
54
+ code: 'SUBAGENT_NONZERO_EXIT', exit_code: exitCode, run_ms: 120, stdout, stderr,
55
+ });
56
+
57
+ // A Quality-shaped worker stub: no spawning, the real error short-circuit shape.
58
+ const STUB_QUALITY = {
59
+ KIND: 'quality', NAME: 'Quality', SPECIALTY: 'stub',
60
+ buildPrompt: () => 'prompt',
61
+ extractJson: (s) => JSON.parse(s),
62
+ computeVerdict: (parsed, meta) => (meta.subprocess_error
63
+ ? { verdict: 'fail', scores: {}, issues: [], notes_md: '', confidence: 0, error: meta.subprocess_error }
64
+ : { verdict: 'pass', scores: { code_quality: 9 }, issues: [], notes_md: '', confidence: 0.9, error: null }),
65
+ };
66
+
67
+ const healthy = (kind, scores) => ({ kind, verdict: 'pass', scores, issues: [], confidence: 0.85, error: null });
68
+
69
+ // ---------- 1. the tier ----------
70
+
71
+ test('rubric: the Quality worker tier is claude-sonnet-5, not the 200k-window haiku', () => {
72
+ assert.equal(rubric.v4.worker_model_tiers.quality, 'claude-sonnet-5');
73
+ assert.doesNotMatch(String(rubric.v4.worker_model_tiers.quality), /haiku/);
74
+ assert.match(rubric.v4.worker_model_tiers_note, /1003318/, 'the note records why the tier moved');
75
+ });
76
+
77
+ test('workerModelFor: the sonnet tier is used for a fable builder, refused for a sonnet builder (ADR 0024 guard)', () => {
78
+ assert.equal(workerModelFor('quality', 'claude-sonnet-5', 'claude-fable-5'), 'claude-sonnet-5');
79
+ assert.equal(workerModelFor('quality', 'claude-opus-5', 'claude-sonnet-5'), 'claude-opus-5',
80
+ 'a sonnet builder must not be graded by its own family — falls back to the panel model');
81
+ });
82
+
83
+ // ---------- 2. the worker ----------
84
+
85
+ await atest('runWorker: a "Prompt is too long" exit is recorded as PROMPT_TOO_LONG and NOT retried', async () => {
86
+ let calls = 0;
87
+ const r = await runWorker(STUB_QUALITY, {
88
+ task: {}, diff: '', changedFiles: [], model: 'm',
89
+ runSubagentFn: async () => { calls++; throw nonzeroExit({ exitCode: 1, stderr: OVERFLOW_LINE }); },
90
+ });
91
+ assert.equal(calls, 1, 'a window overflow is deterministic — the retry only burns another round');
92
+ assert.equal(r.retried, false);
93
+ assert.equal(r.error, 'PROMPT_TOO_LONG');
94
+ assert.equal(TRANSIENT_WORKER_ERRORS.has('PROMPT_TOO_LONG'), false);
95
+ assert.deepEqual(r.scores, {}, 'no axis is fabricated for a worker that never ran');
96
+ assert.equal(r.exit_code, 1);
97
+ assert.match(r.stderr_tail, /Prompt is too long/, 'the CLI line survives so the grade row names its cause');
98
+ });
99
+
100
+ await atest('runWorker: the overflow line on STDOUT classifies the same way', async () => {
101
+ const r = await runWorker(STUB_QUALITY, {
102
+ task: {}, diff: '', changedFiles: [], model: 'm',
103
+ runSubagentFn: async () => { throw nonzeroExit({ exitCode: 1, stdout: OVERFLOW_LINE }); },
104
+ });
105
+ assert.equal(r.error, 'PROMPT_TOO_LONG');
106
+ assert.equal(r.retried, false);
107
+ });
108
+
109
+ await atest('runWorker: an unrelated nonzero exit keeps SUBAGENT_NONZERO_EXIT and its one retry (#465 unchanged)', async () => {
110
+ let calls = 0;
111
+ const r = await runWorker(STUB_QUALITY, {
112
+ task: {}, diff: '', changedFiles: [], model: 'm',
113
+ runSubagentFn: async () => { calls++; throw nonzeroExit({ exitCode: 1, stderr: 'Invalid API key · Run /login' }); },
114
+ });
115
+ assert.equal(calls, 2);
116
+ assert.equal(r.retried, true);
117
+ assert.equal(r.error, 'SUBAGENT_NONZERO_EXIT');
118
+ });
119
+
120
+ const DEAD_QUALITY = {
121
+ kind: 'quality', verdict: 'fail', error: 'PROMPT_TOO_LONG', scores: {}, issues: [],
122
+ confidence: 0, exit_code: 1, stderr_tail: OVERFLOW_LINE,
123
+ };
124
+ const PANEL_WITH_DEAD_QUALITY = [
125
+ healthy('narc', { functional_fidelity: 9 }),
126
+ DEAD_QUALITY,
127
+ healthy('hacker', { security_score: 9 }),
128
+ healthy('efficiency', { efficiency_score: 8 }),
129
+ ];
130
+
131
+ test('aggregatePanel: an overflowed Quality is missing signal → CONCERNS with a null code_quality axis, not a 0', () => {
132
+ const r = aggregatePanel(PANEL_WITH_DEAD_QUALITY);
133
+ assert.equal(r.signals.panel_outcome, 'concerns');
134
+ assert.equal(r.passed, false);
135
+ assert.equal(r.rubric_scores.code_quality, null);
136
+ assert.equal(r.signals.error_count, 1);
137
+ assert.equal(r.signals.fail_count, 0);
138
+ assert.match(r.notes_md, /quality\s+ERROR.*PROMPT_TOO_LONG/);
139
+ });
140
+
141
+ // ---------- 3. the server ----------
142
+
143
+ // What ship-grade-steps.buildGradePayload forwards for the panel above.
144
+ const OVERFLOW_PAYLOAD = {
145
+ functional_fidelity: 9,
146
+ code_quality: null,
147
+ security_score: 9,
148
+ efficiency_score: 8,
149
+ notes_md: 'quality overflowed; the rest passed',
150
+ confidence: 0.3,
151
+ issues: [],
152
+ };
153
+ const OVERFLOW_META = { workers: PANEL_WITH_DEAD_QUALITY, panel_outcome: 'concerns' };
154
+
155
+ test('validateAndScore: corroborated null cq → lens UNAVAILABLE, score on the axis that ran, never the fabricated 4.5', () => {
156
+ const r = validateAndScore(OVERFLOW_PAYLOAD, OVERFLOW_META);
157
+ assert.equal(r.passed, false, 'a gating lens that did not run can never auto-pass');
158
+ assert.equal(r.signals.cq_lens_unavailable, true);
159
+ assert.notEqual(r.signals.schema_error, true);
160
+ assert.equal(r.rubric_scores.code_quality, null, 'json-null axis — the DB buckets it as unavailable, not a 0 fail');
161
+ assert.equal(r.rubric_scores.code_quality_effective, null);
162
+ assert.equal(r.rubric_scores.functional_fidelity, 9);
163
+ assert.equal(r.score, 9, 'the axis that ran — not (9+0)/2');
164
+ assert.match(r.notes_md, /lens unavailable/);
165
+ assert.match(r.notes_md, /manual-confirm/);
166
+ assert.doesNotMatch(r.notes_md, /code_quality below/);
167
+ assert.doesNotMatch(r.notes_md, /cq=0\//);
168
+ });
169
+
170
+ test('validateAndScore: null cq with a HEALTHY Quality in signals → fail-closed schema_error (injection shape)', () => {
171
+ const r = validateAndScore(OVERFLOW_PAYLOAD, { workers: [
172
+ healthy('narc', { functional_fidelity: 9 }),
173
+ { ...healthy('quality', { code_quality: null }) },
174
+ healthy('hacker', { security_score: 9 }),
175
+ ] });
176
+ assert.equal(r.passed, false);
177
+ assert.equal(r.signals.schema_error, true);
178
+ assert.notEqual(r.signals.cq_lens_unavailable, true);
179
+ assert.match(r.notes_md, /without a corroborated Quality outage/);
180
+ });
181
+
182
+ test('validateAndScore: null cq with an errored Quality that still raised a major issue → fail-closed (a finding wins)', () => {
183
+ const r = validateAndScore(OVERFLOW_PAYLOAD, { workers: [
184
+ healthy('narc', { functional_fidelity: 9 }),
185
+ { ...DEAD_QUALITY, issues: [{ severity: 'major', file: 'x.js', summary: 'real finding' }] },
186
+ healthy('hacker', { security_score: 9 }),
187
+ ] });
188
+ assert.equal(r.passed, false);
189
+ assert.equal(r.signals.schema_error, true);
190
+ assert.notEqual(r.signals.cq_lens_unavailable, true);
191
+ });
192
+
193
+ test('validateAndScore: null cq with NO workers signal at all → fail-closed schema_error', () => {
194
+ const r = validateAndScore(OVERFLOW_PAYLOAD, {});
195
+ assert.equal(r.passed, false);
196
+ assert.equal(r.signals.schema_error, true);
197
+ });
198
+
199
+ test('validateAndScore: a JUDGED cq of 0 beside an errored Quality is still a real FAIL (only corroborated null is a lens outage)', () => {
200
+ const r = validateAndScore({ ...OVERFLOW_PAYLOAD, code_quality: 0, confidence: 0.9 }, OVERFLOW_META);
201
+ assert.equal(r.passed, false);
202
+ assert.notEqual(r.signals.cq_lens_unavailable, true);
203
+ assert.equal(r.rubric_scores.code_quality, 0);
204
+ assert.match(r.notes_md, /code_quality below/);
205
+ });
206
+
207
+ test('validateAndScore: both lenses out (null ff + null cq, both corroborated) → unavailable, score 0, no pass', () => {
208
+ const r = validateAndScore({ ...OVERFLOW_PAYLOAD, functional_fidelity: null }, { workers: [
209
+ { kind: 'narc', verdict: 'fail', error: 'SUBAGENT_TIMEOUT', scores: {}, issues: [] },
210
+ DEAD_QUALITY,
211
+ healthy('hacker', { security_score: 9 }),
212
+ ] });
213
+ assert.equal(r.passed, false);
214
+ assert.equal(r.signals.ff_lens_unavailable, true);
215
+ assert.equal(r.signals.cq_lens_unavailable, true);
216
+ assert.equal(r.score, 0);
217
+ assert.equal(r.rubric_scores.functional_fidelity, null);
218
+ assert.equal(r.rubric_scores.code_quality, null);
219
+ });
220
+
221
+ test('validateAndScore: the healthy path is byte-for-byte unchanged (both axes judged → PASS at (9+8)/2)', () => {
222
+ const r = validateAndScore({ functional_fidelity: 9, code_quality: 8, confidence: 0.9, issues: [], notes_md: '' }, {
223
+ workers: [healthy('narc', { functional_fidelity: 9 }), healthy('quality', { code_quality: 8 }), healthy('hacker', { security_score: 9 })],
224
+ });
225
+ assert.equal(r.passed, true);
226
+ assert.equal(r.score, 8.5);
227
+ assert.notEqual(r.signals.cq_lens_unavailable, true);
228
+ });
229
+
230
+ // ---------- the builder-facing card ----------
231
+
232
+ test('formatGradeOutcome: a Quality-lens outage reads as UNAVAILABLE + manual-confirm, never "FAIL 4.5 — fix the issues"', () => {
233
+ const g = validateAndScore(OVERFLOW_PAYLOAD, OVERFLOW_META);
234
+ const out = formatGradeOutcome(g, 1003318);
235
+ assert.match(out, /QUALITY LENS UNAVAILABLE/i);
236
+ assert.match(out, /not a quality rejection/i);
237
+ assert.match(out, /Prompt is too long/, 'the CLI line explains the outage');
238
+ assert.match(out, /--regrade/);
239
+ assert.match(out, /\/confirm/, 'manual-confirm is the release valve');
240
+ assert.doesNotMatch(out, /Fix the issues above/);
241
+ assert.doesNotMatch(out, /blended 4\.5/);
242
+ assert.doesNotMatch(out, /code_quality:\s+0\/10/);
243
+ });
244
+
245
+ // ---------- the review's adversarial shapes (a dark lens never softens the gate) ----------
246
+
247
+ await atest('runWorker: a SUBAGENT_TIMEOUT whose partial stdout quotes the overflow sentence keeps its timeout tag', async () => {
248
+ const r = await runWorker(STUB_QUALITY, {
249
+ task: {}, diff: '', changedFiles: [], model: 'm',
250
+ runSubagentFn: async () => {
251
+ throw Object.assign(new Error('timeout'), { code: 'SUBAGENT_TIMEOUT', run_ms: 360000, stdout: `quoting: ${OVERFLOW_LINE}`, stderr: '' });
252
+ },
253
+ });
254
+ assert.equal(r.error, 'SUBAGENT_TIMEOUT', 'the #467 budget signal is never relabelled');
255
+ });
256
+
257
+ await atest('runWorker: stdout is consulted only when stderr is silent (the model may quote a diff that mentions the phrase)', async () => {
258
+ const r = await runWorker(STUB_QUALITY, {
259
+ task: {}, diff: '', changedFiles: [], model: 'm',
260
+ runSubagentFn: async () => { throw nonzeroExit({ exitCode: 1, stderr: 'Invalid API key · Run /login', stdout: `diff says: ${OVERFLOW_LINE}` }); },
261
+ });
262
+ assert.equal(r.error, 'SUBAGENT_NONZERO_EXIT');
263
+ assert.equal(r.retried, true);
264
+ });
265
+
266
+ const BLOCKER = { severity: 'blocker', file: 'src/gds/routes/x.js', summary: 'ungated write route' };
267
+ const HACKER_FAIL_META = { workers: [
268
+ healthy('narc', { functional_fidelity: 9 }),
269
+ DEAD_QUALITY,
270
+ { kind: 'hacker', verdict: 'fail', scores: { security_score: 2 }, issues: [BLOCKER], confidence: 0.9, error: null },
271
+ ], panel_outcome: 'fail' };
272
+
273
+ test('validateAndScore: a Hacker BLOCKER beside a dead Quality is a FAIL with a missing axis, never an outage', () => {
274
+ const r = validateAndScore({ ...OVERFLOW_PAYLOAD, security_score: 2, confidence: 0, issues: [BLOCKER] }, HACKER_FAIL_META);
275
+ assert.equal(r.passed, false);
276
+ assert.equal(r.signals.cq_lens_unavailable, true, 'the axis really is unavailable');
277
+ assert.equal(r.rubric_scores.code_quality, null);
278
+ assert.doesNotMatch(r.notes_md, /not a quality rejection/);
279
+ assert.doesNotMatch(r.notes_md, /manual-confirm available/);
280
+ assert.match(r.notes_md, /FAIL/);
281
+ });
282
+
283
+ test('formatGradeOutcome: with a blocker from another lens the card prints the findings, not the outage invitation', () => {
284
+ const g = validateAndScore({ ...OVERFLOW_PAYLOAD, security_score: 2, confidence: 0, issues: [BLOCKER] }, HACKER_FAIL_META);
285
+ const out = formatGradeOutcome(g, 1003318);
286
+ assert.doesNotMatch(out, /QUALITY LENS UNAVAILABLE/);
287
+ assert.match(out, /Issues \(1\)/);
288
+ assert.match(out, /ungated write route/);
289
+ assert.match(out, /Fix the issues above/);
290
+ });
291
+
292
+ test('formatGradeOutcome: an authorization refusal outranks the lens outage (task 1002614 stays closed)', () => {
293
+ const r = validateAndScore(OVERFLOW_PAYLOAD, OVERFLOW_META);
294
+ const g = { ...r, signals: { ...r.signals, server_guard: { tripped: [{ guard: 'permission_path' }], authorization_refusal: true } } };
295
+ const out = formatGradeOutcome(g, 1003318);
296
+ assert.match(out, /REFUSED \(authorization\)/);
297
+ assert.doesNotMatch(out, /QUALITY LENS UNAVAILABLE/);
298
+ assert.doesNotMatch(out, /--regrade re-runs the same server check[\s\S]*\/confirm/, 'no confirm curl after a refusal');
299
+ });
300
+
301
+ test('validateAndScore: a forged cq_lens_unavailable flag on a judged grade is cleared, and the card prints the ordinary FAIL', () => {
302
+ const r = validateAndScore({ functional_fidelity: 3, code_quality: 2, confidence: 0.9, issues: [], notes_md: '' }, {
303
+ cq_lens_unavailable: true, ff_lens_unavailable: true,
304
+ workers: [healthy('narc', { functional_fidelity: 3 }), healthy('quality', { code_quality: 2 }), healthy('hacker', { security_score: 9 })],
305
+ });
306
+ assert.equal(r.passed, false);
307
+ assert.equal('cq_lens_unavailable' in r.signals, false, 'client flag must not survive');
308
+ assert.equal('ff_lens_unavailable' in r.signals, false);
309
+ assert.equal(r.rubric_scores.code_quality, 2);
310
+ const out = formatGradeOutcome(r, 1003318);
311
+ assert.doesNotMatch(out, /QUALITY LENS UNAVAILABLE/);
312
+ assert.match(out, /Subagent grade: FAIL/);
313
+ });
314
+
315
+ test('validateAndScore: the outage line still names an absent / low confidence (the #489 signal is not hidden)', () => {
316
+ const absent = validateAndScore({ ...OVERFLOW_PAYLOAD, confidence: null }, OVERFLOW_META);
317
+ assert.equal(absent.passed, false);
318
+ assert.match(absent.notes_md, /UNAVAILABLE —[^\n]*confidence absent/);
319
+ const low = validateAndScore(OVERFLOW_PAYLOAD, OVERFLOW_META); // confidence 0.3 — the panel's fallthrough value
320
+ assert.match(low.notes_md, /UNAVAILABLE —[^\n]*low confidence 0\.3/);
321
+ });
322
+
323
+ console.log(`\n${passed} passed, ${failed} failed`);
324
+ process.exit(failed ? 1 : 0);