mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-01 02:04:24 +00:00
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:
parent
170291b9f7
commit
7eb5ca502c
8 changed files with 627 additions and 1 deletions
3
.github/workflows/rust.yml
vendored
3
.github/workflows/rust.yml
vendored
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
346
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
12
Cargo.toml
12
Cargo.toml
|
|
@ -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"
|
||||
|
|
|
|||
26
lib/components/fabro-petri/Cargo.toml
Normal file
26
lib/components/fabro-petri/Cargo.toml
Normal 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"] }
|
||||
33
lib/components/fabro-petri/README.md
Normal file
33
lib/components/fabro-petri/README.md
Normal 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
|
||||
```
|
||||
17
lib/components/fabro-petri/src/lib.rs
Normal file
17
lib/components/fabro-petri/src/lib.rs
Normal 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.
|
||||
190
lib/components/fabro-petri/tests/runs.rs
Normal file
190
lib/components/fabro-petri/tests/runs.rs
Normal 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);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue