kernel

package
v0.35.0 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

explain.go — the OFF-HOT-PATH dual of Fold: it folds the SAME adjudicator chain to the SAME winning verdict, but additionally records a per-rung Decision trace — the answer to the single most common debugging question in the whole kernel: "why did fak give THIS verdict for THIS tool call?"

Fold (the hot path) keeps only the winning verdict; every rung that ran, what each returned, and which one won are discarded. That is correct for the nanosecond budget of a served call, but it leaves `fak preflight` — the canonical 60-second proof command — printing a single opaque line (verdict=X reason=Y by=Z) with no way to see the eight rungs it actually folded. FoldExplain is the additive answer: callers that only need the verdict use Fold; callers answering "why" (fak preflight --explain/--json, the gateway decision trace, the agent run report) use this.

The Decision is deliberately SAFE TO LOG: it carries an args DIGEST and byte count, never the raw (possibly secret) args, and it surfaces only the bounded-disclosure witness the verdict already chose to disclose — the same deny-channel-is-not-a-policy-oracle discipline the adjudicator enforces.

Package kernel is the fak core: the one concrete implementation of abi.Kernel. It is a driver-blind integrator — it never imports a leaf package; it only WALKS the abi registries (Adjudicators, FastPaths, ResultAdmitters, engines, emitters). That is what lets the leaf subsystems be built in disjoint trees and linked in by a single blank-import line in internal/registrations.

The dispatch chain, in order:

Submit:  vDSO FastPath lookup  ->  (miss) fold Adjudicator chain  ->  route verdict
Reap:    engine.Complete       ->  fold ResultAdmitter chain (context-MMU)

Adjudication happens entirely at Submit and touches neither the engine nor the network, so Submit's latency IS the tool-call adjudication latency the A/B benchmark reports. Reap carries the (slow, mockable) engine round-trip.

Index

Constants

View Source
const (
	FieldElapsedNanos = "elapsed_ns"  // int64: span wall-clock, nanoseconds
	FieldTokenDelta   = "token_delta" // int64: + added (transform/quarantine) / - saved (vdso/radix)
)

