Skip to main content

SopEngine

Struct SopEngine 

pub struct SopEngine { /* private fields */ }
Expand description

Central SOP orchestrator: loads SOPs, matches triggers, manages run lifecycle.

Implementations§

Source§

impl SopEngine

Source

pub fn new(config: SopConfig) -> SopEngine

Create a new engine with the given config. Call reload() to load SOPs.

Source

pub fn with_store(self, store: Arc<dyn SopRunStore>) -> SopEngine

Inject a durable run-state store (used by build_sop_engine). Default is an ephemeral in-memory store, so callers that don’t set one keep today’s behavior exactly.

Source

pub fn with_metrics(self, metrics: Arc<SopMetricsCollector>) -> SopEngine

Inject the metrics collector. build_sop_engine passes the process-shared collector so the engine’s completion metrics and the SOP tools’ reports observe one set; the default per-engine collector keeps tests isolated.

Source

pub fn with_run_notifier(self, tx: Sender<SopRunSummary>) -> SopEngine

Attach a live run-change notifier. build_sop_engine wires the gateway’s sender here so run transitions push to the Runs WebSocket. Returns the engine unchanged when never called (tests, headless embedders).

Source

pub fn subscribe_run_changes(&self) -> Option<Receiver<SopRunSummary>>

Subscribe to the live run-change feed if a notifier is attached. Each item is a fresh SopRunSummary for the run that just transitioned.

Source

pub fn with_capabilities( self, capabilities: Arc<SopCapabilityRegistry>, ) -> SopEngine

Inject a deterministic capability registry. Tests and future daemon wiring can replace the built-ins without adding another execution path.

Source

pub fn with_approval_broker(self, broker: Arc<ApprovalBroker>) -> SopEngine

Inject the approval broker (built from [sop.approval] config). Defaults to a pass-through; build_sop_engine replaces it with the configured broker.

Source

pub fn approval_broker(&self) -> Arc<ApprovalBroker>

The approval broker (membership + quorum authorization). Callers that must deliver an escalation to a policy’s second route read it here.

Source

pub fn resolve_via_broker( &mut self, run_id: &str, decision: ApprovalDecision, principal: ApprovalPrincipal, ) -> Result<BrokerOutcome, Error>

Resolve a gate or deterministic checkpoint THROUGH the broker (membership + quorum), then its single transition owner. This is the entry point out-of-band surfaces (gateway / CLI / tools) should call so a [sop.approval] policy is enforced; with no policy it is exactly resolve_gate for a WaitingApproval run or the historical checkpoint resolver for a PausedCheckpoint run. The broker is cloned out first so it does not borrow self while self is mutated by the chokepoint.

This shared entry point does not consume a resumed deterministic capability tail inline: the caller receives the resumed action and hands it to the shared async executor, which reacquires the engine lock once per capability. That is what lets an operator cancellation persist CancelRequested between post-checkpoint capabilities instead of blocking on the engine mutex until the whole tail has already run.

A caller that genuinely owns an exclusive engine handle and must observe the fully-driven tail before returning wants Self::resolve_via_broker_inline instead — but note that no shared transport qualifies.

Source

pub fn resolve_via_broker_inline( &mut self, run_id: &str, decision: ApprovalDecision, principal: ApprovalPrincipal, ) -> Result<BrokerOutcome, Error>

Resolve through the broker and synchronously drive the resumed deterministic capability tail while the engine mutex is still held.

⚠️ Cancellation cannot interleave with a tail driven this way. Reserved for genuinely exclusive callers (single-owner engine handles and tests that assert on the fully-driven tail). Shared transports — admin HTTP, WebSocket, RPC, channels, and the sop_approve tool — must use Self::resolve_via_broker, which defers the tail.

Source

pub fn resolve_via_broker_deferred( &mut self, run_id: &str, decision: ApprovalDecision, principal: ApprovalPrincipal, ) -> Result<BrokerOutcome, Error>

Explicit alias for Self::resolve_via_broker, kept for call sites that want the deferral to be obvious at the point of use.

Source

pub fn restore_runs(&mut self)

Reconstruct in-flight runs from the store at startup (durable backends). No-op for the in-memory default. Does not overwrite already-present runs.

Source

pub fn is_gate_reference_superseded( &self, run_id: &str, reference_revision: u32, ) -> bool

