mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-01 02:04:24 +00:00
Hand Petri only the environment keys it reads
The settings layer carried every key of every catalog environment, so a run in an environment with `lifecycle`, `labels`, `cwd`, `network` or a Dockerfile warned `ignored.workflow_toml.environments.<id>.<key>` on every admit. Those keys are the platform's and stay with the server's own resolution; the layer now carries the provider, `image.docker` under `docker` and `daytona`, `resources` under `daytona`, and `env`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
22cc96da6b
commit
01713c6aac
1 changed files with 101 additions and 2 deletions
|
|
@ -32,7 +32,9 @@ use std::collections::HashMap;
|
|||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use fabro_config::{Home, SettingsLayer, Storage};
|
||||
use fabro_config::{
|
||||
EnvironmentImageLayer, EnvironmentLayer, Home, MergeMap, SettingsLayer, Storage,
|
||||
};
|
||||
use fabro_interview::ControlInterviewer;
|
||||
use fabro_petri::controls::RunControls;
|
||||
use fabro_petri::engine::{self, Conclusion, Execution, RunRequest};
|
||||
|
|
@ -111,7 +113,7 @@ pub(crate) fn runtime_spec(
|
|||
fn settings_layer_toml(state: &AppState) -> Option<String> {
|
||||
let layer = SettingsLayer {
|
||||
version: Some(1),
|
||||
environments: (*state.environment_store().catalog_layer()).clone(),
|
||||
environments: petri_environments(&state.environment_store().catalog_layer()),
|
||||
run: Some((*state.manifest_run_defaults()).clone()),
|
||||
..SettingsLayer::default()
|
||||
};
|
||||
|
|
@ -124,6 +126,47 @@ fn settings_layer_toml(state: &AppState) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The catalog with only the keys Petri reads on each environment: the
|
||||
/// provider, `image.docker` under `docker` and `daytona`, `resources`
|
||||
/// under `daytona`, and `env`. The rest is the platform's (`cwd`,
|
||||
/// `network`, `lifecycle`, `labels`, `image.dockerfile`, resources the host
|
||||
/// and Docker providers run without, an image the host runs without) and
|
||||
/// stays with the server's own resolution; handing it to Petri would only
|
||||
/// warn `ignored.workflow_toml.environments.<id>.<key>` on every admit.
|
||||
fn petri_environments(catalog: &MergeMap<EnvironmentLayer>) -> MergeMap<EnvironmentLayer> {
|
||||
MergeMap(
|
||||
catalog
|
||||
.0
|
||||
.iter()
|
||||
.map(|(id, environment)| (id.clone(), petri_environment(environment)))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn petri_environment(environment: &EnvironmentLayer) -> EnvironmentLayer {
|
||||
let provider = environment.provider.as_deref();
|
||||
let image = environment
|
||||
.image
|
||||
.as_ref()
|
||||
.filter(|_| provider != Some("local"))
|
||||
.and_then(|image| image.docker.clone())
|
||||
.map(|docker| EnvironmentImageLayer {
|
||||
docker: Some(docker),
|
||||
dockerfile: None,
|
||||
});
|
||||
let resources = environment
|
||||
.resources
|
||||
.clone()
|
||||
.filter(|_| provider == Some("daytona"));
|
||||
EnvironmentLayer {
|
||||
provider: environment.provider.clone(),
|
||||
image,
|
||||
resources,
|
||||
env: environment.env.clone(),
|
||||
..EnvironmentLayer::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// The server's MCP catalog as the text the Fabro frontend resolves
|
||||
/// `[run.agent.mcps.<name>] id = "..."` references against: one table per
|
||||
/// definition, keyed by its id, in the inline `[run.agent.mcps.<name>]`
|
||||
|
|
@ -672,6 +715,62 @@ mod tests {
|
|||
assert_eq!(table["browser"]["tool_timeout"].as_str(), Some("90s"));
|
||||
}
|
||||
|
||||
/// Each catalog environment is serialized with only the keys Petri
|
||||
/// reads, so a run never warns about the platform's keys.
|
||||
#[test]
|
||||
fn the_serialized_catalog_holds_only_the_keys_petri_reads() {
|
||||
let catalog: MergeMap<EnvironmentLayer> = MergeMap(HashMap::from([
|
||||
(
|
||||
"docker".to_string(),
|
||||
toml::from_str(
|
||||
"provider = \"docker\"\ncwd = \"/srv\"\n\
|
||||
[image]\ndocker = \"img:1\"\ndockerfile = \"FROM img:1\"\n\
|
||||
[resources]\ncpu = 2\nmemory = \"4GB\"\n\
|
||||
[network]\nmode = \"block\"\n[lifecycle]\npreserve = true\n\
|
||||
[labels]\nteam = \"x\"\n[env]\nLANG = \"C\"\n",
|
||||
)
|
||||
.expect("a docker environment"),
|
||||
),
|
||||
(
|
||||
"big".to_string(),
|
||||
toml::from_str(
|
||||
"provider = \"daytona\"\n[image]\ndockerfile = \"FROM x\"\n\
|
||||
[resources]\ncpu = 8\n",
|
||||
)
|
||||
.expect("a daytona environment"),
|
||||
),
|
||||
(
|
||||
"local".to_string(),
|
||||
toml::from_str("provider = \"local\"\n[image]\ndocker = \"img:1\"\n")
|
||||
.expect("a local environment"),
|
||||
),
|
||||
]));
|
||||
let text = toml::to_string(&SettingsLayer {
|
||||
version: Some(1),
|
||||
environments: petri_environments(&catalog),
|
||||
..SettingsLayer::default()
|
||||
})
|
||||
.expect("the layer serializes");
|
||||
let table: toml::Table = text.parse().expect("the layer text is TOML");
|
||||
let environments = table["environments"].as_table().expect("environments");
|
||||
let keys = |id: &str| -> Vec<String> {
|
||||
let mut keys: Vec<String> = environments[id]
|
||||
.as_table()
|
||||
.expect("a table")
|
||||
.iter()
|
||||
.flat_map(|(key, value)| match value.as_table() {
|
||||
Some(nested) => nested.keys().map(|k| format!("{key}.{k}")).collect(),
|
||||
None => vec![key.clone()],
|
||||
})
|
||||
.collect();
|
||||
keys.sort();
|
||||
keys
|
||||
};
|
||||
assert_eq!(keys("docker"), ["env.LANG", "image.docker", "provider"]);
|
||||
assert_eq!(keys("big"), ["provider", "resources.cpu"]);
|
||||
assert_eq!(keys("local"), ["provider"]);
|
||||
}
|
||||
|
||||
/// The settings layer carries the server's `[run]` defaults and its
|
||||
/// environment catalog as `[environments.<id>]` tables.
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue