@bongos/core 1.19.387 → 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.
Files changed (69) hide show
  1. package/.bongos-core.json +144 -59
  2. package/.claude/skills/grade-recover/SKILL.md +1 -1
  3. package/.claude/skills/grader-health/SKILL.md +1 -1
  4. package/config/branding.neutral.json +19 -19
  5. package/config/design-tokens.neutral.json +31 -36
  6. package/docs/adr/0196-chrome-world-token-contract-and-the-landing-sheets.md +31 -0
  7. package/docs/adr/README.md +1 -0
  8. package/docs/branding-contract.md +1 -1
  9. package/docs/copy-inventory.md +93 -67
  10. package/docs/copy-registry.json +411 -133
  11. package/docs/file-map.md +15 -0
  12. package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
  13. package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
  14. package/modules/dev-box/app/assets/tray/win-off.png +0 -0
  15. package/modules/dev-box/app/assets/tray/win-on.png +0 -0
  16. package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
  17. package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
  18. package/modules/dev-box/app/build-resources/icon.png +0 -0
  19. package/modules/dev-box/app/renderer/index.html +1 -1
  20. package/modules/dev-box/app/renderer/styles.css +3 -1
  21. package/modules/dev-box/app/renderer/theme.generated.css +13 -13
  22. package/modules/dev-box/app/scripts/gen-brand.js +13 -13
  23. package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +3 -3
  24. package/modules/dev-box/app/scripts/gen-icons.js +3 -3
  25. package/modules/dev-box/app/src/brand.generated.json +1 -1
  26. package/modules/grading/grader-config.js +5 -3
  27. package/modules/grading/grader-rubric.json +2 -2
  28. package/modules/grading/grader-score.js +86 -33
  29. package/modules/grading/grader-workers/index.js +19 -0
  30. package/modules/hall-ui/public/builders.js +8 -3
  31. package/modules/provisioning/routes/provisioning.js +4 -4
  32. package/modules/status-ui/public/explore.html +12 -6
  33. package/package-lock.json +2 -2
  34. package/package.json +1 -1
  35. package/public-landing/assets/cosmos.css +82 -64
  36. package/public-landing/assets/world/og-card.jpg +0 -0
  37. package/public-landing/assets/world/r2-bongos-chrome-a-cut.webp +0 -0
  38. package/public-landing/assets/world/r2-bongos-chrome-a-cut.webp.json +12 -0
  39. package/public-landing/assets/world/r2-bongos-chrome-b.jpg +0 -0
  40. package/public-landing/assets/world/r2-bongos-chrome-b.jpg.json +12 -0
  41. package/public-landing/assets/world/r2-chrome-blob-one-cut.webp +0 -0
  42. package/public-landing/assets/world/r2-chrome-blob-one-cut.webp.json +13 -0
  43. package/public-landing/assets/world/r2-chrome-blob-one.jpg +0 -0
  44. package/public-landing/assets/world/r2-chrome-blob-one.jpg.json +12 -0
  45. package/public-landing/assets/world/r2-chrome-blobs-set-cut.webp +0 -0
  46. package/public-landing/assets/world/r2-chrome-blobs-set-cut.webp.json +13 -0
  47. package/public-landing/assets/world/r2-chrome-blobs-set.jpg +0 -0
  48. package/public-landing/assets/world/r2-chrome-blobs-set.jpg.json +12 -0
  49. package/public-landing/assets/world.css +241 -0
  50. package/public-landing/contact.html +1 -1
  51. package/public-landing/index.html +1139 -580
  52. package/public-landing/privacy.html +1 -1
  53. package/public-landing/projects.html +2 -2
  54. package/public-landing/terms.html +1 -1
  55. package/scripts/gds/ship-grade-steps.js +27 -0
  56. package/src/bongos/platform-visibility-gate.js +8 -7
  57. package/src/bongos/stealth-gate.js +9 -8
  58. package/src/module-api.js +5 -1
  59. package/src/ui/adapters/claude-design/index.js +2 -2
  60. package/tests/cloud_bongos_pack.mjs +22 -18
  61. package/tests/cosmos_token_contrast.mjs +23 -8
  62. package/tests/grader.mjs +7 -6
  63. package/tests/grader_quality_window_overflow.mjs +324 -0
  64. package/tests/landing_legal_pages.mjs +8 -5
  65. package/tests/landing_page.mjs +295 -166
  66. package/tests/platform_boot.mjs +3 -1
  67. package/tests/projects_hub.mjs +9 -5
  68. package/tests/projects_hub_pre_uat.mjs +3 -1
  69. package/tests/world_tokens.mjs +351 -0
@@ -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);
@@ -94,13 +94,16 @@ test('the currency token reads as the instance names it (terms)', () => {
94
94
  test('the landing page and the hub footers link privacy, terms and contact', () => {
95
95
  for (const file of ['index.html', 'projects.html']) {
96
96
  const html = read(file);
97
- const foot = html.match(/<(?:div|footer) class="foot">([\s\S]*?)<\/(?:div|footer)>/);
98
- assert.ok(foot, `${file}: the footer survives`);
97
+ // the landing's footer is a bare <footer> since task 1003298 (the chrome world);
98
+ // the hub still carries the cosmos.css <div class="foot"> until task 1003299
99
+ const m = html.match(/<footer[^>]*>([\s\S]*?)<\/footer>/) || html.match(/<div class="foot">([\s\S]*?)<\/div>/);
100
+ assert.ok(m, `${file}: the footer survives`);
101
+ const foot = m[1];
99
102
  for (const href of ['/privacy', '/terms', '/contact']) {
100
- assert.match(foot[1], new RegExp(`<a href="${href}">`), `${file}: footer links ${href}`);
103
+ assert.match(foot, new RegExp(`<a href="${href}">`), `${file}: footer links ${href}`);
101
104
  }
102
- assert.match(foot[1], /Open source \(AGPL-3\.0\)/, `${file}: the licence link stays`);
103
- assert.match(foot[1], /Built by agents/, `${file}: the attribution half stays`);
105
+ assert.match(foot, /Open source \(AGPL-3\.0\)/, `${file}: the licence link stays`);
106
+ assert.match(foot, /Built by agents/, `${file}: the attribution half stays`);
104
107
  }
105
108
  });
106
109