diff --git a/lib/components/fabro-manifest/src/supplied_workflow.rs b/lib/components/fabro-manifest/src/supplied_workflow.rs index 9f247294c..0970bf9ff 100644 --- a/lib/components/fabro-manifest/src/supplied_workflow.rs +++ b/lib/components/fabro-manifest/src/supplied_workflow.rs @@ -254,6 +254,46 @@ mod tests { } } + #[test] + fn config_entrypoint_must_be_workflow_toml_beside_its_graph() { + let config = "_version = 1\n[workflow]\ngraph = \"g.fabro\"\n[run]\ngoal = \"hello\"\n"; + let accepted = supplied("sub/workflow.toml", &[ + ("sub/workflow.toml", config), + ("sub/g.fabro", "digraph W {}"), + ]); + let root = collect(&accepted) + .versions() + .last() + .unwrap() + .1 + .version() + .clone(); + assert!(root.files().contains_key(&root.config_path())); + + // Same tree under another config name: runtime would never read it. + let renamed = supplied("sub/run.toml", &[ + ("sub/run.toml", config), + ("sub/g.fabro", "digraph W {}"), + ]); + let error = + collect_supplied_workflow_versions(&renamed.entrypoint, &renamed.files).unwrap_err(); + assert!( + format!("{error:#}").contains("must be `sub/workflow.toml`"), + "{error:#}" + ); + // A config that selects a graph in another directory is not its sibling. + let elsewhere = supplied("sub/workflow.toml", &[ + ( + "sub/workflow.toml", + "_version = 1\n[workflow]\ngraph = \"../g.fabro\"\n", + ), + ("g.fabro", "digraph W {}"), + ]); + assert!( + collect_supplied_workflow_versions(&elsewhere.entrypoint, &elsewhere.files).is_err() + ); + } + #[test] fn preserves_literal_scripts_without_executing() { let directory = tempfile::tempdir().unwrap(); diff --git a/lib/components/fabro-manifest/src/workflow_bundler.rs b/lib/components/fabro-manifest/src/workflow_bundler.rs index 9eb0add4a..98202fbe1 100644 --- a/lib/components/fabro-manifest/src/workflow_bundler.rs +++ b/lib/components/fabro-manifest/src/workflow_bundler.rs @@ -97,9 +97,22 @@ impl<'a> WorkflowBundler<'a> { let source = self.read_package_file(&location.graph)?; let config = if let Some(workflow_toml_path) = location.toml.as_ref() { + let config_path = manifest_path_from_absolute(workflow_toml_path, self.package_root)?; + if self.workflow_version_projection { + // A version's config is read at run time from the fixed + // sibling path only, so a config file under any other name + // would be registered and then silently ignored. + let expected = dot_path.parent_or_dot().join("workflow.toml"); + if config_path.as_path() != expected { + bail!( + "workflow configuration `{config_path}` must be `{}` beside its graph \ + `{dot_path}`", + expected.display() + ); + } + } Some(types::ManifestWorkflowConfig { - path: manifest_path_from_absolute(workflow_toml_path, self.package_root)? - .to_string(), + path: config_path.to_string(), source: self.read_package_file(workflow_toml_path)?, }) } else {