A prompt becomes stale only after a replacement presentation is durable. A gate can update its in-memory revision before its parked snapshot saves; finalizing the old prompt in that window would leave operators without a recoverable replacement after a crash.

Source

pub fn reload(&mut self, install_root: &Path)

Load/reload SOPs from the configured directory, resolved against install_root (the install root, config_path’s parent).

Source

pub fn sops(&self) -> &[Sop]

Return all loaded SOP definitions.

Source

pub fn active_runs(&self) -> &HashMap<String, SopRun>

Return all active (in-flight) runs.

Source

pub fn get_run(&self, run_id: &str) -> Option<&SopRun>

Look up a run by ID (active or finished).

Source

pub fn get_sop(&self, name: &str) -> Option<&Sop>

Look up an SOP by name.

Source

pub fn match_trigger(&self, event: &SopEvent) -> Vec<&Sop>

Match an incoming event against all loaded SOPs and return the names of SOPs whose triggers match.

Source

pub fn wants_source(&self, source: SopTriggerSource) -> bool

True when any loaded SOP has a trigger of this source. Fan-in callers use this as a cheap pre-filter before building and dispatching an event.

Source

pub fn can_start(&self, sop_name: &str) -> bool

Check whether a new run can be started for the given SOP (respects cooldown and concurrency limits).

Source

pub fn evaluate_admission(&self, sop_name: &str) -> SopAdmission

A2: decide how to admit a matched trigger for sop_name under its SopAdmissionPolicy. Admit still passes through the authoritative CAS in start_run; the other outcomes are surfaced by the dispatch layer so a non-admitted trigger is never silently lost. A cooldown or unknown SOP drops regardless of policy (a cooldown is a deliberate rate limit, not backpressure).

AUTHORITY: within a SINGLE daemon this decision is authoritative - the engine Mutex serializes evaluate_admission + the CAS claim, so two triggers cannot both admit past the policy. The exec-slot cap is additionally CAS-authoritative via the shared store even ACROSS engines. The pending-approval pool (max_pending_approvals), however, is only ADVISORY across engines: a run parks at approval only AFTER it has executed, so its pending slot cannot be atomically pre-reserved at admission time, and two engines sharing a store can each admit a run that later parks. Making the pending cap cross-engine- authoritative requires a store-level two-phase reservation (a follow-up); the single-daemon deployment - the common case - is fully authoritative today.

Source

pub fn start_run( &mut self, sop_name: &str, event: SopEvent, ) -> Result<SopRunAction, Error>

Source

pub fn advance_step( &mut self, run_id: &str, result: SopStepResult, ) -> Result<SopRunAction, Error>

Source

pub fn cancel_run(&mut self, run_id: &str) -> Result<(), Error>

Cancel an active run immediately without an operator-supplied reason.

Internal callers use this only when no step is executing concurrently. Remotely initiated cancellation must use cancel_run_idempotent, which requests cancellation for a running step and preserves its claim until the driver reaches a boundary.

Source

pub fn finish_requested_cancellation( &mut self, run_id: &str, ) -> Result<Option<SopRunAction>, Error>

Finish a previously requested cancellation at a driver-owned execution boundary. Actor and reason are resolved from the durable request event, avoiding a second in-memory source of truth.

Source

pub fn cancel_run_idempotent( &mut self, run_id: &str, reason: Option<String>, actor: Option<String>, ) -> Result<Option<CancelOutcome>, Error>

Cancel a run by id, idempotently. Classifies the run under a single borrow of engine state (no intervening lookup that a concurrent completion could race) and reports:

  • Ok(Some(Cancelled)) - the run was active and is now Cancelled.
  • Ok(Some(AlreadyTerminal(status))) - the run had already reached a terminal status (a repeat cancel, or it finished normally first); no second cancellation or audit event is recorded.
  • Ok(None) - no run with this id is known to the engine.
  • Err(_) - the terminal persist failed; see err_is_terminal_persistence_retained to tell a retryable store fault (the run stays active and claimed) from any other error.
Source

pub fn approve_step(&mut self, run_id: &str) -> Result<SopRunAction, Error>

Source

pub fn decide_checkpoint( &mut self, run_id: &str, decision: ApprovalDecision, ) -> Result<SopRunAction, Error>

