control

package
v1.3.1 Latest Latest
Warning

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

Go to latest
Published: Jun 11, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

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

View Source
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

View Source
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

func WithClock(now func() time.Time) Option

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

func WithEventBus(b events.EventBus) Option

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

func WithLogger(l *slog.Logger) Option

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

func WithRedactor(r audit.Redactor) Option

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.

Jump to

Keyboard shortcuts

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