@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
package/docs/file-map.md CHANGED
@@ -9,6 +9,9 @@
9
9
  /
10
10
  ├── CLAUDE.md ← index + framing (descriptive; DB is source of truth for state)
11
11
  ├── README.md ← public-facing one-paragraph summary
12
+ ├── PRODUCT.md ← durable product truth for the apex/hall/status surfaces (impeccable init, task 1003296): users, positioning, constraints, brand commitments, principles — no palette, no type
13
+ ├── DESIGN.md ← the visual rulebook written FROM the built landing (task 1003298, ADR 0196): palette + derived recipes, type, materials (the chrome plates), components, the motion grammar, both modes; what H1 (hall) and S1 (status) build against. Sidecar .impeccable/design.json
14
+ ├── .impeccable/ ← impeccable skill artifacts: config.json (buildPath: code), surfaces/<route>.md (per-route briefs), mocks/world-assets/ (Higgsfield renders + prompt sidecars; the SOURCE of public-landing/assets/world/), mocks/decision/ (A1 comps), design.json (DESIGN.md sidecar), review/ (finish captures)
12
15
  ├── .gitignore
13
16
  ├── package.json / package-lock.json
14
17
  ├── CODEOWNERS ← owner review on the merge-gate surfaces (real since task 1002611, ADR 0159); inert until branch protection is on (scripts/gds/gating.js) — keep aligned with gate-review.js HARD_FLOOR_GLOBS
@@ -246,6 +249,17 @@
246
249
  │ ├── assets/tray/ + build-resources/ ← generated icons (scripts/gen-icons.js; build-resources name dodges the global build/ gitignore)
247
250
  │ └── test/unit.mjs ← hermetic unit tests (run by the CI workflow, not the root gate)
248
251
  │
252
+ ├── public-landing/ ← the APEX front door + hub surfaces served by src/platform-server.js (landingPage(): apex-only strip → brand-token stamp → theme injection → ?v= asset stamp). Ships to EVERY instance; <!--apex-only:start/end--> regions are removed on a child
253
+ │ ├── index.html ← the landing (/) — v3 "the template's page" of the chrome world (task 1003298, ADR 0196): server-stamped head, the breath (CSS-only signature), live figures/ledger/orbs from the public feeds baked inside sentinels; page-local <style> only (no :root), links /assets/world.css
254
+ │ ├── projects.html ← the hub (/projects?view=map|mine|manage|new) on cosmos.css (A4, task 1003299, rebuilds it in the world)
255
+ │ ├── privacy.html / terms.html / contact.html ← the footer doors (task 1003251), on cosmos.css
256
+ │ ├── assets/
257
+ │ │ ├── world.css ← the WORLD token layer (ADR 0196): contract tier = the pack, derived tier both modes, root-absolute plate token pairs, the reduced-motion kill switch; no page composition
258
+ │ │ ├── world/ ← the six production plates + <file>.json provenance sidecars (embed-prompt.mjs --scan clean): r2-chrome-blobs-set.jpg, r2-chrome-blob-one.jpg, r2-bongos-chrome-b.jpg (black plates, dark mode, lighten) and their -cut.webp alpha twins (light mode); og-card.jpg (the social card, captured from the page)
259
+ │ │ ├── cosmos.css ← the HUB component sheet (task 1002696): contract tier synced to the pack, its own derived + dark tiers, the .btn/.chip/.strip/.proj/.tbl vocabulary; measured by tests/cosmos_token_contrast.mjs
260
+ │ │ └── verro-medallion.png ← the Verro Labs mark (apex-only block)
261
+ │ └── world/assets/ ← the RETIRED gate's clouds/creatures/plates (task 1002407; the gate folded into / in 1003233) — not the chrome plates
262
+ │
249
263
  ├── public/ ← example.com — the game (product)
250
264
  │ ├── index.html
251
265
  │ ├── assets/
@@ -321,6 +335,7 @@ tests/
321
335
  ├── terrain_picker.mjs ← (ADR 0012): picker dispatch shape + GROUND-ONLY invariant (objects must never affect tile selection).
322
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.
323
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.
324
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.
325
340
  ├── session_search.mjs ← (6D.2): the session-search ORDER BY whitelist (injection guard) + LIMIT clamp.
326
341
  ├── session_eval.mjs ← (6D.3 + 6D.4): classifyTurn / computeOverhead / detectFootguns / summarizeSystemicFindings, incl. a #506-shaped fixture.
@@ -7,7 +7,7 @@
7
7
  <title>Dev Box</title>
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
- <link href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700&family=Quicksand:wght@400;500;600;700&display=swap" rel="stylesheet">
10
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap" rel="stylesheet">
11
11
  <link rel="stylesheet" href="theme.generated.css">
12
12
  <link rel="stylesheet" href="styles.css">
13
13
  </head>
@@ -154,7 +154,9 @@ main {
154
154
  width: 100%;
155
155
  margin-top: 10px;
156
156
  }
157
- .btn:hover:not(:disabled) { background: var(--accent); border-color: var(--accent); }
157
+ /* the hover fill is the raw accent; the chrome world's accent carries a light ink
158
+ at 2.19:1, so the label flips to --ink on it (8.12:1) — task 1003298 */
159
+ .btn:hover:not(:disabled) { background: var(--accent); border-color: var(--accent); color: var(--ink); }
158
160
  .btn:disabled { opacity: 0.55; cursor: default; }
159
161
  .btn--ghost {
160
162
  background: transparent;
@@ -1,16 +1,16 @@
1
1
  /* GENERATED by scripts/gen-brand.js from config/branding.json — do not edit by hand. */
2
2
  :root {
3
- --bg: #f9d9a8;
4
- --bg-card: #fffaf2;
5
- --bg-deep: #f6d8ae;
6
- --ink: #2b170e;
7
- --ink-soft: #6d4a33;
8
- --ink-faint: #785a41;
9
- --rule: #e8a463;
10
- --rule-soft: #f6d8ae;
11
- --accent: #ce4806;
12
- --accent-deep: #9e3406;
13
- --terracotta: #855b3d;
14
- --font-display: "Baloo 2", "Quicksand", system-ui, sans-serif;
15
- --font-body: "Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif;
3
+ --bg: #edecee;
4
+ --bg-card: #fbfafc;
5
+ --bg-deep: #e2e1e4;
6
+ --ink: #17181a;
7
+ --ink-soft: #55565a;
8
+ --ink-faint: #65656a;
9
+ --rule: #c6c6c8;
10
+ --rule-soft: #dcdbdd;
11
+ --accent: #fb9569;
12
+ --accent-deep: #a3481a;
13
+ --terracotta: #c9512c;
14
+ --font-display: "Manrope", "Segoe UI", system-ui, -apple-system, sans-serif;
15
+ --font-body: "Manrope", "Segoe UI", system-ui, -apple-system, sans-serif;
16
16
  }
@@ -73,7 +73,7 @@ function buildBrand(branding) {
73
73
  // Panel header epigraph (branding copy.tagline).
74
74
  TAGLINE: (branding.copy && branding.copy.tagline) || '',
75
75
  // Pre-CSS window background (main.js) — the theme bg so first paint is on-brand.
76
- BG_COLOR: colors.bg || '#fffaf2',
76
+ BG_COLOR: colors.bg || '#fbfafc',
77
77
  };
78
78
  }
79
79
 
