Help us shape the future of Go! Take the 2026 Go Developer Survey

runtime

package
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 37 Imported by: 1

Documentation

Overview

Package runtime is the shared orchestration layer of an MCP server built on mcp-server-core: policy loading, the guardrail registry, the receiving middleware chain, egress provisioning, the harness servant, the planner, the journey runner, evidence sealing and the operator-facing policy operations.

Everything here is platform-agnostic. A server supplies the platform through the agentweave-harness interfaces (signals.SystemProbe, signals.HealthProbe, contain.SystemActuator, egress.Enforcer) and its own dependency set; the server's RunStdio composes the pieces in the order the Windows server pinned.

Index

Constants

View Source
const (
	EnvHarnessPipe = "AGENTWEAVE_CONTROL_PIPE"
	// EnvHarnessToken names the var carrying the bootstrap token, not a secret.
	EnvHarnessToken = "AGENTWEAVE_CONTROL_TOKEN" //nolint:gosec // G101: env var name
)

The bootstrap environment variables the harness sets on this child. They are scrubbed after use so no tool can read the token back.

View Source
const AuditKeyFile = ".audit-key"

AuditKeyFile is the per-machine key file, kept beside the audit directory.

Variables

View Source
var (
	ErrPlanContainsPlanning = errors.New("a plan cannot contain Plan or Apply steps")
	ErrToolNotServed        = errors.New("tool is not in the served surface")
	ErrUnknownPlan          = errors.New("unknown plan_id; call Plan first")
)

ErrPlanContainsPlanning reports a plan whose steps call Plan or Apply — a plan cannot nest or re-enter itself. ErrToolNotServed reports a step naming a tool the current surface does not serve.

View Source
var ErrBadFixtureStatus = errors.New("fixture device status must be pass, fail or error")

ErrBadFixtureStatus reports a device signal status that is not pass/fail/error.

View Source
var ErrHarnessChannelLost = errors.New("agentweave-harness control channel lost")

ErrHarnessChannelLost is the cancel cause when the control channel drops mid-session. It is distinct from a graceful stop so the server exits non-zero: losing the adjudicator is a failure, not an orderly end.

View Source
var ErrPlanApprovalUnavailable = errors.New("no approvals webhook is configured")

ErrPlanApprovalUnavailable reports a plan-gated apply with no authoriser.

Functions

func ApplyEffectiveConfig

func ApplyEffectiveConfig(
	ec wire.EffectiveConfig,
	enforceHTTPS *bool,
	deps *toolkit.BaseDeps,
	localPaths []toolkit.ProtectedPath,
	normalize toolkit.PathNormalizer,
	banner func(string),
	logger *slog.Logger,
)

ApplyEffectiveConfig folds the hello.ack's effective config into this session's settings. The wire contract requires the config to be applied before the server builds its tool surface, and the call site sits between the handshake and RegisterAll so the tools this session serves are bound by it from the first call.

Narrowing only: the harness can enable a protection this server did not configure — enforce HTTPS, additional protected paths — but never relax one the local policy already set. A harness that could switch local protections off would be a way around the document the operator reviewed.

enforceHTTPS is the server's config field, mutated as well as deps because the guardrail Env closes over it, so the may-run endpoint gets the same answer the URL-shaped tools do. localPaths are the protections the server derived itself; they survive the extension.

Harness-declared protected paths deny both read and write. The wire carries only the paths, not why they are protected, and the server cannot tell a tamper-target (write must be refused) from a secret (read must be refused), so it refuses both rather than guess.

The announced egress proxy (port and executable) is consumed by the egress provisioning, which the server feeds directly — see HarnessEgress.

func AuditDestinationIsDir

func AuditDestinationIsDir(dest string) bool

AuditDestinationIsDir mirrors the audit package's directory detection: an explicit trailing separator, or an existing directory. A directory destination protects every session file and the manifest under it.

func AuditKeyDir

func AuditKeyDir(destination string) string

AuditKeyDir returns the directory to keep the key in, or "" when there is no file destination to protect.

func AutoSealEvidence

func AutoSealEvidence(
	tp policy.TransparencyPolicy,
	session string,
	plans []plan.Document,
	posture []byte,
	sink export.Sink,
	keyFile string,
	logger *slog.Logger,
)

AutoSealEvidence seals a bundle into the evidence directory at session end, adding what only exists live: the full approved plan documents and the closing posture snapshot. It never crashes shutdown — a seal failure or a panic is logged and swallowed, because a missing bundle must not turn a clean exit into a crash the host reads as instability.

func BuildRungs

func BuildRungs(p RungPrimitives) map[string]RungFunc

BuildRungs assembles the rung table. The set is closed and named here; a rung absent from it is refused by the servant as unknown.

func BundleEvidence

func BundleEvidence(
	auditDir, session, recordingDir, outPath, keyFile string,
	extra ...evidence.Source,
) (evidence.Manifest, error)

BundleEvidence gathers a session's evidence from an audit directory — its audit chain, the extracted verdicts, and any recording — plus any extra sources the caller supplies (the full plan documents and closing posture, for an auto-seal), and seals it into a signed (or, without a key, unsigned) archive.

func EgressStatus

func EgressStatus(svc *egress.Service, cfg policy.EgressPolicy) *status.EgressStatus

EgressStatus adapts the service to the status snapshot. A nil service means egress is off, and the status field is omitted rather than reported as zero.

func EvaluatePolicy

func EvaluatePolicy(
	ctx context.Context,
	devicePolicy *policy.Policy,
	reg *signals.Registry,
	envFn func() *signals.Env,
) signals.Decision

EvaluatePolicy reads every signal the policy declares and returns the decision for the startup scope.

Every signal is read live, cache bypassed: an operator running this wants the device as it is now, and a cached answer would be answering a question nobody asked. That makes it slow — seconds, on a device where every probe has to run — which is correct for a diagnostic and is exactly why the request path does not work this way.

envFn builds the evaluation environment over the server's platform probes.

func EvidenceSubdir

func EvidenceSubdir(root, sub string) string

EvidenceSubdir returns a subdirectory of the evidence root, or "" when no evidence directory is configured — which disables persistence rather than failing, since a missing output directory is a configuration choice.

func ExportCredentials

func ExportCredentials() export.Credentials

ExportCredentials reads the destination secrets from the environment, once.

They are never flags (argv is world-readable) and never policy fields (the document is a reviewable, checked-in artifact that the agent can read through the filesystem toolset). The names are fixed by the harness export package (see export.EnvSignedURL), which is what puts them behind both defences: the server withholds them from every child process, and ScrubSecretEnv clears them from this one.

func ExportStatus

func ExportStatus(e policy.ExportPolicy, sink export.Sink) *status.ExportStatus

ExportStatus renders the export tier for the status snapshot, or nil when no bundle leaves this device.

Nil for a configured-but-unbuildable destination too, and that is the honest answer: the operator asked for export, it is not running, and reporting the provider name here would let a watcher read intent as posture. The audit chain carries the export.disabled entry that says why.

func GuardrailPaths

func GuardrailPaths(cfg GuardrailPathsConfig, p *policy.Policy) []toolkit.ProtectedPath

