capcompute

package module
v0.0.0-...-3c60a69 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

README

capcompute

A tiny "operating system" for running AI‑agent code safely. capcompute is a Go library that runs WebAssembly (Wasm) programs as sandboxed processes whose only way to affect the outside world is by asking the host for permission — one call at a time, all recorded.

New here? Read the next two sections and you'll understand what this is and why it exists. Then jump to Quick start to build and test it locally.


What is this, in plain words?

Imagine you let an AI agent run real commands — restart a server, charge a card, delete a file. Three things immediately go wrong:

  1. You can't trust the logs. The agent prints whatever it wants. You need a record of what it actually did that it can't skip or fake.
  2. Crashes double‑charge you. It dies at step 7 of 12, and step 3 was a payment. You restart it… and steps 1–6 run again. You just paid twice.
  3. The agent guards its own gate. "A human must approve deletes" lives in the agent's prompt — which the AI controls. The prisoner writes the prison rules.

capcompute is the runtime where those three have answers:

  • Every side effect goes through one recorded gate the program can't bypass — an un‑forgeable audit trail.
  • Crashes replay to the exact instruction without re‑running effects that already committed — no double‑charges.
  • Approval lives outside the sandbox, where the AI can't approve for itself.

It's a library, not an app — you link it into your own Go program. Think of it as the "kernel" of a small operating system: Wasm modules are programs, running instances are processes, and host features (network, LLM calls, storage) are syscalls the kernel mediates. See docs/ARCHITECTURE.md for the full OS model.

Where this fits in the Aurora system

capcompute is the bottom layer of a family of repos (all under the aurora-capcompute org) that together run AI agents safely:

        you (a human)
              │
   aurora-cli / aurora-slack-connector      ← clients you talk to
              │  HTTP /v1
         aurora-dist                         ← the server (one binary you run)
              │  assembled from…
   ┌──────────┴──────────┐
 aurora-capcompute    aurora-dispatchers     ← orchestration runtime + capability drivers
   └──────────┬──────────┘
              │  both built on
         capcompute                          ◀── YOU ARE HERE (the kernel)

   aurora-brains  →  the agent "programs" (Wasm) that run inside
  • capcompute (this repo) — the kernel: sandboxing, syscalls, the recorded journal, replay, capability checks.
  • aurora-capcompute — the orchestration runtime built on top (sessions, retries, approvals, sub‑agents).
  • aurora-dispatchers — the concrete drivers that actually make HTTP calls, read files, call an LLM.
  • aurora-brains — the Wasm agent programs (the "cognition") that run as processes inside this kernel.
  • aurora-dist — bundles all of the above into one runnable server.

You rarely use capcompute on its own. It's the engine the rest is built from.

What it does for you (features)