@@ -85,19 +85,19 @@ function buildThemeCss(branding) {
85
85
  const v = (x, fb) => x || fb;
86
86
  return `/* GENERATED by scripts/gen-brand.js from config/branding.json — do not edit by hand. */
87
87
  :root {
88
- --bg: ${v(c.bg, '#f9d9a8')};
89
- --bg-card: ${v(c.bgCard, '#fffaf2')};
90
- --bg-deep: ${v(c.bgDeep, '#f6d8ae')};
91
- --ink: ${v(c.ink, '#2b170e')};
92
- --ink-soft: ${v(c.inkSoft, '#6d4a33')};
88
+ --bg: ${v(c.bg, '#edecee')};
89
+ --bg-card: ${v(c.bgCard, '#fbfafc')};
90
+ --bg-deep: ${v(c.bgDeep, '#e2e1e4')};
91
+ --ink: ${v(c.ink, '#17181a')};
92
+ --ink-soft: ${v(c.inkSoft, '#55565a')};
93
93
  --ink-faint: ${v(c.inkFaint, '#785a41')};
94
- --rule: ${v(c.rule, '#e8a463')};
95
- --rule-soft: ${v(c.ruleSoft, '#f6d8ae')};
96
- --accent: ${v(c.accent, '#ce4806')};
97
- --accent-deep: ${v(c.accentDeep, '#9e3406')};
98
- --terracotta: ${v(c.terracotta, '#855b3d')};
99
- --font-display: ${v(fonts.display, '"Baloo 2", "Quicksand", system-ui, sans-serif')};
100
- --font-body: ${v(fonts.body, '"Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif')};
94
+ --rule: ${v(c.rule, '#c6c6c8')};
95
+ --rule-soft: ${v(c.ruleSoft, '#dcdbdd')};
96
+ --accent: ${v(c.accent, '#fb9569')};
97
+ --accent-deep: ${v(c.accentDeep, '#a3481a')};
98
+ --terracotta: ${v(c.terracotta, '#c9512c')};
99
+ --font-display: ${v(fonts.display, '"Manrope", "Segoe UI", system-ui, sans-serif')};
100
+ --font-body: ${v(fonts.body, '"Manrope", "Segoe UI", system-ui, -apple-system, sans-serif')};
101
101
  }
102
102
  `;
103
103
  }
@@ -68,9 +68,9 @@ ICON = 100 # dmg.iconSize
68
68
  # it, so its "fallback" would paint a Cloud Bongos colour into a fully-specified
69
69
  # host build. That is exactly what `sea` did to the Applications-well rim.
70
70
  COLOR_ROLES = {
71
- "bg": "#f9d9a8", "bgDeep": "#f6d8ae", "bgCard": "#fffaf2",
72
- "ink": "#2b170e", "inkSoft": "#6d4a33", "ruleSoft": "#f6d8ae",
73
- "accent": "#ce4806", "accentDeep": "#9e3406", "terracotta": "#855b3d",
71
+ "bg": "#edecee", "bgDeep": "#e2e1e4", "bgCard": "#fbfafc",
72
+ "ink": "#17181a", "inkSoft": "#55565a", "ruleSoft": "#dcdbdd",
73
+ "accent": "#fb9569", "accentDeep": "#a3481a", "terracotta": "#c9512c",
74
74
  }
75
75
 
76
76
  FONT_DIRS = (
@@ -62,9 +62,9 @@ const OUT_BUILD = path.join(APP_DIR, 'build-resources');
62
62
  // it has nothing prompting a host to define it, so its "fallback" would paint a
63
63
  // Cloud Bongos colour into a fully-specified host build.
64
64
  const COLOR_ROLES = {
65
- bgCard: '#fffaf2', bgDeep: '#f6d8ae', ink: '#2b170e',
66
- rule: '#e8a463', ruleSoft: '#f6d8ae',
67
- accent: '#ce4806', accentDeep: '#9e3406', terracotta: '#855b3d',
65
+ bgCard: '#fbfafc', bgDeep: '#e2e1e4', ink: '#17181a',
66
+ rule: '#c6c6c8', ruleSoft: '#dcdbdd',
67
+ accent: '#fb9569', accentDeep: '#a3481a', terracotta: '#c9512c',
68
68
  };
69
69
 
70
70
  // The pack, resolved exactly as gen-brand.js resolves it (env → instance → neutral).
@@ -9,5 +9,5 @@
9
9
  "COPYRIGHT": "© Cloud Bongos",
10
10
  "CONFIG_DIR": "cloudbongos",
11
11
  "TAGLINE": "Building Digitally, as simple as Bongos.",
12
- "BG_COLOR": "#f9d9a8"
12
+ "BG_COLOR": "#edecee"
13
13
  }
