Documentation
¶
Overview ¶
Package protocol is the Harbor Protocol layer's runtime-side surface — the transport-agnostic handlers that translate a Protocol method call into a runtime action. Harbor ships the **task control surface**: the ControlSurface type, which maps the ten canonical task-control methods (internal/protocol/methods) onto the already-shipped runtime.
The decoupling rule (RFC §5.1, CLAUDE.md §8) ¶
The Console is a Protocol client; it never reads Runtime internals. The Protocol surface is the contract. ControlSurface is the runtime-side half of the task-control contract: it accepts the Protocol wire types (internal/protocol/types — flat, Protocol-owned structs, never re-exports of runtime Go types), reaches the runtime ONLY through the public tasks.TaskRegistry + the steering.Registry surfaces, and returns Protocol wire types. A Protocol method that mapped 1:1 onto an internal Go signature would be the RFC §5.1 reject-on-sight smell — the control methods deliberately take a flat IdentityScope + payload map, and ControlSurface does the translation.
Transport-agnostic — the wire transport is a sibling layer ¶
RFC §5.4 leaves the wire transport (SSE+REST-leaning) not-yet-locked, and says "the relevant phase blocks until it resolves." takes the explicit consequence: it ships the transport-AGNOSTIC surface now. ControlSurface.Dispatch(ctx, method, req) is a plain Go entry point — a HTTP/SSE handler is a thin adapter that decodes a request, calls Dispatch, and encodes the response (or maps a *errors.Error onto an HTTP status). The whole surface is in-process-invocable and testable today, which is what lets the E2E exercise it as a real §13 consumer.
Identity scope is enforced at the edge (RFC §5.5, CLAUDE.md §6) ¶
Every method fails closed on an incomplete identity triple — RFC §5.5: "the Protocol rejects any request without an identity scope." The nine steering-control methods additionally require a run id (they target a specific run's inbox) and run the per-event scope check via steering.Inbox.Enqueue → steering.CheckScope. The IdentityScope.Scope claim is trust-based until a later phase Protocol auth — exactly the posture events.Filter.Admin holds until then.
Single source for types / methods / errors (CLAUDE.md §8) ¶
Every Protocol message struct is in internal/protocol/types; every method name is in internal/protocol/methods; every error code is in internal/protocol/errors. This package defines NONE of those — it only consumes them. A lint enforces this; the task-control surface lays the foundation correctly so the lint is a no-op formalisation.
Concurrent reuse ¶
ControlSurface is a compiled artifact: every field is set once at construction (the TaskRegistry, the steering Registry, the clock — all immutable after NewControlSurface returns). There is NO per-call state on the struct: Dispatch reads its request-specific data from ctx + the request argument, never from the surface. One ControlSurface is safe to share across N concurrent Dispatch goroutines; concurrent_test.go pins N≥100 under -race.
Index ¶
- Constants
- Variables
- type AppToolContextPayloadRow
- type AppToolContextReader
- type AppToolContextRow
- type AppToolInvoker
- type AppsDeps
- type AppsSurface
- type ArtifactDeletedPayload
- type ArtifactUploadedPayload
- type ArtifactsDeps
- type ArtifactsSurface
- type ControlSurface
- type MCPAccessor
- type MCPAppRefRow
- type MCPAppToolResultRow
- type MCPBindingRow
- type MCPDeps
- type MCPDiscoveryRow
- type MCPHealthBucketRow
- type MCPHealthRow
- type MCPListFilter
- type MCPOAuthAccessor
- type MCPProbeRow
- type MCPPromptArgRow
- type MCPPromptRow
- type MCPReconnectRow
- type MCPResourceArtifactRow
- type MCPResourceContent
- type MCPResourceReader
- type MCPResourceRow
- type MCPServerRow
- type MCPSurface
- type Option
- type PostureDeps
- type PostureSurface
- type ScopeChecker
- type SearchSurface
- type SessionEnsurer
- type TopologyAccessor
Constants ¶
const EventTypeArtifactDeleted events.EventType = "artifacts.deleted"
EventTypeArtifactDeleted is the canonical event type the artifacts.delete success path publishes onto the bus — the audit-visible record of an admin eviction.
const EventTypeArtifactUploaded events.EventType = "artifacts.uploaded"
EventTypeArtifactUploaded is the canonical event type the artifacts.put success path publishes onto the bus (CLAUDE.md §7 rule 6: the audit-visible record of an operator upload).
Variables ¶
var ( // ErrSessionReopenAfterClose — `start` named a session id whose // record is Closed (GC-reaped or operator-closed). Reopening is // forbidden (RFC §6.9); the client must pick a new session id for a // new conversation. Maps to CodeInvalidRequest. ErrSessionReopenAfterClose = stderrors.New("protocol: session reopen-after-close forbidden") // ErrSessionIDReuse — `start` named a session id already opened under // a different (tenant, user). Cross-principal session-id reuse is // rejected. Maps to CodeInvalidRequest. ErrSessionIDReuse = stderrors.New("protocol: session id reused across principals") )
Session-ensure sentinels. The SessionEnsurer seam is error-only and the protocol package does not import the sessions package, so the adapter that wraps a concrete sessions.Registry translates the registry's sentinels into THESE before returning them to dispatchStart. Keeping the mapping vocabulary here means the Protocol surface owns its own error-code contract (CLAUDE.md §8) without coupling to the sessions package.
var ErrAppsMisconfigured = stderrors.New("protocol: AppsSurface missing a mandatory dependency")
ErrAppsMisconfigured — NewAppsSurface was called with a missing mandatory dependency. Fails closed (CLAUDE.md §5).
var ErrArtifactsMisconfigured = stderrors.New("protocol: ArtifactsSurface missing a mandatory dependency")
ErrArtifactsMisconfigured — NewArtifactsSurface was called with a missing mandatory dependency. Fails closed (CLAUDE.md §5) rather than building a surface that would nil-panic on the first Dispatch.
var ErrMCPMisconfigured = stderrors.New("protocol: MCPSurface missing a mandatory dependency")
ErrMCPMisconfigured — NewMCPSurface was called with a missing mandatory dependency. Fails closed (CLAUDE.md §5).
var ErrMisconfigured = stderrors.New("protocol: ControlSurface missing a mandatory dependency")
ErrMisconfigured — NewControlSurface was called with a nil dependency. Both the TaskRegistry and the steering Registry are mandatory: the former is where `start` lands, the latter is where the nine control methods land. Fails closed (CLAUDE.md §5) rather than building a surface that would nil-panic on the first Dispatch.
var ErrPostureMisconfigured = stderrors.New("protocol: PostureSurface missing a mandatory dependency")
ErrPostureMisconfigured — NewPostureSurface was called with a missing mandatory dependency. Fails closed (CLAUDE.md §5) rather than building a surface that would nil-panic on the first Dispatch.
Functions ¶
This section is empty.
Types ¶
type AppToolContextPayloadRow ¶ added in v1.4.1
type AppToolContextPayloadRow struct {
Inline json.RawMessage
Artifact *MCPResourceArtifactRow
}
AppToolContextPayloadRow is the runtime-side projection of one half (input or result) of a captured tool context. EXACTLY ONE of Inline / Artifact is populated — Inline for content captured below the heavy-content threshold, Artifact for content at or above it (offloaded by reference at capture time). The accessor owns the threshold decision; the surface only projects.
type AppToolContextReader ¶ added in v1.4.1
type AppToolContextReader interface {
ToolContext(ctx context.Context, serverID, toolCallID string) (AppToolContextRow, error)
}
AppToolContextReader is the narrow contract the AppsSurface calls into for `mcp.apps.tool_context`. The runtime's tool-context store (over the StateStore + ArtifactStore) satisfies it; the `protocol` package never imports the store.
ToolContext is identity-mandatory: the implementation reads the triple from ctx and resolves the captured context scoped to it, so an unknown or cross-identity (serverID, toolCallID) is not found.
type AppToolContextRow ¶ added in v1.4.1
type AppToolContextRow struct {
Tool string
Input AppToolContextPayloadRow
Result AppToolContextPayloadRow
IsError bool
}
AppToolContextRow is the runtime-side result of a tool-context read. It carries the captured input + lowered result that produced a rendered MCP App, each inline-or-by-reference (the heavy-content discipline matches MCPResourceContent).
type AppToolInvoker ¶ added in v1.4.0
type AppToolInvoker interface {
CallTool(ctx context.Context, tool string, args json.RawMessage) (MCPAppToolResultRow, error)
}
AppToolInvoker is the narrow contract the AppsSurface calls into for `mcp.apps.call_tool`. The Runtime's tool catalog (wrapped with the artifact store + bus for the heavy-content branch) satisfies it. The implementation MUST resolve the tool from the SAME catalog a planner uses and invoke the SAME wrapped descriptor, so the approval gate + tool-side OAuth fire — there is no parallel path.
CallTool is identity-mandatory: the implementation reads the triple from ctx (the gate wrapper's `identity.From`) and fails closed on a missing one.
type AppsDeps ¶ added in v1.4.0
type AppsDeps struct {
// Resource is the `ui://` resource reader (the MCP registry adapter).
// Mandatory.
Resource MCPResourceReader
// Invoker is the app-tool-call proxy (the tool-catalog adapter).
// Mandatory.
Invoker AppToolInvoker
// ToolContext is the tool-context reader (the StateStore + ArtifactStore
// adapter) backing `mcp.apps.tool_context`. Mandatory.
ToolContext AppToolContextReader
}
AppsDeps bundles the runtime-side seams an AppsSurface reads through.
type AppsSurface ¶ added in v1.4.0
type AppsSurface struct {
// contains filtered or unexported fields
}
AppsSurface is the transport-agnostic Harbor Protocol MCP Apps handler. It owns the two MCP Apps host methods — the `mcp.servers.read_resource` `ui://` UI-document fetch and the `mcp.apps.call_tool` app-tool-call proxy — that back the Console's sandboxed-iframe MCP App renderer.
AppsSurface is a sibling of the MCPSurface (the twelve Console `mcp.servers.*` methods), not an extension: it reaches the runtime's MCP driver registry (for resource reads) and the tool catalog (for the proxy's re-entry into the approval / OAuth / identity tool-safety path).
The proxy is a re-entry, not a new path ¶
`mcp.apps.call_tool` does NOT open a fresh tool-dispatch path. It resolves the named tool from the SAME catalog a planner-initiated call resolves and invokes the SAME wrapped descriptor — so the approval gate (the unified pause primitive) and tool-side OAuth fire exactly as they do for a planner call. An app call to a gated tool parks on the pause primitive before any side effect; there is no bypass.
The Console never reads internal Runtime objects (CLAUDE.md §8/§13) ¶
The MCP Apps data flows as canonical Protocol wire types (internal/protocol/types) — never as a re-export of the `mcp` driver or `tools` Go structs. The MCPResourceReader / AppToolInvoker seams are narrow adapters the Runtime wires at boot; the `protocol` package never imports the `mcp` driver or the tool catalog.
AppsSurface is immutable after construction and safe for concurrent use by N goroutines — Dispatch reads request-specific data from ctx + the request argument, never from the surface struct.
func NewAppsSurface ¶ added in v1.4.0
func NewAppsSurface(deps AppsDeps) (*AppsSurface, error)
NewAppsSurface builds the Protocol MCP Apps surface. Both the resource reader and the tool-call invoker are mandatory; a nil fails loud with a wrapped ErrAppsMisconfigured.
The returned AppsSurface is immutable after construction and safe for concurrent use by N goroutines.
func (*AppsSurface) Dispatch ¶ added in v1.4.0
Dispatch is the single transport-agnostic entry point for an MCP Apps method call. method MUST be one of the two MCP Apps methods (methods.IsMCPAppsMethod); req MUST be the wire request type the method expects.
The return is always a *types.<Method>Response or a *protoerrors.Error:
- CodeUnknownMethod — method is not an MCP Apps method.
- CodeInvalidRequest — req is nil or the wrong wire type.
- CodeIdentityRequired — the request's identity triple is incomplete.
- CodeNotFound — the named server / tool / resource is absent.
- CodeRuntimeError — an accessor failure (incl. a gate rejection).
Dispatch holds no per-call state on the AppsSurface.
type ArtifactDeletedPayload ¶ added in v1.3.0
type ArtifactDeletedPayload struct {
events.SafeSealed
// ArtifactID is the content-addressed identifier of the evicted artifact.
ArtifactID string `json:"artifact_id"`
}
ArtifactDeletedPayload is the typed payload of an artifacts.deleted event. SafePayload — it carries the content-addressed artifact id only, never any artifact bytes.
type ArtifactUploadedPayload ¶
type ArtifactUploadedPayload struct {
events.SafeSealed
// ArtifactID is the content-addressed identifier of the upload.
ArtifactID string `json:"artifact_id"`
// MimeType is the IANA media type of the upload.
MimeType string `json:"mime_type,omitempty"`
// SizeBytes is the length of the uploaded bytes.
SizeBytes int64 `json:"size_bytes"`
// Source is the artifact producer — "user_upload" for an
// artifacts.put.
Source string `json:"source,omitempty"`
// Namespace is the logical bucket the artifact landed in.
Namespace string `json:"namespace,omitempty"`
}
ArtifactUploadedPayload is the typed payload of an artifacts.uploaded event. It carries the artifact metadata only — never the uploaded bytes. It is a SafePayload: the fields are content-addressed IDs + sizes + a media type, none secret-shaped, so the bus preserves typed subscriber access without a redactor pass.
type ArtifactsDeps ¶
type ArtifactsDeps struct {
// Store is the runtime's content-addressed artifact store — the
// shipped ArtifactStore. Mandatory.
Store artifacts.ArtifactStore
// Redactor is the audit Redactor every artifacts.put body runs
// through before reaching the store (CLAUDE.md §7 rule 6).
// Mandatory.
Redactor audit.Redactor
// Bus is the canonical event bus the artifacts.put success path
// publishes `artifacts.uploaded` onto. Mandatory.
Bus events.EventBus
// Clock returns the current wall-clock time. Used for the get_ref
// ExpiresAt stamp and the row CreatedAt fallback. Mandatory.
Clock func() time.Time
// DriverName is the configured artifact-store driver name — "inmem"
// | "fs" | "sqlite" | "postgres" | "s3". Surfaced on every
// ArtifactRow so the Console can render the Driver chip. Mandatory.
DriverName string
// MaxBodyBytes bounds an artifacts.put body. A body larger than this
// is rejected with CodeRequestTooLarge. Mandatory and positive — a
// zero or negative value fails loud at construction.
MaxBodyBytes int
}
ArtifactsDeps bundles the runtime-side seams an ArtifactsSurface reads through. The Runtime wires these at boot.
type ArtifactsSurface ¶
type ArtifactsSurface struct {
// contains filtered or unexported fields
}
ArtifactsSurface is the transport-agnostic Harbor Protocol artifacts handler. It owns the three artifacts methods the Console Artifacts page consumes:
- artifacts.list — the identity-scope-filtered catalog, with the filter extensions (mime / source / size / created / tags) applied as a Go-side projection over the driver slice.
- artifacts.put — the Console file-upload pipeline; routes the payload through audit.Redactor then ArtifactStore.PutBytes and returns the canonical ArtifactRef.
- artifacts.get_ref — the read-side presigned-URL resolver per contract; type-asserts the store to artifacts.Presigner and fails loud (CodePresignUnsupported) on a non-S3 driver.
ArtifactsSurface is a sibling of the ControlSurface and the PostureSurface, not an extension: the artifacts methods are not steering controls, they do not reach the task registry, and they carry their own per-method wire types.
Concurrent reuse ¶
ArtifactsSurface is a compiled artifact: the store, redactor, bus, clock, and maxBodyBytes are all set once at construction and never mutated. Dispatch holds no per-call state on the surface — it reads everything from ctx + the request argument. One ArtifactsSurface serves N concurrent Dispatch goroutines safely; artifacts_concurrent_test.go pins N=100 under -race.
Identity at the edge (RFC §5.5, CLAUDE.md §6) ¶
Every method fails closed on an incomplete identity triple with CodeIdentityRequired. A cross-tenant artifacts.list — the request scope's Tenant differing from the caller's ctx-verified tenant — requires the admin (or console:fleet) scope per the closed admin-scope set; without it the response is CodeScopeMismatch. artifacts.put rejects a body whose scope Tenant disagrees with the verified tenant (no silent rewrite — identity is mandatory).
Heavy content by reference ¶
artifacts.list returns metadata-only rows; artifacts.get_ref returns a presigned URL; artifacts.put accepts upload bytes only on the request leg and returns a reference. No raw heavy content crosses the wire on a response, ever — the context-window safety net read into the artifacts surface.
func NewArtifactsSurface ¶
func NewArtifactsSurface(deps ArtifactsDeps) (*ArtifactsSurface, error)
NewArtifactsSurface builds the Protocol artifacts surface. Every ArtifactsDeps seam is mandatory; a missing one fails loud with a wrapped ErrArtifactsMisconfigured.
The returned ArtifactsSurface is immutable after construction and safe for concurrent use by N goroutines.
func (*ArtifactsSurface) Dispatch ¶
func (s *ArtifactsSurface) Dispatch(ctx context.Context, method methods.Method, req any) (any, error)
Dispatch is the single transport-agnostic entry point for a Protocol artifacts-method call. A REST handler decodes a request, calls Dispatch, and encodes the response — Dispatch IS the surface.
method selects the handler; it MUST be one of the three artifacts methods (methods.IsArtifactsMethod). req MUST be the wire request type the method expects (*types.ArtifactsListRequest / *types.ArtifactsPutRequest / *types.ArtifactsGetRefRequest).
The return is always a *types.<Method>Response or a *protoerrors.Error so the wire layer never sees an unstructured runtime error.
Dispatch holds no per-call state on the surface.
type ControlSurface ¶
type ControlSurface struct {
// contains filtered or unexported fields
}
ControlSurface is the transport-agnostic Harbor Protocol task-control handler. It is built once per Runtime process and shared across every Protocol request; Dispatch is safe for concurrent use by N goroutines.
Construct a ControlSurface via NewControlSurface; do not construct one directly.
func NewControlSurface ¶
func NewControlSurface(taskRegistry tasks.TaskRegistry, steeringRegistry *steering.Registry, opts ...Option) (*ControlSurface, error)
NewControlSurface builds the Protocol task-control surface. Two dependencies are mandatory:
- taskRegistry — the task registry the `start` method maps onto (tasks.TaskRegistry.Spawn).
- steeringRegistry — the process-wide steering inbox registry the nine control methods map onto (a control event is enqueued on the run's steering.Inbox).
A nil either fails loud with a wrapped ErrMisconfigured — there is no silent-degradation path (CLAUDE.md §5).
The `topology` accessor is OPTIONAL — a nil topology builds a surface that rejects `topology.snapshot` with CodeUnknownMethod (a Runtime hosting no engine, e.g. validate-only mode). It is wired via the WithTopologyAccessor option so existing two-arg callers compile unchanged.
The returned ControlSurface is immutable after construction and safe for concurrent use by N goroutines.
func (*ControlSurface) Dispatch ¶
Dispatch is the single transport-agnostic entry point for a Protocol task-control method call. A HTTP/SSE handler decodes a request, calls Dispatch, and encodes the response — Dispatch IS the surface; the wire transport is a thin adapter over it.
method selects the handler. req MUST be the wire request type the method expects:
- MethodStart → *types.StartRequest, returns *types.StartResponse
- the nine controls → *types.ControlRequest, returns *types.ControlResponse
Dispatch fails closed with a *errors.Error in every error case (the caller reaches a stable errors.Code via errors.As):
- CodeUnknownMethod — method is not one of the ten canonical methods.
- CodeInvalidRequest — req is nil or the wrong wire type for method.
- CodeIdentityRequired — the request's identity scope is incomplete (RFC §5.5: the Protocol rejects any request without an identity scope); for a control method, a missing run id also lands here.
- CodeScopeMismatch — the caller's steering scope is below the control method's RFC §6.3 minimum (mapped from steering.CheckScope).
- CodePayloadInvalid — the control payload violated an RFC §6.3 bound (mapped from steering.ValidatePayload).
- CodeNotFound — the target run has no live steering inbox.
- CodeRuntimeError — an unclassified runtime-side failure.
Dispatch holds no per-call state on the ControlSurface — it reads everything from ctx + req. One ControlSurface serves N concurrent Dispatch goroutines safely.
type MCPAccessor ¶
type MCPAccessor interface {
// ListServers returns the filtered, paginated server list plus the
// next-page token (empty when the page is the last).
ListServers(ctx context.Context, f MCPListFilter) (rows []MCPServerRow, nextPageToken string, err error)
// GetServer returns one server's detail row.
GetServer(ctx context.Context, name string) (MCPServerRow, error)
// ListResources returns a server's advertised resources.
ListResources(ctx context.Context, name string) ([]MCPResourceRow, error)
// ListPrompts returns a server's advertised prompts.
ListPrompts(ctx context.Context, name string) ([]MCPPromptRow, error)
// RefreshDiscovery re-runs a server's discovery.
RefreshDiscovery(ctx context.Context, name string) (MCPDiscoveryRow, error)
// Probe runs a transport round-trip against a server.
Probe(ctx context.Context, name string) (MCPProbeRow, error)
// Health returns a server's health snapshot.
Health(ctx context.Context, name string) (MCPHealthRow, error)
// SetRawHTMLTrust persists the per-server raw-HTML trust flag and
// returns the prior value.
SetRawHTMLTrust(ctx context.Context, name string, trusted bool) (prev bool, err error)
}
MCPAccessor is the narrow read/control contract the MCPSurface calls into for the nine `mcp.servers.*` read methods plus the raw-HTML trust toggle. The Runtime's `mcp.Registry` is wrapped to satisfy it; the `protocol` package never imports the `mcp` driver.
Every method is identity-mandatory: the implementation reads the triple from ctx and fails closed on a missing one.
type MCPAppRefRow ¶ added in v1.4.0
type MCPAppRefRow struct {
ServerID string
ToolCallID string
ResourceURI string
DisplayMode string
RawHTMLTrusted bool
}
MCPAppRefRow is the runtime-side projection of an MCP App reference parsed from a tool result's `_meta.ui.resourceUri` slot. It is nil for a non-app tool result.
type MCPAppToolResultRow ¶ added in v1.4.0
type MCPAppToolResultRow struct {
Tool string
Inline json.RawMessage
Artifact *MCPResourceArtifactRow
IsError bool
App *MCPAppRefRow
}
MCPAppToolResultRow is the runtime-side result of an app-tool-call proxy invocation. EXACTLY ONE of Inline / Artifact is populated (the heavy-content discipline matches MCPResourceContent). App is non-nil when the tool result declared a `ui://` MCP App.
type MCPBindingRow ¶
type MCPBindingRow struct {
PrincipalID string
BindingScope string
Scopes []string
ExpiresAt time.Time
LastUsedAt time.Time
}
MCPBindingRow is the runtime-side projection of one OAuth binding. It NEVER carries token plaintext (invariant).
type MCPDeps ¶
type MCPDeps struct {
// MCP is the read/control accessor over the `mcp` driver registry.
// Mandatory.
MCP MCPAccessor
// OAuth is the accessor over the `tools/auth` OAuth provider.
// Mandatory.
OAuth MCPOAuthAccessor
// Redactor is the audit Redactor the `mcp.raw_html_trust_toggled`
// payload runs through before the bus publish. Mandatory.
Redactor audit.Redactor
// Bus is the canonical event bus the `mcp.raw_html_trust_toggled`
// audit event is published onto. Mandatory.
Bus events.EventBus
// Clock returns the current wall-clock time. Optional — defaults
// to time.Now.
Clock func() time.Time
}
MCPDeps bundles the runtime-side seams an MCPSurface reads through.
type MCPDiscoveryRow ¶
MCPDiscoveryRow is the runtime-side projection of a refresh-discovery result.
type MCPHealthBucketRow ¶
MCPHealthBucketRow is one handshake-latency sparkline bucket.
type MCPHealthRow ¶
type MCPHealthRow struct {
HandshakeLatencyBuckets []MCPHealthBucketRow
ReconnectHistory []MCPReconnectRow
TransportErrorRate float64
}
MCPHealthRow is the runtime-side projection of a health snapshot.
type MCPListFilter ¶
type MCPListFilter struct {
State []string
Transport []string
HasOAuth *bool
HasRecentError *bool
NamePrefix string
PageToken string
PageSize int
}
MCPListFilter is the runtime-side filter the MCPAccessor's ListServers applies. It mirrors the Protocol `MCPServersListRequest` filter shape.
type MCPOAuthAccessor ¶
type MCPOAuthAccessor interface {
// ListBindings returns the OAuth bindings for a server (metadata
// only — never token plaintext).
ListBindings(ctx context.Context, server string) ([]MCPBindingRow, error)
// InitiateBinding starts an OAuth (re)connect flow and returns the
// AuthorizeURL + flow State the Console opens in a popup.
InitiateBinding(ctx context.Context, server, principalID string) (authorizeURL, state string, err error)
// RevokeBinding revokes a server's OAuth binding.
RevokeBinding(ctx context.Context, server, principalID string) (revoked bool, err error)
}
MCPOAuthAccessor is the narrow contract the MCPSurface calls into for the OAuth binding methods — `bindings.list` (read) and the admin verbs `refresh_binding` / `revoke_binding`. The Runtime's `tools/auth.Provider` is wrapped to satisfy it.
The accessor NEVER returns token plaintext (invariant) — only binding metadata and, for an InitiateFlow, the runtime-minted AuthorizeURL + flow State.
type MCPProbeRow ¶
MCPProbeRow is the runtime-side projection of a transport-probe result.
type MCPPromptArgRow ¶
MCPPromptArgRow is one declared prompt argument.
type MCPPromptRow ¶
type MCPPromptRow struct {
Name string
Description string
Arguments []MCPPromptArgRow
}
MCPPromptRow is the runtime-side projection of one advertised MCP prompt.
type MCPReconnectRow ¶
MCPReconnectRow is one reconnect-history entry.
type MCPResourceArtifactRow ¶ added in v1.4.0
type MCPResourceArtifactRow struct {
ID string
MimeType string
SizeBytes int64
Filename string
SHA256 string
}
MCPResourceArtifactRow is the runtime-side projection of a by-reference heavy-content stub. The accessor returns it when fetched content meets or exceeds the heavy-content threshold (RFC §6.5); the surface maps it onto the wire `types.MCPResourceArtifactRef`.
type MCPResourceContent ¶ added in v1.4.0
type MCPResourceContent struct {
ResourceURI string
MIMEType string
Inline []byte
Artifact *MCPResourceArtifactRow
}
MCPResourceContent is the runtime-side result of a resource read. EXACTLY ONE of Inline / Artifact is populated: Inline for content below the heavy-content threshold, Artifact for content at or above it (the accessor emits the loud bypass event when it offloads). The accessor owns the threshold decision — the surface only projects.
type MCPResourceReader ¶ added in v1.4.0
type MCPResourceReader interface {
ReadResource(ctx context.Context, serverID, resourceURI string) (MCPResourceContent, error)
}
MCPResourceReader is the narrow contract the AppsSurface calls into for `mcp.servers.read_resource`. The Runtime's MCP registry (wrapped with the artifact store + bus for the heavy-content branch) satisfies it; the `protocol` package never imports the `mcp` driver.
ReadResource is identity-mandatory: the implementation reads the triple from ctx and fails closed on a missing one.
type MCPResourceRow ¶
MCPResourceRow is the runtime-side projection of one advertised MCP resource.
type MCPServerRow ¶
type MCPServerRow struct {
Name string
Transport string
URLOrCommand string
State string
LastDiscoveryAt time.Time
ToolCount int
ResourceCount int
PromptCount int
RecentLatencyMs int64
ErrorRatePerMin float64
OAuthBindingCount int
RawHTMLTrusted bool
DisplayModes []string
ContentShapes []string
PolicyTimeoutMs int64
PolicyMaxRetries int
PolicyConcurrency int
}
MCPServerRow is the runtime-side projection of one MCP server. The `mcp` driver's `Registry.ServerView` satisfies the MCPAccessor contract by returning this flat shape; the MCPSurface translates it onto `types.MCPServerView`. Keeping the projection here (not importing the driver type) keeps the `protocol` package driver-free.
type MCPSurface ¶
type MCPSurface struct {
// contains filtered or unexported fields
}
MCPSurface is the transport-agnostic Harbor Protocol MCP-Connections handler. It owns the twelve `mcp.servers.*` methods that back the Console MCP Connections page — nine read methods and three admin verbs.
MCPSurface is a sibling of the ControlSurface and the PostureSurface, not an extension: the MCP methods reach the runtime's MCP driver registry and tool-side OAuth provider, not the steering inbox.
MCPSurface is built once per Runtime process via NewMCPSurface and shared across every Protocol request; Dispatch is safe for concurrent use by N goroutines. Every field is set once at construction and never mutated — Dispatch reads its request-specific data from ctx + the request argument, never from the surface struct.
Identity at the edge (RFC §5.5, CLAUDE.md §6) ¶
Every handler fails closed on an incomplete identity triple with CodeIdentityRequired. The three admin verbs (`refresh_binding` / `revoke_binding` / `set_raw_html_trust`) gate additionally on the `auth.ScopeAdmin` claim per the closed admin-scope set; a missing claim surfaces CodeScopeMismatch. The two control-plane verbs (`refresh_discovery` / `probe`) gate on the `auth.ScopeAdmin` claim too (control- plane verbs mutate the runtime's view of upstream state) — there is NO new scope minted for MCP (closed-set).
The raw-HTML trust audit ¶
A successful `mcp.servers.set_raw_html_trust` emits a `mcp.raw_html_trust_toggled` audit event through the wired Redactor + Bus. Raw HTML / SVG from an MCP server is untrusted by default; the audit event is the load-bearing record of the operator's explicit opt-in. A failed audit emit fails the call loudly (CodeRuntimeError) — an un-auditable trust toggle is refused, never silently applied.
The Console never reads internal Runtime objects (CLAUDE.md §8/§13) ¶
The MCP data flows as canonical Protocol wire types (internal/protocol/types) — never as a re-export of the `mcp` driver or `tools/auth` Go structs. The MCPAccessor / MCPOAuthAccessor seams are narrow read/control adapters the Runtime wires at boot; the `protocol` package never imports the `mcp` driver package (the seam is satisfied structurally — no import cycle).
func NewMCPSurface ¶
func NewMCPSurface(deps MCPDeps) (*MCPSurface, error)
NewMCPSurface builds the Protocol MCP-Connections surface. The MCP / OAuth accessors, the Redactor, and the Bus are all mandatory; a nil fails loud with a wrapped ErrMCPMisconfigured.
The returned MCPSurface is immutable after construction and safe for concurrent use by N goroutines.
func (*MCPSurface) Dispatch ¶
Dispatch is the single transport-agnostic entry point for a Protocol `mcp.servers.*` method call. A REST handler decodes a request, calls Dispatch, and encodes the response — Dispatch IS the surface.
method selects the handler; it MUST be one of the twelve `mcp.servers.*` methods (methods.IsMCPServersMethod). req MUST be the wire request type the method expects.
The return is always a *types.<Method>Response or a *protoerrors.Error:
- CodeUnknownMethod — method is not an MCP method.
- CodeInvalidRequest — req is nil or the wrong wire type.
- CodeIdentityRequired — the request's identity triple is incomplete.
- CodeScopeMismatch — an admin / control verb without the admin scope claim.
- CodeNotFound — the named MCP server does not exist.
- CodeRuntimeError — an accessor or audit-emit failure.
Dispatch holds no per-call state on the MCPSurface.
type Option ¶
type Option func(*ControlSurface)
Option configures a ControlSurface at construction time. Reserved for later Protocol-surface phases (a clock override, a metrics hook); the surface has no options yet, but the variadic seam means a later phase adds one without a signature break.
func WithEventBus ¶
WithEventBus wires the canonical events.EventBus the ControlSurface publishes an `audit.admin_scope_used` event onto when a cross-tenant `topology.snapshot` read is granted under the admin scope (RFC §6.13 — admin-scope use is retroactively auditable). The bus is OPTIONAL: a surface built without it still gates the cross-tenant read on the admin scope, but a successful admin read emits no audit event — the production wiring (cmd/harbor / harbortest devstack) wires the bus so the audit trail is complete. A nil bus is treated as "not supplied".
func WithScopeChecker ¶
func WithScopeChecker(c ScopeChecker) Option
WithScopeChecker overrides the admin-cross-tenant scope predicate. Production leaves it at the default (auth.HasScope); tests inject a deterministic checker to exercise the admin path without standing up an auth.Middleware.
func WithSessionEnsurer ¶ added in v1.2.0
func WithSessionEnsurer(e SessionEnsurer) Option
WithSessionEnsurer wires the create-on-first-use seam the `start` method calls so a brand-new conversation's session row materialises in the SessionRegistry on the first turn. A surface built WITHOUT it does NOT create sessions on start — the explicit "this Runtime has no session registry" posture (e.g. a control-only surface). A nil ensurer passed here is treated as "not supplied" (no silent panic). The production wiring (cmd/harbor, harbortest) always supplies it so `harbor dev` gets create-on-first-use sessions out of the box.
func WithTopologyAccessor ¶
func WithTopologyAccessor(t TopologyAccessor) Option
WithTopologyAccessor wires the engine topology accessor into the ControlSurface so `topology.snapshot` returns a real projection. A surface built WITHOUT it rejects `topology.snapshot` with CodeUnknownMethod — the explicit "this Runtime hosts no engine" posture (CLAUDE.md §13 — no silent degradation; the route simply does not exist on an engine-less Runtime). A nil accessor passed here is treated as "not supplied".
type PostureDeps ¶
type PostureDeps struct {
// Build carries the static build identity (BuildVersion /
// BuildCommit / BuildDate / BuildGoVersion) plus the static
// deployment-declared MCPAppDisplayModes (the host's renderable MCP
// App display modes, sourced from `tools.mcp_app_host.display_modes`).
// The Capabilities, ProtocolVersion, InstanceID, DisplayName, and
// UptimeSeconds fields of the eventual RuntimeInfo are filled by the
// surface — callers leave them zero here.
Build types.RuntimeInfo
// Clock returns the current wall-clock time. Used to compute
// uptime and the SnapshotAt timestamps. Mandatory.
Clock func() time.Time
// BootedAt is the instant the Runtime process started — uptime is
// Clock() minus this. When zero, the surface treats construction
// time as boot time.
BootedAt time.Time
// Health returns the per-subsystem readiness rollup. Mandatory.
Health func(ctx context.Context) []types.SubsystemHealth
// Counters returns the low-cardinality live counters for the
// caller's identity scope. Mandatory.
Counters func(ctx context.Context, ident identity.Identity) types.RuntimeCounters
// Drivers returns the configured driver names per persistence-
// shaped subsystem. Mandatory.
Drivers func() []types.SubsystemDriver
// Metrics returns the Protocol-shaped projection over the
// MetricsRegistry. Mandatory.
Metrics func(ctx context.Context) types.MetricsSnapshot
// Governance is the read-only governance posture
// accessor — the source the `governance.posture` method projects
// onto types.GovernancePostureResponse. Mandatory.
Governance *governance.PostureProvider
// LLM is the read-only LLM posture accessor — the
// source the `llm.posture` method projects onto
// types.LLMPostureResponse. Mandatory.
LLM *llm.PostureProvider
// Redactor is the audit Redactor every cross-tenant
// `*.posture_read_admin` payload runs through before the bus
// publish (CLAUDE.md §7 rule 6). Mandatory.
Redactor audit.Redactor
// Bus is the canonical event bus the cross-tenant
// `*.posture_read_admin` audit events are published onto.
// Mandatory.
Bus events.EventBus
// DisplayName is the operator-configured friendly name for this
// Runtime. Optional — empty when the operator configured none.
DisplayName string
// InstanceID is the stable per-deployment identifier minted at
// boot. Mandatory — a Console attached to multiple Runtimes keys
// each attachment by it.
InstanceID string
// TopologyAvailable indicates this Runtime hosts an engine-graph
// projection — when true, `runtime.info.capabilities` advertises
// `topology_snapshot` so Protocol clients gate their topology
// fetches at attach time. The
// `topology.snapshot` method itself stays gated by the matching
// `ControlSurface.topology` accessor; this flag is the *advertised*
// projection of that wiring decision. Optional — defaults false
// (planner/RunLoop runtimes like `harbor dev` against an agent
// yaml).
TopologyAvailable bool
}
PostureDeps bundles the runtime-side seams a PostureSurface reads through. Every dependency is read-only — the surface mutates none of them. The Runtime wires these at boot (page consumers — the Overview counter cards, the Settings Runtime Info / Governance / LLM-Provider cards — read the resulting Protocol methods, never the seams directly).
type PostureSurface ¶
type PostureSurface struct {
// contains filtered or unexported fields
}
PostureSurface is the transport-agnostic Harbor Protocol posture handler. It owns the seven read-only posture methods:
- runtime.info, runtime.health, runtime.counters, runtime.drivers, metrics.snapshot — the runtime-posture cluster.
- governance.posture, llm.posture — the config- posture pair (the governance `IdentityTiers` view and the bound LLM provider/model/region + `MockMode` flag).
PostureSurface is a sibling of the ControlSurface, not an extension: the posture methods are READ methods (no runtime mutation), and threading the build / health / counters / drivers / metrics / governance / llm seams through NewControlSurface would balloon its dependency set.
PostureSurface is built once per Runtime process via NewPostureSurface and shared across every Protocol request; Dispatch is safe for concurrent use by N goroutines. Every field is set once at construction and never mutated — Dispatch reads its request-specific data from ctx + the request argument, never from the surface struct.
Identity at the edge (RFC §5.5, CLAUDE.md §6) ¶
Every handler fails closed on an incomplete identity triple with CodeIdentityRequired. A cross-tenant query — the request's Identity.Tenant differing from the caller's ctx-verified tenant — requires the admin scope per the closed admin-scope set; without it the response is CodeScopeMismatch. When no auth middleware ran (trust-based posture, no identity on ctx), the request's body identity is authoritative and the cross-tenant gate is a no-op — the same posture every other Protocol surface holds.
Audit on the cross-tenant config reads ¶
An accepted cross-tenant `governance.posture` / `llm.posture` read emits a `*.posture_read_admin` audit event through the wired Redactor + Bus — the same posture the admin-scope path takes. An own-tenant read does NOT emit (matches the sessions.inspect convention). The five `runtime.*` / `metrics.*` reads never emit audit. A failed audit emit fails loudly (CodeRuntimeError) — the read already succeeded, so the operator MUST see the audit drift.
The Console never reads internal Runtime objects (CLAUDE.md §8/§13) ¶
The posture data flows as canonical Protocol wire types (internal/protocol/types) — never as a re-export of an internal Runtime Go struct. The PostureDeps seams are read-only adapters the Runtime wires at boot; PostureSurface translates their output into the wire shape and returns it.
func NewPostureSurface ¶
func NewPostureSurface(deps PostureDeps) (*PostureSurface, error)
NewPostureSurface builds the Protocol posture surface. Every PostureDeps seam except Build / DisplayName / BootedAt is mandatory; a missing one fails loud with a wrapped ErrPostureMisconfigured.
The returned PostureSurface is immutable after construction and safe for concurrent use by N goroutines.
func (*PostureSurface) Dispatch ¶
Dispatch is the single transport-agnostic entry point for a Protocol posture-method call. A REST handler decodes a request, calls Dispatch, and encodes the response — Dispatch IS the surface.
method selects the handler; it MUST be one of the seven posture methods (methods.IsPostureMethod). req MUST be a *types.RuntimeInfoRequest — all seven posture methods share the one read-only request envelope (the governance / llm reads are also identity-only, so they reuse the same shape).
The return is always a *types.<Method>Response or a *protoerrors.Error so the wire layer never sees an unstructured runtime error:
- CodeUnknownMethod — method is not a posture method.
- CodeInvalidRequest — req is nil or not a *types.RuntimeInfoRequest.
- CodeIdentityRequired — the request's identity triple is incomplete.
- CodeScopeMismatch — a cross-tenant query without the admin scope.
- CodeRuntimeError — a posture-accessor or audit-emit failure.
Dispatch holds no per-call state on the PostureSurface — it reads everything from ctx + req. One PostureSurface serves N concurrent Dispatch goroutines safely.
type ScopeChecker ¶
ScopeChecker reports whether ctx carries a given auth scope. It is the seam the admin-cross-tenant gate consults; the production implementation is auth.HasScope. Injecting it (rather than calling auth.HasScope directly) keeps the gate unit-testable without an auth.Middleware in front.
type SearchSurface ¶
type SearchSurface struct {
// contains filtered or unexported fields
}
SearchSurface is the Protocol-side dispatcher for the five `search.*` methods. It is transport-agnostic — the wire transport's `search_handler.go` calls Dispatch from internal/protocol/transports/control via the `transports/control.SearchSurface` interface that this type satisfies. A protocol/conformance test consumer calls Dispatch directly (no HTTP layer needed).
Identity at the edge: every method reads `identity.From(ctx)` and fails closed with CodeIdentityRequired (mapped to 401) on a missing triple. Cross-tenant gating reads `search.ErrCrossTenantRequiresAdmin` from the search subsystem and surfaces it as CodeAuthRejected (403) per the closed admin-scope set.
Concurrent reuse: the SearchSurface is a compiled artifact — the registry + adminScope predicate are set once at construction; per-call state lives in ctx + req.
func NewSearchSurface ¶
func NewSearchSurface(registry *search.SearcherRegistry, adminScope search.ScopeChecker) (*SearchSurface, error)
NewSearchSurface builds the search dispatcher. The registry is mandatory; the adminScope predicate is mandatory. Both nil-checked at construction so a misconfigured surface fails at boot instead of nil-panicking on the first request.
func (*SearchSurface) Dispatch ¶
func (s *SearchSurface) Dispatch(ctx context.Context, method methods.Method, req *types.SearchRequest) (*types.SearchResponse, error)
Dispatch routes the request to the right Searcher (or the aggregate dispatcher for `search.query`). It is the entry point both the wire transport (via the SearchSurface interface in internal/protocol/transports/control) and any in-process Protocol consumer (test, conformance suite) call.
The return is always `(*types.SearchResponse, *protoerrors.Error)` — every error case maps onto a canonical Protocol code so the wire layer never sees an unstructured runtime error.
type SessionEnsurer ¶ added in v1.2.0
SessionEnsurer is the create-on-first-use seam the `start` method calls. The session id is the per-request session the client chose (carried in the request's IdentityScope, sourced from the X-Harbor-Session header by auth.Middleware). When a `start` names a session id that has no registry row yet, EnsureSession materialises it under the verified (tenant, user); a later turn in the same session is a no-op. A closed session id fails loud (the runtime never silently revives a GC-reaped conversation).
Defined here (consumer side, error-only) so the protocol package does not import the sessions package; the concrete *sessions.Registry is adapted to this interface in cmd/harbor / harbortest.
type TopologyAccessor ¶
type TopologyAccessor interface {
// Topology builds the engine's canonical TopologyProjection.
// Identity-mandatory; pure read.
Topology(ctx context.Context) (types.TopologyProjection, error)
// TenantID is the tenant the engine runs under. The
// admin-cross-tenant gate compares it against the caller's tenant:
// a caller whose tenant differs needs the verified auth.ScopeAdmin
// claim.
TenantID() string
}
TopologyAccessor is the narrow read-only contract the ControlSurface calls into for the `topology.snapshot` method. The Runtime engine satisfies it structurally — the engine package never imports the Protocol package; the wiring at cmd/harbor injects the engine as a TopologyAccessor. Keeping the interface here (not in the engine package) is what keeps the engine Protocol-free (no import cycle).
A nil TopologyAccessor is permitted at construction (a Runtime that hosts no engine — e.g. validate-only mode): a `topology.snapshot` call against a nil-accessor surface fails closed with CodeUnknownMethod (the route effectively does not exist on that Runtime), which the smoke script's 404 → SKIP convention picks up.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package auth is the Harbor Protocol's JWT validation surface — the transport-edge cryptographic identity check that turns the wire transports' trust-based identity carriers into verified ones (RFC §5.5: "JWT, asymmetric algorithms only ...
|
Package auth is the Harbor Protocol's JWT validation surface — the transport-edge cryptographic identity check that turns the wire transports' trust-based identity carriers into verified ones (RFC §5.5: "JWT, asymmetric algorithms only ... |
|
Package conformance is the Harbor Protocol conformance suite — the single binding pass/fail definition of "the Protocol surface works at version 0.1.0" (RFC §5 + master-plan detail block).
|
Package conformance is the Harbor Protocol conformance suite — the single binding pass/fail definition of "the Protocol surface works at version 0.1.0" (RFC §5 + master-plan detail block). |
|
Package errors is the single source of truth for Harbor Protocol error codes (CLAUDE.md §8: "Error codes live in internal/protocol/errors/errors.go.
|
Package errors is the single source of truth for Harbor Protocol error codes (CLAUDE.md §8: "Error codes live in internal/protocol/errors/errors.go. |
|
Package methods is the single source of truth for Harbor Protocol method names (CLAUDE.md §8: "Method names live in internal/protocol/methods/methods.go.
|
Package methods is the single source of truth for Harbor Protocol method names (CLAUDE.md §8: "Method names live in internal/protocol/methods/methods.go. |
|
Package singlesource is the Harbor Protocol single-source enforcement checker (CLAUDE.md §8, §13; RFC §5).
|
Package singlesource is the Harbor Protocol single-source enforcement checker (CLAUDE.md §8, §13; RFC §5). |
|
Package transports is the Harbor Protocol wire-transport seam — the binding of RFC §5.4's resolved transport choice (SSE for the event stream + REST/JSON for the control surface) onto net/http.
|
Package transports is the Harbor Protocol wire-transport seam — the binding of RFC §5.4's resolved transport choice (SSE for the event stream + REST/JSON for the control surface) onto net/http. |
|
control
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).
|
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). |
|
cors
Package cors is the Harbor Protocol CORS middleware — the security primitive that unlocks the multi-process Console+Runtime posture (Console on one origin attaches to a Runtime on a different origin) without weakening the browser-side enforcement contract.
|
Package cors is the Harbor Protocol CORS middleware — the security primitive that unlocks the multi-process Console+Runtime posture (Console on one origin attaches to a Runtime on a different origin) without weakening the browser-side enforcement contract. |
|
stream
Package stream — addition: the admin-scoped `agent_config.*` agent-config control-plane HTTP handler.
|
Package stream — addition: the admin-scoped `agent_config.*` agent-config control-plane HTTP handler. |
|
Package types is the single source of truth for Harbor Protocol wire types (CLAUDE.md §8).
|
Package types is the single source of truth for Harbor Protocol wire types (CLAUDE.md §8). |
|
Package wiresurface computes the canonical Harbor Protocol wire-surface digest: a coarse, opaque, stable name-level fingerprint of the surface a Protocol client binds to.
|
Package wiresurface computes the canonical Harbor Protocol wire-surface digest: a coarse, opaque, stable name-level fingerprint of the surface a Protocol client binds to. |