mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-06 08:18:58 +00:00
## Summary
Moves environment definitions out of project/workflow TOML config and
into server-owned files, introducing the `fabro-environment` crate and
enforcing source-aware validation so project/workflow/user configs can
no longer define environment catalogs.
### What changed
**New `fabro-environment` crate** — workspace crate wired into
`fabro-cli` and `fabro-server`. Exposes a `seeded_catalog_layer()` that
CLI commands inject at the call site to fill the environment catalog
that settings resolution requires.
**Config environments are now migration-only** — `defaults.toml` no
longer ships a built-in `[environments.*]` catalog. Instead:
- `SettingsSource` enum tags every parsed layer (ActiveSettings,
Project, Workflow, DirectRun, User).
- `validate_settings_source` rejects `[environments.<id>]` in any source
except `ActiveSettings` with a targeted message: `[environments.<id>] is
now server-managed; move this definition to the server environments
directory`.
- TOML-provided
`run.environment.{image,resources,network,lifecycle,labels,volumes,env}`
overrides are also rejected; only `run.environment.id` survives.
**New migration** (`2026052801_settings_environments_to_server_files`) —
chains after the existing legacy-sandbox migration. Extracts
`[environments.*]` entries from `settings.toml` into sibling
`environments/<id>.toml` files, writes a
`.settings-environments-migration.bak` backup, and fails without
modifying any file if a target already exists.
**Builder API additions** —
`RunSettingsBuilder::load_from_with_catalog`,
`load_default_with_catalog`, `from_toml_with_catalog` let callers inject
a server-side catalog; the bare `from_toml` path now errors if no
catalog is present and a named environment is selected.
`WorkflowSettingsBuilder` test helpers in `src/tests/mod.rs` centralise
catalog injection across all config tests.
**`.fabro/project.toml`** — removed the inline
`[environments.fabro-dev]` block (environment definition now lives
server-side).
### Key design decisions
- CLI offline commands (graph, preflight, validate) use
`seeded_catalog_layer()` as a local stand-in until a running server is
available — matches the pre-existing behaviour without regressing
offline workflows.
- `load_settings_path` no longer runs migrations for non-ActiveSettings
sources, preventing project/workflow files from accidentally triggering
file-system writes.
- The `MigrationReport` type is now the new migration's
`SettingsEnvironmentsMigrationReport` (exposes `contents: String`
instead of a parsed layer), keeping `load.rs` simpler and decoupled from
layer parsing.
### Fabro Details
<details>
<summary>Ran 9 stages in 143m 23s for $105.27</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 11s | – | 0 |
| preflight_lint | 2m 23s | – | 0 |
| implement | 28m 27s | – | 0 |
| simplify_opus | 37m 27s | $53.28 | 0 |
| simplify_gpt | 20m 50s | $12.14 | 0 |
| verify | 6m 3s | – | 0 |
| fixup | 45m 15s | $39.84 | 0 |
| **Total** | **143m 23s** | **$105.27** | **0** |
</details>
<details>
<summary>Ran <code>ImplementPlan.fabro</code> (11 nodes and 14
edges)</summary>
```dot
digraph ImplementPlan {
graph [
goal="Implement and simplify",
model_stylesheet="
* { model: claude-opus-4-7; }
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
toolchain [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", max_retries=0]
fix_lints [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
implement [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD.", model="gpt-55", reasoning_effort="xhigh"]
simplify_opus [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
simplify_gpt [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"]
verify [label="Verify", shape=parallelogram, script="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && { command -v rg >/dev/null 2>&1 || { echo 'rg is required for verify'; exit 127; }; } && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 2>&1", goal_gate=true, retry_target="fixup"]
fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.", max_visits=3]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=succeeded"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=succeeded"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=succeeded"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify_opus -> simplify_gpt -> verify
verify -> exit [condition="outcome=succeeded"]
verify -> fixup
fixup -> verify
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
133 lines
4.1 KiB
TOML
133 lines
4.1 KiB
TOML
[package]
|
|
name = "fabro-cli"
|
|
edition.workspace = true
|
|
version.workspace = true
|
|
publish = false
|
|
license.workspace = true
|
|
description = "Unified CLI for the Fabro AI framework"
|
|
|
|
[[bin]]
|
|
name = "fabro"
|
|
path = "src/main.rs"
|
|
|
|
[features]
|
|
default = []
|
|
sleep_inhibitor = ["dep:core-foundation"]
|
|
|
|
[lints]
|
|
workspace = true
|
|
|
|
[dependencies]
|
|
fabro-auth = { path = "../fabro-auth" }
|
|
fabro-config = { path = "../fabro-config" }
|
|
fabro-environment = { path = "../fabro-environment" }
|
|
fabro-llm = { path = "../fabro-llm" }
|
|
fabro-model = { path = "../fabro-model" }
|
|
fabro-oauth = { path = "../fabro-oauth" }
|
|
fabro-github = { path = "../fabro-github" }
|
|
fabro-agent = { path = "../fabro-agent" }
|
|
fabro-dump = { path = "../fabro-dump" }
|
|
fabro-hooks = { path = "../fabro-hooks" }
|
|
fabro-install = { path = "../fabro-install" }
|
|
fabro-interview = { path = "../fabro-interview" }
|
|
fabro-mcp = { path = "../fabro-mcp" }
|
|
fabro-mcp-server = { path = "../fabro-mcp-server" }
|
|
fabro-manifest = { path = "../fabro-manifest" }
|
|
fabro-proc = { path = "../fabro-proc" }
|
|
fabro-sandbox = { path = "../fabro-sandbox", features = ["daytona"] }
|
|
fabro-checkpoint = { path = "../fabro-checkpoint" }
|
|
fabro-graphviz = { path = "../fabro-graphviz" }
|
|
fabro-validate = { path = "../fabro-validate" }
|
|
fabro-workflow = { path = "../fabro-workflow" }
|
|
fabro-server = { path = "../fabro-server" }
|
|
fabro-client = { path = "../fabro-client" }
|
|
fabro-api = { path = "../fabro-api" }
|
|
fabro-telemetry = { path = "../fabro-telemetry" }
|
|
fabro-store = { path = "../fabro-store" }
|
|
fabro-vault = { path = "../fabro-vault" }
|
|
fabro-types = { path = "../fabro-types", features = ["clap"] }
|
|
fabro-redact.workspace = true
|
|
fabro-util = { path = "../fabro-util" }
|
|
fabro-http.workspace = true
|
|
fabro-static.workspace = true
|
|
fabro-template = { path = "../fabro-template" }
|
|
fabro-tool = { path = "../fabro-tool" }
|
|
clap.workspace = true
|
|
clap_complete.workspace = true
|
|
cli-table.workspace = true
|
|
console.workspace = true
|
|
indicatif.workspace = true
|
|
daytona-sdk.workspace = true
|
|
anyhow.workspace = true
|
|
miette.workspace = true
|
|
dotenvy.workspace = true
|
|
tokio.workspace = true
|
|
tokio-tungstenite.workspace = true
|
|
tracing.workspace = true
|
|
tracing-subscriber.workspace = true
|
|
tracing-appender.workspace = true
|
|
chrono = { workspace = true, features = ["serde"] }
|
|
dirs.workspace = true
|
|
fs2.workspace = true
|
|
serde.workspace = true
|
|
thiserror.workspace = true
|
|
toml.workspace = true
|
|
toml_edit.workspace = true
|
|
futures.workspace = true
|
|
regex.workspace = true
|
|
semver.workspace = true
|
|
progenitor-client = "0.13"
|
|
async-trait.workspace = true
|
|
jsonwebtoken.workspace = true
|
|
base64.workspace = true
|
|
ulid.workspace = true
|
|
scopeguard = "1"
|
|
rustls = { version = "0.23", default-features = false, features = ["std", "ring"] }
|
|
ring = "0.17"
|
|
x509-parser = "0.16"
|
|
rand.workspace = true
|
|
dialoguer.workspace = true
|
|
git2.workspace = true
|
|
axum.workspace = true
|
|
serde_json.workspace = true
|
|
serde_yaml = "0.9"
|
|
tempfile = "3"
|
|
sha2.workspace = true
|
|
shlex = "1"
|
|
walkdir.workspace = true
|
|
object_store.workspace = true
|
|
bytes.workspace = true
|
|
tokio-util.workspace = true
|
|
libc = "0.2"
|
|
nix = { version = "0.30", features = ["fs"] }
|
|
|
|
[target.'cfg(target_os = "macos")'.dependencies]
|
|
core-foundation = { version = "0.9", optional = true }
|
|
|
|
# Vendor openssl only for musl targets. daytona-sdk transitively pulls
|
|
# native-tls via reqwest, which needs libssl. On glibc runners the system
|
|
# libssl is used; on musl runners we compile openssl from source.
|
|
[target.'cfg(target_env = "musl")'.dependencies]
|
|
openssl = { version = "0.10", features = ["vendored"] }
|
|
|
|
[build-dependencies]
|
|
fabro-build-support = { path = "../build-support" }
|
|
chrono = { workspace = true }
|
|
|
|
[dev-dependencies]
|
|
assert_cmd = "2"
|
|
fabro-acp = { path = "../fabro-acp", features = ["test-support"] }
|
|
fabro-build-support = { path = "../build-support" }
|
|
fabro-server = { path = "../fabro-server", features = ["test-support"] }
|
|
insta = { workspace = true, features = ["filters"] }
|
|
paste = "1"
|
|
predicates = "3"
|
|
serde_json.workspace = true
|
|
tempfile = "3"
|
|
temp-env = "0.3"
|
|
httpmock = "0.8"
|
|
fabro-test = { workspace = true }
|
|
fabro-macros = { path = "../fabro-macros" }
|
|
hkdf.workspace = true
|
|
reqwest = { workspace = true, features = ["cookies"] }
|
|
tokio = { workspace = true, features = ["test-util", "macros"] }
|