Skip to main content
The Business Hub renders three families of visual, non-gating charts: the Quality & Risk dials (Command Center), the Cost & Budget spend panels, and Run Analytics (Gantt + per-stage cost/tokens + the parallel benchmark). All three are server-owned projections — the client never re-derives a score or re-computes a band — and all three follow one rule that this codebase enforces deliberately: a missing signal renders as unknown, never as a fabricated zero. A real 0 (contracts exist, nothing was flagged, no spend was recorded against a real cap) is a different, reportable state from “no source was ever present.” These are BI surfaces only — nothing here blocks a gate or changes a verdict.

Quality & Risk dials

Computed in src/observability/dashboard/state/quality-risk.js by computeQualityRisk(run, ...), and assembled per-focus-run by buildQualityRiskProjection(state, ...). The focus run is the active run, else the newest run with a pipelineRollup, else the newest run (focusRun). Aggregated Risk Score (0-100, higher = riskier). Rolled from three real sources, not a bare count:
  • Builder-reported risks, read from either task.builder.risks (live) or the index-persisted task.builder_risk.severity_tally (#527) — the same tally, so a run scores identically whether served fully-parsed or from the Hub’s rollup index. Each risk is weighted by severity (RISK_SEVERITY_WEIGHTS: critical: 25, high: 12, medium: 5, low: 2) and discounted to 0.25 of its weight only when genuinely mitigated (isRiskMitigated — status mitigated/resolved/closed/fixed, or a non-empty mitigation string). An accepted risk is explicitly not mitigated — a knowingly-retained residual keeps its full weight.
  • guardrail_triggered events (GUARDRAIL_BLOCK_WEIGHT: 15), guardrail_overridden events (GUARDRAIL_OVERRIDE_WEIGHT: 8), and task_blocked_by_validator events (VALIDATOR_BLOCK_WEIGHT: 10).
The raw weighted sum is clamped to 0-100 (clampScore). The score is null — honest unknown — only when none of those sources exist at all (riskSignalPresent is false): no builder contract with risks, no guardrail block, no guardrail override, no validator block. If contracts exist and genuinely report nothing, the score is a real 0. Complexity Index (0-100, higher = more complex). Built from structural signal only, never self-report: files touched across builder tasks (capped at FILES_CAP = 200, worth up to 50 pts), number of builder tasks (capped at 15, up to 25 pts), and execution_recorded events from the transient-sandbox execution evidence (capped at 15, up to 25 pts). Same honest-null rule: null only when there are zero builder tasks, zero files touched, and zero executions. Both scores map to a labelled band via bandFor — a null score always maps to the literal band 'unknown': Cost-to-Value (optional) pairs cumulative cost (summed from pipelineRollup.stages[].cost_usd) against proof coverage percent (read from the readiness projection or the Evidence Center summary), yielding cost_per_coverage_pointnull whenever coverage is unknown or 0 (a division the code refuses to fake). Execution posture reports how many recorded executions were container-verified vs. self-reported-only, alongside PASS/FAIL counts.

Rendering

renderQualityRiskCard in src/observability/dashboard/ui/pages/command-center.js reads s.overview.qualityRisk (or the top-level s.qualityRisk fallback) and draws each score as an SVG ring (qrDialHtml) using pathLength="100" so the dash length is the score — a stable node whose stroke-dasharray attribute is patched by the morph renderer, which is what makes the value change animate as a draw rather than a re-render. A non-numeric or out-of-range score is re-guarded client-side (qrNumericScore) even though the projection already clamps 0-100 — belt and braces against a NaN ever reaching stroke-dasharray. An unknown score draws the empty track only, never a zero-length arc (a drawn arc implies a measured value, even a zero one). Chips beneath the dials surface the severity breakdown, mitigated/accepted/guardrail/validator counts, files touched, builder task count, and execution count — all read straight off the projection, no re-derivation.

Cost & Budget

Rendered by src/observability/dashboard/ui/pages/cost-budget.js into #page-cost-budget. Three concerns are kept visually separate so a configured cap can never be mistaken for actual spend:
  1. Current Enforced Policy (configuredBudgetPolicyHtml) — the project’s .rstack/budget.json as validated, per project in scope. availability is one of configured / invalid / inaccessible / (implicitly) missing; only configured renders the run/day/month caps (configuredCapHtml), and only the run cap is annotated “Enforced by goal loop” — day/month caps are configured policy only, with no observed loop enforcement claimed for them.
  2. Budget Consumption — Loop Cost Brake (budgetGovernanceHtml) — actual measured spend against run.loopBudgetUsd, the exact run_budget_usd value the goal loop’s cost brake reads before every iteration. Runs render only when cap.runBudgetUsd !== null; each row shows percent-of-cap used, headroom, or (if cap.status === 'exhausted') “cap reached — the loop will not start another iteration.” A distinct enforcement_stale status renders “position unavailable” when the loop brake’s own metrics.json is stale or missing relative to the event-derived spend — the UI won’t claim a live position it can’t back.
  3. Cost per Run / Spend by Stage (costSummaryHtml, costRunRowsHtml, stageCostAcrossRunsHtml) — actual tracked spend, sourced from either persisted metrics.json totals or a recomputation from the event stream, tagged with a provenance pill (moneySourcePill: “persisted metrics” / “recomputed from events” / “no telemetry”). Zero runs in scope, no telemetry recorded, and “runs exist but nothing reports cost yet” are three distinct empty states — none of them render $0.00.
spendPolicyHistoryHtml additionally compares each run’s budget-policy snapshot at start time against the current .rstack/budget.json, flagging drift (comparison: 'differs') per field.

Run Analytics

Rendered by src/observability/dashboard/ui/pages/run-analytics.js into #page-run-analytics, per selected run (ANALYTICS_RUN_ID):
  • KPI row (analyticsKpisHtml) — duration, tool calls, tasks passed/failed, average quality, cost, and tokens. Cost only renders when source !== 'none' and either cost_usd > 0 or a tokenTotals object exists; otherwise it shows with “no cost telemetry yet” rather than $0.0000. Tokens carry the same provenance pill as Cost & Budget.
  • Gantt (ganttHtml, shared with the run drawer in ui/lib.js) — one row per task segment with a started_at, positioned/sized proportionally across the run’s real time span; bar color is pass / fail / running from the segment’s status, and a still-running segment (no ended_at) labels its duration “running” instead of computing a fake elapsed time.
  • Cost & Tokens by Stage (stageMoneyHtml) — per-stage bars from the persisted metrics.json stageCost/stageTokens maps; a stage with no cost entry shows “cost n/a” for that bar rather than treating a missing key as zero.
  • Parallel Benchmark (benchmarkPanelHtml) — reads artifacts/parallel-benchmark.json (produced by scripts/bench-parallel.mjs) and renders sequential-vs-parallel bars plus an honest mode badge: mode === 'real' draws “measured — real stages” (green), anything else draws “modelled — mock workload” (amber) — a mock measurement is never allowed to look like a live one. A missing artifact, an unparseable one, and one with non-finite seq_time_ms/par_time_ms are three distinct, clearly-labelled failure states.
  • Stage durations / run trend table (renderStageBars, renderTrendTable) — average elapsed time per stage across runs, and a sortable per-run history row (duration, tool calls, passed/failed, quality, cost) built from s.trends.

Try it

Open the Business Hub and select a run in Run Analytics to see its Gantt and per-stage spend:
Then navigate to Command Center for the Quality & Risk dials, Cost & Budget for spend and policy, and Run Analytics for the Gantt/benchmark view. All three update live as the run’s events.jsonl and metrics.json change — nothing here requires a page reload.
Every score, band, and dollar figure on these pages is computed server-side from the run’s real artifacts and events (state/quality-risk.js, the metrics/rollup readers). If a chart looks empty, it’s because the underlying signal — a builder contract, a metrics.json write, a parallel-benchmark.json artifact — hasn’t been produced yet, not because the Hub swallowed an error.