Struct SopEngine
pub struct SopEngine { /* private fields */ }Expand description
Central SOP orchestrator: loads SOPs, matches triggers, manages run lifecycle.
Implementations§
Source§impl SopEngine
impl SopEngine
Sourcepub fn new(config: SopConfig) -> Self
pub fn new(config: SopConfig) -> Self
Create a new engine with the given config. Call reload() to load SOPs.
Sourcepub fn with_store(self, store: Arc<dyn SopRunStore>) -> Self
pub fn with_store(self, store: Arc<dyn SopRunStore>) -> Self
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.
Sourcepub fn with_metrics(self, metrics: Arc<SopMetricsCollector>) -> Self
pub fn with_metrics(self, metrics: Arc<SopMetricsCollector>) -> Self
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.
Sourcepub fn with_run_notifier(self, tx: Sender<SopRunSummary>) -> Self
pub fn with_run_notifier(self, tx: Sender<SopRunSummary>) -> Self
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).
Sourcepub fn subscribe_run_changes(&self) -> Option<Receiver<SopRunSummary>>
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.
Sourcepub fn with_capabilities(self, capabilities: Arc<SopCapabilityRegistry>) -> Self
pub fn with_capabilities(self, capabilities: Arc<SopCapabilityRegistry>) -> Self
Inject a deterministic capability registry. Tests and future daemon wiring can replace the built-ins without adding another execution path.
Sourcepub fn with_approval_broker(self, broker: Arc<ApprovalBroker>) -> Self
pub fn with_approval_broker(self, broker: Arc<ApprovalBroker>) -> Self
Inject the approval broker (built from [sop.approval] config). Defaults to
a pass-through; build_sop_engine replaces it with the configured broker.
Sourcepub fn approval_broker(&self) -> Arc<ApprovalBroker> ⓘ
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.
Sourcepub fn resolve_via_broker(
&mut self,
run_id: &str,
decision: ApprovalDecision,
principal: ApprovalPrincipal,
) -> Result<BrokerOutcome>
pub fn resolve_via_broker( &mut self, run_id: &str, decision: ApprovalDecision, principal: ApprovalPrincipal, ) -> Result<BrokerOutcome>
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.
Sourcepub fn resolve_via_broker_inline(
&mut self,
run_id: &str,
decision: ApprovalDecision,
principal: ApprovalPrincipal,
) -> Result<BrokerOutcome>
pub fn resolve_via_broker_inline( &mut self, run_id: &str, decision: ApprovalDecision, principal: ApprovalPrincipal, ) -> Result<BrokerOutcome>
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.
Sourcepub fn resolve_via_broker_deferred(
&mut self,
run_id: &str,
decision: ApprovalDecision,
principal: ApprovalPrincipal,
) -> Result<BrokerOutcome>
pub fn resolve_via_broker_deferred( &mut self, run_id: &str, decision: ApprovalDecision, principal: ApprovalPrincipal, ) -> Result<BrokerOutcome>
Explicit alias for Self::resolve_via_broker, kept for call sites that
want the deferral to be obvious at the point of use.
Sourcepub fn restore_runs(&mut self)
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.
Sourcepub fn is_gate_reference_superseded(
&self,
run_id: &str,
reference_revision: u32,
) -> bool
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.
Sourcepub fn reload(&mut self, install_root: &Path)
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).
Sourcepub fn active_runs(&self) -> &HashMap<String, SopRun>
pub fn active_runs(&self) -> &HashMap<String, SopRun>
Return all active (in-flight) runs.
Sourcepub fn get_run(&self, run_id: &str) -> Option<&SopRun>
pub fn get_run(&self, run_id: &str) -> Option<&SopRun>
Look up a run by ID (active or finished).
Sourcepub fn match_trigger(&self, event: &SopEvent) -> Vec<&Sop>
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.
Sourcepub fn wants_source(&self, source: SopTriggerSource) -> bool
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.
Sourcepub fn can_start(&self, sop_name: &str) -> bool
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).
Sourcepub fn evaluate_admission(&self, sop_name: &str) -> SopAdmission
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.
pub fn start_run( &mut self, sop_name: &str, event: SopEvent, ) -> Result<SopRunAction>
pub fn advance_step( &mut self, run_id: &str, result: SopStepResult, ) -> Result<SopRunAction>
Sourcepub fn cancel_run(&mut self, run_id: &str) -> Result<()>
pub fn cancel_run(&mut self, run_id: &str) -> Result<()>
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.
Sourcepub fn finish_requested_cancellation(
&mut self,
run_id: &str,
) -> Result<Option<SopRunAction>>
pub fn finish_requested_cancellation( &mut self, run_id: &str, ) -> Result<Option<SopRunAction>>
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.
Sourcepub fn cancel_run_idempotent(
&mut self,
run_id: &str,
reason: Option<String>,
actor: Option<String>,
) -> Result<Option<CancelOutcome>>
pub fn cancel_run_idempotent( &mut self, run_id: &str, reason: Option<String>, actor: Option<String>, ) -> Result<Option<CancelOutcome>>
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 nowCancelled.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; seeerr_is_terminal_persistence_retainedto tell a retryable store fault (the run stays active and claimed) from any other error.
pub fn approve_step(&mut self, run_id: &str) -> Result<SopRunAction>
Sourcepub fn decide_checkpoint(
&mut self,
run_id: &str,
decision: ApprovalDecision,
) -> Result<SopRunAction>
pub fn decide_checkpoint( &mut self, run_id: &str, decision: ApprovalDecision, ) -> Result<SopRunAction>
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.
Sourcepub fn finished_runs(&self, sop_name: Option<&str>) -> Vec<&SopRun>
pub fn finished_runs(&self, sop_name: Option<&str>) -> Vec<&SopRun>
List finished runs, optionally filtered by SOP name.
Sourcepub fn run_summaries(&self, sop_name: Option<&str>) -> Vec<SopRunSummary>
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).
Sourcepub fn deterministic_savings(&self) -> &DeterministicSavings
pub fn deterministic_savings(&self) -> &DeterministicSavings
Return cumulative deterministic execution savings.
Sourcepub fn save_proposal(&self, proposal: &ProposalRecord) -> Result<(), StoreError>
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.
Sourcepub fn load_proposal(
&self,
id: &str,
) -> Result<Option<ProposalRecord>, StoreError>
pub fn load_proposal( &self, id: &str, ) -> Result<Option<ProposalRecord>, StoreError>
Load a procedural-memory proposal by id from the shared SOP store.
Sourcepub fn list_proposals(
&self,
status: Option<ProposalStatus>,
) -> Result<Vec<ProposalRecord>, StoreError>
pub fn list_proposals( &self, status: Option<ProposalStatus>, ) -> Result<Vec<ProposalRecord>, StoreError>
List procedural-memory proposals, optionally filtered by lifecycle status.
Sourcepub fn start_deterministic_run(
&mut self,
sop_name: &str,
event: SopEvent,
) -> Result<SopRunAction>
pub fn start_deterministic_run( &mut self, sop_name: &str, event: SopEvent, ) -> Result<SopRunAction>
Start a deterministic SOP run. Steps execute sequentially without LLM round-trips. Returns the first action (DeterministicStep or CheckpointWait).
pub fn drive_headless_deterministic( &mut self, run_id: &str, first_action: SopRunAction, ) -> Result<SopRunAction>
Sourcepub fn advance_headless_deterministic_step(
&mut self,
run_id: &str,
action: SopRunAction,
) -> Result<SopRunAction>
pub fn advance_headless_deterministic_step( &mut self, run_id: &str, action: SopRunAction, ) -> Result<SopRunAction>
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.
Sourcepub fn advance_deterministic_step(
&mut self,
run_id: &str,
step_output: Value,
step_timestamps: Option<(String, Option<String>)>,
) -> Result<SopRunAction>
pub fn advance_deterministic_step( &mut self, run_id: &str, step_output: Value, step_timestamps: Option<(String, Option<String>)>, ) -> Result<SopRunAction>
Advance a deterministic run with the output of the current step. The output is piped as input to the next step.
Sourcepub fn resume_deterministic_run(
&mut self,
state: DeterministicRunState,
) -> Result<SopRunAction>
pub fn resume_deterministic_run( &mut self, state: DeterministicRunState, ) -> Result<SopRunAction>
Resume a deterministic run from persisted state.
Sourcepub fn load_deterministic_state(path: &Path) -> Result<DeterministicRunState>
pub fn load_deterministic_state(path: &Path) -> Result<DeterministicRunState>
Load a persisted deterministic run state from a JSON file.
pub fn check_approval_timeouts(&mut self) -> Vec<SopRunAction>
pub fn run_maintenance_tick(&mut self) -> MaintenanceSummary
Sourcepub fn set_sops_for_test(&mut self, sops: Vec<Sop>)
pub fn set_sops_for_test(&mut self, sops: Vec<Sop>)
Replace loaded SOPs (for testing from other modules).
pub fn last_finished_run(&self, sop_name: &str) -> Option<&SopRun>
pub fn finish_run( &mut self, run_id: &str, status: SopRunStatus, reason: Option<String>, ) -> Result<SopRunAction>
Sourcepub fn approval_config(&self) -> &SopApprovalConfig
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.
Sourcepub fn current_step_policy_name(&self, run_id: &str) -> Option<String>
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.
Sourcepub fn run_events(
&self,
run_id: &str,
) -> Result<Vec<SopEventRecord>, StoreError>
pub fn run_events( &self, run_id: &str, ) -> Result<Vec<SopEventRecord>, StoreError>
Ordered event/ledger history for a run (from the durable store).
pub fn resolve_gate( &mut self, run_id: &str, decision: ApprovalDecision, principal: ApprovalPrincipal, ) -> Result<ResolveOutcome>
Auto Trait Implementations§
impl !RefUnwindSafe for SopEngine
impl !UnwindSafe for SopEngine
impl Freeze for SopEngine
impl Send for SopEngine
impl Sync for SopEngine
impl Unpin for SopEngine
impl UnsafeUnpin for SopEngine
Blanket Implementations§
§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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