spancost.go is L0 of the self-tax plane (#1147 / #1149): the kernel stamps per-span COST — elapsed-ns and a signed token-delta — onto the OPEN abi.Event.Fields telemetry channel of the lifecycle events it already emits, so a passive observer (internal/rungobs) can fold cost, not just verdict, and the offline read-out reconciles with the live fak_gateway_operation_duration_seconds{adjudicator-rung} twin.

The carrier is Event.Fields ("OPEN; unknown ignored — non-label telemetry only"), never a new field on the FROZEN abi message structs: the spine is additive-only and human-owned, and Fields is exactly the channel internal/journal and internal/trajectory already read off these events.

Which SPAN an elapsed-ns belongs to is disambiguated by the Event.Kind it rides:

  • EvDecide / EvDeny carry the EvSubmit->EvDecide adjudication tax;
  • EvComplete carries the EvDispatch->EvComplete engine cost.

FieldTokenDelta is SIGNED: tokens ADDED by a transform/quarantine re-emit are positive (a mediation cost); tokens SAVED by a vDSO/radix local hit are negative (a round-trip the kernel did not have to pay).

Variables

View Source
var ErrDenied = errors.New("kernel: call denied by adjudicator")

ErrDenied is returned by Reap for a call that adjudication refused.

View Source
var ErrNoHandles = errors.New("kernel: no submission handles")

ErrNoHandles is returned by ReapAny for an empty request set.

Functions

func DenyResult

func DenyResult(c *abi.ToolCall, v abi.Verdict) *abi.Result

DenyResult builds the structured deny-as-value (unit 74): a Result carrying the reason token + derived disposition the next model turn consumes, with bounded witness disclosure (only the offending set, sourced from the verdict).

func Disposition

func Disposition(r abi.ReasonCode) string

Disposition derives the deny-loopback disposition (RETRYABLE / WAIT / ESCALATE / TERMINAL) from the reason's category. Only MISROUTE is model-fixable (RETRYABLE); the rest steer the loop without another wasted model turn.

func Fold

func Fold(ctx context.Context, chain []abi.Adjudicator, c *abi.ToolCall) abi.Verdict

Fold runs an Adjudicator chain and resolves it by the restrictiveness lattice: the most-restrictive conclusive verdict wins (fail-closed). An empty chain yields Deny (default-deny on no policy — unit 15). A Defer from every link also yields Deny (nothing affirmatively allowed it). An Indeterminate is non- committable: a later conclusive rung resolves it; a residual Indeterminate fails closed.

Types

type Counters

type Counters struct {
	Submits      int64
	VDSOHits     int64
	EngineCalls  int64
	Denies       int64
	Transforms   int64
	Quarantines  int64
	ResultDenies int64
	Admitted     int64
}

Counters are the kernel's call-path tallies (read by the metrics tap + tests).

type Decision added in v0.32.0

type Decision struct {
	Tool        string        `json:"tool"`
	ArgsDigest  string        `json:"args_digest,omitempty"` // sha256[:12] of the args bytes — never the raw args
	ArgsBytes   int           `json:"args_bytes"`            // size of the args payload
	Consistency string        `json:"consistency"`           // the call's declared consistency level (#1317): STRICT (the default) / BOUNDED_STALE / BEST_EFFORT / SPECULATIVE — recorded verbatim so the relaxation contract is an audit field, not a hidden mode
	Verdict     string        `json:"verdict"`               // final verdict kind name
	Reason      string        `json:"reason,omitempty"`      // final reason name (omitted when NONE)
	By          string        `json:"by,omitempty"`          // winning rung's By (or synthesized: empty-policy/all-defer)
	Claim       string        `json:"claim,omitempty"`       // final bounded-disclosure witness
	Disposition string        `json:"disposition,omitempty"` // deny loopback: RETRYABLE/WAIT/ESCALATE/TERMINAL
	Posture     string        `json:"posture,omitempty"`     // verdict Meta: e.g. admit_and_log
	WouldDeny   string        `json:"would_deny,omitempty"`  // verdict Meta: the reason a posture downgrade suppressed
	Redacted    []string      `json:"redacted,omitempty"`    // TRANSFORM: arg keys whose value the rung rewrote
	Rungs       []RungVerdict `json:"rungs"`                 // every rung consulted, in fold order
	Explanation string        `json:"explanation"`           // one-line human summary
}

Decision is the full, explainable trace of one adjudication fold. It is the structured dual of the one-line verdict: every rung consulted, what each returned, which won, the bounded-disclosure witness, the loopback disposition, and a one-line human explanation. Built only off the hot path.

func FoldExplain added in v0.32.0

func FoldExplain(ctx context.Context, chain []abi.Adjudicator, c *abi.ToolCall) (abi.Verdict, Decision)

FoldExplain folds an Adjudicator chain EXACTLY as Fold does (same winning verdict, same lattice resolution) and additionally returns the per-rung Decision trace. The returned Verdict is byte-identical to Fold(ctx, chain, c): the trace is pure forensic surplus, never a behavior change.

func (Decision) JSON added in v0.32.0

func (d Decision) JSON() string

JSON renders the Decision as indented JSON for `fak preflight --json`.

func (Decision) Text added in v0.32.0

func (d Decision) Text() string

Text renders the Decision as a human-readable multi-line trace for `fak preflight --explain`. It leads with the verdict summary, then the full rung chain with the winner marked.

type Kernel

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

Kernel is the concrete abi.Kernel. Construct with New.

func New

func New(engineID string, opts ...Option) *Kernel

New builds a kernel bound to a registered engine id ("" => first/any engine, or a no-op engine if none registered). The Resolver comes from the registered RegionBackend (blob store in v0.1). Options (e.g. WithAdjudicators) are applied after construction; New with no options builds the v0.1 kernel unchanged.

func (*Kernel) AdmitResult

func (k *Kernel) AdmitResult(ctx context.Context, c *abi.ToolCall, r *abi.Result) abi.Verdict

AdmitResult runs a produced result through the ResultAdmitter chain (the result-side IFC containment + quarantine + per-trace taint ledger) and returns the resolved admission verdict. It is the EXPORTED dual of Decide (which folds only the pre-call chain): the gateway's served path calls it to arm the result-side stack on a result a CLIENT produced and handed back over the wire, so the exfil floor is no longer inert on the proxy/adjudicate topology. It has exactly the same effect on r as the in-process Reap path's admitResult.

func (*Kernel) BatchDecide

func (k *Kernel) BatchDecide(ctx context.Context, calls []*abi.ToolCall) []abi.Verdict

BatchDecide vectorizes adjudication over a list of calls in one pass (unit 75, the "set" batch shape — dos-plan-price generalized). The result is, by construction, identical to deciding each call serially: it simply folds the same chain per call without a per-call kernel round-trip. The dual of speculative decoding — the expensive model proposes a plan, the cheap kernel prunes it in one pass.

func (*Kernel) Counters

func (k *Kernel) Counters() Counters

Counters returns a snapshot of the kernel's call-path tallies.

func (*Kernel) Decide

func (k *Kernel) Decide(ctx context.Context, c *abi.ToolCall) abi.Verdict

Decide folds ONLY the Adjudicator chain and returns the resolved Verdict. It touches no fast path, engine, or network — it is the pure in-process adjudication path the benchmark times against a spawned hook. Exported so the `fak hook` spawned-baseline mode and BenchmarkDecide share one code path.

func (*Kernel) Negotiate

func (k *Kernel) Negotiate(advertised []abi.Capability) []abi.Capability

Negotiate intersects advertised caps with registered ones.

func (*Kernel) Reap

func (k *Kernel) Reap(ctx context.Context, h abi.SubmissionHandle) (*abi.Result, error)

Reap completes a submission. A fast-path result is returned directly. A denied call yields a structured deny Result (Status=Error, Meta carries reason + disposition) so the loop can consume it (deny-as-value). An allowed call is dispatched to the engine and the result run through the ResultAdmitter chain.

func (*Kernel) ReapAll added in v0.34.0

func (k *Kernel) ReapAll(ctx context.Context, handles []abi.SubmissionHandle) ([]*abi.Result, error)

ReapAll completes every handle in the caller's order and returns results in the same index order. It borrows MPI_Waitall's request-set shape only: each handle was already adjudicated at Submit, and this fan-in performs no new admission or transport progress beyond the underlying local Reap calls.

func (*Kernel) ReapAny added in v0.34.0

func (k *Kernel) ReapAny(ctx context.Context, handles []abi.SubmissionHandle) (abi.SubmissionHandle, *abi.Result, error)

ReapAny completes one handle from a request set. It first consumes an already locally-complete handle (fast-path hit or denied call) if one is present. With no progress engine to poll, an all-pending set is driven by reaping the first handle in the caller's set. The method consumes exactly the returned handle; other handles remain pending for later Reap/ReapAll calls.

It matches a completion to its submission by SubmissionHandle.Seq; the Queue/Opaque fields are reserved, inert routing/correlation slots no scheduler reads here (there is one global engine fold, no multi-queue tag-matcher). The async-addressing seam — what Seq/Queue/Opaque and the Ext/ExtKey sidecar are shaped for, and the MPI-analogue caveats — is documented in docs/proofs/async-addressing.md.

func (*Kernel) Resolver

func (k *Kernel) Resolver() abi.Resolver

Resolver is the active Ref backend.

func (*Kernel) SetVDSO

func (k *Kernel) SetVDSO(enabled bool)

SetVDSO toggles the vDSO fast path (the --vdso on/off ablation, unit 5). When off, Submit never consults a FastPath, so every call falls through to adjudication + engine — the "off" arm of the A/B benchmark.

func (*Kernel) Submit

Submit adjudicates and admits the call. The vDSO is consulted FIRST (unit 30); a hit returns immediately with no adjudication and no engine call. On a miss the Adjudicator chain is folded; a denied call is never enqueued.

func (*Kernel) Syscall

func (k *Kernel) Syscall(ctx context.Context, c *abi.ToolCall) (*abi.Result, abi.Verdict)

Syscall is Submit then Reap (the synchronous convenience every caller uses).

func (*Kernel) TestHandle added in v0.34.0

func (k *Kernel) TestHandle(h abi.SubmissionHandle) abi.Status

TestHandle is the concrete-kernel non-consuming poll for a submitted handle. It borrows MPI_Test's request-poll shape only: polling drives no progress engine and does not dispatch a tool. StatusPending means this process still has an admitted call that must run its engine round-trip via Reap. Already-ready fast path hits, denied calls, and unknown/reaped handles are locally complete from the poller's point of view and report StatusOK; Reap remains the consuming path.

func (*Kernel) VDSOEnabled

func (k *Kernel) VDSOEnabled() bool

VDSOEnabled reports whether the fast path is active.

type Option added in v0.32.0

type Option func(*Kernel)

Option configures a Kernel at construction (a functional option, additive: New with no options builds the v0.1 kernel unchanged).

func WithAdjudicators added in v0.32.0

func WithAdjudicators(chain []abi.Adjudicator) Option

WithAdjudicators injects an EXPLICIT adjudicator chain the kernel folds INSTEAD of the process-global abi.AdjudicatorsFor registry. It is the per-kernel adjudicator injection (issue #500): a replay driver can hand each of K policy arms its own monitor (e.g. []abi.Adjudicator{adjudicator.New(arm.Policy)}) and run them in parallel goroutines, with NO arm mutating the shared adjudicator.Default. The chain is folded per call through the same restrictiveness lattice and the same CallScope tool-scoping as the global path (via abi.ScopedFor), so a kernel given the global chain is verdict-identical to one reading the registry. Passing an empty/nil chain is a NO-OP — the kernel falls back to the global registry — so it can never silently install an empty (default-deny-everything) policy.

type RungVerdict added in v0.32.0

type RungVerdict struct {
	Index    int    `json:"index"`            // position in the folded chain (rank order)
	Rung     string `json:"rung"`             // concrete adjudicator type, e.g. "adjudicator.Adjudicator"
	By       string `json:"by,omitempty"`     // verdict.By: who claims the decision ("" on a bare defer)
	Kind     string `json:"kind"`             // ALLOW/DENY/TRANSFORM/QUARANTINE/WITNESS/DEFER/...
	Reason   string `json:"reason,omitempty"` // reason name, omitted when NONE
	Claim    string `json:"claim,omitempty"`  // bounded-disclosure witness, if this rung disclosed one
	Rank     int    `json:"rank"`             // FoldRank(kind): restrictiveness-lattice position
	Deferred bool   `json:"deferred"`         // Kind==DEFER: this rung abstained, did not participate
	Winner   bool   `json:"winner"`           // this rung's verdict was the folded result
	Elided   bool   `json:"elided,omitempty"` // this rung did NOT run: a max-rank verdict short-circuited the fold before it
}

RungVerdict is one adjudicator's contribution to a folded decision — the per-rung detail Fold throws away. Rung is the concrete adjudicator type (a stable identity even when the rung DEFERS and sets no By), By is the verdict's self-reported decider, and Winner marks the rung whose verdict the lattice fold selected.

Jump to

Keyboard shortcuts

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