execution

package
v1.0.220 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

Package execution implements the PR-11 guarded Model-A execution path — the runtime.ExecutionProvider that turns the decision-only Gateway into a bounded, rollout-mode-gated executor. It is wired ONLY for the Gateway capability and ONLY when rollout distribution arms it (disabled by default). It composes the existing engines without weakening any of them: rollout (mode/scope/hard-failure), the PR-6 policy decision (already computed), PR-7 inspection/DLP for the response, the PR-4 credential broker (materialization with no token passthrough), the PR-8 events manager (commit-before-side-effect), and the PR-11 upstream client.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ReconcileOrphan added in v1.0.218

func ReconcileOrphan(ctx context.Context, w Witness, orphan RecoveredAttempt, expectServer, expectMethod string, now int64) (model.ReconciliationEvidence, error)

ReconcileOrphan derives authoritative knowledge about one recovered orphan.

It never manufactures certainty. A witness that is unavailable, times out, returns malformed data, or cannot prove completeness leaves the attempt at reconciliation_required — the resting state — rather than producing a verdict the evidence does not support.

func ReconcileTransitionAllowed added in v1.0.218

func ReconcileTransitionAllowed(from, to model.ReconciliationResult) bool

ReconcileTransitionAllowed pins the state machine (§9). Reconciliation only ever moves an attempt from reconciliation_required to a knowledge state, and there is NO transition back to executable — the recovery/reconciliation surfaces expose no execution capability at all.

func SetIngestGuard added in v1.0.218

func SetIngestGuard(fn func(ingest func() error) error)

SetIngestGuard installs the default ingest-serialization guard used by NewDiscovery. The composition root calls it once at startup; nil clears it (tests). It keeps this package decoupled from tool trust — it only ever runs the ingest it is handed inside whatever critical section the composition root wraps it in, and that gating can never widen usability.

func SetReconcileHook added in v1.0.218

func SetReconcileHook(fn func())

SetReconcileHook installs the default post-ingest reconcile callback used by NewDiscovery. The composition root calls it once at startup; nil clears it (tests). It keeps this package decoupled from tool trust — it only ever invokes the func it is handed, and that func can only withdraw-or-re-affirm trust, never widen usability.

Types

type AttemptState added in v1.0.218

type AttemptState string

AttemptState is the derived disposition of one attempt as the durable ledger describes it.

const (
	// AttemptReconciliationRequired — an intent with no valid terminal outcome. Only
	// an authoritative independent witness can resolve it.
	AttemptReconciliationRequired AttemptState = "reconciliation_required"
	// AttemptSettled — an intent with exactly one valid, consistent terminal outcome.
	AttemptSettled AttemptState = "settled"
)

type CanarySafety added in v1.0.218

type CanarySafety interface {
	// Breach reports that an authoritative whole-Canary breach OCCURRED. code is a
	// canary.AbortConditions() taxonomy code; an unrecognised code fails closed to a
	// whole-Canary latch at the controller, so a typo can only ever stop the experiment, never
	// silently continue it.
	Breach(capability string, gen uint64, code string)

	// AttemptSettled reports ONE settled post-admission attempt so the composition layer's
	// population detectors (elevated error rate, latency pathology) can judge. failed is an
	// ordinary execution failure; latency is the observed attempt duration.
	//
	// The engine reports only ADMITTED attempts that settled. It does NOT report request-scoped
	// denials — a policy deny, a scope refusal, an allowance already consumed — because those are
	// what a healthy Canary does all day, and counting them would let a Canary abort itself for
	// correctly refusing requests. Conditions carrying their own immediate whole-Canary
	// classification are likewise not laundered through a rate: they trip directly.
	AttemptSettled(capability string, gen uint64, failed bool, latency time.Duration)
}

CanarySafety receives authoritative safety facts from the execution engine.

type Config

