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. Phase 54 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 Phase 20 tasks.TaskRegistry + Phase 52/53 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 Phase 60 ¶
RFC §5.4 leaves the wire transport (SSE+REST-leaning) not-yet-locked, and says "the relevant phase blocks until it resolves." Phase 54 takes the explicit consequence: it ships the transport-AGNOSTIC surface now. ControlSurface.Dispatch(ctx, method, req) is a plain Go entry point — a Phase 60 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 Wave 9 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 Phase 52 per-event scope check via steering.Inbox.Enqueue → steering.CheckScope. The IdentityScope.Scope claim is trust-based until Phase 61 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. Phase 58 formalises the lint that enforces this; Phase 54 lays the foundation correctly so Phase 58 is a no-op formalisation.
Concurrent reuse (D-025) ¶
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 ArtifactDeletedPayload
- type ArtifactUploadedPayload
- type ArtifactsDeps
- type ArtifactsSurface
- type ControlSurface
- type MCPAccessor
- type MCPBindingRow
- type MCPDeps
- type MCPDiscoveryRow
- type MCPHealthBucketRow
- type MCPHealthRow
- type MCPListFilter
- type MCPOAuthAccessor
- type MCPProbeRow
- type MCPPromptArgRow
- type MCPPromptRow
- type MCPReconnectRow
- 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 (Phase 108o / D-187) — 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 (D-171). 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 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 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 (Phase 108o / D-187). SafePayload — it carries the content-addressed artifact id only, never any artifact bytes (D-026).
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 (D-026). 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 Phase 17–19 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 + D-020).
// 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 Phase 73l (Wave 13 / D-120) 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 Phase 73l filter extensions (mime / source / size / created / tags) applied as a Go-side projection over the driver slice.
- artifacts.put — the file-upload pipeline per Brief 11 §PG-2; routes the payload through audit.Redactor (D-020) then ArtifactStore.PutBytes and returns the canonical ArtifactRef.
- artifacts.get_ref — the read-side presigned-URL resolver per D-022 / D-026; type-asserts the store to artifacts.Presigner and fails loud (CodePresignUnsupported) on a non-S3 driver.
ArtifactsSurface is a sibling of the Phase 54 ControlSurface and the Phase 72f 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 (D-025) ¶
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 D-079; 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 (D-026) ¶
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 D-026 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 (D-025) 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 Phase 60 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 (D-025).
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 (D-025).
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 Phase 20 task registry the `start` method maps onto (tasks.TaskRegistry.Spawn).
- steeringRegistry — the Phase 52/53 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 Phase 74 `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 (D-025) 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 Phase 60 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 (D-025). 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 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 (D-083 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 (D-083 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 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 (Phase 73k / D-119). 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 Phase 54 ControlSurface and the Phase 72f 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 (D-025). 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 D-079; a missing claim surfaces CodeScopeMismatch. The two control-plane verbs (`refresh_discovery` / `probe`) gate on the `auth.ScopeAdmin` claim too (D-066 — control- plane verbs mutate the runtime's view of upstream state) — there is NO new scope minted for MCP (D-079 closed-set).
The raw-HTML trust audit (brief 11 §8) ¶
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 (D-025) 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 Phase 60 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 (D-025).
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 Phase 54 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 (D-171) 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 Phase 74 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). 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 Phase 56
// MetricsRegistry. Mandatory.
Metrics func(ctx context.Context) types.MetricsSnapshot
// Governance is the Phase 72g (D-112) read-only governance posture
// accessor — the source the `governance.posture` method projects
// onto types.GovernancePostureResponse. Mandatory.
Governance *governance.PostureProvider
// LLM is the Phase 72g (D-112) 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 (round-8 F1 / phase 84a). 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 (Stage-2 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 Phase 72f (D-111) runtime-posture cluster.
- governance.posture, llm.posture — the Phase 72g (D-112) config- posture pair (the D-081 governance `IdentityTiers` view and the bound LLM provider/model/region + D-089 `MockMode` flag).
PostureSurface is a sibling of the Phase 54 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 (D-025). 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 D-079; without it the response is CodeScopeMismatch. When no auth middleware ran (Phase 60 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 (Phase 72g / D-112) ¶
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 Phase 72b 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 (D-025) 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 Phase 60 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 (D-025). 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 Phase 72c (D-108) Protocol-side dispatcher for the five `search.*` methods. It is transport-agnostic — the Phase 60 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 D-079.
Concurrent reuse (D-025): 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 Phase 72c 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 (D-171). 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 (D-079).
TenantID() string
}
TopologyAccessor is the narrow read-only contract the ControlSurface calls into for the Phase 74 `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 Phase 61 transport-edge cryptographic identity check that turns the Phase 60 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 Phase 61 transport-edge cryptographic identity check that turns the Phase 60 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 Phase 62 detail block; D-080).
|
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 Phase 62 detail block; D-080). |
|
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 Phase 60 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 Phase 60 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 Phase 83v security primitive that unlocks the D-091 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 Phase 83v security primitive that unlocks the D-091 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 — Wave 13 additions (Phase 73e): the `agents.*` HTTP handler.
|
Package stream — Wave 13 additions (Phase 73e): the `agents.*` 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). |