handler

package
v0.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 6, 2026 License: GPL-3.0 Imports: 3 Imported by: 0

README

internal/handler

The presentation layer: the code that turns a request (an HTTP call from a browser, or an MCP tool call from an agent) into a domain call and maps the result onto a versioned wire contract. Transport (the loopback HTTP server, its bind, and the CORS / loopback / DNS-rebind / bearer middleware) does NOT live here - it lives in internal/httpx. Data access does NOT live here - handlers call the repositories.

The rule: a handler subpackage mirrors its proto package

A wire-mapping subpackage internal/handler/<name> owns the over-the-wire concerns of the protobuf package magus.<name>.v1alpha1 and is NAMED to match it, so the two are trivially correlated:

handler subpackage proto package owns
internal/handler/viewer magus.viewer.v1alpha1 ViewerService (the run browser and one run's journal), domain-event -> proto mapping, fragment/SSE encode, the live SSE server
internal/handler/status magus.status.v1alpha1 status-report -> proto mapping + encoder, the GET /api/v1/status and /api/v1/events handlers
internal/handler/graph magus.graph.v1alpha1 knowledge-graph -> proto mapping, the GET /api/v1/graph handler

When you add a new wire contract proto/magus/foo/v1alpha1, its mapping goes in a new internal/handler/foo package - same name, no exceptions for the wire packages.

Each console handler is an http.Handler receiver type holding a NARROW consumer interface that is satisfied by the pure-logic internal/service/console service. The service returns DOMAIN values; the handler owns the wire encoding.

Name that interface <name>Source and keep it UNEXPORTED: graphSource, statusSource, insightSource. It is the consumer's statement of what it needs, not a type any caller has to name - Go satisfies it structurally, so the service that implements it never mentions it. Export it only when the composition root genuinely has to write the type down, which today is true of exactly one (plan.Source, named in five places outside its package).

A handler that needs no service takes a concrete dependency instead and declares no interface at all; attention is the example. Do not invent a one-implementation interface to match the shape of its neighbours.

MCP is always compiled in - there are no build tags. Test files use the SAME package as the code they test (package status, never package status_test).

Packages that mirror a ROUTE instead of a proto

Some console data rides plain JSON /api/v1/* routes rather than a Connect service. Those handlers have no proto package to be named after, so they are named after the route namespace they serve, and the rule is otherwise the same: one package per namespace, and it owns that namespace outright.

handler subpackage serves
internal/handler/diff /api/v1/diff and everything under it
internal/handler/plan /api/v1/plan
internal/handler/attention /api/v1/attention
internal/handler/ledger /api/v1/ledger

A route package is NOT a place to put whatever has no home yet. handler/status accumulated five of these before they were split out, while its own doc still described one thing: mapping the status report onto StatusService's two RPCs. The tell was in the constructor names - every one of them read NewDiff* inside package status.

Deliberate non-mirror packages

Two subpackages mirror neither a proto package nor a route:

  • internal/handler/mcp - the MCP request handlers (the tool implementations, the descriptor catalog in registry.go, the dispatch pipeline in mcp.go, and the transports in transport.go: the streamable-HTTP handler builder + stdio). It mirrors the agent-facing MCP tool surface. Its bearer token store lives in internal/auth; the guards in internal/httpx.
  • internal/handler/trailrpc - the audit interceptor for the Connect services, which records mutating unary RPCs to the activity trail by construction.

Naming a handler package against its domain twin

Mirroring a proto or a route means a handler often shares its name with the domain package it calls: handler/graph and internal/graph, handler/ledger and internal/ledger, and the same for memory and notes. Go resolves that with an alias, and this tree spells it two ways consistently:

  • INSIDE a handler, the domain import is aliased store: store "github.com/egladman/magus/internal/notes".
  • In internal/daemon, which imports every handler at once, each is aliased <name>handler: noteshandler "github.com/egladman/magus/internal/handler/notes".

Sharing the name with the domain package is expected and fine. Two packages sharing a name with NO such relationship is not: internal/diff and internal/interactive/diff were peers claiming one word, and callers invented a third name for one of them to tell them apart. That is what the alias is telling you when it appears anywhere other than the two spellings above.

Layering

transport    internal/httpx           (one loopback Server + middleware)
handler      internal/handler/*       (this package - request -> domain -> wire)
service      internal/service/*       (pure application logic - no http/proto)
repository   internal/cache, knowledge  (data access)
composition  internal/daemon          (assembles the daemon server)

Keep the arrows pointing down: a handler imports its service, httpx (to mount routes), and the repositories; nothing in a repository or in httpx imports a handler.

Documentation

Index

Constants

View Source
const MaxWireBodyBytes = 8 << 20 // 8 MiB

MaxWireBodyBytes caps the request body a daemon HTTP handler will read into memory. It is generous for real payloads - a unified diff, a patch, a review remark - but bounds an authenticated or LAN-reachable client (a connector-token MCP client, a share viewer) so a multi-gigabyte POST cannot exhaust daemon memory. The proc socket path caps its frames separately (internal/proc).

Variables

This section is empty.

Functions

func AllowGet added in v0.4.0

func AllowGet(w http.ResponseWriter, r *http.Request) bool

AllowGet answers a CORS preflight (204) and rejects non-GET methods (405), returning false when the caller should stop.

Here rather than beside any one route: every read handler in every handler package applies the same gate, and the copy that lived next to the insight route was the one six unrelated files reached across for.

func LimitRequestBody added in v0.4.0

func LimitRequestBody(w http.ResponseWriter, r *http.Request)

LimitRequestBody caps r.Body at MaxWireBodyBytes so a later decode fails once the body exceeds the cap instead of reading an unbounded stream. Call it before reading the body.

func WriteJSON added in v0.4.0

func WriteJSON(w http.ResponseWriter, v any)

WriteJSON marshals v and writes it as an uncached JSON body, matching the read handlers' no-store posture: these reads reflect live daemon state.

Types

type Base

type Base struct {
	http.Handler
	Log *slog.Logger
}

Base is the embedded core of every HTTP route handler: the http.Handler that actually serves the route, plus a request-scoped logger the handler can use. Embedding it makes each handler a concrete named type (not a bare interface) that callers can hold, reference, and log through. Construct it with New so the logger is never nil.

func New

func New(serve http.HandlerFunc, log *slog.Logger) Base

New builds a Base wrapping serve, defaulting Log to slog.Default() when nil.

Directories

Path Synopsis
Package activity is the console-facing ActivityService handler: it lists recent activity events (newest first, filtered) and serves a payload blob by ref for the /dashboard and log viewer.
Package activity is the console-facing ActivityService handler: it lists recent activity events (newest first, filtered) and serves a payload blob by ref for the /dashboard and log viewer.
Package diff serves the review session's plain-JSON routes under /api/v1/diff.
Package diff serves the review session's plain-JSON routes under /api/v1/diff.
Package graph holds the graph surfaces the daemon serves and the magus.graph.v1alpha1 wire mapping behind them: the GET /api/v1/graph route (a bulk subgraph document) and the GraphService RPCs (ranked retrieval - query, resolve, explain, path, stats).
Package graph holds the graph surfaces the daemon serves and the magus.graph.v1alpha1 wire mapping behind them: the GET /api/v1/graph route (a bulk subgraph document) and the GraphService RPCs (ranked retrieval - query, resolve, explain, path, stats).
Package insight is the console-facing InsightService handler: it serves every insight lens (the four VCS-history lenses from one cached git-log scan, plus the run-outcome volatility lens folded in fresh) as the magus.insight.v1alpha1 wire type.
Package insight is the console-facing InsightService handler: it serves every insight lens (the four VCS-history lenses from one cached git-log scan, plus the run-outcome volatility lens folded in fresh) as the magus.insight.v1alpha1 wire type.
Package job is the console-facing JobService handler: the daemon's CONTROL surface, the mutating sibling of the read-only activity/status/viewer handlers.
Package job is the console-facing JobService handler: the daemon's CONTROL surface, the mutating sibling of the read-only activity/status/viewer handlers.
mcp
Package mcp implements the MCP (Model Context Protocol) server for magus.
Package mcp implements the MCP (Model Context Protocol) server for magus.
origin
Package origin carries agent origin metadata across goroutines via context.
Package origin carries agent origin metadata across goroutines via context.
Package memory is the console-facing MemoryService handler: an observable, editable view over the durable handoff-journal entries the MCP magus_memory tool writes.
Package memory is the console-facing MemoryService handler: an observable, editable view over the durable handoff-journal entries the MCP magus_memory tool writes.
Package metrics is the daemon's derived-dashboard presentation layer for magus's OTel metrics.
Package metrics is the daemon's derived-dashboard presentation layer for magus's OTel metrics.
Package notes is the console-facing NotesService handler: a READ-ONLY view over the workspace's human-authored notes, both the shared store in the checkout and the private one on this machine.
Package notes is the console-facing NotesService handler: a READ-ONLY view over the workspace's human-authored notes, both the shared store in the checkout and the private one on this machine.
Package status maps the live status report onto the magus.status.v1alpha1 wire message and base64-encodes it for the dashboard's SSE stream.
Package status maps the live status report onto the magus.status.v1alpha1 wire message and base64-encodes it for the dashboard's SSE stream.
Package token is the console-facing TokenService handler: the typed management surface for the daemon's auth tokens.
Package token is the console-facing TokenService handler: the typed management surface for the daemon's auth tokens.
Package tool serves the toolchain view: every binary a workspace's spells drive, the version each one reported, and the window it is held to.
Package tool serves the toolchain view: every binary a workspace's spells drive, the version each one reported, and the window it is held to.
Package trailrpc is the audit interceptor for the daemon's Connect services: a connect.Interceptor that records MUTATING unary RPCs to the activity trail by construction, so auditing a state change is a structural default of the mount rather than a per-handler line a developer must remember to add.
Package trailrpc is the audit interceptor for the daemon's Connect services: a connect.Interceptor that records MUTATING unary RPCs to the activity trail by construction, so auditing a state change is a structural default of the mount rather than a per-handler line a developer must remember to add.
Package viewer holds the magus.viewer.v1alpha1 wire contract: the code that maps captured DOMAIN events onto the versioned protobuf tool-page contract and encodes them for a browser (a URL-fragment blob for a finished run, or a live SSE stream), plus the viewer's filter DSL.
Package viewer holds the magus.viewer.v1alpha1 wire contract: the code that maps captured DOMAIN events onto the versioned protobuf tool-page contract and encodes them for a browser (a URL-fragment blob for a finished run, or a live SSE stream), plus the viewer's filter DSL.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL