Skip to main content

App

Struct App 

Source
pub struct App<CS: StateStoreBackend, A: AgentBackend, X: ActionBackend = NoActions, C: ConditionBackend = NoConditions, T: StateTyping = JsonState, H: LifecycleHooks = NoHooks, E: ChildExecutor = InlineExecutor> { /* private fields */ }
Expand description

An open application generation.

Owns one durable application generation, its transferred agents and accepted work.

Supply a concrete storage_kit::StateStoreBackend through AppConfig. Each AgentSpec preserves its concrete agents_kit::AgentBackend and its associated Workspace/Conversation types. An empty agent inventory can execute state-only workflows; the explicit Agent type below constructs no Agent value.

Keep the Tokio runtime and any caller-owned lower runtimes alive and driven through Self::shutdown. Root drop revokes admission and requests supervised best-effort cleanup; it cannot substitute for awaited shutdown. Workspace and run observers do not keep admission alive after the App is revoked.

§Example

use agents_kit::Agent;
use app_kit::{App, AppConfig, Shutdown, WorkspaceConfig};
use app_kit::workflow::{CheckpointSelector, ExecutionEvent, ExecutionOptions, RunSelector,
    RunStatus, WorkflowDefinition, WorkflowState};
use storage_kit::StateStore;
use storage_kit::state::config::{LanceStateStoreConfig, StateStoreConfig};

let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build()?;
runtime.block_on(async {
let store = StateStore::connect(StateStoreConfig::Lance(LanceStateStoreConfig::memory())).await?;
let definition = WorkflowDefinition::parse(r#"{
    "schema":"app-kit.workflow/v2", "id":"counter", "revision":"r1",
    "state":{"count":0}, "required":["count"],
    "reducers":{"count":"reject_concurrent"}, "entry":"increment",
    "nodes":[{"id":"increment","step":{"kind":"state","mutations":[
        {"kind":"increment","target":"count","value":{"kind":"literal","value":1}}
    ]}}], "routes":{}
}"#)?;
let config = AppConfig::<_, Agent>::new(store, Vec::new(), vec![definition]);
let mut app = App::open(config).await?;
let mut workspace = app.create_workspace(WorkspaceConfig {
    name: Some("local counter".into()), root: std::env::current_dir()?,
}).await?;
let workflow = workspace.workflow("counter".parse()?).await?;
let run = workflow.start(ExecutionOptions::default()).await?;
let snapshot = run.wait().await?;
assert!(matches!(snapshot.status, RunStatus::Succeeded));
assert_eq!(snapshot.state.values()["count"], 1);
app.shutdown(Shutdown::Drain { timeout: std::time::Duration::from_secs(5) }).await?;
})?;

Implementations§

Source§

impl<CS: StateStoreBackend, A: AgentBackend, X: ActionBackend, C: ConditionBackend, T: StateTyping, H: LifecycleHooks, E: ChildExecutor> App<CS, A, X, C, T, H, E>

Source

pub async fn open( config: AppConfig<CS, A, X, C, T, H, E>, ) -> Result<Self, AppError>

Opens or reconciles one application generation.

Validates bindings and retained records, claims one durable generation, and reconstructs recovery ownership before returning. A successful newer opener fences handles from the previous generation. Configuration is consumed.

§Durable reopen and deployment

Reopen with a fresh handle to the same caller-owned StateStore, the same compatible workflow definitions, and freshly constructed or reopened lower integrations. Never reuse a consumed Agent or infer a new logical identity from its display representation. The state-only example below needs no Agent value and demonstrates that shutdown retains history until explicit deletion.

Existing App v6 records require a cold, one-way v6-to-v7 migration. Stop older clients, settle all their owners and take a whole-store physical backup before opening with this version. Opening performs the supported migration before claiming the new generation. Mixed-version operation, in-place downgrade, reset and corruption repair are not supported. Restore the physical backup for a rollback deployment; do not open migrated data with an older client. Unsupported epochs and corrupt current records fail rather than falling back to another decoder.

After Agent ownership transfer, ordinary failure awaits consuming Agent cleanup and preserves the primary failure. Cancelling this open future during transfer does not promise graceful cleanup: reopen the durable lower products and App to reconcile their retained facts.

§Errors

Returns AppError::InvalidInput for invalid or conflicting construction metadata, AppError::Conflict for incompatible retained revisions or lost ownership, and AppError::Integration for unsupported durable versions, invalid retained records, or lower failures. Recovery can also report capacity, closure, or deadline errors; no live App is returned on failure.

