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
- Variables
- func ApplyEffectiveConfig(ec wire.EffectiveConfig, enforceHTTPS *bool, deps *toolkit.BaseDeps, ...)
- func AuditDestinationIsDir(dest string) bool
- func AuditKeyDir(destination string) string
- func AutoSealEvidence(tp policy.TransparencyPolicy, session string, plans []plan.Document, ...)
- func BuildRungs(p RungPrimitives) map[string]RungFunc
- func BundleEvidence(auditDir, session, recordingDir, outPath, keyFile string, ...) (evidence.Manifest, error)
- func EgressStatus(svc *egress.Service, cfg policy.EgressPolicy) *status.EgressStatus
- func EvaluatePolicy(ctx context.Context, devicePolicy *policy.Policy, reg *signals.Registry, ...) signals.Decision
- func EvidenceSubdir(root, sub string) string
- func ExportCredentials() export.Credentials
- func ExportStatus(e policy.ExportPolicy, sink export.Sink) *status.ExportStatus
- func GuardrailPaths(cfg GuardrailPathsConfig, p *policy.Policy) []toolkit.ProtectedPath
- func HarnessAddress() (pipe, token string)
- func HeartbeatFromAck(ack wire.HelloAck) time.Duration
- func JourneyStatusMessage(passed bool, failed int, missing []string) string
- func KillPolicyConfig(devicePolicy *policy.Policy) contain.KillActionConfig
- func LoadPolicy(path string, reg *signals.Registry, logger *slog.Logger) (*policy.Policy, error)
- func NewGuardrailRegistry(env EnvNames, logger *slog.Logger) *signals.Registry
- func NewLogger(logFile string) (*slog.Logger, func(), error)
- func ProvisionEgress(ctx context.Context, devicePolicy *policy.Policy, auditLog *audit.AuditLog, ...) (*egress.Service, func(), func(), error)
- func ProvisionExport(devicePolicy *policy.Policy, auditLog *audit.AuditLog, logger *slog.Logger) export.Sink
- func ReceivingChain(harnessEnforcing bool, auditMW mcp.Middleware, telemetryMW mcp.Middleware, ...) []mcp.Middleware
- func ReconcileEvidence(expected []string, artifacts []toolkit.EvidenceArtifact) (captured, missing []string)
- func ResolveAuditKey(env EnvNames, destination string, logger *slog.Logger) []byte
- func ResolveStatusToken(t policy.TransparencyPolicy, logger *slog.Logger) (string, error)
- func RugpullVerifier(rp *watch.RugPull, tools func() []*mcp.Tool, trip func(string)) watch.VerifyFunc
- func ScrubSecretEnv(env EnvNames, devicePolicy *policy.Policy, logger *slog.Logger)
- func SentinelToken(controlDir string, auditLog *audit.AuditLog, logger *slog.Logger) string
- func SessionStamp() string
- func SnapshotFn(startedAt time.Time, rp *watch.RugPull, hb *watch.Heartbeat, ...) status.SnapshotProvider
- func TripFunc(trigger string, armed bool, kill *contain.KillSwitch, auditLog *audit.AuditLog, ...) func(string)
- func ValidatePolicy(path string, reg *signals.Registry) (*policy.Policy, error)
- func VerifyEvidence(zipPath, pubKeyHex string) (evidence.Report, error)
- func WarnExportEgress(devicePolicy *policy.Policy, logger *slog.Logger)
- func WithToolset(toolsets []string, id string) []string
- func WriteRunRecord(dir, name, session string, rec *runrecord.Recorder) (string, error)
- type DecisionHolder
- type DialFunc
- type EgressRestoreHolder
- type EnforcementDescriber
- type EnvNames
- func (e EnvNames) ApprovalKey() string
- func (e EnvNames) AuditKey() string
- func (e EnvNames) EvidenceKeyFile() string
- func (e EnvNames) GraphClientID() string
- func (e EnvNames) GraphClientSecret() string
- func (e EnvNames) GraphTenant() string
- func (e EnvNames) OTLPHeaders() string
- func (e EnvNames) RemotePolicyToken() string
- func (e EnvNames) Secrets() []string
- type GuardrailPathsConfig
- type HarnessEgress
- type HarnessServant
- func (s *HarnessServant) Close() error
- func (s *HarnessServant) FireLost(err error)
- func (s *HarnessServant) PushAuditAnchor(seq uint64, head string) error
- func (s *HarnessServant) PushCredentialEvent(kind string, ids []string) error
- func (s *HarnessServant) PushHeartbeat() error
- func (s *HarnessServant) Serve(ctx context.Context)
- func (s *HarnessServant) StartHeartbeat(ctx context.Context, interval time.Duration)
- type InventoryRegistry
- type JourneyReport
- type JourneyRun
- type JourneyTracer
- type Planner
- func (p *Planner) Apply(ctx context.Context, planID string) (toolkit.PlanApplication, error)
- func (p *Planner) Propose(ctx context.Context, rawPlan []byte) (toolkit.PlanProposal, error)
- func (p *Planner) StoredPlans() []plan.Document
- func (p *Planner) WithApprovals(approver enforce.Approver, sessionID string, timeout time.Duration) *Planner
- func (p *Planner) WithReadRegister(r toolkit.ReadRegister) *Planner
- func (p *Planner) WithStepObserver(fn func(StepEvent)) *Planner
- type PolicyCoverage
- type PolicyCoverageRule
- type PolicyTestCaseResult
- type PolicyTestReport
- type RungFunc
- type RungPrimitives
- type ServantDeps
- type StepEvent
- type StepOutcome
- type ToolIndex
- type ToolRunner
Constants ¶
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.
const AuditKeyFile = ".audit-key"
AuditKeyFile is the per-machine key file, kept beside the audit directory.
Variables ¶
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.
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.
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.
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 ¶
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 ¶
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 ¶
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 ¶
HeartbeatFromAck resolves the heartbeat cadence, defaulting when the harness leaves it unset.
func JourneyStatusMessage ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
VerifyEvidence checks a bundle against its manifest and, when given, an expected public key.
func WarnExportEgress ¶
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 ¶
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 ¶
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 ¶
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 ¶
ApprovalKey names the variable holding the dual-control signing key.
func (EnvNames) EvidenceKeyFile ¶
EvidenceKeyFile names the path to the evidence signing key.
func (EnvNames) GraphClientID ¶
func (EnvNames) GraphClientSecret ¶
func (EnvNames) GraphTenant ¶
GraphTenant, GraphClientID and GraphClientSecret name the tier-2 Graph credentials.
func (EnvNames) OTLPHeaders ¶
OTLPHeaders names the collector auth headers.
func (EnvNames) RemotePolicyToken ¶
RemotePolicyToken names the device token for the remote policy decision point.
func (EnvNames) Secrets ¶
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 ¶
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 ¶
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 (*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 (*Planner) Apply ¶
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 ¶
Propose validates and adjudicates a submitted plan, stores it under its content hash, and returns the change manifest and plan id.
func (*Planner) StoredPlans ¶
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 ¶
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 ¶
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 ¶
NewToolIndex builds the index from the assembled inventory.
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.