GuardrailPaths lists the guardrail files the file tool must not touch: the credentials file (no read — a read is plaintext into the model's context — and no write), the audit log (no write), and the policy document (no write). Reading the audit log or the policy is allowed; both are meant to be inspected.

func HarnessAddress

func HarnessAddress() (pipe, token string)

HarnessAddress reads the bootstrap contract from the environment. The empty string means no harness is present and the server runs standalone.

func HeartbeatFromAck

func HeartbeatFromAck(ack wire.HelloAck) time.Duration

HeartbeatFromAck resolves the heartbeat cadence, defaulting when the harness leaves it unset.

func JourneyStatusMessage

func JourneyStatusMessage(passed bool, failed int, missing []string) string

JourneyStatusMessage renders the run span's status description.

func KillPolicyConfig

func KillPolicyConfig(devicePolicy *policy.Policy) contain.KillActionConfig

KillPolicyConfig maps the policy's containment actions to the executor's config.

func LoadPolicy

func LoadPolicy(path string, reg *signals.Registry, logger *slog.Logger) (*policy.Policy, error)

LoadPolicy resolves the active device policy.

With no document the embedded default applies: the engine is present, every declared signal is evaluated and every verdict recorded, but nothing is refused. That is deliberate — an engine that arrived enforcing would start refusing tool calls on devices that worked the day before, with no operator action and no document to point at.

A named policy that fails to load is fatal. Falling back to the default would silently run a device under weaker policy than its operator wrote, which is the worst of the available outcomes.

func NewGuardrailRegistry

func NewGuardrailRegistry(env EnvNames, logger *slog.Logger) *signals.Registry

NewGuardrailRegistry builds the signal registry: the tier-1 local checks, the just-in-time at-source device-posture checks, and — when the credentials are present in the environment — the authoritative tier-2 Graph and remote-policy providers.

Registering a signal only makes it available for a policy to declare. Nothing here decides whether it is evaluated; that is the policy's job.

func NewLogger

func NewLogger(logFile string) (*slog.Logger, func(), error)

NewLogger returns a structured logger writing to logFile (debug level) or, if empty, to stderr (info level). stdout is never used, so it stays clean for the MCP stdio transport.

func ProvisionEgress

func ProvisionEgress(
	ctx context.Context,
	devicePolicy *policy.Policy,
	auditLog *audit.AuditLog,
	logger *slog.Logger,
	harnessProxy HarnessEgress,
	enforcer egress.Enforcer,
) (*egress.Service, func(), func(), error)

ProvisionEgress starts the device egress proxy when the policy asks for it, and returns a cleanup that runs exactly once however the session ends.

It mirrors credential provisioning: two independent paths reach the teardown — the normal-exit defer and the kill executor's Finalize — and both must be safe to call, in either order.

When the harness announced a proxy, the local listener is skipped — that is the only thing skipped. Recovery still runs (rules outlive processes), the elevation refusal still applies, and OS enforcement still installs, pointed at the harness's port with the allow rule naming the harness executable. Fail-closed holds across a harness death: the firewall stands while the proxy dies, so traffic is cut rather than freed.

enforcer is the platform firewall enforcer (the harness supplies a no-op stub where none exists); it is injected rather than constructed here so each server plugs in its own.

func ProvisionExport

func ProvisionExport(
	devicePolicy *policy.Policy,
	auditLog *audit.AuditLog,
	logger *slog.Logger,
) export.Sink

ProvisionExport builds the evidence-export sink for a configured destination and records the intent in the audit chain.

It returns a nil sink when export is off, so the caller does not special-case it — export.Ship is never reached with a nil sink because autoSealEvidence checks first.

A construction failure disables export with a warning rather than failing startup, the same contract as telemetry and anchoring: transparency is always on, but it never gates the session. The distinction from egress enforcement is deliberate — a policy that names applications and cannot elevate is serving a weaker posture than it describes, where an export that cannot start leaves the evidence exactly where it already was, on the device.

It must be called before ScrubSecretEnv: the credentials are read here, once, into the sink that uses them.

func ReceivingChain

func ReceivingChain(
	harnessEnforcing bool,
	auditMW mcp.Middleware,
	telemetryMW mcp.Middleware,
	rugpullMWs []mcp.Middleware,
	enforceMW mcp.Middleware,
) []mcp.Middleware

ReceivingChain assembles RunStdio's receiving-middleware chain, outermost first, from the already-constructed layers. It exists as a seam so the one mode-dependent decision — how much of the local guardrail stack runs — is pinned by tests rather than buried in RunStdio.

Standalone (or under an observe-mode harness), the full stack runs: audit → telemetry (when configured) → the rug-pull layers → enforce, enforce innermost so nothing routes around it while audit and rug-pull still observe what it refuses.

Under an enforcing harness only the audit layer remains. The harness's decider already refused anything refusable before this process saw the frame, its own chain audits and fingerprints the wire, and running the local duplicates would double-decide every call — but the local audit middleware stays, because this host's chain is the record of what the process actually served, kept precisely so the harness's account of the session is not the only one.

func ReconcileEvidence

func ReconcileEvidence(expected []string, artifacts []toolkit.EvidenceArtifact) (captured, missing []string)

ReconcileEvidence compares what the journey said a passing run must produce against what it actually captured.

func ResolveAuditKey

func ResolveAuditKey(env EnvNames, destination string, logger *slog.Logger) []byte

ResolveAuditKey returns the key the audit chain is written under.

The operator's environment variable (EnvNames.AuditKey) wins when set: a key supplied out of band is the only arrangement that survives an attacker with write access to the box, because a key stored next to the log is readable by anyone who can rewrite the log. Absent that, a key is generated on first use and persisted with restrictive permissions.

A stderr destination gets no key: there is no file to protect, and nowhere beside it to keep one.

func ResolveStatusToken

func ResolveStatusToken(t policy.TransparencyPolicy, logger *slog.Logger) (string, error)

ResolveStatusToken produces the bearer credential for the status endpoint, preferring the environment variable over the inline value.

The endpoint's POST /revoke trips the kill switch and runs the whole containment ladder, so this is a trigger credential. Every other one -- <PREFIX>AUDIT_KEY, _APPROVAL_KEY, _GRAPH_CLIENT_SECRET, _REMOTE_POLICY_TOKEN -- is read from the environment, and egress even takes the name of a variable rather than a value, on the stated grounds that the document is meant to be reviewable and checked in.

status_token was the exception: an inline secret in a document that is registered as an agent-readable protected path, and that the shell toolset reaches with no protected-path check at all. An agent served either toolset could read it and actuate containment -- the escalation the per-session sentinel token was introduced to close, and the reason the agent-facing Kill tool routes to StopGracefully rather than the ladder.

The inline form still works, because removing a schema key breaks documents that run today and unknown keys are rejected outright. It warns instead.

func RugpullVerifier

func RugpullVerifier(rp *watch.RugPull, tools func() []*mcp.Tool, trip func(string)) watch.VerifyFunc

RugpullVerifier is the in-flight monitor's local rug-pull recheck. It is installed only alongside a pinned local baseline: under an enforcing harness there is none, because the fingerprints are taken from the wire where a tampered server cannot vouch for itself.

func ScrubSecretEnv

func ScrubSecretEnv(env EnvNames, devicePolicy *policy.Policy, logger *slog.Logger)

ScrubSecretEnv removes the startup secrets from this process's environment, once every component that needs them holds its own copy.

It is the second of two defences, and it exists because the first cannot be complete. The server's process runner withholds every prefixed variable when it builds a child environment — but any future code path that spawns a process without going through it would inherit the parent environment as it stands. Clearing the values here means there is nothing left to inherit, whatever route a child is started by.

It also covers the two variables the prefix rule cannot know about: the egress proxy's shared secret and the status endpoint's bearer token are named *by the policy document*, so their variables can be called anything at all.

Order matters. Call this only after the audit log, the approvals client, the signal registry, the telemetry exporter and the egress proxy have been constructed; each reads its secret exactly once, at startup, into the struct that uses it.

func SentinelToken

func SentinelToken(controlDir string, auditLog *audit.AuditLog, logger *slog.Logger) string

SentinelToken mints the token a kill sentinel must carry, and tells the operator what it is.

The sentinel used to be authenticated by its own existence: any file named "kill" in the control directory tripped the containment ladder. That made it the one authoritative trigger an agent could fire by itself — a single FileSystem write, or any shell redirect, and a policy with isolate or shutdown armed did exactly that. It is the escalation the design is explicit that an agent must never be able to ask for, since the agent-facing Kill tool is deliberately routed away from containment for the same reason.

The token is minted per session and written nowhere: it goes to the log and the audit chain, both of which the operator reads and neither of which the agent can write. Putting it in a file beside the sentinel would hand it back to anything that can read the control directory, which is the position the agent is in.

An empty control directory means the trigger is not configured, so there is nothing to authenticate.

func SessionStamp

func SessionStamp() string

SessionStamp mints the one stamp a session's audit file, recording and evidence bundle correlate by name.

func SnapshotFn

func SnapshotFn(
	startedAt time.Time,
	rp *watch.RugPull,
	hb *watch.Heartbeat,
	auditLog *audit.AuditLog,
	kill *contain.KillSwitch,
	egressSvc *egress.Service,
	egressCfg policy.EgressPolicy,
	exportSt *status.ExportStatus,
) status.SnapshotProvider

SnapshotFn builds the always-on server-status snapshot provider.

func TripFunc

func TripFunc(
	trigger string,
	armed bool,
	kill *contain.KillSwitch,
	auditLog *audit.AuditLog,
	logger *slog.Logger,
) func(string)

TripFunc returns the trip function for one kill trigger. When the trigger is armed it is the switch's Trip, which runs the full containment ladder. Otherwise it is report-only: the event is still audited and logged, because transparency must not be conditional on containment — the operator sees that a trigger fired even when they chose not to act on it.

func ValidatePolicy

func ValidatePolicy(path string, reg *signals.Registry) (*policy.Policy, error)

ValidatePolicy loads and validates a policy document against the signals the registry knows.

It touches no device: validation is about the document and the set of signals this build can evaluate, so it runs anywhere, including in CI on a machine with no secure enclave and no domain.

func VerifyEvidence

func VerifyEvidence(zipPath, pubKeyHex string) (evidence.Report, error)

VerifyEvidence checks a bundle against its manifest and, when given, an expected public key.

func WarnExportEgress

func WarnExportEgress(devicePolicy *policy.Policy, logger *slog.Logger)

warnExportEgress reports the one contradiction the policy loader cannot settle on its own.

When egress is enabled the device may only reach the hosts egress.allow names, and a signed_url destination lives entirely in an environment variable — so there is no host in the document to check it against. Worse, the export runs from the audit-close defer, which is *after* the egress cleanup defer has already stopped the proxy, so the upload cannot be routed through it either. Under scoped or global enforcement the server's own image needs an outbound allow rule or the bundle never leaves.

Said out loud rather than left to be discovered during a shutdown nobody is watching, and warned rather than refused, because the combination is legitimate.

func WithToolset

func WithToolset(toolsets []string, id string) []string

WithToolset adds id to a toolset selection. A nil selection means "the default set", so it is made explicit as "default" plus id rather than collapsing to id alone, which would silently drop every default toolset.

func WriteRunRecord

func WriteRunRecord(dir, name, session string, rec *runrecord.Recorder) (string, error)

WriteRunRecord serialises the trace next to the session's other evidence. Failing to write it never fails the run: the journey's verdict is already on the audit chain, and losing the richer record is not a reason to report a passing suite as broken.

Types

type DecisionHolder

type DecisionHolder struct {
	// contains filtered or unexported fields
}

DecisionHolder stores the latest decision for the status surface.

func (*DecisionHolder) Get

func (h *DecisionHolder) Get() signals.Decision

Get returns the latest decision.

func (*DecisionHolder) Set

func (h *DecisionHolder) Set(d signals.Decision)

Set records the latest decision.

type DialFunc

type DialFunc func(addr string) (io.ReadWriteCloser, error)

DialFunc connects to the control channel at the bootstrap address. Governed servers implement their own dialer against the documented bootstrap contract rather than importing the harness's internal transport: a named pipe on Windows, a unix socket elsewhere. It is injected so this package stays platform-free.

type EgressRestoreHolder

type EgressRestoreHolder struct {
	// contains filtered or unexported fields
}

EgressRestoreHolder carries the undo an egress_apply installed, so the explicit egress_restore rung and the exit defer both reach it and neither double-runs it.

func (*EgressRestoreHolder) Run

func (h *EgressRestoreHolder) Run() error

Run executes and clears the stashed restore. A second call is a no-op, so the exit defer and an explicit egress_restore cannot both fire it.

func (*EgressRestoreHolder) Set

func (h *EgressRestoreHolder) Set(fn func() error)

type EnforcementDescriber

type EnforcementDescriber interface {
	Describe() map[string]any
}

EnforcementDescriber is implemented by an enforcer whose tiers differ from the policy's words: it adds the OS reading to the egress.enforce.applied record (pf, for one, scopes by uid rather than by application).

type EnvNames

type EnvNames struct {
	// Prefix is the server's variable prefix, including the trailing underscore.
	Prefix string
}

EnvNames derives the names of the secret-carrying environment variables from a server's prefix, e.g. "MACOS_MCP_" or "WINDOWS_MCP_".

Secrets are read from the environment rather than from flags or the policy document because argv is world-readable and a policy document is meant to be reviewable and checked in. This mirrors the credentials invariant — a secret may be used, but it is never written somewhere it can be read back.

func (EnvNames) ApprovalKey

func (e EnvNames) ApprovalKey() string

ApprovalKey names the variable holding the dual-control signing key.

func (EnvNames) AuditKey

func (e EnvNames) AuditKey() string

AuditKey names the variable holding the audit chain's HMAC key.

func (EnvNames) EvidenceKeyFile

func (e EnvNames) EvidenceKeyFile() string

EvidenceKeyFile names the path to the evidence signing key.

func (EnvNames) GraphClientID

func (e EnvNames) GraphClientID() string

func (EnvNames) GraphClientSecret

func (e EnvNames) GraphClientSecret() string

func (EnvNames) GraphTenant

func (e EnvNames) GraphTenant() string

GraphTenant, GraphClientID and GraphClientSecret name the tier-2 Graph credentials.

func (EnvNames) OTLPHeaders

func (e EnvNames) OTLPHeaders() string

OTLPHeaders names the collector auth headers.

func (EnvNames) RemotePolicyToken

func (e EnvNames) RemotePolicyToken() string

RemotePolicyToken names the device token for the remote policy decision point.

func (EnvNames) Secrets

func (e EnvNames) Secrets() []string

Secrets lists every variable holding a secret this server reads once at startup, including the export destinations whose names the harness fixes.

A pre-signed URL carries its signature in the query string, so the whole URL is a write credential for the evidence bucket — which is why those are listed here even though their names are the harness's, not this server's.

type GuardrailPathsConfig

type GuardrailPathsConfig struct {
	CredentialsFile string
	PolicyConfig    string
	Normalize       toolkit.PathNormalizer
}

GuardrailPathsConfig names the operator-supplied files the file tool must not touch, plus the normaliser the server's protected paths are matched with.

type HarnessEgress

type HarnessEgress struct {
	Port       int
	Executable string
}

HarnessEgress is the harness-announced proxy the server points its OS enforcement at instead of starting a listener of its own: the hello.ack's egress_proxy_port and egress_proxy_executable. The zero value means no harness proxy (standalone, or an observe-mode harness with egress off).

func (HarnessEgress) Announced

func (h HarnessEgress) Announced() bool

type HarnessServant

type HarnessServant struct {
	// contains filtered or unexported fields
}

HarnessServant serves the control channel for one session.

func AttachHarness

func AttachHarness(
	dial DialFunc,
	pipe, token, serverVersion, sessionStamp string,
	deps ServantDeps,
) (*HarnessServant, wire.HelloAck, error)

AttachHarness dials the control channel and completes the handshake. It is the real-transport entry point; connectServant underneath is transport- agnostic so the protocol is testable over an in-memory pipe.

func ConnectServant

func ConnectServant(
	conn io.ReadWriteCloser,
	token, serverVersion, sessionStamp string,
	deps ServantDeps,
) (*HarnessServant, wire.HelloAck, error)

ConnectServant performs the hello/hello.ack handshake over conn and returns the servant plus the harness's ack. It is separated from dialing so the protocol is testable over an in-memory pipe.

func (*HarnessServant) Close

func (s *HarnessServant) Close() error

Close ends the servant connection. Safe to call more than once.

func (*HarnessServant) FireLost

func (s *HarnessServant) FireLost(err error)

func (*HarnessServant) PushAuditAnchor

func (s *HarnessServant) PushAuditAnchor(seq uint64, head string) error

PushAuditAnchor reports this server's local chain head so the harness chain can pin the pair.

func (*HarnessServant) PushCredentialEvent

func (s *HarnessServant) PushCredentialEvent(kind string, ids []string) error

PushCredentialEvent reports a credential lifecycle transition — identifiers only, never values.

func (*HarnessServant) PushHeartbeat

func (s *HarnessServant) PushHeartbeat() error

PushHeartbeat sends one liveness beat. Its DesktopAlive field is the one thing the harness cannot infer from the proxied MCP stream: whether the platform engine's serialized thread is still answering.

func (*HarnessServant) Serve

func (s *HarnessServant) Serve(ctx context.Context)

Serve reads control messages until the channel closes or a shutdown is requested. Dispatch tolerance mirrors the harness side: an unknown notification is ignored, an unknown request is answered with an error rather than left to hang.

func (*HarnessServant) StartHeartbeat

func (s *HarnessServant) StartHeartbeat(ctx context.Context, interval time.Duration)

StartHeartbeat pushes a beat every interval until ctx ends. A zero or negative interval disables it.

type InventoryRegistry

type InventoryRegistry struct {
	Inv  *inventory.Inventory
	Deps any
}

InventoryRegistry runs a plan step by finding the tool in the served inventory and calling its handler directly, with the same dependencies the middleware injects. Plan and Apply are excluded from being step tools before we get here (see Propose), so this never recurses.

func (InventoryRegistry) Has

func (r InventoryRegistry) Has(tool string) bool

func (InventoryRegistry) Invoke

func (r InventoryRegistry) Invoke(
	ctx context.Context,
	tool string,
	rawArgs json.RawMessage,
) (*mcp.CallToolResult, error)

type JourneyReport

type JourneyReport struct {
	Name      string `json:"name"`
	PlanID    string `json:"plan_id"`
	Passed    bool   `json:"passed"`
	Completed int    `json:"completed"`
	Failed    int    `json:"failed"`
	Skipped   int    `json:"skipped"`
	// Report is the human-readable per-step apply log.
	Report string `json:"report"`
	// Manifest is the change manifest the plan adjudicated to before running.
	Manifest string `json:"manifest,omitempty"`
	// RunRecord is where the run's OTLP/JSON trace was written, when an evidence
	// directory is configured.
	RunRecord string `json:"run_record,omitempty"`
	// Evidence lists the images the run captured.
	Evidence []string `json:"evidence,omitempty"`
	// MissingEvidence lists expected_evidence labels the run did not produce. A
	// non-empty list fails the run even when every assertion passed.
	MissingEvidence []string `json:"missing_evidence,omitempty"`
}

JourneyReport is the outcome of running one journey.

func RunJourney

func RunJourney(ctx context.Context, raw []byte, name string, run JourneyRun) (JourneyReport, error)

RunJourney compiles a journey document to a plan and executes it against the live platform, through the same planner Apply uses: every step is policy-evaluated, audited as plan.step, and fail-stopped on the first failure. A failed assertion is an Assert/WaitFor tool error — a failed step — so the run stops and reports it, which is what makes a journey a test.

raw is the journey document as written; name is used in error messages.

type JourneyRun

type JourneyRun struct {
	// Engine is the policy engine with its tool index already set.
	Engine *policy.Engine
	// Inventory is the served manifest. It must include the testing toolset.
	Inventory *inventory.Inventory
	// Deps is the server's dependency set. It must implement toolkit.EvidenceSink
	// and toolkit.ReadRegister (toolkit.BaseDeps does) and must have the
	// evidence directory wired, or captures are not persisted.
	Deps any
	// EvidenceSink and ReadRegister are the two optional capabilities the run
	// reads off Deps; the server passes the same object when it has them.
	EvidenceSink toolkit.EvidenceSink
	ReadRegister toolkit.ReadRegister
	// Planner hook: the run wires its planner into Deps through this, so the
	// Plan/Apply tools (if served) and the run share one. May be nil.
	SetPlanner func(toolkit.Planner)
	// AuditLog is the session's chain.
	AuditLog *audit.AuditLog
	// SessionStamp ties the run's audit chain and evidence together.
	SessionStamp string
	// EvidenceRoot is transparency.evidence_dir; "" disables persistence.
	EvidenceRoot string
	// ServiceName and Version identify the server in the run record.
	ServiceName string
	Version     string
	Logger      *slog.Logger
}

JourneyRun is everything a journey needs from the server that is running it. The server stands up the platform engine, the policy engine and the served inventory exactly as a session would; this type carries them in.

type JourneyTracer

type JourneyTracer struct {
	// contains filtered or unexported fields
}

JourneyTracer turns the planner's step events into the run's OpenTelemetry trace, described in docs/journey-evidence.md.

The shape follows the flattened compilation: an observe or an action is a child of the run, and the assertions and captures that follow an action are children of it. That is what makes the trace read like the journey rather than like the plan it compiled to.

func NewJourneyTracer

func NewJourneyTracer(
	rec *runrecord.Recorder,
	origins []journeys.Origin,
	deps any,
	run runrecord.SpanID,
) *JourneyTracer

func (*JourneyTracer) ObserveStep

func (t *JourneyTracer) ObserveStep(e StepEvent)

ObserveStep records one executed plan step. It is the planner's step observer.

type Planner

type Planner struct {
	// contains filtered or unexported fields
}

Planner is the plan-and-apply engine. It adjudicates a whole plan up front and, on Apply, runs it verbatim — the steps' arguments come from the stored plan, not from the Apply call, so within an Apply the executed operations are exactly the approved ones by construction. The only divergence surface is a direct call made outside a plan, which the audit chain records as a tool.call with no owning plan.step.

func NewPlanner

func NewPlanner(engine *policy.Engine, auditLog *audit.AuditLog, runner ToolRunner, killed func() bool) *Planner

func (*Planner) Apply

func (p *Planner) Apply(ctx context.Context, planID string) (toolkit.PlanApplication, error)

Apply runs a stored plan by id: it re-checks posture, then executes the steps in order, stopping at the first that is refused or fails.

func (*Planner) Propose

func (p *Planner) Propose(ctx context.Context, rawPlan []byte) (toolkit.PlanProposal, error)

Propose validates and adjudicates a submitted plan, stores it under its content hash, and returns the change manifest and plan id.

func (*Planner) StoredPlans

func (p *Planner) StoredPlans() []plan.Document

StoredPlans returns every plan proposed this session, ordered by id, so the evidence bundle can carry the full approved documents (the compact audit chain records only their digests).

func (*Planner) WithApprovals

func (p *Planner) WithApprovals(approver enforce.Approver, sessionID string, timeout time.Duration) *Planner

WithApprovals wires the out-of-band authoriser used for approve-disposition steps. Called only when the policy configures an approvals webhook; a plan step that hits an hold rule then blocks on the same authoriser a direct call would, so apply is never a way around dual control.

func (*Planner) WithReadRegister

func (p *Planner) WithReadRegister(r toolkit.ReadRegister) *Planner

WithReadRegister wires the run-scoped result register, so a read step's output is available to a later result.text assertion.

The register is passed to the assertion as environment, never spliced into its arguments: a plan runs verbatim from the stored document and its argument digest is what the audit chain records, so rewriting arguments at execution time would break the binding between what was approved and what ran.

func (*Planner) WithStepObserver

func (p *Planner) WithStepObserver(fn func(StepEvent)) *Planner

WithStepObserver wires a per-step callback, used by a journey run to build its OpenTelemetry trace. The planner stays ignorant of journeys: it reports what it executed, and the caller decides what that means.

type PolicyCoverage

type PolicyCoverage struct {
	Tool string `json:"tool"`
	// Known reports whether the tool is in the served manifest. A rule can still
	// cover an unknown tool through a toolset "*" match, and saying so is the
	// point: an operator who mistyped a name should see that, not an empty result
	// that reads like "nothing applies".
	Known   bool                 `json:"known"`
	Facts   policy.ToolFacts     `json:"facts"`
	Rules   []PolicyCoverageRule `json:"rules"`
	Signals []string             `json:"required_signals"`
}

PolicyCoverage is what covers one tool, for `policy explain`.

func ExplainPolicy

func ExplainPolicy(devicePolicy *policy.Policy, reg *signals.Registry, index ToolIndex, tool string) PolicyCoverage

ExplainPolicy reports which rules cover a tool and what they require.

It evaluates nothing: an operator asking why a call was refused should not have to run device probes, and should be able to ask on a machine that is not the one that refused it. index is the served manifest's tool index.

type PolicyCoverageRule

type PolicyCoverageRule struct {
	Name     string   `json:"name"`
	Requires []string `json:"requires"`
	OnFail   string   `json:"on_fail"`
}

PolicyCoverageRule is one rule that covers the tool.

type PolicyTestCaseResult

type PolicyTestCaseResult struct {
	Name   string `json:"name"`
	OK     bool   `json:"ok"`
	Detail string `json:"detail,omitempty"`
}

PolicyTestCaseResult is the outcome of one asserted call.

type PolicyTestReport

type PolicyTestReport struct {
	Fixture string                 `json:"fixture"`
	Cases   []PolicyTestCaseResult `json:"cases"`
}

PolicyTestReport is the result of running one fixture file.

func RunPolicyFixtures

func RunPolicyFixtures(known []string, fixturePaths []string) ([]PolicyTestReport, error)

RunPolicyFixtures runs every fixture and returns a report per file. The returned error is for I/O or malformed fixtures only; a failed assertion is a non-OK case, so one run reports every mismatch rather than stopping at the first.

func (PolicyTestReport) Passed

func (r PolicyTestReport) Passed() bool

Passed reports whether every case in the report held.

type RungFunc

type RungFunc func(params json.RawMessage) wire.ActuateResult

RungFunc executes one actuation rung and reports what the OS did. It is injected by the server from the same primitives the local kill executor uses, so there is one actuation path whether the trip is decided in-process or by the harness.

type RungPrimitives

type RungPrimitives struct {
	Actuator     contain.SystemActuator
	Banner       func(text string)
	Seal         func() error       // flush + seal the audit chain
	Finalize     func()             // finalize the recording (idempotent)
	CleanupCreds func()             // revoke installed session credentials
	SetRestore   func(func() error) // hand the network-isolation undo to the exit path
	// Egress is the OS firewall enforcer the egress rungs actuate through. The
	// harness holds the composed egress policy and drives its OS enforcement
	// on the host over the channel; this is a fresh platform enforcer because
	// the policy path's enforcer state lives on disk, not in a shared object.
	Egress egress.Enforcer
	// EgressRestore holds the undo returned by an egress_apply, so the
	// egress_restore rung and the exit defer can both run it, once.
	EgressRestore *EgressRestoreHolder
	Logger        *slog.Logger
}

RungPrimitives is what RunStdio supplies to build the actuation table. Every field is one already-wired capability of the running server.

type ServantDeps

type ServantDeps struct {
	Registry   *signals.Registry
	EnvFn      func() *signals.Env
	Rungs      map[string]RungFunc
	Alive      func() bool
	RunContext signals.RunContext
	Elevated   bool
	Logger     *slog.Logger
	OnLost     func(error)
}

ServantDeps is what the server supplies to stand up the servant.

type StepEvent

type StepEvent struct {
	Index   int
	Step    plan.Step
	Started time.Time
	Ended   time.Time
	Verdict string
	Outcome StepOutcome
	// Detail is the failure or refusal reason, and for an assertion carries the
	// expected-versus-observed comparison the tool reported.
	Detail string
}

StepEvent reports one executed plan step. It carries the timing the audit chain does not: the chain proves a step ran, and this says how long it took, which is what makes a suite's drift toward flakiness measurable.

type StepOutcome

type StepOutcome string

StepOutcome is how one plan step ended.

const (
	StepOK          StepOutcome = "ok"
	StepFailed      StepOutcome = "failed"
	StepRefused     StepOutcome = "refused"
	StepNotApproved StepOutcome = "not_approved"
)

type ToolIndex

type ToolIndex map[string]policy.ToolFacts

ToolIndex adapts the served inventory to policy.ToolIndex, so policy rules can match on the toolset and annotations a tool actually carries.

It is a snapshot taken once the manifest is assembled, not a live view: the manifest cannot change while the process runs — the rug-pull detector trips the kill switch if it does — so a snapshot is accurate, and it keeps the lookup a map read on the request path rather than a filter pass over every tool.

func NewToolIndex

func NewToolIndex(ctx context.Context, inv *inventory.Inventory) ToolIndex

NewToolIndex builds the index from the assembled inventory.

func (ToolIndex) Lookup

func (i ToolIndex) Lookup(tool string) (policy.ToolFacts, bool)

Lookup implements policy.ToolIndex.

type ToolRunner

type ToolRunner interface {
	Has(tool string) bool
	Invoke(ctx context.Context, tool string, rawArgs json.RawMessage) (*mcp.CallToolResult, error)
}

ToolRunner invokes a tool by name and reports whether one is served. The planner executes steps through it rather than re-entering MCP dispatch, so a plan step does not confuse rug-pull or circuit-breaker accounting; the server implementation runs the real tool handler, and a fake backs the tests.

Jump to

Keyboard shortcuts

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