§Example
use agents_kit::Agent;
use app_kit::{App, AppConfig, Shutdown, WorkspaceConfig};
use app_kit::workflow::{ExecutionOptions, RunSelector, RunStatus, WorkflowDefinition};
use storage_kit::StateStore;
use storage_kit::state::config::{LanceStateStoreConfig, StateStoreConfig};
use std::time::Duration;
let directory = std::env::temp_dir().join(format!("ai-kit-app-doc-{}", uuid::Uuid::new_v4()));
std::fs::create_dir(&directory)?;
let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build()?;
let result = runtime.block_on(async {
    let definition_source = r#"{
        "schema":"app-kit.workflow/v2","id":"counter","revision":"r1",
        "state":{"count":0},"required":["count"],"reducers":{"count":"reject_concurrent"},
        "entry":"increment","nodes":[{"id":"increment","step":{"kind":"state",
        "mutations":[{"kind":"increment","target":"count","value":{"kind":"literal","value":1}}]}}],
        "routes":{}
    }"#;
    let config = StateStoreConfig::lance(LanceStateStoreConfig::directory(&directory));
    let store = StateStore::connect(config.clone()).await?;
    let mut app = App::open(AppConfig::<_, Agent>::new(store, Vec::new(), vec![WorkflowDefinition::parse(definition_source)?])).await?;
    let first = async {
        let workspace = app.create_workspace(WorkspaceConfig { name: None, root: directory.clone() }).await?;
        let workflow = workspace.workflow("counter".parse()?).await?;
        let run = workflow.start(ExecutionOptions::default()).await?;
        let terminal = run.wait().await?;
        assert!(matches!(terminal.status, RunStatus::Succeeded));
        Ok::<_, app_kit::AppError>((workspace.id().clone(), run.id().clone()))
    }.await;
    let shutdown = app.shutdown(Shutdown::CancelAndDrain { timeout: Duration::from_secs(5) }).await;
    drop(app);
    let (workspace_id, run_id) = first?;
    shutdown?;

    let fresh_store = StateStore::connect(config).await?;
    let mut reopened = App::open(AppConfig::<_, Agent>::new(fresh_store, Vec::new(), vec![WorkflowDefinition::parse(definition_source)?])).await?;
    let readback = async {
        let workspace = reopened.workspace(workspace_id).await?;
        let workflow = workspace.workflow("counter".parse()?).await?;
        let mut run = workflow.open_run(RunSelector::Latest(run_id.clone())).await?;
        let terminal = run.snapshot().await?;
        assert!(matches!(terminal.status, RunStatus::Succeeded));
        assert_eq!(terminal.state.values()["count"], 1);
        run.delete(terminal.checkpoint).await?;
        assert!(matches!(workflow.open_run(RunSelector::Latest(run_id)).await,
            Err(app_kit::AppError::NotFound(_))));
        Ok::<(), app_kit::AppError>(())
    }.await;
    let shutdown = reopened.shutdown(Shutdown::CancelAndDrain { timeout: Duration::from_secs(5) }).await;
    drop(reopened);
    readback?;
    shutdown?;
    Ok::<(), Box<dyn std::error::Error>>(())
});
// A shutdown timeout bounds the waiter, not necessarily the owned finalizer.
// Remove the directory only after both generations demonstrably settled.
if result.is_ok() {
    std::fs::remove_dir_all(&directory)?;
} else {
    eprintln!("Retain {} for cleanup reconciliation", directory.display());
}
result?;
Examples found in repository?
examples/history_and_recovery.rs (lines 55-59)
31    async fn open_app(&self) -> Result<App<StateStore, Agent>, AppError> {
32        let definition = WorkflowDefinition::parse(
33            &json!({
34                "schema":"app-kit.workflow/v2", "id":"history-counter", "revision":"r1",
35                "state":{"count":0}, "required":["count"],
36                "reducers":{"count":"reject_concurrent"}, "entry":"first",
37                "nodes":[
38                    {"id":"first", "step":{"kind":"state", "mutations":[{
39                        "kind":"increment", "target":"count",
40                        "value":{"kind":"literal", "value":1}
41                    }]}},
42                    {"id":"second", "step":{"kind":"state", "mutations":[{
43                        "kind":"increment", "target":"count",
44                        "value":{"kind":"literal", "value":1}
45                    }]}}
46                ],
47                "routes":{"first":{"mode":"first", "branches":[{
48                    "target":{"kind":"node", "id":"second"}
49                }]}}
50            })
51            .to_string(),
52        )?;
53        // Agent supplies the static type for an empty inventory. No Agent value
54        // is constructed, and these native State nodes need no lower service.
55        App::open(AppConfig::<_, Agent>::new(
56            self.store.clone(),
57            Vec::new(),
58            vec![definition],
59        ))
60        .await
61    }
More examples
Hide additional examples
examples/actions_and_inputs.rs (lines 39-42)
23    async fn run() -> Result<(), Box<dyn Error + Send + Sync>> {
24        let definition = WorkflowDefinition::parse(
25            &json!({
26                "schema":"app-kit.workflow/v2", "id":"invoice", "revision":"r1",
27                "state":{"unit_price":7,"total":0}, "required":["unit_price","total"],
28                "reducers":{"unit_price":"reject_concurrent","total":"reject_concurrent"},
29                "entry":"calculate",
30                "nodes":[{"id":"calculate","step":{
31                    "kind":"action","action":"invoice","revision":"r1","project_result":false
32                }}], "routes":{}
33            })
34            .to_string(),
35        )?;
36        let store =
37            StateStore::connect(StateStoreConfig::Lance(LanceStateStoreConfig::memory())).await?;
38        // Native caller actions need no Agent value or provider service.
39        let mut app = App::open(
40            AppConfig::<_, Agent>::new(store, Vec::new(), vec![definition])
41                .with_actions(vec![InvoiceAction::new()]),
42        )
43        .await?;
44        let observed = tokio::time::timeout(Duration::from_secs(15), async {
45            let workspace = app
46                .create_workspace(WorkspaceConfig {
47                    name: Some("invoice example".into()),
48                    root: PathBuf::from(env!("CARGO_MANIFEST_DIR")),
49                })
50                .await?;
51            let workflow = workspace.workflow("invoice".parse()?).await?;
52            let mut run = workflow.prepare(WorkflowState::default()).await?;
53            let mut execution = run
54                .execution(ExecutionOptions {
55                    inputs: Map::from_iter([
56                        ("quantity".into(), json!(6)),
57                        ("trace".into(), json!("sample")),
58                    ]),
59                    ..ExecutionOptions::default()
60                })
61                .await?;
62            let Some(ExecutionEvent::Partial { result, .. }) = execution.next().await? else {
63                return Err(AppError::Integration(
64                    "expected the real action partial".into(),
65                ));
66            };
67            if result.get("progress") != Some(&json!("calculated")) {
68                return Err(AppError::Integration("partial changed".into()));
69            }
70            let Some(ExecutionEvent::Committed { outcomes, .. }) = execution.step().await? else {
71                return Err(AppError::Integration(
72                    "expected one committed boundary".into(),
73                ));
74            };
75            let expected =
76                json!({"quantity":6,"label":"order","currency":"USD","trace":"sample","total":42});
77            if outcomes.len() != 1 || Value::Object(outcomes[0].result.clone()) != expected {
78                return Err(AppError::Integration(
79                    "resolved inputs or action result changed".into(),
80                ));
81            }
82            let terminal = execution.finish().await?;
83            if !matches!(terminal.status, RunStatus::Succeeded)
84                || terminal.state.values()
85                    != json!({"unit_price":7,"total":42}).as_object().unwrap()
86            {
87                return Err(AppError::Integration(
88                    "only the explicit total write should persist".into(),
89                ));
90            }
91            println!(
92                "Partial: calculated; committed total: 42; transient inputs stayed out of state"
93            );
94            Ok::<(), AppError>(())
95        })
96        .await;
97        let closed = tokio::time::timeout(
98            Duration::from_secs(15),
99            app.shutdown(Shutdown::CancelAndDrain {
100                timeout: Duration::from_secs(10),
101            }),
102        )
103        .await;
104        drop(app);
105        observed.map_err(|_| AppError::Timeout("invoice execution timed out".into()))??;
106        closed.map_err(|_| AppError::Timeout("invoice cleanup timed out".into()))??;
107        Ok(())
108    }
examples/performance.rs (lines 1106-1114)
1054    async fn run(mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
1055        let manifest = if matches!(self.command, Command::ReopenHistory { .. }) {
1056            self.root = self.root.canonicalize()?;
1057            Some(self.read_manifest()?)
1058        } else {
1059            fs::create_dir(&self.root)?;
1060            self.root = self.root.canonicalize()?;
1061            fs::create_dir(self.root.join("workspace"))?;
1062            let mut marker = OpenOptions::new()
1063                .write(true)
1064                .create_new(true)
1065                .open(self.root.join(".performance-root"))?;
1066            marker.write_all(MANIFEST_SCHEMA.as_bytes())?;
1067            marker.sync_all()?;
1068            None
1069        };
1070        let actions = match self.command {
1071            Command::Linear => 1_000,
1072            Command::SeedHistory { rows } => (rows - 4) / 3,
1073            Command::Ready => 32 * 32,
1074            Command::Map => 4_096,
1075            Command::Repeated => 100,
1076            Command::NestedCancel => 16,
1077            Command::DelayedObserver => 1,
1078            Command::ReopenHistory { .. } => {
1079                manifest.as_ref().unwrap()["actions"].as_u64().unwrap()
1080            }
1081        };
1082        let linear = matches!(self.command, Command::Linear);
1083        let (definitions, definition_bytes) = self.definitions(actions)?;
1084        let probe = match self.command {
1085            Command::Ready => Some(WorkloadProbe::new(SourceMode::Permuted, 1_024)),
1086            Command::Map | Command::Repeated => {
1087                Some(WorkloadProbe::new(SourceMode::Parallel, actions as usize))
1088            }
1089            Command::NestedCancel => Some(WorkloadProbe::new(SourceMode::Held, 16)),
1090            Command::DelayedObserver => Some(WorkloadProbe::new(SourceMode::Partials, 1)),
1091            Command::Linear | Command::SeedHistory { .. } | Command::ReopenHistory { .. } => None,
1092        };
1093        if let Some(manifest) = &manifest {
1094            assert_eq!(manifest["definition_bytes"], json!(definition_bytes));
1095        }
1096        let mut app = None;
1097        let started = Instant::now();
1098        let observed = AssertUnwindSafe(tokio::time::timeout(WORKLOAD_BUDGET, async {
1099            let connecting = Instant::now();
1100            let config = LanceStateStoreConfig::directory(self.root.join("state"))
1101                .with_operation_timeout(Duration::from_secs(30))?;
1102            let store = StateStore::connect(StateStoreConfig::Lance(config)).await?;
1103            let connect = connecting.elapsed();
1104            let opening = Instant::now();
1105            app = Some(
1106                App::open(
1107                    AppConfig::<_, Agent>::new(store, Vec::new(), definitions).with_actions(
1108                        probe
1109                            .as_ref()
1110                            .map(WorkloadAction::new)
1111                            .into_iter()
1112                            .collect(),
1113                    ),
1114                )
1115                .await?,
1116            );
1117            let open = opening.elapsed();
1118            let mut result = match self.command {
1119                Command::Linear | Command::SeedHistory { .. } => {
1120                    self.seed(app.as_ref().unwrap(), actions, linear).await?
1121                }
1122                Command::Ready => {
1123                    self.ready(app.as_ref().unwrap(), probe.as_ref().unwrap())
1124                        .await?
1125                }
1126                Command::Map => {
1127                    self.mapped(app.as_ref().unwrap(), probe.as_ref().unwrap())
1128                        .await?
1129                }
1130                Command::Repeated => {
1131                    self.repeated(app.as_ref().unwrap(), probe.as_ref().unwrap())
1132                        .await?
1133                }
1134                Command::NestedCancel => {
1135                    self.nested_cancel(app.as_ref().unwrap(), probe.as_ref().unwrap())
1136                        .await?
1137                }
1138                Command::DelayedObserver => {
1139                    self.delayed_observer(app.as_ref().unwrap(), probe.as_ref().unwrap())
1140                        .await?
1141                }
1142                Command::ReopenHistory { open_only } => {
1143                    self.reopen(
1144                        app.as_ref().unwrap(),
1145                        manifest.as_ref().unwrap(),
1146                        actions,
1147                        open_only,
1148                    )
1149                    .await?
1150                }
1151            };
1152            result["store_connect_ns"] = json!(connect.as_nanos());
1153            result["app_open_ns"] = json!(open.as_nanos());
1154            result["workload_elapsed_ns"] = json!(started.elapsed().as_nanos());
1155            Ok::<_, Box<dyn Error + Send + Sync>>(result)
1156        }))
1157        .catch_unwind()
1158        .await;
1159        let success = matches!(&observed, Ok(Ok(Ok(_))));
1160        let closing = Instant::now();
1161        let cleanup = if let Some(mut app) = app.take() {
1162            let closed = AssertUnwindSafe(Self::close(&mut app, success))
1163                .catch_unwind()
1164                .await;
1165            drop(app);
1166            Some(closed)
1167        } else {
1168            None
1169        };
1170        if !success {
1171            eprintln!(
1172                "performance workload failed; cleanup completed={}",
1173                matches!(&cleanup, Some(Ok(Ok(()))))
1174            );
1175        }
1176        let mut result = match observed {
1177            Ok(Ok(result)) => result?,
1178            Ok(Err(_)) => {
1179                return Err(
1180                    AppError::Timeout("performance workload budget exhausted".into()).into(),
1181                );
1182            }
1183            Err(panic) => resume_unwind(panic),
1184        };
1185        match cleanup {
1186            Some(Ok(result)) => result?,
1187            Some(Err(panic)) => resume_unwind(panic),
1188            None => return Err("successful workload did not retain its App cleanup owner".into()),
1189        }
1190        // All run/workflow/App owners and the caller-created store have dropped.
1191        // A cleanup timeout remains a failure even if reattachment later joined.
1192        result["schema"] = json!(MANIFEST_SCHEMA);
1193        result["settings"] = Self::settings(actions);
1194        if let Some(probe) = &probe {
1195            probe.assert_idle();
1196            result["caller_sources"] = probe.report();
1197            result["settings"]
1198                .as_object_mut()
1199                .unwrap()
1200                .remove("history_row_ceiling");
1201            result["settings"]["private_task_counts"] =
1202                json!("not exposed; App shutdown proves joined ownership");
1203            result["settings"]["lower_agents_and_tool_handlers"] = json!("none constructed");
1204        }
1205        result["definition_bytes"] = json!(definition_bytes);
1206        result["process_id"] = json!(std::process::id());
1207        result["os"] = json!(std::env::consts::OS);
1208        result["architecture"] = json!(std::env::consts::ARCH);
1209        result["package_version"] = json!(env!("CARGO_PKG_VERSION"));
1210        result["cleanup_joined"] = json!(true);
1211        result["cleanup_ns"] = json!(closing.elapsed().as_nanos());
1212        let encoded = serde_json::to_vec(&result)?;
1213        assert!(
1214            encoded.len() <= 16_384,
1215            "result stays within the manifest bound"
1216        );
1217        if matches!(self.command, Command::SeedHistory { .. }) {
1218            let mut file = OpenOptions::new()
1219                .write(true)
1220                .create_new(true)
1221                .open(self.root.join("manifest.json"))?;
1222            file.write_all(&encoded)?;
1223            file.sync_all()?;
1224        }
1225        println!("{}", std::str::from_utf8(&encoded)?);
1226        Ok(())
1227    }
Source

