Add the fabro-petri crate and pin the Petri packages

Fabro runs its workflows on Petri. The six Petri packages and the testkit
are pinned by revision in the workspace manifest under `petri_*` keys, and
`fabro-petri` is the one crate that depends on them. The crate's tests run
the `hello` bundle in memory on the stub registry and a command-only
workflow on the host sandbox; both skip without the sandbox-driver host
plugin, and the sandbox-plugins CI job requires it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-09-17 19:23:30 -04:00
parent 170291b9f7
commit 7eb5ca502c
No known key found for this signature in database
8 changed files with 627 additions and 1 deletions

View file

@ -169,6 +169,9 @@ jobs:
# integration tests.
- run: cargo nextest run --locked --profile ci --status-level slow -p fabro-sandbox --test plugin_provider
- run: cargo nextest run --locked --profile ci --status-level slow --run-ignored only -p fabro-sandbox --test docker_streaming
# The Petri runs (not ignored: they skip without the host plugin, which
# the environment above forbids).
- run: cargo nextest run --locked --profile ci --status-level slow -p fabro-petri
- run: cargo nextest run --locked --profile ci --status-level slow --run-ignored only -p fabro-workflow --test it -E 'test(asset_collection_docker_sandbox)'
test-macos:

View file

@ -125,6 +125,7 @@ Fabro is an AI-powered workflow orchestration platform. Workflows are defined as
- **fabro-cli** — CLI entry point. Commands: `run`, `exec`, `serve`, `validate`, `parse`, `cp`, `model`, `doctor`, `install`, `ps`, `system prune`
- **fabro-workflow** — Core workflow engine. Parses Graphviz graphs, runs stages, manages checkpoints/resume, hooks, and human-in-the-loop interactions
- **fabro-sandbox** — Local, Docker, and Daytona sandbox providers. `RunSandbox` is also the `Environment` pebble's coding agent runs its tools through; agent stages, Ask Fabro, hook evaluators, and `fabro exec` all run on the `pebble-coding-agent` crate (pinned by rev in the workspace `Cargo.toml`). `RunSandbox` is also the `Environment` pebble's coding agent runs its tools through; agent stages, Ask Fabro, hook evaluators, and `fabro exec` all run on the `pebble-coding-agent` crate (pinned by rev in the workspace `Cargo.toml`). Docker is the default runtime provider and creates clone-based `/workspace` containers through the operator's Docker daemon; Daytona uses the same GitHub-only clone-source contract. Docker daemon access is host-root-equivalent and assumes trusted callers/payloads.
- **fabro-petri** — Fabro's adapters over Petri, the workflow engine: the one crate that imports the Petri packages (pinned by rev in the workspace `Cargo.toml`), holding the run store over SQLite and the platform adapters
- **fabro-server** — Axum HTTP server. Routes for runs, sessions, models, completions, usage. SSE event streaming. Demo mode via header
- **fabro-llm** — Unified LLM client with providers: Anthropic, OpenAI, Gemini, OpenAI-compatible, plus retry/middleware/streaming
- **fabro-api** — Auto-generated Rust types and reqwest HTTP client from OpenAPI spec (build.rs + progenitor)

346
Cargo.lock generated
View file

@ -248,6 +248,12 @@ dependencies = [
"rustversion",
]
[[package]]
name = "arraydeque"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]]
name = "arrayvec"
version = "0.7.6"
@ -1102,6 +1108,16 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
[[package]]
name = "borsh"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a"
dependencies = [
"bytes",
"cfg_aliases",
]
[[package]]
name = "brotli"
version = "8.0.1"
@ -2087,6 +2103,12 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "doc-comment"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9"
[[package]]
name = "document-features"
version = "0.2.12"
@ -2860,6 +2882,21 @@ dependencies = [
"serde_json",
]
[[package]]
name = "fabro-petri"
version = "0.357.0-nightly.0"
dependencies = [
"petri-attractor-steps",
"petri-execution",
"petri-frontend-attractor",
"petri-frontend-fabro",
"petri-runtime",
"petri-store",
"petri-testkit",
"tempfile",
"tokio",
]
[[package]]
name = "fabro-proc"
version = "0.357.0-nightly.0"
@ -4020,6 +4057,15 @@ dependencies = [
"foldhash 0.2.0",
]
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown 0.15.5",
]
[[package]]
name = "hashlink"
version = "0.11.1"
@ -5022,6 +5068,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "marked-yaml"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a76cf4e66a8ffccfce983161b0faafe61a5ef03fe875ef2e3deb897e4e915fa"
dependencies = [
"doc-comment",
"hashlink 0.10.0",
"yaml-rust2",
]
[[package]]
name = "matchers"
version = "0.2.0"
@ -5952,6 +6009,272 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "petri-attractor-steps"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"async-trait",
"globset",
"jsonschema",
"lithos-llm",
"pebble-agent",
"pebble-coding-agent",
"petri-execution",
"petri-executor",
"petri-frontend",
"petri-frontend-attractor",
"petri-ir",
"petri-runtime",
"petri-steps",
"regex",
"reqwest 0.13.4",
"serde",
"serde_json",
"shlex",
"smol_str",
"tar",
"thiserror 2.0.18",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "petri-driver"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"async-trait",
"getrandom 0.3.4",
"petri-engine",
"petri-executor",
"petri-ir",
"petri-steps",
"petri-store",
"serde",
"serde_json",
"smol_str",
"thiserror 2.0.18",
"tokio",
"tracing",
]
[[package]]
name = "petri-engine"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"petri-ir",
"serde",
"serde_json",
"smol_str",
"thiserror 2.0.18",
]
[[package]]
name = "petri-execution"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"async-trait",
"petri-driver",
"petri-engine",
"petri-executor",
"petri-executor-sandbox",
"petri-ir",
"petri-runtime",
"petri-steps",
"petri-store",
"regex",
"serde",
"serde_json",
"smol_str",
"thiserror 2.0.18",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "petri-executor"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"async-trait",
"libc",
"petri-ir",
"serde",
"serde_json",
"smol_str",
"thiserror 2.0.18",
"tokio",
]
[[package]]
name = "petri-executor-sandbox"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"async-trait",
"petri-executor",
"petri-ir",
"sandbox-driver",
"sandbox-driver-daytona-config",
"sandbox-driver-docker-config",
"sandbox-driver-protocol",
"serde",
"serde_json",
"sha2 0.10.9",
"smol_str",
"thiserror 2.0.18",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "petri-frontend"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"marked-yaml",
"petri-ir",
"serde",
"serde_json",
"smol_str",
"thiserror 2.0.18",
"yaml-rust2",
]
[[package]]
name = "petri-frontend-attractor"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"minijinja",
"petri-frontend",
"petri-ir",
"regex",
"serde",
"serde_json",
"shlex",
"smol_str",
"thiserror 2.0.18",
"toml 0.9.12+spec-1.1.0",
]
[[package]]
name = "petri-frontend-fabro"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"petri-frontend",
"petri-frontend-attractor",
"petri-ir",
"regex",
"serde",
"serde_json",
"shlex",
"smol_str",
"toml 0.9.12+spec-1.1.0",
]
[[package]]
name = "petri-frontend-native"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"petri-frontend",
"petri-ir",
"serde_json",
"smol_str",
]
[[package]]
name = "petri-ir"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"regex",
"serde",
"serde_json",
"sha2 0.10.9",
"smol_str",
"thiserror 2.0.18",
]
[[package]]
name = "petri-runtime"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"async-trait",
"petri-driver",
"petri-engine",
"petri-executor",
"petri-executor-sandbox",
"petri-frontend",
"petri-frontend-native",
"petri-ir",
"petri-steps",
"petri-store",
"serde_json",
"smol_str",
"thiserror 2.0.18",
"tracing",
]
[[package]]
name = "petri-steps"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"async-trait",
"petri-executor",
"petri-ir",
"serde",
"serde_json",
"smol_str",
"thiserror 2.0.18",
"tokio",
"tracing",
]
[[package]]
name = "petri-store"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"async-trait",
"getrandom 0.3.4",
"petri-ir",
"serde",
"serde_json",
"smol_str",
"thiserror 2.0.18",
"tokio",
]
[[package]]
name = "petri-testkit"
version = "0.1.0"
source = "git+https://github.com/lithoscomputer/petri.git?rev=a0d2ceb3887287a460e69ae101e3c718aac75c9e#a0d2ceb3887287a460e69ae101e3c718aac75c9e"
dependencies = [
"async-trait",
"petri-driver",
"petri-engine",
"petri-executor",
"petri-executor-sandbox",
"petri-ir",
"petri-steps",
"petri-store",
"serde",
"serde_json",
"smol_str",
"tokio",
]
[[package]]
name = "pin-project"
version = "1.1.11"
@ -7599,6 +7922,16 @@ dependencies = [
"serde",
]
[[package]]
name = "smol_str"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523"
dependencies = [
"borsh",
"serde_core",
]
[[package]]
name = "socket2"
version = "0.6.2"
@ -7649,7 +7982,7 @@ dependencies = [
"futures-io",
"futures-util",
"hashbrown 0.16.1",
"hashlink",
"hashlink 0.11.1",
"indexmap 2.13.0",
"log",
"memchr",
@ -9729,6 +10062,17 @@ version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
[[package]]
name = "yaml-rust2"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9"
dependencies = [
"arraydeque",
"encoding_rs",
"hashlink 0.10.0",
]
[[package]]
name = "yansi"
version = "1.0.1"

View file

@ -127,6 +127,18 @@ sandbox-driver-testing = { git = "https://github.com/lithoscomputer/sandbox-driv
pebble-agent = { git = "https://github.com/lithoscomputer/pebble", rev = "a39f43e26effdf99635eaf343f095c17157c9c93" }
pebble-coding-agent = { git = "https://github.com/lithoscomputer/pebble", rev = "a39f43e26effdf99635eaf343f095c17157c9c93", features = ["mcp", "search-providers"] }
pebble-cli-core = { git = "https://github.com/lithoscomputer/pebble", rev = "a39f43e26effdf99635eaf343f095c17157c9c93" }
# petri: the workflow engine Fabro runs its workflows on. Pinned by rev, the
# same way pebble and sandbox-driver are. Petri pins the same pebble,
# lithos-llm and sandbox-driver revisions as this file, so the workspace links
# one copy of each. Only `fabro-petri` may depend on these packages; the keys
# carry the `petri_` prefix so the crate names say where they come from.
petri_runtime = { git = "https://github.com/lithoscomputer/petri.git", rev = "a0d2ceb3887287a460e69ae101e3c718aac75c9e", package = "petri-runtime" }
petri_execution = { git = "https://github.com/lithoscomputer/petri.git", rev = "a0d2ceb3887287a460e69ae101e3c718aac75c9e", package = "petri-execution" }
petri_store = { git = "https://github.com/lithoscomputer/petri.git", rev = "a0d2ceb3887287a460e69ae101e3c718aac75c9e", package = "petri-store" }
petri_attractor_steps = { git = "https://github.com/lithoscomputer/petri.git", rev = "a0d2ceb3887287a460e69ae101e3c718aac75c9e", package = "petri-attractor-steps" }
petri_frontend_attractor = { git = "https://github.com/lithoscomputer/petri.git", rev = "a0d2ceb3887287a460e69ae101e3c718aac75c9e", package = "petri-frontend-attractor" }
petri_frontend_fabro = { git = "https://github.com/lithoscomputer/petri.git", rev = "a0d2ceb3887287a460e69ae101e3c718aac75c9e", package = "petri-frontend-fabro" }
petri_testkit = { git = "https://github.com/lithoscomputer/petri.git", rev = "a0d2ceb3887287a460e69ae101e3c718aac75c9e", package = "petri-testkit" }
sentry = { version = "0.35", default-features = false, features = ["backtrace", "contexts", "ureq", "rustls"] }
fork = "0.2"
exec = "0.3"

View file

@ -0,0 +1,26 @@
[package]
name = "fabro-petri"
edition.workspace = true
version.workspace = true
publish = false
license.workspace = true
description = "Fabro's adapters over Petri, the workflow engine: the one place Fabro touches it"
[lib]
doctest = false
[lints]
workspace = true
[dependencies]
petri_runtime.workspace = true
petri_execution.workspace = true
petri_store.workspace = true
petri_attractor_steps.workspace = true
petri_frontend_attractor.workspace = true
petri_frontend_fabro.workspace = true
[dev-dependencies]
petri_testkit.workspace = true
tempfile = "3"
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }

View file

@ -0,0 +1,33 @@
# fabro-petri
Fabro's adapters over Petri, the workflow engine Fabro runs its workflows on.
## Layering rule
Only this crate imports Petri. The workspace `Cargo.toml` pins the Petri
packages by revision under `petri_*` keys, and `fabro-petri` is the only
member that lists them as dependencies. Every other Fabro crate reaches the
engine through what this crate exports. A Petri pin move is therefore a change
to this crate and the lockfile, nothing else.
## What it holds
Every adapter the integration plan describes lands here: the run store over
Fabro's SQLite database, then the platform adapters (hooks, interviews,
secrets, output storage, run tools, the event projection).
## How it is tested
Integration tests live under `tests/`:
- `runs.rs` runs the `hello` bundle in memory through `Runtime::standard()`
with the Fabro frontend and the model-free stub registry, then a
command-only workflow on the host sandbox through the real step registry.
The sandbox test skips, and says why, when the `sandbox-driver-host` plugin
executable is not on `PATH`.
Run them with:
```sh
ulimit -n 4096 && cargo nextest run -p fabro-petri
```