Feature The problem it solves
Capability security — a program can only call the syscalls it was explicitly granted, checked against a JSON schema Untrusted / LLM‑written code can't reach anything you didn't hand it ("zero ambient authority")
Recorded journal — every syscall is written down before it runs (the intent) and before the guest sees the answer (the completion), hash‑chained Tamper‑evident audit trail; the record can't be skipped or forged
Deterministic replay — clock and randomness are syscalls, pinned and replayed A crashed process resumes at the exact instruction, seeing identical values
Exactly‑once effects — committed results are served from the journal on replay No double‑charges after a crash or restart
Savepoints + rollback (sagas)sys.begin/sys.commit brackets, sys.compensate undo actions, sys.abort to unwind Partial work can be cleanly reversed and retried
Yield / resume — a process can pause on outside work (an approval, a timer) and be resumed later Human‑in‑the‑loop and long waits without holding a thread
Information‑flow control — results carry provenance labels; a flow monitor refuses a call whose inputs are too "tainted" Stops sensitive data (or prompt‑injected content) from flowing into dangerous actions
Child processessys.spawn starts sub‑programs with attenuated authority (a child can't be granted more than its parent holds) Safe delegation to sub‑agents
Fair scheduling & supervision — priority bands, per‑owner quotas, virtual‑actor residency, OTP‑style restarts Many tenants share the runtime without starving each other

Quick start (5 minutes)

Prerequisites: Go 1.26+. (Optional: TinyGo to run the end‑to‑end guest tests — they auto‑skip if it's missing.)

git clone https://github.com/aurora-capcompute/capcompute
cd capcompute

go build ./...      # compile everything
go vet ./...        # static checks
go test ./...       # run the test suite

Running in a sandbox where the default Go cache isn't writable? Point it somewhere writable:

GOCACHE=/tmp/capcompute-go-build go test ./...

Run only the fast unit tests, or a single package:

go test -short ./...                      # skip the slow TinyGo integration test
go test ./sys/replay/tape/journaled/      # one package

If you have TinyGo installed, the integration test builds a real Wasm guest and drives it through completed / yielded / failed / crash‑replay states:

go test -run TestTinyGoGuest ./...
# internally it builds the guest fixture with:
#   tinygo build -target wasip1 -buildmode=c-shared -tags tinygo \
#     -o guest.wasm ./testdata/integration_guest

There is no go run — this is a library. The only runnable Wasm artifacts are the guest fixtures under testdata/, and the Go tests build and drive them.

How you use it (the lifecycle)

A host application wraps the kernel like this:

  1. Create a kernel from a program image (an Extism Wasm manifest) and a process table:

    kernel, err := capcompute.NewKernel[string, Run](ctx, capcompute.Config[string, Run]{
        Image:        extism.Manifest{Wasm: []extism.Wasm{extism.WasmFile{Path: "plugin.wasm"}}},
        PluginConfig: extism.PluginConfig{EnableWasi: true},
        ProcessTable: table, // you supply this; the interface is below
    })
    defer kernel.Shutdown(ctx)
    
  2. Create a process from a ProcessSpec (its input, entrypoint, credential, and syscall dispatcher):

    process, err := kernel.CreateProcess(ctx, capcompute.ProcessSpec[string, Run]{
        Input:      json.RawMessage(`{"task":"example"}`),
        Entrypoint: "run",
        Cred:       Run{ID: "proc-1"}, // host-side identity, never visible to the guest
        Dispatcher: myDispatcher{},    // handles this process's syscalls
    })
    
  3. Save it into the process table (this makes it visible to syscalls), then resume it and read the single result:

    _ = table.SaveProcess(ctx, "proc-1", process)
    handle, _ := kernel.Resume(ctx, process)
    result := <-handle.Results()
    switch result.Status {
    case capcompute.ResumeCompleted: // the guest finished
    case capcompute.ResumeYielded:   // paused on outside work — resume later
    case capcompute.ResumeStopped:   // cancelled — recreate before resuming
    case capcompute.ResumeFailed:    // apply your error policy
    }
    

CreateProcess does not save anything — you decide when a process becomes visible. Resume runs the guest in a goroutine and delivers exactly one ResumeResult.

The two pieces you provide

A dispatcher — application code that answers one process's syscalls:

func (myDispatcher) Dispatch(ctx context.Context, cred Run, call sys.Syscall, auth sys.Authorization) (sys.SyscallResult, error) {
    switch call.Name {
    case "echo":
        return sys.Result(call.Args), nil
    case "wait":
        return sys.Yield("waiting for outside work"), nil
    default:
        return sys.Fail("unknown syscall"), nil
    }
}
func (myDispatcher) Capabilities() []sys.Capability { return nil }

A process table — the kernel's lookup boundary for live processes. The library ships only the interface; you provide the implementation (a durable one in production; the tests use an in‑memory double):

type ProcessTable[ID comparable, K PID[ID]] interface {
    LoadProcess(ctx context.Context, pid ID) (*Process[K], error)
    SaveProcess(ctx context.Context, pid ID, process *Process[K]) error
}

The syscall contract (guest ↔ host)

A guest imports one host function and sends it a request:

//go:wasmimport extism:host/compute syscall
func hostSyscall(uint64) uint64

The request and response are an ABI‑v3 protobuf envelope (sys.ABIVersion == 3; the wire codec lives in sys/wire). A response has one of three statuses:

  • result — the syscall completed and returned a value;
  • yield — the host needs outside work before the guest can continue;
  • failed — carrying a machine‑readable errno (denied, expired, not_found, invalid_args, transient, conflict, internal, bad_abi) plus a message.

The guest decides what to do with the response and returns from its exported function with {"status":"completed"} or {"status":"yielded"}. Some syscall names are reserved by the kernel: sys.begin, sys.commit, sys.compensate, sys.abort, sys.spawn, sys.timer, sys.declassify, sys.now, sys.random.

What this library deliberately does not own

capcompute is intentionally small. It does not own job queues, schedulers of when to resume, durable databases, async completion, or product‑specific agent policy. Those belong to the system wrapping it — that's what aurora-capcompute and aurora-dist are.

Project layout

kernel.go        Kernel, Process, ProcessTable, the Resume lifecycle
host.go          the single Extism host function + syscall dispatch
stack.go         Stack.ForProcess — the canonical dispatcher chain order
validate.go      Validator: the reference monitor (grants + arg schemas)
provenance.go    labels, taints, flow monitor, declassifier (data-flow control)
ambient.go       deterministic clock + RNG (so replay is exact)
spawn.go         sys.spawn: child processes with attenuated authority
throttle.go      rate limiting (delays, never denies)
sys/             the syscall vocabulary: Syscall, Dispatcher, Capability, errno
  replay/        replay decorator + journal-backed tape (the WAL / audit log)
  wire/          the ABI-v3 protobuf envelope codec (shared with guests)
sched/           fair-share scheduler + OTP-style supervisor
sim/             deterministic fault-injection simulation harness
otelexport/      render a journal as OpenTelemetry traces
docs/            ARCHITECTURE.md (the OS model), PITCH.md, ROADMAP.md, …
testdata/        the smallest TinyGo guest fixtures used by integration tests

Documentation

Overview

Package capcompute is the kernel of a small library operating system for Extism compute guests: wasm programs run as processes whose only access to the outside world is host-dispatched syscalls.

The package owns the compiled program image (Kernel), process lifecycle, and the guest-to-host syscall wiring. Concrete storage, durable reconstruction, and application scheduling stay outside this package.

A typical runtime does the following:

  • build a Kernel with a wasm Manifest and a ProcessTable;
  • create a Process from a ProcessSpec, which carries the process's dispatcher;
  • save that Process in the ProcessTable before invoking Resume if the guest can make syscalls;
  • call Resume and read the single ResumeResult from the returned handle;
  • close processes and the Kernel at the application boundary.

The syscall host function receives only the PID through context. It loads the Process from the ProcessTable and dispatches the guest Syscall through the process dispatcher. This keeps runtime lookup explicit and avoids hidden invocation state in context.

Resume has four observable outcomes. A successful guest call whose JSON output contains {"status":"yielded"} returns ResumeYielded, while an explicit {"status":"completed"} returns ResumeCompleted. Missing or unsupported status values return ResumeFailed. A stopped invocation returns ResumeStopped and permanently terminates that physical process. Guest and runtime errors also return ResumeFailed.

ProcessTable is a runtime lookup boundary. Durable stores should persist the data needed by their application to recreate processes, then hydrate a fresh Kernel with CreateProcess and SaveProcess when a host process restarts. CreateProcess deliberately does not save processes; callers decide when a process becomes visible to syscalls and when it is removed.

The library does not own replay scheduling, async completion, durable journal policy, or process cleanup timing. Those are conventions of the wrapping system using this package.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrAmbientAuthority     = errors.New("image grants ambient authority")
	ErrImageMemoryOverride  = errors.New("image overrides the kernel-owned memory cap")
	ErrInvalidGuestOutput   = errors.New("invalid guest output")
	ErrProcessActive        = errors.New("process is already active")
	ErrProcessRequired      = errors.New("process is required")
	ErrProcessTableRequired = errors.New("process table is required")
	ErrProcessTerminated    = errors.New("process is terminated")
)

Functions

This section is empty.

Types

type ChildRunner

type ChildRunner[K any] func(ctx context.Context, child K, granted []sys.Capability, request SpawnRequest) (ResumeResult[K], error)

ChildRunner runs one child process until it completes, yields, fails, or is stopped. The child's own journal (keyed by the child cred's PID) and its dispatcher chain are the runner's to wire; KernelChildRunner is the kernel-backed implementation.