@@ -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>
@@ -99,8 +99,8 @@ function esc(s) {
99
99
  const ACCENT = (() => {
100
100
  try {
101
101
  const c = api.branding().theme?.ui?.colors || {};
102
- return { sun: c.accent || '#ce4806', sun600: c.accentDeep || '#9e3406' };
103
- } catch { return { sun: '#ce4806', sun600: '#9e3406' }; }
102
+ return { sun: c.accent || '#fb9569', sun600: c.accentDeep || '#a3481a' };
103
+ } catch { return { sun: '#fb9569', sun600: '#a3481a' }; }
104
104
  })();
105
105
 
106
106
  // Brand styling for the standalone onboarding pages — sunrise palette + Baloo 2 /
@@ -108,7 +108,7 @@ const ACCENT = (() => {
108
108
  // under the instance's own config/branding.json). Light + dark aware; no external
109
109
  // dependency beyond an optional Google-Fonts link (system fallback if it doesn't load).
110
110
  const PAGE_STYLE = `
111
- :root{color-scheme:light dark;--sun:${ACCENT.sun};--sun600:${ACCENT.sun600};--coral:#fb7185;--sky:#38bdf8;--sky600:#0284c7;--ok:#16a34a;--warn:#f59e0b;--ink:#2a1f17;--inkSoft:#6b5d52;--card:#fffdfa;--line:#f0e2cf;--bg1:#fde8c8;--bg2:#fff4e6;--bg3:#fffdfa}
111
+ :root{color-scheme:light dark;--sun:${ACCENT.sun};--sun600:${ACCENT.sun600};--sunInk:#2a1305;--coral:#fb7185;--sky:#38bdf8;--sky600:#0284c7;--ok:#16a34a;--warn:#f59e0b;--ink:#2a1f17;--inkSoft:#6b5d52;--card:#fffdfa;--line:#f0e2cf;--bg1:#fde8c8;--bg2:#fff4e6;--bg3:#fffdfa}
112
112
  @media (prefers-color-scheme:dark){:root{--ink:#f5efe6;--inkSoft:#cbbdad;--card:#241b13;--line:#3a2c1e;--bg1:#2a1d10;--bg2:#1b130b;--bg3:#130d07}}
113
113
  *{box-sizing:border-box}html,body{height:100%}
114
114
  body{margin:0;font-family:"Quicksand",system-ui,-apple-system,"Segoe UI",sans-serif;color:var(--ink);background:linear-gradient(180deg,var(--bg1) 0%,var(--bg2) 45%,var(--bg3) 100%);background-attachment:fixed;display:flex;align-items:center;justify-content:center;padding:1.5rem;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
@@ -135,7 +135,7 @@ code{font-family:"Fira Code",ui-monospace,SFMono-Regular,monospace;background:rg
135
135
  @media (prefers-reduced-motion:reduce){.step.active .dot,.step.active .dot::after{animation:none}}
136
136
  .status-line{min-height:1.4em;color:var(--inkSoft);font-size:.94rem;margin:.4rem auto 0;max-width:24rem;line-height:1.45}
137
137
  .cta{margin-top:1.4rem}
138
- .btn{display:inline-block;background:var(--sun);color:#fff;text-decoration:none;font-weight:700;font-family:"Baloo 2",system-ui,sans-serif;padding:.72rem 1.5rem;border-radius:999px;font-size:1.02rem;box-shadow:0 10px 22px -8px rgba(249,115,22,.7);transition:transform .15s ease,box-shadow .15s ease}
138
+ .btn{display:inline-block;background:var(--sun);color:var(--sunInk);text-decoration:none;font-weight:700;font-family:"Baloo 2",system-ui,sans-serif;padding:.72rem 1.5rem;border-radius:999px;font-size:1.02rem;box-shadow:0 10px 22px -8px rgba(249,115,22,.7);transition:transform .15s ease,box-shadow .15s ease}
139
139
  .btn:hover{transform:translateY(-1px);box-shadow:0 14px 28px -8px rgba(249,115,22,.8)}
140
140
  .muted{color:var(--inkSoft);opacity:.9;font-size:.85rem;margin:1.1rem auto 0;max-width:24rem;line-height:1.45}
141
141
  .hidden{display:none!important}
@@ -8,14 +8,19 @@
8
8
  /* Self-contained so the showcase renders correctly whether or not the host
9
9
  injects theme tokens. Warm Cloud Bongos defaults; overridable by the pack. */
10
10
  :root {
11
- --bg: #f9d9a8; --card: #fffaf2; --ink: #2b170e; --ink-soft: #6d4a33;
12
- --rule: #e8a463; --accent: #ce4806;
11
+ /* a hand-written mirror of config/branding.neutral.json theme.ui.colors —
12
+ this page is served without the branding injection, so a drifted value
13
+ renders a palette the product no longer paints (tests/design_tokens_sync.mjs).
14
+ Chrome world, task 1003298. --accent-deep is the TEXT-safe accent: the
15
+ salmon --accent is a fill and measures 2.1:1 as type on the card. */
16
+ --bg: #edecee; --card: #fbfafc; --ink: #17181a; --ink-soft: #55565a;
17
+ --rule: #c6c6c8; --accent: #fb9569; --accent-deep: #a3481a;
13
18
  }
14
19
  * { box-sizing: border-box; }
15
20
  body {
16
21
  margin: 0; padding: 2.5rem 1.25rem 4rem;
17
22
  background: var(--bg); color: var(--ink);
18
- font-family: "Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif;
23
+ font-family: "Manrope", system-ui, -apple-system, "Segoe UI", sans-serif;
19
24
  line-height: 1.5;
20
25
  }
21
26
  .wrap { max-width: 960px; margin: 0 auto; }
@@ -28,14 +33,15 @@
28
33
  padding: 1.1rem 1.2rem; text-decoration: none; color: inherit;
29
34
  display: flex; flex-direction: column; gap: .4rem; transition: transform .12s ease, box-shadow .12s ease;
30
35
  }
31
- .card:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(234,88,12,.12); }
36
+ .card:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,.12); }
32
37
  .card h2 { font-size: 1.15rem; margin: 0; }
33
38
  .card p { margin: 0; color: var(--ink-soft); font-size: .95rem; }
34
- .card .origin { color: var(--accent); font-size: .82rem; word-break: break-all; margin-top: auto; }
39
+ .card .origin { color: var(--accent-deep); font-size: .82rem; word-break: break-all; margin-top: auto; }
35
40
  .card .art { width: 100%; height: 120px; object-fit: cover; border-radius: 10px; margin-bottom: .3rem; }
36
41
  .empty, .err { text-align: center; color: var(--ink-soft); padding: 3rem 1rem; }
37
42
  @media (prefers-color-scheme: dark) {
38
- :root { --bg: #1a1410; --card: #241c16; --ink: #f5ece3; --ink-soft: #b9a998; --rule: #4a3728; }
43
+ :root { --bg: #000000; --card: #101012; --ink: #ffffff; --ink-soft: #c0c0c0; --rule: rgba(255,255,255,.16);
44
+ --accent-deep: color-mix(in srgb, var(--accent) 45%, #ffffff); }
39
45
  }
40
46
  </style>
41
47
  </head>
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.387",
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.387",
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.387",
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",