type Config struct {
	// State is the capability-local rollout state (Gateway). Required.
	State *rollout.State
	// Broker materializes the approved-server credential (nil ⇒ no credential is
	// attached; the upstream call carries no Authorization).
	Broker *broker.Broker
	// Events is the PR-8 durable-event manager. Required for the commit-before-
	// side-effect guarantee; a nil Events fails every execution closed.
	Events *events.Manager
	// Upstream is the bounded upstream MCP client. Required.
	Upstream UpstreamCaller
	// ResponseProfile is the PR-7 inspection profile used to inspect + DLP the
	// upstream response before returning it to the client.
	ResponseProfile inspection.Profile
	// Metrics is the optional rollout telemetry sink.
	Metrics Metrics
	// Clock is injected for tests; nil ⇒ time.Now.
	Clock func() time.Time
	// Actor labels events emitted by this executor.
	Actor string
	// LiveGate is the OPTIONAL composition-layer side-effect gate consulted at the boundary
	// BEFORE the executor's own tool-freshness + emergency-kill re-check, so the kill re-read
	// stays the LAST authoritative check before Upstream.Call (PREREQ-MCP-KILL-1). It owns the
	// gates that live OUTSIDE this package — Canary blast-radius budget reservation, runtime
	// live-execution trust revalidation, and read-first enforcement. nil ⇒ the executor is
	// byte-identical to the pre-gate path (the ShadowEvaluator and any non-live composition
	// never set it). See livegate.go.
	LiveGate LiveExecutionGate
	// Safety is the OPTIONAL narrow whole-Canary breach seam (blocker #7). Nil means no Canary is
	// composed; it is replaced by a no-op so call sites never branch. It is deliberately separate
	// from Metrics — see safety.go for why observability and control must not share a sink.
	Safety CanarySafety
}

Config wires an Executor for the Gateway capability.

type CredentialPlanner added in v1.0.218

type CredentialPlanner interface {
	Plan(broker.PlanInput) (broker.CredentialPlan, error)
}