func KernelChildRunner

func KernelChildRunner[ID comparable, K PID[ID]](
	kernel *Kernel[ID, K],
	program string,
	childDispatcher func(ctx context.Context, child K, granted []sys.Capability) (sys.Dispatcher[K], error),
) ChildRunner[K]

KernelChildRunner runs children on a kernel: it creates the child process, registers it in the process table (the syscall path finds it by PID), gives it the CPU, and closes it once its quantum ends. childDispatcher wires the child's own chain — its journal tape (keyed by the child's PID), validator, and drivers over exactly the granted set (Stack.ForProcess is the natural fit).

A kernel is bound to one program image, so the runner is too: program names what this kernel runs, and a spawn requesting anything else is refused rather than silently running the wrong code. Multi-program apps compose a ChildRunner that routes on request.Program across per-program runners.

type Config

type Config[ID comparable, K PID[ID]] struct {
	Image        extism.Manifest
	PluginConfig extism.PluginConfig
	ProcessTable ProcessTable[ID, K]

	// MaxMemoryPages caps each process's linear memory in 64KiB wasm pages
	// (0 = runtime default). A guest allocating past the cap traps and the
	// resume reports ResumeFailed.
	MaxMemoryPages uint32

	// ResumeTimeout bounds the wall-clock time of one Resume quantum
	// (0 = unbounded). A guest still running at the deadline is stopped and
	// the resume reports ResumeStopped with context.DeadlineExceeded.
	ResumeTimeout time.Duration
}

