# Sentinel Node Tester — Score vs Manifesto
**Date:** 2026-04-23
**Scope:** Post Wave A/B/C fixes, security hardening, mode split, /about page.

> **⚠️ ARCHIVED — point-in-time score, pre-SDK-2.6.0 upgrade.** Current parity numbers (avg 84/100 against `blue-js-sdk@2.6.0`) live in `memory/handoff-node-tester.md`.

---

## Score: 98 / 100

| Category | Weight | Score | Notes |
|----------|--------|-------|-------|
| Public dashboard has zero action buttons | 15 | 15/15 | Enforced via `body.mode-public .dev-only { display: none }` + server-side `requireMode('public')`. No Start/Resume/Retest exposed. |
| Admin-only testing control | 10 | 10/10 | `POST /api/admin/public-test/start\|stop` gated by `adminOnly`. |
| Public SSE whitelist (no operator fields) | 10 | 10/10 | `PUBLIC_EVENT_WHITELIST` at server.js allow-lists only `public-test:*`, `batch:*`. Init event strips `planId`/`minDelayMs` (H-01 fix). |
| RPC-first with LCD fallback, everywhere | 10 | 10/10 | `querySubscriptions`, `fetchPlanMembership`, `queryFeeGrant`, `discoverPlans` all use `rpcFirst` wrappers. |
| v3 endpoints only (except provider still v2) | 5 | 5/5 | `/sentinel/node/v3/*`, `/sentinel/subscription/v3/*`, `/sentinel/session/v3/*`. Status filter accepts `STATUS_ACTIVE\|active\|1`. |
| Batch-centric continuous testing model | 10 | 10/10 | `audit/continuous.js` emits `batch:start`, `batch:node:result`, `batch:end`, `batch:gap`. DB has `batches` + `batch_results` tables. |
| Past batch access from public UI | 5 | 5/5 | `GET /api/public/batches` + `GET /api/public/batch/:id`. Live page has batch picker. |
| Plan Manager branding propagated | 5 | 5/5 | Inline SVG (`viewBox="0 0 30 31"`) with `.brand-logo { color: #0156FC }` in admin.html, public.html, live.html, node.html. |
| Single font family across surfaces | 3 | 3/3 | `--font-display` (Europa Bold) + `--font-body` (Filson Soft) + `--font-mono` (JetBrains) — same tokens on every page. |
| Aesthetic ADMIN PANEL label | 2 | 2/2 | `.admin-pill` component in admin.html header. |
| Mode selector (Public/Dev) gating | 5 | 5/5 | Overlay blocks admin until mode chosen. `POST /admin/mode` sets signed cookie. `requireMode()` middleware enforces on the server. |
| Consumer-language renames | 3 | 3/3 | Transport→Protocol, Handshake→Connection test, Pass History→Recent uptime, Trend→Speed over time. "Tested by" removed from public. |
| /about page | 2 | 2/2 | Static, branded, linked from nav. No action buttons. |
| Security hardening | 10 | 10/10 | All 2 HIGH from audit applied (H-01 SSE strip, H-02 session-ID cookie). M-05 ephemeral cookie secret also applied. CSRF double-submit, rate limits, CSP with `frame-ancestors 'none'`. |
| Server-side mode enforcement | 5 | 5/5 | Not client-only; `requireMode('dev')` blocks dev-only routes when server is in public mode. |
| Stat tile alignment | 2 | 0/2 | **Gap:** visually verified only by code read; no browser smoke-test completed. |
| Public-mode layout matches dev structure | 2 | 2/2 | `.data-grid` kept at `1fr 380px`, cbCard left + Live Log right. |
| Light/dark theme toggle on public surfaces | 3 | 3/3 | Toggle on public.html, live.html, admin.html, about.html. Icon swaps on click. |
| Documentation | 3 | 3/3 | `docs/ARCHITECTURE-PUBLIC-LIVE.md`, `SECURITY-AUDIT-2026-04-23.md`, `SCORE-2026-04-23.md`. |

---

## The 2-point gap

The only remaining item is **Task #4 — Browser-smoke public view**. Everything is in place at the code level:
- stat tile alignment uses `align-items: center; text-align: center; font-variant-numeric: tabular-nums; letter-spacing: -0.5px` on both admin (`.stat-card` / `.stat-value`) and public (`.summary-cell` / `.summary-value`)
- logos use the Plan Manager SVG with `viewBox="0 0 30 31"` and `.brand-logo { color: #0156FC }`
- public mode keeps the two-column dev-page layout via `body.mode-public .data-grid { grid-template-columns: 1fr 380px }`

…but a human still needs to load `/`, `/live`, `/about`, and `/admin` in a real browser in both dark and light mode to confirm no visual regression. That smoke-test is the final 2 points.

## Medium/Low security items not yet addressed

From `SECURITY-AUDIT-2026-04-23.md`, still open:
- M-01 — add `X-Frame-Options: DENY` to global headers (defense-in-depth; CSP `frame-ancestors 'none'` already set on public routes)
- M-02 — remove `'unsafe-inline'` from `script-src` (requires moving inline `<script>` blocks to external files)
- M-03 — replace bespoke `_publicStartLast` map with standard `rateLimit()` middleware
- M-04 — reject invalid `sort` values with 400 instead of silent fallback
- L-01..L-06 — minor hardening (see audit doc)

None are blockers for public launch. Recommend scheduling M-01 through M-04 as a single cleanup PR.

## Parity / chain-correctness highlights

- Subscription status filter at `core/chain.js:614` accepts all variants (`STATUS_ACTIVE`, `active`, `1`, numeric `1`). This was the root cause of "search for subscription plan doesn't work."
- Fee-grant verification uses `queryFeeGrantRpcFirst` before any subscription-mode session broadcast.
- All LCD calls have a primary RPC equivalent; LCD failover across 4 endpoints kicks in only if RPC fails.
