Documentation
¶
Overview ¶
r3mount.go mounts the R2 observability dashboard INSIDE the R3 background-worker service (task t13-r3-mount-integration): it composes the dashboard's store → broker → handlers collaborators, stitches the REST/SSE handler mount onto R3's HTTP/SSE edge via RegisterMount (spec D5 — the browser-facing arm of the r3 §2A surface→transport map, NEVER the UDS control plane), and bridges R3's EventBus into the broker so R3's in-process publish is the dashboard's PRIMARY event source.
This is the counterpart to the standalone Server (server.go): where the standalone binary owns hosting (bind address, static assets) and runs the fswatch watcher as its push source, the R3-mounted composition owns neither — R3 owns hosting, and the R3 bus bridge replaces fswatch as the primary source. fswatch (t06) therefore demotes to the fallback the standalone binary keeps for writers that bypass the service (a developer running `da score run` manually); no watcher runs here.
Package server wires the R2 observability dashboard's four in-process collaborators — the read Store (t02) decorated with recompute-on-miss (t06), the SSE Broker (t04), the REST+SSE Handlers mount (t03/t05), and the filesystem Watcher (t06) — into a single net/http server bound to a loopback address.
The Server type is the reusable composition root: the standalone cmd/da-dashboard binary constructs one and calls Serve, and the future R3 service (t13) can construct one and mount its Handler under its own HTTP/SSE edge. Hosting policy (bind address, dev-asset proxying, embedded vs. on-disk static assets) is Config; route ownership stays in the handlers package.
Routing (net/http most-specific-wins):
- GET /api/health → a bare liveness 200 for the standalone process.
- /api/ (subtree) → the handlers mount (REST + SSE, full-path routes).
- / (everything else) → the SPA static handler (or the Vite dev proxy).
Index ¶
Constants ¶
const DefaultAddr = "127.0.0.1:7300"
DefaultAddr is the loopback bind used when Config.Addr is empty — the same deliberate loopback-by-default posture as the R3 service edge (anything wider is an explicit opt-in via --addr).
Variables ¶
var ErrNilBus = errors.New("dashboard/server: R3 edge exposes no event bus")
ErrNilBus is returned by Mount when the R3 edge exposes no EventBus: the dashboard mount's whole point in R3 is to bridge that bus into the SSE stream, so a nil bus is a wiring error, not a silently REST-only mode.
var ErrNilEdge = errors.New("dashboard/server: R3 edge is required")
ErrNilEdge is returned by Mount when the R3 edge is nil.
Functions ¶
func Mount ¶
func Mount(edge R3Edge, cfg MountConfig) (io.Closer, error)
Mount composes the dashboard (store t02 + recompute t06 → broker t04 → handlers t03/t05), registers the handler mount under handlers.Prefix on edge's HTTP/SSE edge (RegisterMount, full-path routing, no prefix stripping), and bridges edge's R3 EventBus into the broker as the dashboard's primary event source. It starts no filesystem watcher.
The returned io.Closer detaches the bus bridge and closes the broker (disconnecting every SSE subscriber); the R3 runtime calls it on shutdown. Mount fails on a nil edge, a nil bus, or a handler/mount-registration error; on any failure it releases the broker it built so no goroutine leaks.
Types ¶
type Config ¶
type Config struct {
// Addr is the TCP bind address; empty → DefaultAddr. Use a :0 port to
// bind an ephemeral port (tests read the resolved port via Addr()).
Addr string
// IterLogDirs are the iter-log roots the Store reads and the Watcher
// watches. Empty is legal — the server still serves (REST returns empty
// collections; the watcher degrades to a no-op poll).
IterLogDirs []string
// RepoDir is the repository root the recompute pipeline runs git
// topology against (store.NewRecompute's repoDir); empty → ".".
RepoDir string
// TranscriptDirs are optional agent session-log roots the recompute
// pipeline uses for token backfill and transcript-derived checks.
TranscriptDirs []string
// DevAssetProxy, when set, is the base URL of a running Vite dev server;
// every non-/api request is reverse-proxied there instead of served from
// the embedded/static bundle (front-end hot-reload during development).
DevAssetProxy string
// StaticDir overrides the go:embed'd dist/ with an on-disk build
// directory (e.g. a freshly built web/dashboard/dist). Ignored when
// DevAssetProxy is set.
StaticDir string
// Logger receives structured server, handler, and watcher logs; nil →
// a discard logger.
Logger *slog.Logger
}
Config is the standalone dashboard server's hosting policy. The zero value is usable (loopback bind, embedded assets, no watched roots).
type MountConfig ¶
type MountConfig struct {
// IterLogDirs are the iter-log roots the Store reads. Empty is legal.
IterLogDirs []string
// RepoDir is the repository root the recompute pipeline runs git topology
// against; empty → ".".
RepoDir string
// TranscriptDirs are optional agent session-log roots the recompute
// pipeline uses for token backfill and transcript-derived checks.
TranscriptDirs []string
// Logger receives structured handler, store, and bridge logs; nil → a
// discard logger.
Logger *slog.Logger
}
MountConfig is the dashboard-mount policy the R3 runtime supplies. The zero value is usable (no roots → REST returns empty collections; repoDir ".").
type R3Edge ¶
type R3Edge interface {
// RegisterMount stitches handler under prefix on the HTTP/SSE edge.
RegisterMount(prefix string, handler http.Handler) error
// Bus returns the EventBus the runtime publishes background-task events on.
Bus() svcevents.EventBus
}
R3Edge is the slice of the R3 HTTP/SSE edge (internal/service/http.Server) the dashboard mount needs: the RegisterMount reservation point and the D4.1 EventBus the runtime's background tasks publish on. *service/http.Server satisfies it; depending on the interface keeps this composition testable and free of a direct import of the service runtime.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the composed dashboard runtime. Construct it with New, start it with Start (non-blocking) or Serve (blocking until ctx is cancelled), and release it with Stop.
func New ¶
New builds a Server over cfg, wiring the store → broker → handlers → watcher collaborators and composing the HTTP handler. It binds no socket; call Start or Serve for that. It fails only when the handlers mount or the static asset FS cannot be constructed.
func (*Server) Addr ¶
Addr returns the bound listen address once Start has run; before that it is the configured address (which may carry a :0 wildcard port).
func (*Server) Handler ¶
Handler returns the composed request multiplexer so R3 can mount it and tests can drive routes without binding a socket.
func (*Server) Serve ¶
Serve starts the server and blocks until ctx is cancelled (or the background serve fails), then gracefully stops within the shutdown timeout. It is the entry point the standalone binary wires to signal-driven cancellation.
func (*Server) Start ¶
Start binds the listener, launches the filesystem watcher, and serves in the background. It returns once the socket is bound (so Addr is resolved), surfacing any bind or watcher-start error synchronously.
func (*Server) Stop ¶
Stop tears the server down in dependency order: the watcher stops producing events, the broker disconnects every SSE client (so their long-lived handlers return and the HTTP drain can complete), the HTTP server drains in-flight requests within ctx's deadline, and finally pending best-effort recompute sidecar writes are flushed so a just-computed score is not lost with the process. It is safe to call once after Start.