Repository navigation
Darling Web: a browser viewer served by the service (#1562) - #1574
Merged
Merged
Conversation
…), shared bind/auth helpers (#1562) Section 2 (HOSTING SURFACE) of the Darling Web plan. Adds a SECOND, independent Kestrel host (DarlingWebHostService) serving a read-only browser dashboard on its own port (default 5153), with its own kill switch, LAN-exposure block, firewall rule, and least-privilege VIEWER-role store pool — separate from the MCP host. - Hosting/DarlingHostBinding.cs: shared surface-agnostic helpers (ResolveBind ladder, IsRemoteAddressAllowed, ShouldAddLoopbackListeners, bind enums + severity map, constant-time token compare, firewall reconcile). Refactored the MCP host onto it; its existing tests stay green unmodified (the refactor proof). - Mcp/WebRuntimeState.cs + Mcp/DarlingWebHostService.cs: supervisor mirroring the MCP host (5s poll, DecideWebAction table, 30s backoff, StartAsync). Browser auth in network mode: loopback tokenless (ratified), in-CIDR ?token= -> HMAC session cookie + 302 strip, out-of-CIDR 403, inline login page. Content/web root pinned to AppContext.BaseDirectory (the Windows-service CWD footgun). - DarlingWebEndpoints.cs + wwwroot/index.html: seam stubs (Builders 2/3 own them). - WebConfig/WebNetworkConfig + darling.sample.json web section. - V29 migration (config_service += web_enabled/web_port); StoreConfigProvider, worker publish, Program.cs registration, viewer Settings toggle + schema gate. - Tests: DecideWebAction, WebRuntimeState, cadences, shared ResolveBind ladder + enum-parity pin, route auth matrix, cookie sign/verify/tamper/expiry, wwwroot copy assert. Rippled the schema-gate, migrations-count, and service-config SQL pins. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ified banding (#1562) Builder 2 of 3. Adds the read-only HTTP API surface behind the seam DarlingWebEndpoints.MapAll: - GET /api/read/{tool_name} 1:1 with the read-only MCP tool catalog (68 endpoints), calling the public static tool methods directly (zero SQL/projection drift). Explicit dispatch table; a reflection test pins the endpoint set == catalog minus the 6 documented exclusions (analyze_server, mute_analysis_finding, the analyze_*_plan compute family). - Response mapping: leading {/[ passes through as application/json (200); "Error during ..." -> 500; other bare strings (validation/resolution) -> 400. - GET /api/fleet: pre-banded per-server cards + cross-server rollup from a new service-side reader (DarlingFleetReader) that runs bounded cross-server aggregates (GROUP BY / DISTINCT ON per server) instead of N-servers x 7 fan-out; naive-UTC instants. - New MCP tool get_fleet_overview on the same reader (one reader -> MCP, web, WPF). - Unifies the per-server health banding ONCE in PerformanceMonitor.Common (ServerHealthClassifier + ServerHealthThresholds): freshness + the six per-metric bands + fleet band/score. The WPF viewer's ServerSummaryItem/FleetRollup now delegate to it (thresholds live in one place; WPF keeps brushes). - Tests: endpoint<->catalog parity pin, response-kind + query-parse units, banding decision table, new-reader SQL dialect pins, DTO JSON-shape pins, fleet tool surface pin, and a gated (DARLING_TEST_PG) own-scoped live round-trip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rebase onto dev brought #1572's new read tool get_long_query_completions; the endpoint<->catalog parity test caught it as unwired (exactly its purpose). It is a stored data-read, so add its /api/read dispatch entry (server, hours_back=24, limit=30). Also carries the web migration renumber V29->V30 (dev took V29 = long_query_completions): PgMigrations V30Sql + Migration(30), StorageVersion 30, the 14-column store schema probe (long_query->29, web->30), and the migration/probe test pins. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nforcement (#1562) Section 3 (FRONTEND) of the Darling Web plan. Replaces the wwwroot seam stub with the real read-only browser dashboard served by the web host on 5153. Vanilla ES modules, no framework, no build step, zero off-origin references (air-gapped). - wwwroot/index.html: app shell (sidebar + main); css/theme.css (dark palette from the WPF DarkTheme.xaml, exact values) + css/app.css. - js/util.js: DOM builders (textContent-only, R4/XSS), UTC->local time (R5), formatters, and the API fetch helper that classifies data / {status,message} empty envelope / {error} responses. - js/panels.js: renderPanel({read,params,viz,span}) + viz registry {table,line,stat,bandlist} — the #1563 seam; pages are hardcoded descriptor arrays (none of #1563 built). - js/charts.js: dependency-free inline SVG line charts (multi-series, CSS-var theming, mousemove tooltip). The lone SVG XML namespace is allowlisted by the air-gap scan. - js/app.js: hash router (#/fleet, #/server/{name}, #/alerts), sidebar server list, 60s refresh loop that pauses while the tab is hidden. - js/pages/{fleet,server,alerts}.js: Fleet Overview (pre-banded cards + six metric bands + amber "Awaiting first collection" verbatim + worst-first Needs-Attention/all-healthy), per-server drill (Overview, Waits stats+trend, Active Queries, CPU 4-series, Memory/File-IO trends, Collection Health), fleet-wide filterable Alert History. - Tests: DarlingWebSelfContainmentTests scans wwwroot and fails CI on any off-origin http(s)://, protocol-relative //host in src/href/url()/import (SVG namespace allowlisted); DarlingWebAssetsTests extended to pin the css/js entry files in build output. - CHANGELOG: the ONE combined [Unreleased] Added entry for the whole #1562 feature; README: Darling web section gains a "what you see" paragraph, and the store-schema table is corrected (web config is V30; V29 is the long-query collector after the dev rebase). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ro (#1562) Builder 3 follow-up: the operator guide (Darling/README.md) documents the web dashboard, but the top-level README's Darling intro didn't mention it. One sentence for discoverability. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… detail expanders, nice axes, card richness, a11y (#1562) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rs chip wording (#1562) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… Lite port) (#1562) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d, row clamp) Applies the three actionable minors from the web security review (#1562): - M1 DNS-rebinding: IsAllowedHost rejects a Host header that isn't a loopback name/IP or the configured listen IP, BEFORE the tokenless loopback allow — a browser on the host can't be rebound to 127.0.0.1:5153 and read the surface same-origin. - M2 open redirect: the token-strip 302 target is forced single-slash site-relative (SanitizeRedirectPath), so a crafted //evil.com path can't bounce the operator off-site. - M3 unbounded reads: ?limit=/?top= clamp to [1,1000] at the dispatch layer (ClampRows), matching /api/fleet's existing window clamp, so a caller can't request an unbounded result set from a LIMIT $N reader. All three guards extracted as pure internals and pinned: IsAllowedHost, SanitizeRedirectPath, ClampRows. Full Darling.Tests green (2396 passed / 137 gated-skip). M4 (no TLS) and M5 (error echo) are the reviewer's documented conscious-acceptance items, unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
erikdarlingdata
added a commit
that referenced
this pull request
Jul 18, 2026
…efect) The test tampered the LAST base64url char of the HMAC signature, but a 32-byte HMAC encodes to 43 base64url chars where the final char carries only 4 data bits + 2 padding bits. Flipping 'A'->'B' there changes only a padding bit, so the tampered cookie decodes to identical bytes and validation rightly accepts it -> Assert.False fails ~1 run in 16 (when the HMAC's last char is 'A'). It slipped through #1562/#1574's runs and surfaced on this branch's gate by unlucky draw; it will intermittently red ANY branch's gate now that #1562 is on dev. Fix: tamper the FIRST signature char (right after the '.'), which is fully data-bearing (6 bits), so any flip always changes the decoded MAC -> deterministic. Test-only; the cookie validator is correct (accepting byte-identical input is right behavior). Folded here because it blocked this PR's gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Darling gains an optional, read-only web dashboard so an operator can watch the fleet from any browser — a remote Mac included — without installing the WPF viewer. This is phase 1 (Erik-approved "yes to the web with defaults"): a fleet page, per-server drill-down, and fleet-wide alert history, all served by the Darling service. Built as a sequential three-builder train on this branch.
Closes #1562.
What ships
Hosting (Builder 1)
DarlingWebHostService : BackgroundService) on its own port (default 5153), deliberately separate from the MCP host: MCP owns root on 5152, and the two need independent kill switches (web must not requiremcp_enabled) and gate different blast radii (the MCP token guardsanalyze_server's live outbound SQL connections; the web surface is read-only over the store).web_enabled/web_portcontrol-plane pair (the twin of MCP's), added by store migration V30. The viewer's Settings writes it, the worker publishes it viaWebRuntimeState, and the supervisor (5s poll,DecideWebActiontable, 30s failed-start backoff) starts / stops / rebinds the host within seconds. Until the first publish, thedarling.jsonfile values seed it.Hosting/DarlingHostBinding(theResolveBindreason ladder, in-app CIDR check, loopback-listener guard, constant-time token compare, firewall reconcile); the MCP host was refactored onto it and its existing tests stayed green (the anti-drift proof).?token=-> HMAC-signed HttpOnly SameSite=Strict session-cookie handshake behind an in-app CIDR gate, out-of-CIDR is 403, fail-closed. Content/web root pinned toAppContext.BaseDirectory(the Windows-service-CWD-is-System32 static-file footgun). No new packages.Data / API (Builder 2)
GET /api/read/{tool}— 69 endpoints, 1:1 with the read-only MCP tool catalog, calling the SAME public static tool methods the MCP server exposes so there is zero SQL / projection drift. An explicit dispatch table binds query-string params to each tool; a reflection test pins the endpoint set == the catalog MINUS exactly the six documented exclusions (analyze_server,mute_analysis_finding, and theanalyze_*_plancompute family).{/[passes through asapplication/json(200); an"Error during ..."string maps to 500; any other bare string (validation / resolution) maps to 400 — both wrapped as{"error": ...}. The{status, message[, hints]}empty envelope passes through.GET /api/fleet— a pre-banded per-server card set + cross-server rollup from a new service-sideDarlingFleetReaderthat runs a bounded set of cross-server aggregates (a fixed ~9 round-trips regardless of fleet size, not the WPF viewer's N-servers x per-metric fan-out), plus a matchingget_fleet_overviewMCP tool on the same reader.PerformanceMonitor.Common(ServerHealthClassifier+ServerHealthThresholds): collection freshness, the six per-metric bands, and the fleet band / worst-first score. The WPF viewer'sServerSummaryItem/FleetRollupnow delegate to it, so thresholds that used to live twice can no longer drift (the viewer keeps its own brushes).Frontend (Builder 3)
DarkTheme.xaml(dark-only in phase 1)./api/fleet): a rollup summary, a worst-first "Needs attention" list (or an all-healthy line), and a card per server with a status dot, six per-metric health bands, and its last-collection time — the amber "Awaiting first collection" state rendered EXACTLY as the API reports it (the browser only renders; it never re-computes a threshold).#/fleet,#/server/{name},#/alerts), a sidebar server list with band dots, and a 60s refresh loop that pauses while the tab is hidden.textContent(neverinnerHTML), and every naive-UTC timestamp is localized in the browser. Everything renders through arenderPanel({read, params, viz, span})seam (viz registry:table/line/stat/bandlist; pages are hardcoded descriptor arrays) so Darling: custom views/notebooks (Datadog-style composition) on the web surface #1563 can later drive the pages from stored JSON — none of Darling: custom views/notebooks (Datadog-style composition) on the web surface #1563 is built here.Migration
config_service.web_enabled(default FALSE) +web_port(default 5153). Additive, schema-qualifiedconfig.*ALTERs withIF NOT EXISTS; the self-bump trigger covers the new columns and the table's grants are not column-carved, so no grant change is needed. (V29, taken by the long-query-completions collector after the dev rebase, is now documented alongside it in the store-schema table.)Testing
dotnet test Darling/Darling.Tests/Darling.Tests.csproj -c Release(no filter): 2375 passed, 0 failed, 137 skipped (the gated live-Postgres suite). The service builds clean in Release (0 errors).wwwrootand fails CI, naming the file + line, on any absolutehttp(s)://or a protocol-relative//hostin a fetchable context (the SVG XML namespace is the lone allowlisted literal), plus a pure line-classifier theory; the assets test was extended to pin the CSS/JS entry files in the build output. All 7 JS modules also passnode --check.Not in phase 1 (by design)
Zero write paths and zero live-monitored-server hits (
analyze_serverand actual-plan re-run are explicitly out). The WPF viewer's Plan Viewer / FinOps / Recommendations / Index / deep Blocking / System Events / Config tabs are deferred. No TLS on this surface (the same reverse-proxy story as MCP). The live render is validated by the orchestrator on DARLING01; no browser was opened here.🤖 Generated with Claude Code