pub async fn create_workspace( &self, config: WorkspaceConfig, ) -> Result<Workspace<CS, A, X, C, T, H, E>, AppError>

Creates and materializes one workspace.

Materializes the complete configured Agent key/revision inventory before publishing the App workspace. Durable intent precedes each lower operation; unresolved partial progress remains recoverable rather than a visible success.

§Errors

Returns AppError::InvalidInput for invalid configuration, AppError::Busy when admission is full, or AppError::Closed after revocation. Conflicting materialization evidence, lower/storage failures, and bounded waits report Conflict, Integration, or Timeout respectively.

§Example
let workspace = app.create_workspace(WorkspaceConfig {
    name: Some("review".into()), root: std::env::current_dir()?,
}).await?;
assert_eq!(workspace.snapshot().await?.revision, 1);
Examples found in repository?
examples/performance.rs (lines 397-400)
390    async fn seed(
391        &self,
392        app: &App<StateStore, Agent, WorkloadAction>,
393        actions: u64,
394        linear: bool,
395    ) -> Result<Value, Box<dyn Error + Send + Sync>> {
396        let workspace = app
397            .create_workspace(WorkspaceConfig {
398                name: Some("performance consumer".into()),
399                root: self.root.join("workspace"),
400            })
401            .await?;
402        let workflow = workspace.workflow("performance".parse()?).await?;
403        let mut run = workflow.prepare(WorkflowState::default()).await?;
404        let mut execution = run
405            .execution(ExecutionOptions {
406                halt_after: if linear {
407                    Vec::new()
408                } else {
409                    vec!["count".into()]
410                },
411                ..ExecutionOptions::default()
412            })
413            .await?;
414        let mut samples = Vec::with_capacity(actions as usize);
415        let started = Instant::now();
416        for ordinal in 0..actions {
417            let step_started = Instant::now();
418            let event = execution.step().await?;
419            samples.push(step_started.elapsed());
420            let Some(ExecutionEvent::Committed { outcomes, .. }) = event else {
421                panic!("each serial workload step must commit one action");
422            };
423            assert_eq!(outcomes.len(), 1);
424            assert_eq!(outcomes[0].ordinal, ordinal);
425            assert_eq!(
426                outcomes[0].node,
427                if linear {
428                    format!("n{ordinal:04}")
429                } else {
430                    "count".into()
431                }
432            );
433            assert!(outcomes[0].result.is_empty());
434            if !linear && ordinal == 0 {
435                // One public halt and explicit resume add two control rows to
436                // Initial + three checkpoints per action + Terminal.
437                let halted = execution.finish().await?;
438                assert!(matches!(&halted.status, RunStatus::Halted { node, result }
439                    if node == "count" && result == &Some(Map::new())));
440                assert_eq!(halted.checkpoint, 4);
441                assert_eq!(
442                    halted.state.values(),
443                    &Map::from_iter([("count".into(), json!(1))])
444                );
445                assert_eq!(halted.last_action_ordinal, Some(0));
446                assert!(halted.outcomes == outcomes);
447                assert!(halted.interactions.is_empty());
448                execution = run.execution(ExecutionOptions::default()).await?;
449            }
450        }
451        let snapshot = execution.finish().await?;
452        Self::verify_snapshot(&snapshot, actions);
453        let execution_elapsed = started.elapsed();
454        let history = self
455            .history(&run, actions, linear, snapshot.checkpoint)
456            .await?;
457        if let Command::SeedHistory { rows } = self.command {
458            assert_eq!(history["rows"], rows);
459        }
460        Ok(
461            json!({"dataset":if linear {"linear"} else {"history"},"actions":actions,
462            "workspace":workspace.id().to_string(),"run":run.id().to_string(),
463            "occurrence":snapshot.occurrence,
464            "expected_state":snapshot.state.values(),"last_action_ordinal":snapshot.last_action_ordinal,
465            "terminal_checkpoint":snapshot.checkpoint,"retained_rows":history["rows"],
466            "execution_ns":execution_elapsed.as_nanos(),
467            "actions_per_second":actions as f64 / execution_elapsed.as_secs_f64(),
468            "step_latency":Self::samples(samples),"history":history}),
469        )
470    }
471
472    async fn reopen(
473        &self,
474        app: &App<StateStore, Agent, WorkloadAction>,
475        manifest: &Value,
476        actions: u64,
477        open_only: bool,
478    ) -> Result<Value, Box<dyn Error + Send + Sync>> {
479        let started = Instant::now();
480        let workspace: WorkspaceId = manifest["workspace"]
481            .as_str()
482            .ok_or("missing workspace ID")?
483            .parse()?;
484        let run_id: RunId = manifest["run"].as_str().ok_or("missing run ID")?.parse()?;
485        let occurrence = manifest["occurrence"]
486            .as_str()
487            .ok_or("missing run occurrence")?
488            .to_owned();
489        let workspace = app.workspace(workspace).await?;
490        let workflow = workspace.workflow("performance".parse()?).await?;
491        let run = workflow
492            .open_run(RunSelector::Exact {
493                id: run_id,
494                occurrence: occurrence.clone(),
495            })
496            .await?;
497        let snapshot = run.snapshot().await?;
498        Self::verify_snapshot(&snapshot, actions);
499        assert_eq!(snapshot.occurrence, occurrence);
500        assert_eq!(json!(snapshot.checkpoint), manifest["terminal_checkpoint"]);
501        assert_eq!(json!(snapshot.state.values()), manifest["expected_state"]);
502        assert_eq!(
503            json!(snapshot.last_action_ordinal),
504            manifest["last_action_ordinal"]
505        );
506        let lookup = started.elapsed();
507        if open_only {
508            return Ok(
509                json!({"dataset":"history","actions":actions,"mode":"open-only",
510                "lookup_and_snapshot_ns":lookup.as_nanos(),
511                "seed_retained_rows":manifest["retained_rows"],"observed_history_rows":null}),
512            );
513        }
514        let history = self
515            .history(&run, actions, false, snapshot.checkpoint)
516            .await?;
517        assert_eq!(history["rows"], manifest["retained_rows"]);
518        Ok(
519            json!({"dataset":"history","actions":actions,"mode":"full-history",
520            "lookup_and_snapshot_ns":lookup.as_nanos(),"history":history}),
521        )
522    }
523
524    fn definitions(&self, actions: u64) -> Result<(Vec<WorkflowDefinition>, usize), AppError> {
525        if matches!(
526            self.command,
527            Command::Linear | Command::SeedHistory { .. } | Command::ReopenHistory { .. }
528        ) {
529            let (definition, bytes) =
530                Self::definition(matches!(self.command, Command::Linear), actions)?;
531            return Ok((vec![definition], bytes));
532        }
533        let leaf = json!({
534            "schema":"app-kit.workflow/v2","id":"performance-leaf","revision":"r1",
535            "state":{"value":0,"result":0},"required":["value","result"],
536            "reducers":{"value":"reject_concurrent","result":"reject_concurrent"},
537            "entry":"work","nodes":[{"id":"work","deadline_ms":NODE_DEADLINE_MS,
538                "step":{"kind":"action","action":"performance-action","revision":"r1"}}],"routes":{}
539        });
540        let documents = match self.command {
541            Command::Ready => {
542                let mut nodes = vec![json!({"id":"fork","step":{"kind":"state","mutations":[]}})];
543                let mut branches = Vec::new();
544                for index in 0..32 {
545                    let node = format!("n{index:02}");
546                    nodes.push(json!({"id":node,"deadline_ms":NODE_DEADLINE_MS,
547                        "step":{"kind":"action","action":"performance-action","revision":"r1"}}));
548                    branches.push(json!({"target":{"kind":"node","id":node}}));
549                }
550                vec![json!({
551                    "schema":"app-kit.workflow/v2","id":"performance","revision":"r1",
552                    "state":{"value":0,"result":0},"required":["value","result"],
553                    "reducers":{"value":"reject_concurrent","result":"collect"},
554                    "entry":"fork","nodes":nodes,
555                    "routes":{"fork":{"mode":"broadcast","branches":branches}}
556                })]
557            }
558            Command::Map | Command::NestedCancel => {
559                let nested = matches!(self.command, Command::NestedCancel);
560                let items = if nested { 2 } else { 4_096 };
561                let mut documents = vec![
562                    json!({
563                        "schema":"app-kit.workflow/v2","id":"performance","revision":"r1",
564                        "state":{"value":0,"result":0},"required":["value","result"],
565                        "reducers":{"value":"reject_concurrent","result":"sum_integer"},
566                        "entry":"map","nodes":[{"id":"map","deadline_ms":if nested {NODE_DEADLINE_MS} else {WORKLOAD_BUDGET.as_millis() as u64},
567                            "step":{"kind":"map","workflow":if nested {"performance-mid"} else {"performance-leaf"},
568                            "items":{"kind":"literal","value":(0..items).collect::<Vec<_>>()},
569                            "input_key":"value","max_parallel":if nested {2} else {8},
570                            "reductions":[{"source":"result","target":"result","reducer":"sum_integer"}]}}],"routes":{}
571                    }),
572                    leaf,
573                ];
574                if nested {
575                    documents.push(json!({
576                        "schema":"app-kit.workflow/v2","id":"performance-mid","revision":"r1",
577                        "state":{"value":0,"result":0},"required":["value","result"],
578                        "reducers":{"value":"reject_concurrent","result":"sum_integer"},
579                        "entry":"map","nodes":[{"id":"map","deadline_ms":NODE_DEADLINE_MS,
580                            "step":{"kind":"map","workflow":"performance-leaf",
581                            "items":{"kind":"literal","value":(0..8).collect::<Vec<_>>()},
582                            "input_key":"value","max_parallel":8,
583                            "reductions":[{"source":"result","target":"result","reducer":"sum_integer"}]}}],"routes":{}
584                    }));
585                }
586                documents
587            }
588            Command::Repeated | Command::DelayedObserver => vec![leaf],
589            Command::Linear | Command::SeedHistory { .. } | Command::ReopenHistory { .. } => {
590                unreachable!()
591            }
592        };
593        let mut bytes = 0;
594        let mut definitions = Vec::with_capacity(documents.len());
595        for document in documents {
596            let source = document.to_string();
597            assert!(source.len() <= 1_048_576);
598            bytes += source.len();
599            definitions.push(WorkflowDefinition::parse(&source)?);
600        }
601        Ok((definitions, bytes))
602    }
603
604    async fn ready(
605        &self,
606        app: &App<StateStore, Agent, WorkloadAction>,
607        probe: &WorkloadProbe,
608    ) -> Result<Value, Box<dyn Error + Send + Sync>> {
609        let workspace = app
610            .create_workspace(WorkspaceConfig {
611                name: None,
612                root: self.root.join("workspace"),
613            })
614            .await?;
615        let workflow = workspace.workflow("performance".parse()?).await?;
616        let mut waves = Vec::with_capacity(32);
617        let mut releases = Vec::with_capacity(32 * 31);
618        let mut barriers = Vec::with_capacity(32);
619        let started = Instant::now();
620        for permutation in 0..32 {
621            probe.assert_idle();
622            let mut run = workflow.prepare(WorkflowState::default()).await?;
623            let observer = workflow
624                .open_run(RunSelector::Latest(run.id().clone()))
625                .await?;
626            let mut execution = run.execution(ExecutionOptions::default()).await?;
627            let Some(ExecutionEvent::Committed { outcomes, .. }) = execution.step().await? else {
628                panic!("the native fork must commit before the ready wave");
629            };
630            assert_eq!(outcomes.len(), 1);
631            assert_eq!(
632                (outcomes[0].node.as_str(), outcomes[0].ordinal),
633                ("fork", 0)
634            );
635            let wave = Instant::now();
636            let mut finish = Box::pin(execution.finish());
637            tokio::select! {
638                _ = probe.wait_for(&probe.constructed, (permutation + 1) * 32) => {}
639                result = &mut finish => panic!("gated ready wave finished before admission: {}", result.is_ok()),
640            }
641            assert_eq!(probe.active.load(Ordering::SeqCst), 32);
642            let order = (0..32)
643                .map(|offset| (permutation + 31 - offset) % 32)
644                .collect::<Vec<_>>();
645            for (released, index) in order.iter().take(31).enumerate() {
646                let releasing = Instant::now();
647                probe.release(*index);
648                tokio::select! {
649                    _ = probe.wait_for(&probe.completed, permutation * 32 + released + 1) => {}
650                    result = &mut finish => panic!("a partial ready wave committed: {}", result.is_ok()),
651                }
652                releases.push(releasing.elapsed());
653                let visible = observer.snapshot().await?;
654                assert_eq!(
655                    visible.state.values()["result"],
656                    0,
657                    "staged siblings cannot expose a partial barrier"
658                );
659                assert_eq!(visible.last_action_ordinal, Some(0));
660            }
661            let barrier = Instant::now();
662            probe.release(order[31]);
663            let snapshot = finish.await?;
664            barriers.push(barrier.elapsed());
665            waves.push(wave.elapsed());
666            assert!(matches!(snapshot.status, RunStatus::Succeeded));
667            assert!(snapshot.interactions.is_empty());
668            assert_eq!(
669                snapshot.state.values()["result"],
670                json!((1..=32).collect::<Vec<_>>())
671            );
672            assert_eq!(snapshot.last_action_ordinal, Some(32));
673            assert_eq!(snapshot.outcomes.len(), 32);
674            for (index, outcome) in snapshot.outcomes.iter().enumerate() {
675                assert_eq!(outcome.node, format!("n{index:02}"));
676                assert_eq!(outcome.ordinal, index as u64 + 1);
677                assert_eq!(
678                    outcome.result,
679                    Map::from_iter([("value".into(), json!(index + 1))])
680                );
681            }
682            assert_eq!(std::mem::take(&mut *probe.order.lock().unwrap()), order);
683            assert_eq!(
684                probe.completed.load(Ordering::SeqCst),
685                (permutation + 1) * 32
686            );
687            probe.assert_idle();
688        }
689        assert_eq!(probe.peak.load(Ordering::SeqCst), 32);
690        let elapsed = started.elapsed();
691        Ok(
692            json!({"dataset":"ready32","permutations":32,"ready_actions_per_wave":32,
693            "caller_action_commits":1024,"native_fork_commits":32,
694            "execution_ns":elapsed.as_nanos(),"caller_actions_per_second":1024.0 / elapsed.as_secs_f64(),
695            "wave_latency_including_snapshot_probes":Self::samples(waves),
696            "release_to_source_completion_latency":Self::samples(releases),
697            "last_release_to_committed_barrier_latency":Self::samples(barriers)}),
698        )
699    }
700
701    async fn mapped(
702        &self,
703        app: &App<StateStore, Agent, WorkloadAction>,
704        probe: &WorkloadProbe,
705    ) -> Result<Value, Box<dyn Error + Send + Sync>> {
706        let workspace = app
707            .create_workspace(WorkspaceConfig {
708                name: None,
709                root: self.root.join("workspace"),
710            })
711            .await?;
712        let workflow = workspace.workflow("performance".parse()?).await?;
713        let mut run = workflow.prepare(WorkflowState::default()).await?;
714        let observer = workflow
715            .open_run(RunSelector::Latest(run.id().clone()))
716            .await?;
717        let started = Instant::now();
718        let mut finish = Box::pin(run.execution(ExecutionOptions::default()).await?.finish());
719        tokio::select! {
720            _ = probe.wait_for(&probe.constructed, 8) => {}
721            result = &mut finish => panic!("map finished before its first eight blocked sources: {}", result.is_ok()),
722        }
723        assert_eq!(probe.active.load(Ordering::SeqCst), 8);
724        assert_eq!(probe.constructed.load(Ordering::SeqCst), 8);
725        let unchanged = observer.snapshot().await?;
726        assert_eq!(unchanged.state.values()["result"], 0);
727        assert!(unchanged.last_action_ordinal.is_none());
728        probe.release_all();
729        let snapshot = finish.await?;
730        let elapsed = started.elapsed();
731        assert!(matches!(snapshot.status, RunStatus::Succeeded));
732        assert!(snapshot.interactions.is_empty());
733        assert_eq!(snapshot.state.values()["result"], 4_096_u64 * 4_097 / 2);
734        assert_eq!(snapshot.last_action_ordinal, Some(0));
735        assert_eq!(snapshot.outcomes.len(), 1);
736        assert_eq!(snapshot.outcomes[0].node, "map");
737        assert_eq!(probe.constructed.load(Ordering::SeqCst), 4_096);
738        assert_eq!(probe.completed.load(Ordering::SeqCst), 4_096);
739        assert_eq!(probe.peak.load(Ordering::SeqCst), 8);
740        probe.assert_idle();
741        let child = workspace.workflow("performance-leaf".parse()?).await?;
742        let mut cursor = None;
743        let mut values = BTreeSet::new();
744        let verifying = Instant::now();
745        loop {
746            let page = child
747                .list_runs(cursor, NonZeroUsize::new(PAGE_SIZE).unwrap())
748                .await?;
749            assert!(page.len() <= PAGE_SIZE);
750            if page.is_empty() {
751                break;
752            }
753            for row in &page {
754                assert!(matches!(row.status, RunStatus::Succeeded));
755                let value = row.state.values()["value"].as_u64().unwrap();
756                assert!(
757                    value < 4_096 && values.insert(value),
758                    "every mapped input has exactly one retained child"
759                );
760                assert_eq!(row.state.values()["result"], value + 1);
761                assert_eq!(row.last_action_ordinal, Some(0));
762                assert_eq!(row.outcomes.len(), 1);
763                assert_eq!(row.outcomes[0].node, "work");
764            }
765            let last = page.last().unwrap();
766            cursor = Some(RunSelector::Exact {
767                id: last.id.clone(),
768                occurrence: last.occurrence.clone(),
769            });
770            assert!(values.len() <= 4_096);
771        }
772        assert_eq!(values.len(), 4_096);
773        Ok(json!({"dataset":"map4096","items":4096,"max_parallel":8,
774            "parent_node_deadline_ms":WORKLOAD_BUDGET.as_millis(),
775            "execution_ns":elapsed.as_nanos(),"child_actions_per_second":4096.0 / elapsed.as_secs_f64(),
776            "map_completion_latency":Self::samples(vec![elapsed]),
777            "child_inventory_verification_ns":verifying.elapsed().as_nanos(),"verified_children":values.len()}))
778    }
779
780    async fn repeated(
781        &self,
782        app: &App<StateStore, Agent, WorkloadAction>,
783        probe: &WorkloadProbe,
784    ) -> Result<Value, Box<dyn Error + Send + Sync>> {
785        let workspace = app
786            .create_workspace(WorkspaceConfig {
787                name: None,
788                root: self.root.join("workspace"),
789            })
790            .await?;
791        let workflow = workspace.workflow("performance-leaf".parse()?).await?;
792        let started = Instant::now();
793        // Admit one run at a time, then directly poll at most 16 original
794        // executions together. No consumer task is spawned or detached.
795        let mut runs = std::pin::pin!(
796            stream::iter(0..100_u64)
797                .then(|value| {
798                    let workflow = &workflow;
799                    async move {
800                        let started = Instant::now();
801                        let run = workflow
802                            .prepare(WorkflowState::new(Map::from_iter([(
803                                "value".into(),
804                                json!(value),
805                            )]))?)
806                            .await?;
807                        Ok::<_, AppError>((value, run, started))
808                    }
809                })
810                .map(|prepared| async move {
811                    let (value, mut run, started) = prepared?;
812                    let snapshot = run
813                        .execution(ExecutionOptions::default())
814                        .await?
815                        .finish()
816                        .await?;
817                    assert!(matches!(snapshot.status, RunStatus::Succeeded));
818                    assert!(snapshot.interactions.is_empty());
819                    assert_eq!(snapshot.state.values()["value"], value);
820                    assert_eq!(snapshot.state.values()["result"], value + 1);
821                    assert_eq!(snapshot.last_action_ordinal, Some(0));
822                    assert_eq!(snapshot.outcomes.len(), 1);
823                    Ok::<_, AppError>((run.id().clone(), started.elapsed()))
824                })
825                .buffer_unordered(16)
826        );
827        tokio::select! {
828            _ = probe.wait_for(&probe.constructed, 16) => {}
829            result = runs.next() => panic!(
830                "run finished before the first 16 blocked sources: {:?}",
831                result.map(|result| result.map(|_| ())),
832            ),
833        }
834        assert_eq!(probe.active.load(Ordering::SeqCst), 16);
835        assert_eq!(probe.constructed.load(Ordering::SeqCst), 16);
836        probe.release_all();
837        let mut ids = BTreeSet::new();
838        let mut samples = Vec::with_capacity(100);
839        while let Some(result) = runs.next().await {
840            let (id, elapsed) = result?;
841            assert!(ids.insert(id));
842            samples.push(elapsed);
843        }
844        assert_eq!(ids.len(), 100);
845        assert_eq!(probe.constructed.load(Ordering::SeqCst), 100);
846        assert_eq!(probe.completed.load(Ordering::SeqCst), 100);
847        assert_eq!(probe.peak.load(Ordering::SeqCst), 16);
848        probe.assert_idle();
849        let rows = workflow
850            .list_runs(None, NonZeroUsize::new(128).unwrap())
851            .await?;
852        assert_eq!(rows.len(), 100);
853        assert_eq!(
854            rows.iter()
855                .map(|row| row.id.clone())
856                .collect::<BTreeSet<_>>(),
857            ids
858        );
859        assert!(
860            rows.iter()
861                .all(|row| matches!(row.status, RunStatus::Succeeded))
862        );
863        let elapsed = started.elapsed();
864        Ok(
865            json!({"dataset":"repeated100","runs":100,"caller_in_flight_limit":16,"admission":"sequential",
866            "production_active_limit":"unchanged; this measures only the caller's 16-journey envelope",
867            "execution_ns":elapsed.as_nanos(),"runs_per_second":100.0 / elapsed.as_secs_f64(),
868            "run_latency_including_admission":Self::samples(samples),"indefinite_stability_proof":false}),
869        )
870    }
871
872    async fn nested_cancel(
873        &self,
874        app: &App<StateStore, Agent, WorkloadAction>,
875        probe: &WorkloadProbe,
876    ) -> Result<Value, Box<dyn Error + Send + Sync>> {
877        let workspace = app
878            .create_workspace(WorkspaceConfig {
879                name: None,
880                root: self.root.join("workspace"),
881            })
882            .await?;
883        let workflow = workspace.workflow("performance".parse()?).await?;
884        let run = workflow.start(ExecutionOptions::default()).await?;
885        tokio::select! {
886            _ = probe.wait_for(&probe.constructed, 16) => {}
887            result = run.wait() => panic!("nested run ended before its held leaf sources: {}", result.is_ok()),
888        }
889        assert_eq!(probe.active.load(Ordering::SeqCst), 16);
890        let cancelling = Instant::now();
891        let snapshot = tokio::time::timeout(Duration::from_secs(30), async {
892            loop {
893                let before = run.snapshot().await?;
894                assert_eq!(before.state.values()["result"], 0);
895                assert!(before.outcomes.is_empty() && before.last_action_ordinal.is_none());
896                match run.cancel(before.checkpoint).await {
897                    Ok(_) => break,
898                    Err(AppError::Conflict(_)) => tokio::task::yield_now().await,
899                    Err(error) => return Err(error),
900                }
901            }
902            run.wait().await
903        })
904        .await
905        .map_err(|_| {
906            AppError::Timeout("nested cancellation exceeded its declared 30-second grace".into())
907        })??;
908        let elapsed = cancelling.elapsed();
909        assert!(matches!(snapshot.status, RunStatus::Cancelled));
910        assert!(snapshot.interactions.is_empty() && snapshot.outcomes.is_empty());
911        assert!(snapshot.last_action_ordinal.is_none());
912        assert_eq!(snapshot.state.values()["result"], 0);
913        assert_eq!(probe.constructed.load(Ordering::SeqCst), 16);
914        assert_eq!(probe.completed.load(Ordering::SeqCst), 0);
915        probe.assert_idle();
916        for (id, count) in [
917            ("performance", 1),
918            ("performance-mid", 2),
919            ("performance-leaf", 16),
920        ] {
921            let child = workspace.workflow(id.parse()?).await?;
922            let rows = child
923                .list_runs(None, NonZeroUsize::new(128).unwrap())
924                .await?;
925            assert_eq!(rows.len(), count);
926            for row in rows {
927                assert!(matches!(row.status, RunStatus::Cancelled));
928                assert!(row.outcomes.is_empty() && row.last_action_ordinal.is_none());
929                assert_eq!(row.state.values()["result"], 0);
930            }
931        }
932        Ok(
933            json!({"dataset":"nested-cancel","depth":2,"intermediate_children":2,"leaf_children":16,
934            "cooperative_cancel_grace_ms":30_000,"cancellation_latency":Self::samples(vec![elapsed]),
935            "source_replays":0,"caller_sources_dropped":16,"agent_unknown_replay":"not applicable: no Agent attempt"}),
936        )
937    }
938
939    async fn delayed_observer(
940        &self,
941        app: &App<StateStore, Agent, WorkloadAction>,
942        probe: &WorkloadProbe,
943    ) -> Result<Value, Box<dyn Error + Send + Sync>> {
944        let workspace = app
945            .create_workspace(WorkspaceConfig {
946                name: None,
947                root: self.root.join("workspace"),
948            })
949            .await?;
950        let workflow = workspace.workflow("performance-leaf".parse()?).await?;
951        let run = workflow.start(ExecutionOptions::default()).await?;
952        let observer = workflow
953            .open_run(RunSelector::Latest(run.id().clone()))
954            .await?;
955        // This public observation is deliberately never polled while the source
956        // produces its 256 partials and commits. It owns no execution work.
957        let delayed = observer.wait();
958        let produced = run.wait().await?;
959        assert!(matches!(produced.status, RunStatus::Succeeded));
960        assert_eq!(probe.completed.load(Ordering::SeqCst), 1);
961        probe.assert_idle();
962        tokio::time::sleep(Duration::from_secs(2)).await;
963        let observing = Instant::now();
964        let snapshot = delayed.await?;
965        let elapsed = observing.elapsed();
966        assert!(matches!(snapshot.status, RunStatus::Succeeded));
967        assert_eq!(snapshot.checkpoint, produced.checkpoint);
968        assert_eq!(snapshot.state.values()["result"], 1);
969        assert_eq!(snapshot.last_action_ordinal, Some(0));
970        assert_eq!(snapshot.outcomes.len(), 1);
971        assert_eq!(
972            snapshot.outcomes[0].result,
973            Map::from_iter([("value".into(), json!(1))])
974        );
975        assert_eq!(probe.constructed.load(Ordering::SeqCst), 1);
976        assert_eq!(probe.partials.load(Ordering::SeqCst), 256);
977        probe.assert_idle();
978        let terminal = run.snapshot().await?;
979        assert_eq!(terminal.checkpoint, snapshot.checkpoint);
980        assert_eq!(terminal.state, snapshot.state);
981        let mut cursor = None;
982        let mut actions = 0;
983        let mut rows = 0;
984        loop {
985            let page = run
986                .history(cursor, NonZeroUsize::new(PAGE_SIZE).unwrap())
987                .await?;
988            for row in &page.checkpoints {
989                rows += 1;
990                assert!(rows <= 32);
991                if matches!(row.kind, CheckpointKind::Action) {
992                    actions += 1;
993                    assert_eq!(row.action_ordinal, Some(0));
994                    assert_eq!(row.outcomes.len(), 1);
995                }
996            }
997            cursor = page.next;
998            if cursor.is_none() {
999                break;
1000            }
1001        }
1002        assert_eq!(
1003            actions, 1,
1004            "partials cannot become extra durable action commits"
1005        );
1006        Ok(
1007            json!({"dataset":"delayed-observer","unobserved_partial_events":256,"delay_after_terminal_confirmation_ms":2_000,
1008            "observation_latency":Self::samples(vec![elapsed]),"retained_rows":rows,
1009            "memory_scope":"external process RSS; this consumer retains no partial payloads"}),
1010        )
1011    }
More examples
Hide additional examples
examples/history_and_recovery.rs (lines 68-71)
63    async fn create_fork(
64        &self,
65        app: &App<StateStore, Agent>,
66    ) -> Result<(WorkspaceId, RunSelector, RunSelector), Box<dyn Error + Send + Sync>> {
67        let workspace = app
68            .create_workspace(WorkspaceConfig {
69                name: Some("history example".into()),
70                root: std::env::current_dir()?,
71            })
72            .await?;
73        let workflow = workspace.workflow("history-counter".parse()?).await?;
74        let mut source = workflow.prepare(WorkflowState::default()).await?;
75        let completed = source
76            .execution(ExecutionOptions::default())
77            .await?
78            .finish()
79            .await?;
80        if !matches!(completed.status, RunStatus::Succeeded)
81            || completed.state.values()["count"] != 2
82        {
83            return Err(
84                AppError::Integration("source did not complete both increments".into()).into(),
85            );
86        }
87
88        // An action ordinal selects the whole committed boundary containing it;
89        // it is independent of the checkpoint sequence and storage revision.
90        let first = source.checkpoint(CheckpointSelector::Action(0)).await?;
91        if !matches!(first.kind, CheckpointKind::Action)
92            || first.position.as_deref() != Some("first")
93            || first.state.values()["count"] != 1
94        {
95            return Err(
96                AppError::Integration("first action history is inconsistent".into()).into(),
97            );
98        }
99        let source_selector = RunSelector::Exact {
100            id: source.id().clone(),
101            occurrence: first.occurrence.clone(),
102        };
103        let mut fork = workflow
104            .restore(RestoreOptions {
105                source: source_selector.clone(),
106                checkpoint: CheckpointSelector::Action(0),
107                overrides: WorkflowState::new(Map::from_iter([("count".into(), json!(40))]))?,
108                position: ResumePosition::AfterCompleted,
109                fallback: None,
110            })
111            .await?;
112        let parked = fork.snapshot().await?;
113        if !matches!(parked.status, RunStatus::Paused)
114            || parked.checkpoint != 0
115            || parked.state.values()["count"] != 40
116            || !parked.outcomes.is_empty()
117        {
118            return Err(
119                AppError::Integration("restore did not create a parked clean copy".into()).into(),
120            );
121        }
122
123        source.delete(completed.checkpoint).await?;
124        if !matches!(source.snapshot().await, Err(AppError::NotFound(_))) {
125            return Err(
126                AppError::Integration("explicit source deletion did not finish".into()).into(),
127            );
128        }
129        // Deletion does not invalidate the copied state. AfterCompleted skips
130        // the first increment, so only the second changes the override: 40 -> 41.
131        let completed = fork
132            .execution(ExecutionOptions::default())
133            .await?
134            .finish()
135            .await?;
136        if !matches!(completed.status, RunStatus::Succeeded)
137            || completed.state.values()["count"] != 41
138            || completed.last_action_ordinal != Some(1)
139        {
140            return Err(
141                AppError::Integration("fork resumed from the wrong boundary".into()).into(),
142            );
143        }
144        println!("Source count: 2; copied count: 40; completed fork count: 41");
145        Ok((
146            workspace.id().clone(),
147            source_selector,
148            RunSelector::Exact {
149                id: fork.id().clone(),
150                occurrence: completed.occurrence,
151            },
152        ))
153    }
examples/actions_and_inputs.rs (lines 46-49)
23    async fn run() -> Result<(), Box<dyn Error + Send + Sync>> {
24        let definition = WorkflowDefinition::parse(
25            &json!({
26                "schema":"app-kit.workflow/v2", "id":"invoice", "revision":"r1",
27                "state":{"unit_price":7,"total":0}, "required":["unit_price","total"],
28                "reducers":{"unit_price":"reject_concurrent","total":"reject_concurrent"},
29                "entry":"calculate",
30                "nodes":[{"id":"calculate","step":{
31                    "kind":"action","action":"invoice","revision":"r1","project_result":false
32                }}], "routes":{}
33            })
34            .to_string(),
35        )?;
36        let store =
37            StateStore::connect(StateStoreConfig::Lance(LanceStateStoreConfig::memory())).await?;
38        // Native caller actions need no Agent value or provider service.
39        let mut app = App::open(
40            AppConfig::<_, Agent>::new(store, Vec::new(), vec![definition])
41                .with_actions(vec![InvoiceAction::new()]),
42        )
43        .await?;
44        let observed = tokio::time::timeout(Duration::from_secs(15), async {
45            let workspace = app
46                .create_workspace(WorkspaceConfig {
47                    name: Some("invoice example".into()),
48                    root: PathBuf::from(env!("CARGO_MANIFEST_DIR")),
49                })
50                .await?;
51            let workflow = workspace.workflow("invoice".parse()?).await?;
52            let mut run = workflow.prepare(WorkflowState::default()).await?;
53            let mut execution = run
54                .execution(ExecutionOptions {
55                    inputs: Map::from_iter([
56                        ("quantity".into(), json!(6)),
57                        ("trace".into(), json!("sample")),
58                    ]),
59                    ..ExecutionOptions::default()
60                })
61                .await?;
62            let Some(ExecutionEvent::Partial { result, .. }) = execution.next().await? else {
63                return Err(AppError::Integration(
64                    "expected the real action partial".into(),
65                ));
66            };
67            if result.get("progress") != Some(&json!("calculated")) {
68                return Err(AppError::Integration("partial changed".into()));
69            }
70            let Some(ExecutionEvent::Committed { outcomes, .. }) = execution.step().await? else {
71                return Err(AppError::Integration(
72                    "expected one committed boundary".into(),
73                ));
74            };
75            let expected =
76                json!({"quantity":6,"label":"order","currency":"USD","trace":"sample","total":42});
77            if outcomes.len() != 1 || Value::Object(outcomes[0].result.clone()) != expected {
78                return Err(AppError::Integration(
79                    "resolved inputs or action result changed".into(),
80                ));
81            }
82            let terminal = execution.finish().await?;
83            if !matches!(terminal.status, RunStatus::Succeeded)
84                || terminal.state.values()
85                    != json!({"unit_price":7,"total":42}).as_object().unwrap()
86            {
87                return Err(AppError::Integration(
88                    "only the explicit total write should persist".into(),
89                ));
90            }
91            println!(
92                "Partial: calculated; committed total: 42; transient inputs stayed out of state"
93            );
94            Ok::<(), AppError>(())
95        })
96        .await;
97        let closed = tokio::time::timeout(
98            Duration::from_secs(15),
99            app.shutdown(Shutdown::CancelAndDrain {
100                timeout: Duration::from_secs(10),
101            }),
102        )
103        .await;
104        drop(app);
105        observed.map_err(|_| AppError::Timeout("invoice execution timed out".into()))??;
106        closed.map_err(|_| AppError::Timeout("invoice cleanup timed out".into()))??;
107        Ok(())
108    }
Source

