Documentation
¶
Overview ¶
Package control is the Harbor Protocol REST/JSON control transport — the client→server half of the wire binding RFC §5.4 resolves to (SSE for events + REST/JSON for control). It is a thin adapter over the transport-agnostic protocol.ControlSurface that Phase 54 shipped: a Handler decodes an HTTP request body into the Protocol wire request type the method expects, calls ControlSurface.Dispatch, and encodes the wire response — or maps a *protocol/errors.Error onto an HTTP status (status.go) and a JSON error body.
The route shape ¶
One route serves every task-control method:
POST /v1/control/{method}
where {method} is one of the ten canonical method names (internal/protocol/methods). `start` carries a types.StartRequest; the nine steering controls carry a types.ControlRequest. The method name is read from the path, never hardcoded — the handler validates it against methods.IsValidMethod and the Phase 58 single-source lint forbids a method string literal anywhere under internal/protocol/ outside the methods package.
Identity at the edge (RFC §5.5, CLAUDE.md §6) ¶
The identity triple (+ run for a control) lives in the request body's `identity` object — the flat types.IdentityScope. The handler does NOT re-validate it: ControlSurface.Dispatch already fails closed on an incomplete triple with CodeIdentityRequired, which status.go maps to 401. The edge structure — decode, hand the whole request to Dispatch, map the error — is the single choke point Phase 61 slots JWT validation into without reshaping the handler.
Concurrent reuse (D-025) ¶
Handler is a compiled artifact: the ControlSurface and the logger are set once at construction and never mutated. ServeHTTP holds no per-request state on the Handler — every request's data lives on the *http.Request and the response is written to its own http.ResponseWriter. One Handler serves N concurrent requests safely; internal/protocol/transports/concurrent_test.go pins it under -race.
Index ¶
- Constants
- Variables
- func HTTPStatus(code protoerrors.Code) int
- type ArtifactsSurface
- type Handler
- type MCPSurface
- type Option
- func WithArtifactsSurface(s ArtifactsSurface) Option
- func WithClock(now func() time.Time) Option
- func WithEventBus(b events.EventBus) Option
- func WithLogger(l *slog.Logger) Option
- func WithMCPSurface(s MCPSurface) Option
- func WithPostureSurface(s PostureSurface) Option
- func WithRedactor(r audit.Redactor) Option
- func WithSearchSurface(s SearchSurface) Option
- type PostureSurface
- type SearchSurface
Constants ¶
const RoutePattern = "POST /v1/control/{method}"
RoutePattern is the http.ServeMux pattern the control transport registers under. The {method} wildcard is read via r.PathValue. Exported so internal/protocol/transports can mount the handler under the same pattern it documents.
Variables ¶
var ErrMisconfigured = errors.New("control: REST transport missing a mandatory dependency")
ErrMisconfigured — NewHandler was called with a nil ControlSurface.
Functions ¶
func HTTPStatus ¶ added in v1.3.1
func HTTPStatus(code protoerrors.Code) int
HTTPStatus maps a canonical Protocol error Code onto a stable HTTP status. The mapping is part of the wire contract: a Protocol client branches on the JSON body's `code`, but an intermediary (a proxy, a load balancer, a browser's network panel) branches on the HTTP status, so the two must agree and stay stable across a Runtime refactor.
The Code set is single-sourced in internal/protocol/errors (CLAUDE.md §8); this function is the one place the Protocol wire transport binds each Code to a status. A Code with no explicit entry falls through to 500 — fail loud rather than silently returning a misleading 200.
Exported since Phase 113a (D-209): `cmd/harbor-gen-protocol-docs` renders the published error reference from the SAME binding the wire transport serves, so the docs cannot drift from this function.
Types ¶
type ArtifactsSurface ¶
type ArtifactsSurface interface {
// Dispatch handles one of the three canonical artifacts methods.
// Returns either a *types.ArtifactsListResponse /
// *types.ArtifactsPutResponse / *types.ArtifactsGetRefResponse, or
// a *errors.Error.
Dispatch(ctx context.Context, method methods.Method, req any) (any, error)
}
ArtifactsSurface is the narrow contract the control transport calls into for the three Phase 73l (D-120) artifacts methods — `artifacts.list`, `artifacts.put`, `artifacts.get_ref`. The production implementation is *protocol.ArtifactsSurface; tests inject a deterministic surface. A nil surface means the handler rejects artifacts calls with CodeUnknownMethod — preserving the 404 → SKIP path the smoke script relies on while the surface is being wired through.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler is the Protocol REST/JSON control transport. It is built once per Runtime process via NewHandler and shared across every control request; ServeHTTP is safe for concurrent use by N goroutines (D-025).
Phase 72b extension: when `bus` AND `redactor` are wired via WithEventBus / WithRedactor, the handler accepts admin-impersonation requests (IdentityScope.Impersonating set) and emits a redacted `audit.admin_scope_used` event on the bus on every accepted impersonation. When either dependency is missing, impersonation requests are rejected loudly with `CodeRuntimeError` — never silently accepted without the audit emit (CLAUDE.md §5, §7 rule 6, §13 "Silent degradation").
Phase 72c (D-108): when constructed with `WithSearchSurface`, the handler routes the five `search.*` methods to the search dispatcher instead of the task-control ControlSurface. The same handler still serves all the Phase 54 task-control methods unchanged.
Phase 72f / 72g (D-111 / D-112): when constructed with `WithPostureSurface`, the handler routes the seven posture methods — the five `runtime.*` / `metrics.*` reads plus `governance.posture` / `llm.posture` — to the posture dispatcher. Like the search surface, this is additive — handlers built without it reject posture calls with CodeUnknownMethod (the 404 → SKIP path the smoke script relies on).
func NewHandler ¶
func NewHandler(surface *protocol.ControlSurface, opts ...Option) (*Handler, error)
NewHandler builds the Protocol REST/JSON control transport over the transport-agnostic ControlSurface. The surface is mandatory — a nil fails loud with ErrMisconfigured rather than building a handler that would nil-panic on the first request (CLAUDE.md §5).
The bus + redactor are OPTIONAL at construction so existing tests that don't exercise the Phase 72b impersonation path can call `NewHandler(surface)` unchanged. Production callers (transports.NewMux) MUST wire both via WithEventBus + WithRedactor; an impersonation request without them is rejected with CodeRuntimeError (CLAUDE.md §13 "Silent degradation" — no quiet accept).
The returned *Handler is immutable after construction (D-025) and safe for concurrent use by N goroutines.
func (*Handler) ServeHTTP ¶
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler. It decodes the request into the wire type the path's method expects, dispatches it through the ControlSurface, and writes the JSON wire response — or a JSON error body with the mapped HTTP status.
type MCPSurface ¶
type MCPSurface interface {
// Dispatch handles one of the twelve `mcp.servers.*` methods.
// Returns either a *types.<Method>Response or a *errors.Error.
Dispatch(ctx context.Context, method methods.Method, req any) (any, error)
}
MCPSurface is the narrow contract the control transport calls into for the twelve `mcp.servers.*` Protocol methods (Phase 73k / D-119). The production implementation is *protocol.MCPSurface; tests inject a deterministic surface. A nil surface means the handler rejects MCP calls with CodeUnknownMethod — preserving the 404 → SKIP path the smoke script relies on while the surface is being wired through.
type Option ¶
type Option func(*Handler)
Option configures a Handler at construction time.
func WithArtifactsSurface ¶
func WithArtifactsSurface(s ArtifactsSurface) Option
WithArtifactsSurface wires the Phase 73l (D-120) artifacts dispatcher into the control handler. When supplied, the handler routes the three artifacts methods — `artifacts.list`, `artifacts.put`, `artifacts.get_ref` — to s.Dispatch instead of falling through to the task-control ControlSurface. Optional — handlers built without it reject artifacts calls with CodeUnknownMethod (the 404 → SKIP path the smoke script relies on). A nil surface is treated as "WithArtifactsSurface not supplied".
func WithClock ¶
WithClock overrides the handler's wall clock — used by tests so the `OccurredAt` field on the published audit event is deterministic. A nil clock keeps the default (time.Now).
func WithEventBus ¶
WithEventBus wires the canonical events.EventBus into the handler so the Phase 72b admin-impersonation gate can publish a typed `audit.admin_scope_used` event onto the bus when an impersonation request is accepted. The bus is OPTIONAL — when not supplied, the handler refuses impersonation requests with CodeRuntimeError (the audit emit is the load-bearing accountability surface for impersonation; rejecting fails-closed rather than silently degrading). A nil bus is treated as "WithEventBus not supplied".
func WithLogger ¶
WithLogger sets the slog.Logger the handler logs decode / dispatch failures to. A nil logger (the default) routes to slog.Default().
func WithMCPSurface ¶
func WithMCPSurface(s MCPSurface) Option
WithMCPSurface wires the Phase 73k (D-119) MCP-Connections dispatcher into the control handler. When supplied, the handler routes the twelve `mcp.servers.*` methods to s.Dispatch instead of falling through to the task-control ControlSurface. Optional — handlers built without it reject MCP calls with CodeUnknownMethod (the 404 → SKIP path the smoke script relies on).
func WithPostureSurface ¶
func WithPostureSurface(s PostureSurface) Option
WithPostureSurface wires the Phase 72f / 72g posture dispatcher into the control handler. When supplied, the handler routes the seven posture methods — the five `runtime.*` / `metrics.*` reads plus `governance.posture` / `llm.posture` — to s.Dispatch instead of falling through to the task-control ControlSurface. Optional — handlers built without it reject posture calls with CodeUnknownMethod (the 404 → SKIP path the smoke script relies on).
func WithRedactor ¶
WithRedactor wires the audit.Redactor the handler runs the impersonation audit payload through before publishing onto the event bus (CLAUDE.md §7 rule 6: "every payload goes through audit.Redactor"). The redactor is OPTIONAL at the type level but MANDATORY in practice for impersonation: a handler without a redactor refuses impersonation requests with CodeRuntimeError, same as the missing-bus case. A nil redactor is treated as "WithRedactor not supplied".
func WithSearchSurface ¶
func WithSearchSurface(s SearchSurface) Option
WithSearchSurface wires the Phase 72c search dispatcher into the control handler. When supplied, the handler routes the five `search.*` methods to s.Dispatch instead of falling through to the task-control ControlSurface. Optional — handlers built without it reject search calls with CodeUnknownMethod (the 404 → SKIP path the smoke script relies on).
type PostureSurface ¶
type PostureSurface interface {
// Dispatch handles one of the seven canonical posture methods.
// Returns either a *types.RuntimeInfo / *types.RuntimeHealth /
// *types.RuntimeCounters / *types.RuntimeDrivers /
// *types.MetricsSnapshot / *types.GovernancePostureResponse /
// *types.LLMPostureResponse, or a *errors.Error.
Dispatch(ctx context.Context, method methods.Method, req any) (any, error)
}
PostureSurface is the narrow contract the control transport calls into for the seven posture Protocol methods — the five `runtime.*` / `metrics.*` reads (Phase 72f / D-111) plus `governance.posture` and `llm.posture` (Phase 72g / D-112). The production implementation is *protocol.PostureSurface; tests inject a deterministic surface. A nil surface means the handler rejects posture calls with CodeUnknownMethod — preserving the 404 → SKIP path the smoke script relies on while the surface is being wired through.
All seven posture methods share the one read-only request envelope `*types.RuntimeInfoRequest` — the governance / llm reads are also identity-only, so they reuse the same shape rather than threading two near-identical wire types.
type SearchSurface ¶
type SearchSurface interface {
// Dispatch handles one of the five canonical search.* methods.
// Returns either a *types.SearchResponse or a *errors.Error.
Dispatch(ctx context.Context, method methods.Method, req *types.SearchRequest) (*types.SearchResponse, error)
}
SearchSurface is the narrow contract the control transport calls into for the five `search.*` Protocol methods. The production implementation lives in internal/protocol/control/search.go (the SearchHandler shaped over a search.SearcherRegistry); tests inject a deterministic surface. A nil surface means the handler will reject search calls with CodeUnknownMethod — preserving the 404 path the smoke script's `skip_if_404` relies on while the surface is being wired through.