unitares-dashboard — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited unitares-dashboard (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The live dashboard is `dashboard/redesign/` — buildless (raw HTML/CSS/JS, no framework, no bundle), served by http_dashboard_redesign in src/http_api.py at / and /dashboard. The classic dashboard and its allowlist / script-load-chain / vite build were retired — ignore any older guidance about index.html, allowed_files, MetricColors, or Chart.defaults. There is no allowlist (a directory resolver serves redesign/**) and no restart needed (files are read per request).
A "section" is one nav tab. Each is a self-contained module that renders into its own mount and is wired in app.html.
A section is an IIFE that attaches window.<Name> = { load } (add retheme if it draws charts). load() renders into its mount on first call and updates in place on subsequent calls (so the 10s auto-refresh doesn't flicker or reset form state). Worked references: sections/eisv.js (charts, retheme) and sections/metrics.js (charts + a picker that survives auto-refresh).
dashboard/redesign/)| # | Do | File |
|---|---|---|
| 1 | Create the module sections/NAME.js → window.NAME = { load[, retheme] } | sections/NAME.js |
| 2 | Add a data accessor returning {source, data} via withFallback(liveFn, snapFn) | data.js |
| 3 | Add a snapshot mock so the section renders offline/portably | snapshot.js |
| 4 | Nav link <a href="#NAME" data-section="NAME">Name</a> | app.html (nav) |
| 5 | Pane <section class="section" data-pane="NAME" hidden> with <div id="NAME-mount"> | app.html (main) |
| 6 | <script src="./sections/NAME.js"></script> (order-independent — modules self-init via load) | app.html |
| 7 | lazyLoad entry: if (id === "NAME" && window.NAME) { loaded[id]=true; window.NAME.load(); } | app.html |
| 8 | If it's a live monitor view, add to RELOAD (10s tick) | app.html |
| 9 | If it draws charts, call window.NAME.retheme() in the theme-toggle handler | app.html |
Views never call fetch directly. They await DATA.x(), which returns { source: "live" | "snapshot", data }. The accessor tries the live endpoint (authFetch for REST, callTool for /v1/tools/call) and falls back to the bundled SNAPSHOT on any failure, so the dashboard renders portably (opened as a file, cross-origin, or server down). Badge freshness in the view with <span class="src-badge ${source}">${source}</span>.
async metricsCatalog() {
return withFallback(
async () => { const j = await authFetch("/v1/metrics/catalog");
return j && Array.isArray(j.metrics) ? j.metrics : null; },
() => S().metrics.catalog, // snapshot fallback
);
}Returning null/empty from liveFn triggers the snapshot fallback. For headline cards where a stale snapshot under a "live" badge would mislead, prefer returning null per-field and rendering "—" (see data.js::stats).
Chart.js still fights the theme, but the redesign fix is design tokens, not hardcoded hex. Read colours from CSS custom properties so the chart re-renders correctly in both ink (dark) and paper (light):
const cssVar = (n) => getComputedStyle(document.documentElement).getPropertyValue(n).trim();
const tick = cssVar("--muted"), grid = rgba(cssVar("--ink"), 0.06), line = cssVar("--accent");Then expose retheme() that rebuilds the chart from cached data (token values change on toggle), and call it from the theme handler in app.html. Copy the option shape from sections/eisv.js::baseOptions.
No date adapter. app.html loads chart.umd from CDN without chartjs-adapter-date-fns, so type: "time" scales will not work. Use a category x-axis with pre-formatted labels (e.g. MM-DD) — see sections/metrics.js::fmtLabel.
RELOAD[id] fires every ~10s while the section is active, the tab is visible, and no input is focused. So load() must: update charts/data in place (don't new Chart() every tick), and not rebuild a <select>/<input> the operator is using. The global tick already skips while an input is focused, so repopulating a closed dropdown on refresh is safe — but preserve the current selection. sections/metrics.js shows the first-render-then-update pattern.
The redesign sends only the read bearer token. Operator write actions (archive/resume agent, request review, discovery status) are not wired — they need the X-Unitares-Operator header under STRICT_IDENTITY (PLAN.md). Don't assume a section can mutate state; if you add a write surface, that's a deliberate new capability, not a copy-paste.
The dashboard is buildless, so the gate is lint plus a cheap logic check:
cd dashboard && npm run lint — must be 0 errors (warnings allowed).snapshot.js + data.js +sections/NAME.js in a jsdom stub with window.Chart stubbed and call NAME.load(); assert the mount populated and (for charts) the paint path ran. See the Metrics-port verification for the harness shape.
/#NAME against a running server; confirm thesrc-badge reads live and the section renders on real data.
retheme is wired).| Anti-pattern | Why it's wrong |
|---|---|
type: "time" Chart.js axis | No date adapter loaded — silently blank axis. Use category labels. |
Hardcoded hex / MetricColors | Classic-era; breaks the paper theme. Read tokens via getComputedStyle. |
new Chart() on every refresh tick | Flicker + leaks. Update datasets in place; rebuild only on theme change. |
Full innerHTML rebuild of a section with a live <select> | Clobbers operator's selection. First-render once, update in place. |
Calling fetch in a view | Bypasses the live-or-snapshot seam; the section stops rendering offline. |
| Looking for an allowlist / restarting the server | Neither exists for the redesign — files are served directly from redesign/. |
phase.html / /phase D3 view — different page, different rules.agents/*/ resident agents — they don't have panels.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.