pub async fn workspace( &self, id: WorkspaceId, ) -> Result<Workspace<CS, A, X, C, T, H, E>, AppError>

Reopens one workspace by typed identity.

A retained terminal head can establish a history-only scope after coordination is deleted. That handle permits retained-run lookup, not fresh admission.

§Errors

Returns AppError::NotFound when neither active coordination nor readable retained history exists in this scope. Revocation, incompatible lifecycle progress, and unreadable durable evidence report Closed, Conflict, or Integration.

§Example
let id = workspace.id().clone();
let reopened = app.workspace(id.clone()).await?;
assert_eq!(reopened.id(), &id);
Examples found in repository?
examples/history_and_recovery.rs (line 162)
155    async fn read_reopened(
156        &self,
157        app: &App<StateStore, Agent>,
158        workspace: WorkspaceId,
159        source: RunSelector,
160        fork: RunSelector,
161    ) -> Result<(), AppError> {
162        let workspace = app.workspace(workspace).await?;
163        let workflow = workspace.workflow("history-counter".parse()?).await?;
164        let fork = workflow.open_run(fork).await?;
165        let committed = fork.checkpoint(CheckpointSelector::Action(1)).await?;
166        let lineage = committed
167            .lineage
168            .as_ref()
169            .ok_or_else(|| AppError::Integration("reopened fork lost its source lineage".into()))?;
170        if committed.state.values()["count"] != 41
171            || !matches!(&source, RunSelector::Exact { id, occurrence }
172                if &lineage.source == id && &lineage.source_occurrence == occurrence)
173            || lineage.action_ordinal != Some(0)
174        {
175            return Err(AppError::Integration(
176                "reopened fork history changed".into(),
177            ));
178        }
179        println!(
180            "Reopened fork {} at count 41; source lineage remains readable",
181            fork.id()
182        );
183        Ok(())
184    }
More examples
Hide additional examples
examples/performance.rs (line 489)
472    async fn reopen(
473        &self,
474        app: &App<StateStore, Agent, WorkloadAction>,
475        manifest: &Value,
476        actions: u64,
477        open_only: bool,
478    ) -> Result<Value, Box<dyn Error + Send + Sync>> {
479        let started = Instant::now();
480        let workspace: WorkspaceId = manifest["workspace"]
481            .as_str()
482            .ok_or("missing workspace ID")?
483            .parse()?;
484        let run_id: RunId = manifest["run"].as_str().ok_or("missing run ID")?.parse()?;
485        let occurrence = manifest["occurrence"]
486            .as_str()
487            .ok_or("missing run occurrence")?
488            .to_owned();
489        let workspace = app.workspace(workspace).await?;
490        let workflow = workspace.workflow("performance".parse()?).await?;
491        let run = workflow
492            .open_run(RunSelector::Exact {
493                id: run_id,
494                occurrence: occurrence.clone(),
495            })
496            .await?;
497        let snapshot = run.snapshot().await?;
498        Self::verify_snapshot(&snapshot, actions);
499        assert_eq!(snapshot.occurrence, occurrence);
500        assert_eq!(json!(snapshot.checkpoint), manifest["terminal_checkpoint"]);
501        assert_eq!(json!(snapshot.state.values()), manifest["expected_state"]);
502        assert_eq!(
503            json!(snapshot.last_action_ordinal),
504            manifest["last_action_ordinal"]
505        );
506        let lookup = started.elapsed();
507        if open_only {
508            return Ok(
509                json!({"dataset":"history","actions":actions,"mode":"open-only",
510                "lookup_and_snapshot_ns":lookup.as_nanos(),
511                "seed_retained_rows":manifest["retained_rows"],"observed_history_rows":null}),
512            );
513        }
514        let history = self
515            .history(&run, actions, false, snapshot.checkpoint)
516            .await?;
517        assert_eq!(history["rows"], manifest["retained_rows"]);
518        Ok(
519            json!({"dataset":"history","actions":actions,"mode":"full-history",
520            "lookup_and_snapshot_ns":lookup.as_nanos(),"history":history}),
521        )
522    }
Source

