protocol

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: 19 Imported by: 0

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

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

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

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

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

View Source
var ErrMCPMisconfigured = stderrors.New("protocol: MCPSurface missing a mandatory dependency")

ErrMCPMisconfigured — NewMCPSurface was called with a missing mandatory dependency. Fails closed (CLAUDE.md §5).

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

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

func (s *ControlSurface) Dispatch(ctx context.Context, method methods.Method, req any) (any, error)

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

type MCPDiscoveryRow struct {
	DiscoveryID   string
	ToolCount     int
	ResourceCount int
	PromptCount   int
}

MCPDiscoveryRow is the runtime-side projection of a refresh-discovery result.

type MCPHealthBucketRow

type MCPHealthBucketRow struct {
	StartMs   int64
	LatencyMs int64
}

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

type MCPProbeRow struct {
	OK        bool
	LatencyMs int64
	Error     string
}

MCPProbeRow is the runtime-side projection of a transport-probe result.

type MCPPromptArgRow

type MCPPromptArgRow struct {
	Name        string
	Description string
	Required    bool
}

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

type MCPReconnectRow struct {
	OccurredAt time.Time
	Reason     string
}

MCPReconnectRow is one reconnect-history entry.

type MCPResourceRow

type MCPResourceRow struct {
	URI       string
	MimeType  string
	SizeBytes int64
	Name      string
	Title     string
}

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

func (s *MCPSurface) Dispatch(ctx context.Context, method methods.Method, req any) (any, error)

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

func WithEventBus(b events.EventBus) Option

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

func (s *PostureSurface) Dispatch(ctx context.Context, method methods.Method, req any) (any, error)

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

type ScopeChecker func(ctx context.Context, s auth.Scope) bool

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

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

type SessionEnsurer interface {
	EnsureSession(ctx context.Context, ident identity.Identity) error
}

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.

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).

Jump to

Keyboard shortcuts

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