View file

@ -0,0 +1,17 @@
//! Fabro's adapters over Petri, the workflow engine Fabro runs its workflows
//! on.
//!
//! This crate is the one place Fabro touches Petri. Every other Fabro crate
//! reaches the engine through the types and functions exported here and never
//! depends on a Petri package itself. That keeps the engine's API surface in
//! one crate, so a Petri pin move is a change to this crate alone.
//!
//! What lives here, as the integration plan lands it:
//!
//! - the run store over Fabro's SQLite database, so Petri's records are the
//! run's source of truth in Fabro's tables;
//! - the platform adapters: hooks, interviews, secrets, output storage, the run
//! tools, the event projection.
//!
//! The Petri packages are pinned by revision in the workspace `Cargo.toml`
//! under `petri_*` keys.

View file

@ -0,0 +1,190 @@
//! A Fabro workflow runs through Petri from this crate: the `hello` bundle in
//! memory on the stub registry, and a command-only workflow on the host
//! sandbox through the real step registry.
//!
//! Every run, stubbed or real, acquires its scope's environment through the
//! sandbox-driver host plugin, so both tests skip when that executable is not
//! found, unless `FABRO_REQUIRE_SANDBOX_PLUGINS` is set. Fabro's CI installs
//! the plugin on `PATH` in the sandbox-plugins job and requires it there.
#![expect(
clippy::disallowed_methods,
reason = "the tests locate the plugin executable through the process environment"
)]
#![expect(clippy::print_stderr, reason = "a skipped test says why on its stderr")]
use std::env;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use petri_execution::host::{self, HostRun};
use petri_execution::inspect::{self, RunInspection};
use petri_frontend_fabro::Fabro;
use petri_runtime::executor::Retention;
use petri_runtime::frontend::CompileInputs;
use petri_runtime::ir::RunStatus;
use petri_runtime::{RunOptions, Runtime};
use petri_store::{Access, MemoryRunStore, RunKey, RunStore as _};
use tokio::fs;
const HOST_PLUGIN: &str = "sandbox-driver-host";
const HOST_PLUGIN_OVERRIDE: &str = "PETRI_SANDBOX_HOST_PLUGIN";
const REQUIRE_ENV: &str = "FABRO_REQUIRE_SANDBOX_PLUGINS";
/// A command-only workflow: one script stage between start and exit.
const COMMAND_WORKFLOW: &str = r#"digraph Command {
graph [goal="Run one command"]
start [shape=Mdiamond]
exit [shape=Msquare]
say [shape=parallelogram, script="echo hello from petri"]
start -> say -> exit
}"#;
const COMMAND_SETTINGS: &str = "_version = 1\n\n[workflow]\ngraph = \"workflow.fabro\"\n";
/// The `.fabro/workflows/hello` bundle checked into this repository.
fn hello_bundle() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../.fabro/workflows/hello")
}
/// The host plugin as Petri's lookup finds it: the override variable, else
/// the executable on `PATH`. `None`, after saying so, when the test should
/// skip; a panic when the environment forbids a skip.
fn host_plugin() -> Option<PathBuf> {
let found = env::var_os(HOST_PLUGIN_OVERRIDE)
.map(PathBuf::from)
.or_else(|| {
env::split_paths(&env::var_os("PATH")?)
.map(|dir| dir.join(HOST_PLUGIN))
.find(|candidate| candidate.is_file())
});
if found.is_none() {
assert!(
env::var_os(REQUIRE_ENV).is_none(),
"{REQUIRE_ENV} is set, but {HOST_PLUGIN} is not on PATH and {HOST_PLUGIN_OVERRIDE} is unset"
);
eprintln!("skipping: {HOST_PLUGIN} is not on PATH and {HOST_PLUGIN_OVERRIDE} is unset");
}
found
}
/// Write a bundle's files into `<root>/.fabro/workflows/<name>` so the
/// frontend sees a bundle root of its own, with no project settings layer
/// above it. Returns the workflow file.
async fn install_bundle(root: &Path, name: &str, files: &[(&str, &str)]) -> PathBuf {
let bundle = root.join(".fabro").join("workflows").join(name);
fs::create_dir_all(&bundle)
.await
.expect("the bundle directory is creatable");
for (file, text) in files {
fs::write(bundle.join(file), text)
.await
.expect("the bundle file is writable");
}
bundle.join("workflow.fabro")
}
fn run_options(run_dir: &Path, key: &str) -> RunOptions {
let mut options = RunOptions::new(run_dir);
options.grace = Duration::from_secs(2);
options.retention = Retention::Never;
options.echo = false;
options.run_key = Some(RunKey::new(key));
options
}
/// Lower `workflow`, run it to completion, and inspect the run through its
/// store.
async fn run_workflow(
rt: &Runtime,
store: &MemoryRunStore,
key: &str,
workflow: &Path,
) -> RunInspection {
let lowered = rt
.check(workflow, None, None, &CompileInputs::new())
.expect("the workflow file loads");
let graph = lowered
.graph
.unwrap_or_else(|| panic!("the workflow lowers: {:?}", lowered.diagnostics));
let host_run = HostRun::new(graph).with_children(lowered.children);
let report = host::run_configured(rt, host_run, |_, _| {})
.await
.expect("the run completes");
assert_eq!(
report.status,
RunStatus::Success,
"errors: {:?}; history: {:#?}",
report.state.errors(),
report.state.history()
);
let logs = store
.open(&RunKey::new(key), Access::Read)
.await
.expect("the run opens for reading");
inspect::inspect_run(&*logs)
.await
.expect("the stored run inspects")
}
/// The `hello` bundle, whose one stage is a prompt, completes on the stub
/// registry with no model, and its record in the memory store says so. The
/// stubbed stages never run a command, but the run still takes its host
/// scope through the plugin.
#[tokio::test]
async fn the_hello_bundle_runs_in_memory_on_the_stub_registry() {
if host_plugin().is_none() {
return;
}
let root = tempfile::tempdir().expect("a temp dir");
let bundle = hello_bundle();
let workflow_text = fs::read_to_string(bundle.join("workflow.fabro"))
.await
.expect("the hello workflow is checked in");
let settings_text = fs::read_to_string(bundle.join("workflow.toml"))
.await
.expect("the hello settings are checked in");
let workflow = install_bundle(root.path(), "hello", &[
("workflow.fabro", &workflow_text),
("workflow.toml", &settings_text),
])
.await;
let store = Arc::new(MemoryRunStore::new());
let rt = petri_attractor_steps::register_stubs(Runtime::standard().frontend(Fabro::new()))
.store(store.clone())
.options(run_options(&root.path().join("run"), "hello"));
let inspection = run_workflow(&rt, &store, "hello", &workflow).await;
assert!(inspection.complete, "{:?}", inspection.incomplete);
assert_eq!(inspection.status.as_deref(), Some("success"));
assert_eq!(inspection.run_key, RunKey::new("hello"));
assert_eq!(inspection.executions.len(), 1);
}
/// A command-only workflow runs its script on the host sandbox through the
/// real step registry, and its record in the memory store says so.
#[tokio::test]
async fn a_command_workflow_runs_on_the_host_sandbox() {
if host_plugin().is_none() {
return;
}
let root = tempfile::tempdir().expect("a temp dir");
let workflow = install_bundle(root.path(), "command", &[
("workflow.fabro", COMMAND_WORKFLOW),
("workflow.toml", COMMAND_SETTINGS),
])
.await;
let store = Arc::new(MemoryRunStore::new());
let rt = petri_attractor_steps::register(Runtime::standard().frontend(Fabro::new()))
.store(store.clone())
.options(run_options(&root.path().join("run"), "command"));
let inspection = run_workflow(&rt, &store, "command", &workflow).await;
assert!(inspection.complete, "{:?}", inspection.incomplete);
assert_eq!(inspection.status.as_deref(), Some("success"));
assert_eq!(inspection.run_key, RunKey::new("command"));
assert_eq!(inspection.executions.len(), 1);
}