pub async fn list_workspaces( &self, after: Option<WorkspaceId>, limit: NonZeroUsize, ) -> Result<Vec<WorkspaceSnapshot>, AppError>

Lists workspaces in canonical identity order.

The cursor is exclusive and pages contain active workspaces only. Limits are 1–1,000; larger requests are rejected rather than silently clamped.

§Errors

Returns AppError::InvalidInput for an excessive limit, AppError::NotFound for an unknown cursor, and Closed or Integration for revoked or unreadable application state.

§Example
let page = app.list_workspaces(None, std::num::NonZeroUsize::new(1).unwrap()).await?;
assert_eq!(&page[0].id, workspace.id());
let next = app.list_workspaces(Some(page[0].id.clone()), std::num::NonZeroUsize::new(1).unwrap()).await?;
assert!(next.is_empty());
Source

pub async fn shutdown(&mut self, policy: Shutdown) -> Result<(), AppError>

Finalizes this application according to policy.

RejectActive leaves admission open if work remains. Drain policies seal admission; CancelAndDrain also requests explicit cancellation. Successful completion joins App-owned work and consumes each transferred Agent once. The timeout bounds this observer, not the continuing finalizer. Cancelling the observer or timing out never reopens admission or detaches accepted cleanup.

§Errors

Returns AppError::Busy when RejectActive encounters accepted work, AppError::InvalidInput for an invalid timeout, or AppError::Timeout when observation expires. Durable conflicts and lower cleanup failures remain Conflict or Integration; cleanup does not erase an earlier failure.

