fabro/lib/components/fabro-workflow/src/run_materialization.rs
Bryan Helmkamp c06c60214a
refactor: simplify readiness-fallback plumbing
The fallback provider set was always catalog.all_provider_ids(), computed
at every call site and threaded through five layers alongside the catalog
itself. Fold it into Catalog::resolve_selection_with_catalog_fallback and
carry only a catalog_fallback flag through the transform/validate/
materialize entry points.

- materialize_run delegates to resolve_run_model again instead of
  re-inlining its provider normalization and selection
- run_preflight derives ready providers from llm_result instead of
  taking both, so callers cannot pass inconsistent pairs; the legacy
  tests now exercise the production ready-first routing path
- AppState::resolve_llm_client_with_ready_ids replaces three copies of
  resolve-then-extract-provider-ids, and ready_llm_provider_ids
  delegates to it
- the unreachable "model resolution failed" preflight check becomes an
  invariant error where the materialized run is produced
- validate_prepared_manifest_with_vars/_for_preflight share the
  ValidateInput construction

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 08:49:01 -04:00

101 lines
3.1 KiB
Rust

use std::collections::HashSet;
use fabro_graphviz::graph::Graph;
use fabro_model::{Catalog, ModelSelectionError, ProviderId};
use fabro_types::WorkflowSettings;
use fabro_types::settings::InterpString;
use fabro_types::settings::run::RunGoal;
use crate::error::Error;
pub fn materialize_run(
settings: WorkflowSettings,
graph: &Graph,
catalog: &Catalog,
configured_providers: &[ProviderId],
) -> Result<WorkflowSettings, Error> {
materialize_run_with_eligible_providers(settings, graph, catalog, configured_providers, false)
}
/// Materialize while resolving the run model against the ready providers
/// first, falling back to the full catalog only for provider-readiness
/// selection failures.
pub fn materialize_run_with_ready_providers(
settings: WorkflowSettings,
graph: &Graph,
catalog: &Catalog,
ready_providers: &[ProviderId],
) -> Result<WorkflowSettings, Error> {
materialize_run_with_eligible_providers(settings, graph, catalog, ready_providers, true)
}
fn materialize_run_with_eligible_providers(
mut settings: WorkflowSettings,
graph: &Graph,
catalog: &Catalog,
eligible_providers: &[ProviderId],
catalog_fallback: bool,
) -> Result<WorkflowSettings, Error> {
let configured_model = settings.run.model.name.take();
let configured_provider = settings.run.model.provider.take();
let graph_provider = graph
.attrs
.get("default_provider")
.and_then(|value| value.as_str())
.map(str::to_string);
let graph_model = graph
.attrs
.get("default_model")
.and_then(|value| value.as_str())
.map(str::to_string);
let provider = configured_provider.or(graph_provider);
let model = configured_model.or(graph_model);
let eligible = eligible_providers.iter().cloned().collect::<HashSet<_>>();
let (resolved_model, resolved_provider) = resolve_run_model(
catalog,
&eligible,
model.as_deref(),
provider.as_deref(),
catalog_fallback,
)?;
settings.run.model.name = Some(resolved_model);
settings.run.model.provider = Some(resolved_provider.into_inner());
let goal = graph.goal().to_string();
settings.run.goal = if goal.is_empty() {
None
} else {
Some(RunGoal::Inline(InterpString::parse(&goal)))
};
if settings
.run
.pull_request
.as_ref()
.is_some_and(|pull_request| !pull_request.enabled)
{
settings.run.pull_request = None;
}
Ok(settings)
}
pub(crate) fn resolve_run_model(
catalog: &Catalog,
eligible: &HashSet<ProviderId>,
model: Option<&str>,
provider: Option<&str>,
catalog_fallback: bool,
) -> Result<(String, ProviderId), ModelSelectionError> {
let provider = provider
.filter(|provider| !provider.is_empty())
.map(ProviderId::new);
let selected = if catalog_fallback {
catalog.resolve_selection_with_catalog_fallback(model, provider.as_ref(), eligible)?
} else {
catalog.resolve_selection(model, provider.as_ref(), eligible)?
};
Ok((selected.model, selected.provider))
}