Resolve a checkpoint decision (PausedCheckpoint). Approve resumes the success path (records the checkpoint Completed, pipes forward down routing.next); Deny takes the failure path (records the checkpoint Failed and routes through the step’s on_failure, exactly like a step that failed execution). This is the single entry point for both outcomes; callers never branch on status. approve_step is the Approve-only alias.

Source

pub fn finished_runs(&self, sop_name: Option<&str>) -> Vec<&SopRun>

List finished runs, optionally filtered by SOP name.

Source

pub fn run_summaries(&self, sop_name: Option<&str>) -> Vec<SopRunSummary>

Summaries of every run the engine currently holds: live runs from the active set plus retained terminal runs, newest first by start time. This is the enumeration the Runs surface polls; it never touches the durable store directly, so it reflects exactly what the running engine knows (active set + max_finished_runs retention window).

Source

pub fn deterministic_savings(&self) -> &DeterministicSavings

Return cumulative deterministic execution savings.

Source

pub fn save_proposal(&self, proposal: &ProposalRecord) -> Result<(), StoreError>

Save a procedural-memory proposal into the shared SOP store. This is the production-facing engine surface EPIC F consumes for approval/write-back.

Source

pub fn load_proposal( &self, id: &str, ) -> Result<Option<ProposalRecord>, StoreError>

Load a procedural-memory proposal by id from the shared SOP store.

Source

pub fn list_proposals( &self, status: Option<ProposalStatus>, ) -> Result<Vec<ProposalRecord>, StoreError>

List procedural-memory proposals, optionally filtered by lifecycle status.

Source

pub fn start_deterministic_run( &mut self, sop_name: &str, event: SopEvent, ) -> Result<SopRunAction, Error>

Start a deterministic SOP run. Steps execute sequentially without LLM round-trips. Returns the first action (DeterministicStep or CheckpointWait).

Source

pub fn drive_headless_deterministic( &mut self, run_id: &str, first_action: SopRunAction, ) -> Result<SopRunAction, Error>

Source

pub fn advance_headless_deterministic_step( &mut self, run_id: &str, action: SopRunAction, ) -> Result<SopRunAction, Error>

Execute at most one deterministic capability step. Async drivers call this once per engine-lock acquisition so an operator cancellation can acquire the lock and become visible before the next step dispatches.

Source

pub fn advance_deterministic_step( &mut self, run_id: &str, step_output: Value, step_timestamps: Option<(String, Option<String>)>, ) -> Result<SopRunAction, Error>

Advance a deterministic run with the output of the current step. The output is piped as input to the next step.

Source

pub fn resume_deterministic_run( &mut self, state: DeterministicRunState, ) -> Result<SopRunAction, Error>

Resume a deterministic run from persisted state.

Source

pub fn load_deterministic_state( path: &Path, ) -> Result<DeterministicRunState, Error>

Load a persisted deterministic run state from a JSON file.

Source

pub fn check_approval_timeouts(&mut self) -> Vec<SopRunAction>

Source

pub fn run_maintenance_tick(&mut self) -> MaintenanceSummary

Source

pub fn set_sops_for_test(&mut self, sops: Vec<Sop>)

Replace loaded SOPs (for testing from other modules).

Source

pub fn last_finished_run(&self, sop_name: &str) -> Option<&SopRun>

Source

pub fn finish_run( &mut self, run_id: &str, status: SopRunStatus, reason: Option<String>, ) -> Result<SopRunAction, Error>

Source

pub fn config(&self) -> &SopConfig

Read-only config access for the approval resolver.

Source

pub fn approval_config(&self) -> &SopApprovalConfig

The live [sop.approval] config - the single source of truth for approval groups and policies. The broker resolves membership/policy from this at use-time rather than holding a cloned copy that could drift on reload.

Source

pub fn current_step_policy_name(&self, run_id: &str) -> Option<String>

The name of the approval policy that applies to the run’s current step, if that step names one. Read surfaces collapse unavailable live state to None; the broker uses the fallible lookup above to fail closed.

Source

pub fn run_events( &self, run_id: &str, ) -> Result<Vec<SopEventRecord>, StoreError>

Ordered event/ledger history for a run (from the durable store).

Source

pub fn resolve_gate( &mut self, run_id: &str, decision: ApprovalDecision, principal: ApprovalPrincipal, ) -> Result<ResolveOutcome, Error>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more