§Example
app.shutdown(Shutdown::Drain { timeout: std::time::Duration::from_secs(5) }).await?;
Examples found in repository?
examples/history_and_recovery.rs (lines 189-191)
186    async fn close(&self, app: &mut App<StateStore, Agent>) -> Result<(), AppError> {
187        tokio::time::timeout(
188            Duration::from_secs(15),
189            app.shutdown(Shutdown::CancelAndDrain {
190                timeout: Duration::from_secs(10),
191            }),
192        )
193        .await
194        .map_err(|_| AppError::Timeout("example App cleanup timed out".into()))?
195    }
More examples
Hide additional examples
examples/performance.rs (line 1036)
1013    async fn close(
1014        app: &mut App<StateStore, Agent, WorkloadAction>,
1015        success: bool,
1016    ) -> Result<(), AppError> {
1017        let deadline = Instant::now() + CLEANUP_BUDGET;
1018        let mut first_failure = None;
1019        loop {
1020            let remaining = deadline.saturating_duration_since(Instant::now());
1021            if remaining.is_zero() {
1022                return Err(first_failure.unwrap_or_else(|| {
1023                    AppError::Timeout("performance cleanup budget exhausted".into())
1024                }));
1025            }
1026            let policy = if success {
1027                Shutdown::Drain {
1028                    timeout: CLEANUP_OBSERVATION,
1029                }
1030            } else {
1031                Shutdown::CancelAndDrain {
1032                    timeout: CLEANUP_OBSERVATION,
1033                }
1034            };
1035            let result =
1036                tokio::time::timeout(remaining.min(CLEANUP_OBSERVATION), app.shutdown(policy))
1037                    .await;
1038            match result {
1039                Ok(Ok(())) => return first_failure.map_or(Ok(()), Err),
1040                Ok(Err(error @ AppError::Timeout(_))) => {
1041                    first_failure.get_or_insert(error);
1042                }
1043                Err(_) => {
1044                    first_failure.get_or_insert_with(|| {
1045                        AppError::Timeout("performance cleanup observation timed out".into())
1046                    });
1047                }
1048                Ok(Err(error)) => return Err(first_failure.unwrap_or(error)),
1049            }
1050            // A timed-out observer reattaches to its original finalizer.
1051        }
1052    }
examples/actions_and_inputs.rs (lines 99-101)
23    async fn run() -> Result<(), Box<dyn Error + Send + Sync>> {
24        let definition = WorkflowDefinition::parse(
25            &json!({
26                "schema":"app-kit.workflow/v2", "id":"invoice", "revision":"r1",
27                "state":{"unit_price":7,"total":0}, "required":["unit_price","total"],
28                "reducers":{"unit_price":"reject_concurrent","total":"reject_concurrent"},
29                "entry":"calculate",
30                "nodes":[{"id":"calculate","step":{
31                    "kind":"action","action":"invoice","revision":"r1","project_result":false
32                }}], "routes":{}
33            })
34            .to_string(),
35        )?;
36        let store =
37            StateStore::connect(StateStoreConfig::Lance(LanceStateStoreConfig::memory())).await?;
38        // Native caller actions need no Agent value or provider service.
39        let mut app = App::open(
40            AppConfig::<_, Agent>::new(store, Vec::new(), vec![definition])
41                .with_actions(vec![InvoiceAction::new()]),
42        )
43        .await?;
44        let observed = tokio::time::timeout(Duration::from_secs(15), async {
45            let workspace = app
46                .create_workspace(WorkspaceConfig {
47                    name: Some("invoice example".into()),
48                    root: PathBuf::from(env!("CARGO_MANIFEST_DIR")),
49                })
50                .await?;
51            let workflow = workspace.workflow("invoice".parse()?).await?;
52            let mut run = workflow.prepare(WorkflowState::default()).await?;
53            let mut execution = run
54                .execution(ExecutionOptions {
55                    inputs: Map::from_iter([
56                        ("quantity".into(), json!(6)),
57                        ("trace".into(), json!("sample")),
58                    ]),
59                    ..ExecutionOptions::default()
60                })
61                .await?;
62            let Some(ExecutionEvent::Partial { result, .. }) = execution.next().await? else {
63                return Err(AppError::Integration(
64                    "expected the real action partial".into(),
65                ));
66            };
67            if result.get("progress") != Some(&json!("calculated")) {
68                return Err(AppError::Integration("partial changed".into()));
69            }
70            let Some(ExecutionEvent::Committed { outcomes, .. }) = execution.step().await? else {
71                return Err(AppError::Integration(
72                    "expected one committed boundary".into(),
73                ));
74            };
75            let expected =
76                json!({"quantity":6,"label":"order","currency":"USD","trace":"sample","total":42});
77            if outcomes.len() != 1 || Value::Object(outcomes[0].result.clone()) != expected {
78                return Err(AppError::Integration(
79                    "resolved inputs or action result changed".into(),
80                ));
81            }
82            let terminal = execution.finish().await?;
83            if !matches!(terminal.status, RunStatus::Succeeded)
84                || terminal.state.values()
85                    != json!({"unit_price":7,"total":42}).as_object().unwrap()
86            {
87                return Err(AppError::Integration(
88                    "only the explicit total write should persist".into(),
89                ));
90            }
91            println!(
92                "Partial: calculated; committed total: 42; transient inputs stayed out of state"
93            );
94            Ok::<(), AppError>(())
95        })
96        .await;
97        let closed = tokio::time::timeout(
98            Duration::from_secs(15),
99            app.shutdown(Shutdown::CancelAndDrain {
100                timeout: Duration::from_secs(10),
101            }),
102        )
103        .await;
104        drop(app);
105        observed.map_err(|_| AppError::Timeout("invoice execution timed out".into()))??;
106        closed.map_err(|_| AppError::Timeout("invoice cleanup timed out".into()))??;
107        Ok(())
108    }