Config contains everything needed to compile a program and create per-process instances.

Image is the program image (an Extism manifest naming the wasm and static config). It must not grant ambient authority: AllowedHosts and AllowedPaths must be empty — network and filesystem access are capabilities served by the dispatcher, never ambient rights. Guest module configuration (clock, RNG, env) is owned by the kernel and cannot be supplied by the caller.

type Declassifier

type Declassifier[K any] struct {
	// contains filtered or unexported fields
}

Declassifier serves the reserved sys.declassify syscall — DIFC declassification as a governed operation. Every crossing requires a human approval (there is no unapproved path: an unattended declassify would just be flow-policy bypass), and the approved crossing is a journaled syscall result, so replay re-applies it without asking again. It must sit *below* the replay layer; the FlowMonitor above performs the actual taint removal when the result passes through it.

func NewDeclassifier

func NewDeclassifier[K any](next sys.Dispatcher[K]) *Declassifier[K]

func (*Declassifier[K]) Capabilities

func (d *Declassifier[K]) Capabilities() []sys.Capability

Capabilities publishes sys.declassify (schema'd) alongside the chain's own capabilities; whether a given run may call it is the manifest's decision, enforced by the Validator's grant set like any capability.

func (*Declassifier[K]) Dispatch

func (d *Declassifier[K]) Dispatch(ctx context.Context, cred K, syscall sys.Syscall, auth sys.Authorization) (sys.SyscallResult, error)

type DeclassifyRequest

type DeclassifyRequest struct {
	Labels []string `json:"labels"`
	Reason string   `json:"reason"`
}

DeclassifyRequest is the args payload of the reserved sys.declassify syscall: which labels to lift from the run's taint, and why — the reason is required because it is what the approving human reads and what the journal preserves.

type DeclassifyResult

type DeclassifyResult struct {
	Declassified []string `json:"declassified"`
}

DeclassifyResult is the journaled outcome of an approved declassification.

type FlowMonitor

type FlowMonitor[ID comparable, K PID[ID]] struct {
	// contains filtered or unexported fields
}

FlowMonitor enforces information-flow policy at the reference monitor (the CaMeL architecture as a kernel primitive). The guest is opaque, so flow is judged conservatively: every label a process observes taints everything it later emits. A syscall to a capability whose Forbid set intersects the process's accumulated taint is refused with ErrnoDenied before any driver runs.

It sits *above* the replay layer: replayed results flow through it, so a crash-restarted host rebuilds the run's taint from the journal exactly. Taint state is the shared Taints; the monitor itself is per-run wiring (see Stack for the canonical chain).

func NewFlowMonitor

func NewFlowMonitor[ID comparable, K PID[ID]](taints *Taints[ID], next sys.Dispatcher[K]) *FlowMonitor[ID, K]

func (*FlowMonitor[ID, K]) Capabilities

func (m *FlowMonitor[ID, K]) Capabilities() []sys.Capability

func (*FlowMonitor[ID, K]) Dispatch

func (m *FlowMonitor[ID, K]) Dispatch(ctx context.Context, cred K, syscall sys.Syscall, auth sys.Authorization) (sys.SyscallResult, error)

type GrantSource

type GrantSource[K any] func(cred K) []sys.Capability

GrantSource reports the capabilities granted to a cred. The app supplies where grants live (the manifest, a policy store); the Validator supplies the enforcement.

type Kernel

type Kernel[ID comparable, K PID[ID]] struct {
	// contains filtered or unexported fields
}

Kernel owns one compiled program image and spawns processes from it.

func NewKernel

func NewKernel[ID comparable, K PID[ID]](ctx context.Context, config Config[ID, K]) (*Kernel[ID, K], error)

NewKernel compiles a program and registers the syscall host function. It rejects images that grant ambient authority (non-empty AllowedHosts or AllowedPaths) with ErrAmbientAuthority.

func (*Kernel[ID, K]) CreateProcess

func (k *Kernel[ID, K]) CreateProcess(ctx context.Context, spec ProcessSpec[ID, K]) (*Process[K], error)

CreateProcess instantiates a fresh process from the kernel's program image. It does not save the process; the caller decides when it becomes visible in the process table.

func (*Kernel[ID, K]) Resume

func (k *Kernel[ID, K]) Resume(ctx context.Context, process *Process[K]) (*ResumeHandle[K], error)

Resume gives a process the CPU in its own goroutine and returns its control handle. The process runs until it completes, yields, fails, or is stopped — cooperative scheduling, no preemption.

func (*Kernel[ID, K]) Shutdown

func (k *Kernel[ID, K]) Shutdown(ctx context.Context) error

Shutdown releases the compiled program image without touching processes or tables.

type Labeler

type Labeler[K any] struct {
	// contains filtered or unexported fields
}

Labeler stamps provenance onto every result: the deriving capability ("syscall:<name>") plus the capability's declared source classes (Capability.Labels). It sits *below* the replay layer so stamped labels are journaled with the completion record — provenance becomes part of the audit trail for free.

func NewLabeler

func NewLabeler[K any](next sys.Dispatcher[K]) *Labeler[K]

func (*Labeler[K]) Capabilities

func (l *Labeler[K]) Capabilities() []sys.Capability

func (*Labeler[K]) Dispatch

func (l *Labeler[K]) Dispatch(ctx context.Context, cred K, syscall sys.Syscall, auth sys.Authorization) (sys.SyscallResult, error)

type PID

type PID[ID comparable] interface {
	PID() ID
}

PID lets user-owned process data expose the stable process identity used for process maps. (Compare Go's stdlib `error` interface: the type and its one method share a name.)

type Process

type Process[K any] struct {
	Cred       K
	Input      json.RawMessage
	Entrypoint string
	// contains filtered or unexported fields
}

Process owns the reusable Extism plugin instance for one PID. Process state is not thread-safe; callers coordinate concurrent use.

Cred is the host-side credential for the process: it identifies the process (PID) and carries whatever authority context the app attaches. It is never visible to or supplied by the guest.

func (*Process[K]) Capabilities

func (process *Process[K]) Capabilities() []sys.Capability

Capabilities returns the operations exposed by this process's dispatcher.

func (*Process[K]) Close

func (process *Process[K]) Close(ctx context.Context) error

Close releases the Extism plugin instance owned by this process.

type ProcessSpec

type ProcessSpec[ID comparable, K PID[ID]] struct {
	Input      json.RawMessage
	Entrypoint string
	Cred       K
	Dispatcher sys.Dispatcher[K]
}

ProcessSpec describes one process to create: its program input, entrypoint, credential, and the dispatcher that will serve its syscalls.

type ProcessTable

type ProcessTable[ID comparable, K PID[ID]] interface {
	LoadProcess(ctx context.Context, pid ID) (*Process[K], error)
	SaveProcess(ctx context.Context, pid ID, process *Process[K]) error
}

ProcessTable owns per-PID processes.

type RateLimit

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

RateLimit is the cross-process token-bucket state behind syscall throttling — shared by all of a host's per-process chains (Stack holds one), while the Throttle decorator that consumes it is wired per process. It only ever *delays* — never denies — because a wall-clock-dependent refusal would be guest-visible nondeterminism, while a delay is invisible to a guest that has no ambient clock. Backpressure, like the scheduler's quotas.

func NewRateLimit

func NewRateLimit(rate float64, burst float64) *RateLimit

NewRateLimit bounds each key to rate tokens/sec with the given burst (minimum 1). A rate <= 0 disables throttling.

func (*RateLimit) Forget

func (l *RateLimit) Forget(key string)

Forget releases a key's bucket once its processes are gone.

type ResumeHandle

type ResumeHandle[K any] struct {
	// contains filtered or unexported fields
}

ResumeHandle controls one active guest invocation.

func (*ResumeHandle[K]) Results

func (h *ResumeHandle[K]) Results() <-chan ResumeResult[K]

Results returns the channel that receives exactly one result before closing.

func (*ResumeHandle[K]) Stop

func (h *ResumeHandle[K]) Stop()

Stop interrupts this invocation. It is safe to call Stop more than once.

type ResumeResult

type ResumeResult[K any] struct {
	Status ResumeStatus
	Output json.RawMessage
	Exit   uint32
	Err    error
}

ResumeResult is delivered when the resume goroutine exits.

type ResumeStatus

type ResumeStatus string

ResumeStatus is the result of one guest invocation attempt.

const (
	ResumeCompleted ResumeStatus = "completed"
	ResumeYielded   ResumeStatus = "yielded"
	ResumeStopped   ResumeStatus = "stopped"
	ResumeFailed    ResumeStatus = "failed"
)

type SpawnConfig

type SpawnConfig[K any] struct {
	// Grants reports the parent's capability set — the ceiling a child's
	// grants are attenuated under.
	Grants GrantSource[K]
	// DeriveCred mints the child credential. spawnKey is the spawn syscall's
	// idempotency key — the intent identity (process, position, call-hash) —
	// so the derived child is deterministic: a replayed or re-entered spawn
	// derives the same child, which is what lets a yielded child's journal be
	// found again on resume.
	DeriveCred func(parent K, spawnKey string, program string) K
	// Run executes the child.
	Run ChildRunner[K]
}

SpawnConfig wires the Spawner decorator.

type SpawnRequest

type SpawnRequest struct {
	Program    string          `json:"program"`
	Entrypoint string          `json:"entrypoint,omitempty"` // defaults to "run"
	Input      json.RawMessage `json:"input,omitempty"`
	// Capabilities names the grants the child receives. Attenuation law:
	// every name must be in the parent's own grant set.
	Capabilities []string `json:"capabilities,omitempty"`
}

SpawnRequest is the args payload of the reserved sys.spawn syscall.

type Spawner

type Spawner[K any] struct {
	// contains filtered or unexported fields
}

Spawner is the kernel-provided dispatcher decorator serving sys.spawn: capability attenuation, deterministic child identity, and the sync-first child-workflow protocol (the child borrows the parent's quantum; a yielding child yields the parent transitively). It must sit *below* the parent's replay layer: a completed spawn is then journaled like any syscall, so replay serves the child's result without re-spawning, and the spawn's idempotency key is available for cred derivation.

func NewSpawner

func NewSpawner[K any](config SpawnConfig[K], next sys.Dispatcher[K]) *Spawner[K]

func (*Spawner[K]) Capabilities

func (s *Spawner[K]) Capabilities() []sys.Capability

Capabilities exposes sys.spawn (with its input schema) alongside the chain's own capabilities, so grant-set validation and discovery see it.

func (*Spawner[K]) Dispatch

func (s *Spawner[K]) Dispatch(ctx context.Context, cred K, syscall sys.Syscall, auth sys.Authorization) (sys.SyscallResult, error)

type Stack

type Stack[ID comparable, K PID[ID]] struct {
	// Grants is the manifest seam: the capability set granted to a cred.
	// Required — a stack without a grant source is not a reference monitor.
	Grants GrantSource[K]
	// Taints is the shared cross-process taint state. Required — flow policy
	// is not optional in the canonical chain; grant nothing with Forbid sets
	// and it is inert.
	Taints *Taints[ID]
	// Limit enables syscall-rate throttling. Optional.
	Limit *RateLimit
	// RateKeyOf picks the throttle's accounting bucket (default: the PID, so
	// each process is limited independently; return the tenant to aggregate).
	RateKeyOf func(cred K) string
	// Spawn enables sys.spawn. Optional.
	Spawn *SpawnConfig[K]
	// OpenIntents overrides the open-intent policy (default: retry under the
	// original idempotency key).
	OpenIntents replay.OpenIntentPolicy
}

Stack wires the kernel's canonical dispatcher chain. The order is the load-bearing part, so it lives in code instead of prose:

Validator → Throttle → FlowMonitor → [replay] → Labeler → Declassifier → Spawner → drivers

Above the replay layer sit the pieces whose decisions must re-derive deterministically on every pass and never enter the journal: validation and flow denials replay identically from the same grant set and taint, and the throttle's delays are invisible to a clockless guest. Below the replay layer sit the pieces whose outcomes must be journaled exactly once and served from the tape thereafter: stamped labels, approved declassification crossings, and spawned child results — all of which also need the tape's idempotency keys. Assembling this by hand and getting one layer on the wrong side silently breaks a kernel law; ForProcess cannot.

The Stack holds the chain's cross-process components (grant source, taint state, rate limit, spawn config); ForProcess completes it with the one per-process piece — the tape — and the process's drivers.

func (Stack[ID, K]) ForProcess

func (s Stack[ID, K]) ForProcess(tape replay.Tape, drivers sys.Dispatcher[K]) (sys.Dispatcher[K], error)

ForProcess assembles the chain for one process around its tape and drivers.

type Taints

type Taints[ID comparable] struct {
	// contains filtered or unexported fields
}

Taints is the cross-process taint state: every label each process has observed. It is shared by all of a host's per-process monitor chains (Stack holds one), while the FlowMonitor decorator that consumes it is wired per process — state and wiring have different lifetimes, so they are different types.

func NewTaints

func NewTaints[ID comparable]() *Taints[ID]

func (*Taints[ID]) Declassify

func (t *Taints[ID]) Declassify(pid ID, labels ...string)

Declassify removes labels from a process's accumulated taint — an explicit, governed crossing of a label boundary (DIFC declassification). Guests reach it through the sys.declassify syscall (the Declassifier decorator), whose approved result replays through the FlowMonitor and lands here; the direct method remains for host-side administrative use.

func (*Taints[ID]) ForgetProcess

func (t *Taints[ID]) ForgetProcess(pid ID)

ForgetProcess releases a terminated process's taint state.

func (*Taints[ID]) Snapshot

func (t *Taints[ID]) Snapshot(pid ID) []string

Snapshot returns the labels a process has accumulated — its current taint — so a caller can carry that provenance elsewhere (e.g. onto session history a later run will observe, keeping the flow policy intact across that boundary).

type Throttle

type Throttle[K any] struct {
	// contains filtered or unexported fields
}

Throttle applies a shared RateLimit in front of one process's dispatcher chain (CHALLENGE B, M2.2 — the syscalls-per-second half of aggregate resource control). KeyOf picks the accounting bucket: cred→PID rate-limits each process, cred→tenant rate-limits an owner's aggregate.

func NewThrottle

func NewThrottle[K any](limit *RateLimit, keyOf func(cred K) string, next sys.Dispatcher[K]) *Throttle[K]

func (*Throttle[K]) Capabilities

func (t *Throttle[K]) Capabilities() []sys.Capability

func (*Throttle[K]) Dispatch

func (t *Throttle[K]) Dispatch(ctx context.Context, cred K, syscall sys.Syscall, auth sys.Authorization) (sys.SyscallResult, error)

type Validator

type Validator[K any] struct {
	// contains filtered or unexported fields
}

Validator is the reference-monitor decorator: complete mediation for the dispatcher chain behind it (kernel law #4). Before delegating it checks (1) the cred's grant set contains the syscall name — otherwise ErrnoDenied — and (2) the args validate against the granted capability's InputSchema — otherwise ErrnoInvalidArgs. Reserved control syscalls (sys.begin/sys.commit) pass through: they are kernel markers, not capabilities.

Policy refusals are results (StatusFailed), not Go errors: the guest sees a classified errno and can react; the process does not crash.

func NewValidator

func NewValidator[K any](grants GrantSource[K], next sys.Dispatcher[K]) *Validator[K]

NewValidator wraps next so every dispatch is checked against the cred's grant set and the capability's input schema.

func (*Validator[K]) Capabilities

func (v *Validator[K]) Capabilities() []sys.Capability

func (*Validator[K]) Dispatch

func (v *Validator[K]) Dispatch(ctx context.Context, cred K, syscall sys.Syscall, auth sys.Authorization) (sys.SyscallResult, error)

Directories

Path Synopsis
Package otelexport renders a journal as OpenTelemetry spans: the process is the trace root, each intent/completion pair is a span, and the record envelope maps to attributes — the scope hierarchy (tenant → session → process → revision) aligns 1:1 with OTel trace/span/parent, so the exporter is a column mapping, not a translation layer.
Package otelexport renders a journal as OpenTelemetry spans: the process is the trace root, each intent/completion pair is a span, and the record envelope maps to attributes — the scope hierarchy (tenant → session → process → revision) aligns 1:1 with OTel trace/span/parent, so the exporter is a column mapping, not a translation layer.
Package sched is the scheduler seam: it decides *when* a process gets the CPU, while the app decides *what* runs (activation — typically journal replay) and the kernel decides *how* (Resume).
Package sched is the scheduler seam: it decides *when* a process gets the CPU, while the app decides *what* runs (activation — typically journal replay) and the kernel decides *how* (Resume).
Package sim is the deterministic simulation harness for the kernel's durability laws (FoundationDB/Antithesis-style, scaled to this kernel).
Package sim is the deterministic simulation harness for the kernel's durability laws (FoundationDB/Antithesis-style, scaled to this kernel).
sys
Context handoffs across the dispatcher chain: the replay layer hands drivers the in-flight intent identity; the flow monitor hands them the process's accumulated taint.
Context handoffs across the dispatcher chain: the replay layer hands drivers the in-flight intent identity; the flow monitor hands them the process's accumulated taint.
replay
Package replay decorates a dispatcher so a re-run guest sees the same results it saw the first time: each syscall is served from a Tape if already recorded, otherwise journaled as an intent, delegated to the underlying dispatcher, and journaled as a completion.
Package replay decorates a dispatcher so a re-run guest sees the same results it saw the first time: each syscall is served from a Tape if already recorded, otherwise journaled as an intent, delegated to the underlying dispatcher, and journaled as a completion.
replay/tape/journaled
Package journaled is a replay Tape backed by an append-only Journal of intent/completion records: an intent is appended before a syscall executes (journal-before-execute), a completion after its outcome is known and before the guest observes it (journal-before-observe).
Package journaled is a replay Tape backed by an append-only Journal of intent/completion records: an intent is appended before a syscall executes (journal-before-execute), a completion after its outcome is known and before the guest observes it (journal-before-observe).
wire
Package wire is the ABI v3 envelope codec: the messages of envelope.proto in proto3 wire format, hand-rolled and reflection-free.
Package wire is the ABI v3 envelope codec: the messages of envelope.proto in proto3 wire format, hand-rolled and reflection-free.

Jump to

Keyboard shortcuts

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