CredentialPlanner is the NARROW, plan-only credential capability a caller supplies to a Shadow evaluator. Plan is metadata-only: no provider call, no cache decrypt, no secret; it deliberately does NOT expose Materialize. *broker.Broker satisfies it. NOTE: this interface is the CALLER-FACING config type only — a ShadowEvaluator does NOT retain the planner as an interface value (which would keep the concrete broker recoverable by a type assertion). NewShadowEvaluator extracts the bound `Plan` METHOD VALUE and drops the interface (see the `plan` field + `NewShadowEvaluator`), so the materialize-capable concrete value is genuinely unreachable from the stored evaluator, not merely un-called (SEC — Codex P2 on PR #1226).

type Discovery

type Discovery struct {
	Registry *registry.Registry
	Catalog  *catalog.Catalog
	Upstream UpstreamCaller
	// OnIngest, when set, is invoked after a SUCCESSFUL catalog ingest (a new snapshot was
	// published). Ingestion can only ever land a tool Quarantined/ReviewRequired — it never
	// produces catalog.Usable — so a re-discovered tool that exactly matches an active trust
	// approval would otherwise stay non-Usable until the next inventory read, Shadow
	// preflight, or the periodic 30s reconcile tick, contaminating an in-flight Shadow
	// experiment. The composition root wires this to the tool-trust reconcile hook so the
	// approval's projection is re-materialized immediately. Optional and nil-safe; it must
	// only WITHDRAW-or-re-affirm trust (it can never widen usability), so calling it here is
	// safe even though this package holds no trust authority.
	OnIngest func()
	// IngestGuard, when set, runs the catalog ingest (its snapshot PUBLISH) inside the
	// tool-trust reconcile critical section so a catalog revision advance is MUTUALLY
	// EXCLUSIVE with an in-flight trust approval. Without it, ingestion advances the live
	// catalog revision without holding the coordinator's derive lock, so an approval that
	// captured the pre-advance revision between its target load and its durable commit would
	// validate a stale copy — an identical rediscovery (same fingerprint, bumped revision) or
	// an F1→F2→F1 flap in that window would be approved instead of returning the required
	// stale-target conflict (ADR-0034 optimistic concurrency, PR round-15). Optional and
	// nil-safe; when nil the ingest runs directly. Like OnIngest it only ever gates WHEN a
	// publish lands, never what a publish produces, so carrying it grants this package no
	// trust authority.
	IngestGuard func(ingest func() error) error
}

Discovery performs a real upstream tools/list against a registered server and feeds the result through the PR-2 catalog ingestion path (fingerprints → drift classification → quarantine of unknown/expanded tools). It reuses the PR-1 kernel on the upstream leg (via the upstream client's strict decode) and never auto-approves a new or changed fingerprint. A discovery failure returns a classified error and leaves the previous known-good catalog snapshot UNCHANGED.

func NewDiscovery

func NewDiscovery(reg *registry.Registry, cat *catalog.Catalog, up UpstreamCaller) (*Discovery, error)

NewDiscovery constructs a Discovery. It fails closed on missing collaborators and installs the default post-ingest reconcile hook (SetReconcileHook) plus the ingest-serialization guard (SetIngestGuard) so the ingest path reconciles trust and serializes its publish with in-flight approvals without the caller having to wire OnIngest / IngestGuard itself.

func (*Discovery) Discover

func (d *Discovery) Discover(ctx context.Context, serverID string) (*catalog.Report, error)

Discover runs the ordered discovery sequence for one registered server:

  1. resolve + validate the server registration + trusted identity;
  2. fetch a bounded tools/list via the upstream client (strict shared-kernel decode of the response);
  3. feed the exact result bytes into the PR-2 catalog ingestion path;
  4. drift classification + quarantine happen inside Ingest (unknown/expanded fingerprints land Quarantined and never auto-clear);
  5. on ANY failure, the previous catalog snapshot is retained unchanged.

It returns the ingestion Report (safe drift/quarantine evidence) on success.

type EvidenceReader added in v1.0.218

type EvidenceReader interface {
	CommittedForExport(part model.Partition, afterSeq uint64, maxRecords int) ([]model.Event, []uint64, uint64, error)
}

EvidenceReader is the narrow read seam over the authoritative committed event stream. *spool.Spool satisfies it; tests supply deterministic fixtures.

type Executor

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

Executor implements runtime.ExecutionProvider. It is the LIVE object: it possesses the upstream client and the materialize-capable broker and is composed ONLY for Canary/Production (both prohibited today). Its Shadow-fallback disposition (out-of-scope Canary → Shadow) is delegated to a distinct, capability-reduced *ShadowEvaluator so the shadow path never touches this object's live capabilities.

func New

func New(cfg Config) (*Executor, error)

New constructs an Executor. It fails closed if the required collaborators are missing.

func (*Executor) Execute

Execute is the runtime.ExecutionProvider entry. It acts on the PRE-RESOLVED mode/scope disposition (it never re-resolves mode or scope — F7 single resolution, Codex P2 #1234) and dispatches record-only / block / execute.

It re-reads ONLY the emergency kill: the kill switch is an immediate admission stop, so a kill engaged AFTER Resolve but before the irreversible upstream call must still stop it. This is orthogonal to single-resolution — it reads only the monotonic kill flag and can only make the outcome MORE restrictive (an emergency block), so it cannot reopen the routing TOCTOU F7 closed. Fail-closed here matters most on the LIVE path: it stops an upstream side effect that Resolve had cleared microseconds before the operator hit kill.

func (*Executor) KillActive added in v1.0.218

func (e *Executor) KillActive() bool

KillActive implements runtime.ExecutionProvider: it reports whether this capability's emergency kill switch is engaged, for the runtime's record-only fall-through re-check.

func (*Executor) ReconcileAndReport added in v1.0.218

func (e *Executor) ReconcileAndReport(ctx context.Context, w Witness, orphan RecoveredAttempt,
	expectServer, expectMethod string, capability string, now int64,
) (model.ReconciliationEvidence, error)

ReconcileAndReport runs ReconcileOrphan and routes an AUTHORITATIVE physical-effect contradiction to the whole-Canary breach seam (First Controlled Canary review, blocker #7 §10).

This is the load-bearing path the review requires: when reconciliation eventually says the world disagrees with Culvert's own record of what it did — a duplicated attempt, a reservation reused, a binding that does not match the intent — that verdict must already be able to stop the experiment, not merely be written down. All three of those shapes arrive here as ReconConflict; the #1306 derivation folds a >1 observation count and a mismatched binding into exactly that verdict, so conflict is the single signal to act on and there is no second classification to keep in sync.

It does NOT create a production witness. With no witness wired, ReconcileOrphan returns reconciliation_required and nothing trips — the shipped posture, and blocker #1/#8's to change. What this closes is the gap where the signal existed and had nowhere to go.

func (*Executor) Resolve added in v1.0.218

func (e *Executor) Resolve(in runtime.ExecInput) rollout.Resolution

Resolve implements runtime.ExecutionProvider: it resolves the effective rollout disposition for this request EXACTLY ONCE (no side effect), so routing and execution use the same snapshot. A killed capability resolves to an emergency block.

type LiveExecutionGate added in v1.0.218

type LiveExecutionGate interface {
	// AdmitSideEffect decides whether this request may cross the irreversible upstream
	// boundary. It performs NO upstream call and NO credential materialization.
	AdmitSideEffect(in LiveGateInput) LiveGateDecision

	// AdmitAuxiliary decides whether a NON-side-effect-bearing invocation — MCP
	// lifecycle and discovery traffic — may cross the same upstream boundary.
	//
	// It is a SEPARATE admission because the two questions are genuinely different,
	// and collapsing them in either direction is a defect this interface has already
	// suffered once in each direction:
	//
	//   - Routing auxiliary traffic through AdmitSideEffect spends a Canary execution
	//     reservation on a call that can cause no side effect (so MaxTotalExecutions
	//     stops measuring physical invocations) and refuses it on a tool-trust
	//     revalidation that has no tool to bind (so an armed node cannot complete a
	//     session handshake or list tools).
	//   - Skipping the gate ENTIRELY for auxiliary traffic — the first fix for the
	//     above — also discards the questions that DO apply to it: whether the live
	//     tier is armed at all, and whether it has begun quiescing. Those are the
	//     operator's disarm and wind-down controls, and after a restart the tier is
	//     deliberately left composed-but-unarmed while the persisted rollout mode may
	//     still resolve EffectExecute. Auxiliary traffic reaching a third-party
	//     upstream — establishing a session, reading its catalog, and carrying a
	//     materialized credential when the decision obliges one — on a tier the
	//     operator has disarmed or is winding down is the fail-closed restart posture
	//     (§17) being lost for every method except tools/call.
	//
	// So this admission runs the LIFECYCLE half and nothing else: no budget
	// reservation, no read-first, no live-trust revalidation. A denial fails the
	// invocation closed with the gate's bounded reason and Upstream.Call is never
	// reached; an admit returns a Release the executor runs exactly once after the
	// upstream leg, exactly as AdmitSideEffect does.
	//
	// It is part of the REQUIRED interface rather than an optional one on purpose: a
	// gate that does not answer this question must not be able to answer it by
	// omission, because omission is the permissive direction.
	AdmitAuxiliary(in LiveGateInput) LiveGateDecision
}

LiveExecutionGate is the OPTIONAL composition-layer gate the live Executor consults at the side-effect boundary. It exists so the gates that must live OUTSIDE this package — Canary blast-radius BUDGET reservation, runtime live-execution TRUST revalidation, and READ-FIRST operation enforcement — can run immediately before the irreversible upstream call WITHOUT this package importing the composition layer (rollout-runtime / tooltrust / canary singletons).

PLACEMENT IS A SAFETY INVARIANT. The executor invokes the gate at the TOP of callUpstream, BEFORE preCallGuard's tool-freshness + emergency-kill re-check, so the kill re-read remains the LAST authoritative check before Upstream.Call (PREREQ-MCP-KILL-1): nothing the gate does (which may block on a durable budget persist) sits between the kill re-read and the side effect. A gate DENIAL therefore fails the execution closed and Upstream.Call is never reached; a gate ADMIT returns a Release the executor runs exactly once after the upstream leg (success, failure, or a later boundary refusal), so a reserved concurrency slot is never leaked — including the §11 case where a subsequent kill/freshness abort occurs AFTER the gate admitted (and after any credential materialization).

The gate is nil in every non-live composition (the ShadowEvaluator has no LiveGate, and the disabled-by-default build composes no live executor), so the executor is byte-identical to the pre-gate path when it is unset.

type LiveGateDecision added in v1.0.218

type LiveGateDecision struct {
	Admit  bool
	Reason mcperr.Reason
	// Revalidate (non-nil only when Admit) is a FINAL-BOUNDARY re-check the executor runs inside
	// preCallGuard, immediately before the emergency-kill re-read. It returns false when the
	// activation this request was admitted under is no longer current — e.g. a concurrent Canary
	// demotion invalidated the reserved generation AFTER admission but BEFORE the irreversible call.
	// Because the admission-time reservation cannot see a later demotion, and preCallGuard's kill
	// re-read does not consult the Canary generation, WITHOUT this an already-admitted request could
	// still reach the upstream after a leaving-live transition returned success (Codex P1 round-8,
	// PR #1290). It is a composition-layer concern (the generation lives in the canary runtime), so it
	// enters this package only as an injected predicate — the executor stays generic and byte-identical
	// when the gate (or Revalidate) is nil.
	Revalidate func() bool
	Release    func()
	// ReservationID (set only when Admit) names the budget slot this side effect was
	// authorized against. It binds a physical attempt to the reservation that paid
	// for it, so an effect can never be attributed to an unauthorized slot and an
	// orphan can be traced back to the exact grant. An empty value is tolerated:
	// gates that do not meter (nil/legacy) keep the executor byte-identical.
	ReservationID string
	// ActivationGeneration (set only when Admit) is the Canary activation generation
	// in force at admission. It is recorded on the attempt so an orphan from a
	// superseded generation stays recognizable after a restart and can never be
	// mistaken for fresh execution allowance. Zero when the gate does not meter.
	ActivationGeneration uint64
}

LiveGateDecision is the gate's verdict. Admit==false fails closed with Reason and Upstream.Call is never reached. Release (non-nil only when Admit) is run exactly once after the upstream leg.

type LiveGateInput added in v1.0.218

type LiveGateInput struct {
	Capability protocol.Capability
	// Operation is the policy-engine operation class (read-first is decided from THIS, never
	// the server-provided readOnlyHint).
	Operation policy.OperationClass
	// Tenant / Principal identify the authenticated subject for the blast-radius ceilings.
	Tenant    string
	Principal string
	// ServerID / ToolName / Fingerprint are the exact reviewed target the live-trust
	// revalidation binds against (Fingerprint is the hex composite fingerprint the decision
	// was computed against; tool freshness separately proves it still matches the live tool).
	ServerID    string
	ToolName    string
	Fingerprint string
	Now         time.Time
}

LiveGateInput carries the authoritative, already-resolved facts the composition-layer gate needs. Every field derives from the pre-resolved decision (never a request-supplied claim): the executor builds it from runtime.ExecInput at the boundary.

type Metrics

type Metrics interface {
	ObserveResolution(capability string, res rollout.Resolution)
	ObserveBlock(capability string, reason mcperr.Reason)
	ObserveExecution(capability string, ok bool)
	ObserveUpstream(capability string, outcome string)
	ObserveDLPBlock(capability string, response bool)
	// ObserveOutcomeEvidenceLoss records that a post-execution outcome event could
	// not be committed. The side effect already happened, so this is the archive
	// losing its record of it — best-effort must mean "does not block the response",
	// never "fails invisibly".
	ObserveOutcomeEvidenceLoss(capability string)
	// ObserveShadowOutcome records ONE non-executing Shadow evaluation and its formal
	// Model-1 verdict. outcome is a ShadowOutcome value (a bounded, low-cardinality enum:
	// would_execute / would_block / would_require_* / would_fail_*) — never a tenant,
	// subject, tool, or argument. It is emitted only by the ShadowEvaluator; the live
	// executor never calls it.
	ObserveShadowOutcome(capability string, outcome string)
}

Metrics is the optional bounded, low-cardinality rollout telemetry sink. All labels are bounded enums (capability, disposition, reason code, hard class) — never a tenant, subject, tool argument, URL, or token.

type RecoveredAttempt added in v1.0.218

type RecoveredAttempt struct {
	AttemptID            string
	ReservationID        string
	ActivationGeneration uint64
	State                AttemptState
	// TerminalSendState is the committed physical-send state when State is
	// AttemptSettled, and the zero value otherwise. It is never synthesized for an
	// orphan.
	TerminalSendState model.PhysicalSendState
	// Reconciliation is the DERIVED knowledge from append-only witness evidence. It
	// is ReconRequired when no witness has answered — for an orphan AND for a settled
	// attempt, since a settled attempt whose send state is may_have_been_sent still
	// has an open question. It never changes execution authority — only what is known.
	Reconciliation model.ReconciliationResult
}

RecoveredAttempt is the reconstructed identity of one attempt. Every field comes from the durable intent; recovery NEVER mints a new AttemptID, because a new identity would be unmatchable against a witness that recorded the original.

func (RecoveredAttempt) NeedsReconciliation added in v1.0.218

func (a RecoveredAttempt) NeedsReconciliation() bool

NeedsReconciliation reports whether an independent witness could still change what is known about this attempt.

It is deliberately NOT "State == AttemptReconciliationRequired". A terminal outcome makes an attempt SETTLED as to execution authority, but says nothing about whether its physical fate is known: an upstream POST that ended without a response settles as may_have_been_sent, which is the single most important case a witness exists to resolve, and gating on State alone made it permanently unreconcilable (Codex round 8, P1).

The converse matters just as much. Once a witness has RESOLVED an attempt, asking again can only move knowledge backwards: a witness outage would answer reconciliation_required, and the append-only ledger correctly refuses that downgrade — so the query itself would turn a healthy resolved attempt into a recovery failure. Resolved knowledge is therefore final here.

type RecoveryReport added in v1.0.218

type RecoveryReport struct {
	// Orphans require reconciliation. They consume their original allowance
	// permanently and are never re-executed.
	Orphans []RecoveredAttempt
	// Settled attempts reached a valid terminal outcome.
	Settled []RecoveredAttempt
	// ReservationBreaches names every slot bound to more than one attempt. A
	// non-empty slice means N accepted reservations produced more than N potential
	// physical invocations.
	//
	// Surfacing this was a red-team finding. Recovery previously listed such
	// attempts individually — VISIBLE, but not the same as DETECTED: nothing
	// distinguished "two attempts" from "two attempts that one slot paid for".
	ReservationBreaches []ReservationBreach
}

RecoveryReport is the result of one derivation over the durable stream.

func RecoverAttempts added in v1.0.218

func RecoverAttempts(r EvidenceReader) (RecoveryReport, error)

RecoverAttempts derives every attempt's disposition from the durable event stream.

It FAILS CLOSED on any ambiguity rather than choosing the newest record. A ledger that describes one attempt two different ways is not a ledger to pick a winner from — the ambiguity is itself unsafe evidence, and silently resolving it is how a duplicate physical effect would disappear from the record.

func (RecoveryReport) HasReservationBreach added in v1.0.218

func (r RecoveryReport) HasReservationBreach() bool

HasReservationBreach reports whether any slot authorized more than one attempt.

type ReservationBreach added in v1.0.218

type ReservationBreach struct {
	ReservationID string
	AttemptIDs    []string
}

ReservationBreach names one budget slot that authorized MORE THAN ONE physical attempt. It is the review-blocker-#6 invariant breach expressed in the ledger:

one accepted execution reservation  =>  at most one physical tool invocation

It is REPORTED rather than raised as an error, deliberately. Failing the whole derivation closed would leave the operator with no report at all — including no account of the very attempts that need reconciling — so the breach is named, carried alongside a usable report, and impossible to overlook.

type ShadowConfig added in v1.0.218

type ShadowConfig struct {
	// State is the capability-local rollout state (Gateway). Required.
	State *rollout.State
	// Planner is the OPTIONAL plan-only credential capability. nil ⇒ credential
	// readiness is reported as not-evaluated (a Shadow evaluator is constructible with
	// no credential capability at all).
	Planner CredentialPlanner
	// Events is the durable-event manager. Required: evidence-before-report is
	// mandatory, so a nil Events fails every evaluation closed.
	Events *events.Manager
	// Metrics is the optional rollout telemetry sink.
	Metrics Metrics
	// Clock is injected for tests; nil ⇒ time.Now.
	Clock func() time.Time
	// Actor labels events emitted by this evaluator.
	Actor string
}

ShadowConfig wires a ShadowEvaluator. It CANNOT carry an UpstreamCaller or a materialize-capable *broker.Broker — those fields do not exist on this struct. That absence is the Layer-B capability-security invariant.

type ShadowDecision added in v1.0.218

type ShadowDecision struct {
	EvaluatedAction    string        // the raw policy action (never softened)
	Outcome            ShadowOutcome // Model-1 enforcement prediction
	ShadowOverride     bool          // policy itself is restrictive (non-allow-class)
	CredentialPlan     string        // credential_plan_valid / _invalid / no_credential_profile
	MaterializeReady   string        // always not_evaluated (§12 — Shadow never materializes)
	RequestInspection  string        // would_pass / would_fail (from the pre-executor hard-fail source)
	ResponseInspection string        // always not_evaluated (§13 — no upstream response exists)
}

ShadowDecision is the structured, truthful verdict recorded for a Shadow evaluation. It preserves the policy verdict (EvaluatedAction) SEPARATELY from the enforcement prediction (Outcome), so a DENY / REQUIRE_APPROVAL / REQUIRE_CONFIRMATION is never laundered into a plain WOULD_EXECUTE.

type ShadowEvaluator added in v1.0.218

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

ShadowEvaluator is the non-executing Shadow capability object. It implements runtime.ExecutionProvider, but holds no upstream client and no materialize-capable broker, so it cannot perform an upstream call or materialize a credential. A request that resolves to EffectExecute (impossible in Shadow mode) fails CLOSED here.

func NewShadowEvaluator added in v1.0.218

func NewShadowEvaluator(cfg ShadowConfig) (*ShadowEvaluator, error)

NewShadowEvaluator constructs a Shadow evaluator. Note what it does NOT require: no upstream client, no materializing broker. A Shadow evaluator is fully constructible with neither. It NARROWS the supplied planner to its Plan method value and drops the interface, so no materialize-capable concrete value is retained (Codex P2).

func (*ShadowEvaluator) Execute added in v1.0.218

Execute is the runtime.ExecutionProvider entry for a Shadow-only runtime. It acts on the PRE-RESOLVED mode/scope disposition — it never re-resolves mode or scope (F7 single resolution, Codex P2 #1234) — and dispatches. It has no execute path.

The one thing it DOES re-read is the emergency kill: the kill switch is an immediate admission stop (admin surface + runbook contract), so a kill engaged AFTER Resolve but before this evaluation commits must still stop it — otherwise the evaluator would commit durable evidence and return a would_* verdict AFTER the operator's emergency stop. This is orthogonal to single-resolution: it reads only the monotonic kill flag and can only make the outcome MORE restrictive (an emergency block), never turn a record-only into an evaluation or an evaluation into an execute, so it cannot reopen the routing TOCTOU that F7 closed (Codex P2, PR #1234).

func (*ShadowEvaluator) KillActive added in v1.0.218

func (s *ShadowEvaluator) KillActive() bool

KillActive implements runtime.ExecutionProvider: it reports whether this capability's emergency kill switch is engaged, for the runtime's record-only fall-through re-check.

func (*ShadowEvaluator) Resolve added in v1.0.218

Resolve implements runtime.ExecutionProvider: it resolves the rollout disposition for this request EXACTLY ONCE (no side effect) so the runtime can route on it and hand the SAME resolution back to Execute. A killed capability resolves to an emergency block (never record-only), so the runtime routes it to Execute rather than its inline path.

type ShadowOutcome added in v1.0.218

type ShadowOutcome string

ShadowOutcome is the formal Model-1 Shadow verdict: what a fully-enforcing mode (Canary/Production) WOULD do with this request, computed without executing it. It is never a permissive-passthrough label — a policy DENY is WOULD_BLOCK, never WOULD_EXECUTE (SH-INV, §8 of the phase brief).

const (
	ShadowWouldExecute                 ShadowOutcome = "would_execute"
	ShadowWouldBlock                   ShadowOutcome = "would_block"
	ShadowWouldRequireApproval         ShadowOutcome = "would_require_approval"
	ShadowWouldRequireConfirmation     ShadowOutcome = "would_require_confirmation"
	ShadowWouldFailCredentialReadiness ShadowOutcome = "would_fail_credential_readiness"
	ShadowWouldFailInspection          ShadowOutcome = "would_fail_inspection"
	ShadowWouldFailStaleDecision       ShadowOutcome = "would_fail_stale_decision"
	ShadowWouldFailHardControl         ShadowOutcome = "would_fail_hard_control"
)

The bounded set of Model-1 Shadow outcomes. Each names what a fully-enforcing mode would do at the pre-side-effect boundary; the differential equivalence test pins them to the live executor's decision.

type UpstreamCaller

type UpstreamCaller interface {
	Call(ctx context.Context, target upstreamclient.Target, method string, params json.RawMessage, opts upstreamclient.CallOptions) (*upstreamclient.Response, error)
}

UpstreamCaller is the injected upstream client (interface for testability).

type Witness added in v1.0.218

type Witness interface {
	LookupAttempt(ctx context.Context, attemptID string) (WitnessObservation, error)
}

Witness is the narrow authoritative lookup seam. The PRODUCTION implementation belongs to the controlled-upstream work (review blocker #1) and is deliberately unwired here; this PR establishes the contract and proves it against controlled infrastructure.

type WitnessObservation added in v1.0.218

type WitnessObservation struct {
	// Count is the number of physical invocations the witness recorded for this
	// AttemptID. Retained verbatim: >1 is a physical-effect breach, not a duplicate
	// to be normalized away.
	Count int
	// Complete reports whether the witness can PROVE its observation set is complete
	// for the relevant interval. Absence from an incomplete log proves nothing.
	Complete bool
	// CompletenessWatermark is the evidence backing Complete — a durable monotonic
	// watermark, closed window, or authoritative sequence boundary. Required for a
	// definitive "not received".
	CompletenessWatermark string
	// Binding metadata the witness observed, used to confirm the observation really
	// belongs to this attempt rather than merely sharing its id.
	ServerID      string
	Method        string
	ReservationID string
	// Source identifies the witness, so an audit can tell whose evidence this was.
	Source string
	// WindowStartUnixNano / WindowEndUnixNano bound the observed interval.
	WindowStartUnixNano int64
	WindowEndUnixNano   int64
	ObservedAtUnixNano  int64
	// EvidenceDigest is a bounded, non-reversible reference to the underlying record.
	EvidenceDigest string
}

WitnessObservation is what an independently controlled witness REPORTS. It is facts only: how many matching invocations it saw, what it can prove about the completeness of its own view, and what binding metadata it recorded. It carries no verdict, because a verdict from the witness would be an assertion this engine must not simply trust.

Jump to

Keyboard shortcuts

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