Trait Implementations§

Source§

impl<CS: StateStoreBackend, A: AgentBackend, X: ActionBackend, C: ConditionBackend, T: StateTyping, H: LifecycleHooks, E: ChildExecutor> Drop for App<CS, A, X, C, T, H, E>

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

§

impl<CS, A, X = NoActions, C = NoConditions, T = JsonState, H = NoHooks, E = InlineExecutor> !RefUnwindSafe for App<CS, A, X, C, T, H, E>

§

impl<CS, A, X = NoActions, C = NoConditions, T = JsonState, H = NoHooks, E = InlineExecutor> !UnwindSafe for App<CS, A, X, C, T, H, E>

§

impl<CS, A, X, C, T, H, E> Freeze for App<CS, A, X, C, T, H, E>
where Arc<AppRuntime<CS, A, X, C, T, H, E>>: Freeze,

§

impl<CS, A, X, C, T, H, E> Send for App<CS, A, X, C, T, H, E>
where Arc<AppRuntime<CS, A, X, C, T, H, E>>: Send,

§

impl<CS, A, X, C, T, H, E> Sync for App<CS, A, X, C, T, H, E>
where Arc<AppRuntime<CS, A, X, C, T, H, E>>: Sync,

§

impl<CS, A, X, C, T, H, E> Unpin for App<CS, A, X, C, T, H, E>
where Arc<AppRuntime<CS, A, X, C, T, H, E>>: Unpin,

§

impl<CS, A, X, C, T, H, E> UnsafeUnpin for App<CS, A, X, C, T, H, E>
where Arc<AppRuntime<CS, A, X, C, T, H, E>>: UnsafeUnpin,

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
Source§

impl<T> ArchivePointee for T

Source§

type ArchivedMetadata = ()

The archived version of the pointer metadata for this type.
Source§

fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata

Converts some archived metadata to the pointer metadata for itself.
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
Source§

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

Source§

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

Source§

impl<T> Conv for T

Source§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> FmtForward for T

Source§

fn fmt_binary(self) -> FmtBinary<Self>
where Self: Binary,

Causes self to use its Binary implementation when Debug-formatted.
Source§

fn fmt_display(self) -> FmtDisplay<Self>
where Self: Display,

Causes self to use its Display implementation when Debug-formatted.
Source§

fn fmt_lower_exp(self) -> FmtLowerExp<Self>
where Self: LowerExp,

Causes self to use its LowerExp implementation when Debug-formatted.
Source§

fn fmt_lower_hex(self) -> FmtLowerHex<Self>
where Self: LowerHex,

Causes self to use its LowerHex implementation when Debug-formatted.
Source§

fn fmt_octal(self) -> FmtOctal<Self>
where Self: Octal,

Causes self to use its Octal implementation when Debug-formatted.
Source§

fn fmt_pointer(self) -> FmtPointer<Self>
where Self: Pointer,

Causes self to use its Pointer implementation when Debug-formatted.
Source§

fn fmt_upper_exp(self) -> FmtUpperExp<Self>
where Self: UpperExp,

Causes self to use its UpperExp implementation when Debug-formatted.
Source§

fn fmt_upper_hex(self) -> FmtUpperHex<Self>
where Self: UpperHex,

Causes self to use its UpperHex implementation when Debug-formatted.
Source§

fn fmt_list(self) -> FmtList<Self>
where &'a Self: for<'a> IntoIterator,

Formats each item in a sequence. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

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

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

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
Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<L> LayerExt<L> for L

Source§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in Layered.
Source§

impl<T> LayoutRaw for T

Source§

fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>

Returns the layout of the type.
Source§

impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
where T: SharedNiching<N1, N2>, N1: Niching<T>, N2: Niching<T>,

Source§

unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool

Returns whether the given value has been niched. Read more
Source§

fn resolve_niched(out: Place<NichedOption<T, N1>>)

Writes data to out indicating that a T is niched.
Source§

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

Source§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where Self: Sized,

Pipes by value. This is generally the method you want to use. Read more
Source§

fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R
where R: 'a,

Borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> R
where R: 'a,

Mutably borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
Source§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
Source§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

Borrows self, then passes self.as_ref() into the pipe function.
Source§

fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.as_mut() into the pipe function.
Source§

fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
Source§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Pointee for T

Source§

type Metadata = ()

The metadata type for pointers and references to this type.
Source§

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

Source§

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
Source§

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
Source§

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> Tap for T

Source§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
Source§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
Source§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
Source§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
Source§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
Source§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
Source§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
Source§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
Source§

fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self

Calls .tap() only in debug builds, and is erased in release builds.
Source§

fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self

Calls .tap_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Calls .tap_borrow() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Calls .tap_borrow_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Calls .tap_ref() only in debug builds, and is erased in release builds.
Source§

fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Calls .tap_ref_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
Source§

impl<T> TryConv for T

Source§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
Source§

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

Source§

type Error = !

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

fn try_from(value: U) -> Result<T, !>

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.
Source§

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

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

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
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

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