mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-11 22:53:00 +00:00
Snapshot run settings for execution
This commit is contained in:
parent
bafab58442
commit
947db0713c
23 changed files with 478 additions and 607 deletions
|
|
@ -6,7 +6,6 @@ use fabro_config::FabroSettingsExt;
|
|||
use fabro_interview::FileInterviewer;
|
||||
use fabro_store::RuntimeState;
|
||||
use fabro_workflows::event::EventEmitter;
|
||||
use fabro_workflows::git::GitAuthor;
|
||||
use fabro_workflows::operations::{
|
||||
StartServices, open_or_hydrate_run, resume as resume_run, start as start_run,
|
||||
};
|
||||
|
|
@ -14,8 +13,6 @@ use fabro_workflows::records::{RunRecord, RunRecordExt};
|
|||
|
||||
use crate::shared;
|
||||
use crate::store;
|
||||
use crate::user_config;
|
||||
|
||||
pub(crate) async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bool) -> Result<()> {
|
||||
let _ = fabro_proctitle::init();
|
||||
|
||||
|
|
@ -24,7 +21,6 @@ pub(crate) async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bo
|
|||
});
|
||||
|
||||
let run_record = RunRecord::load(&run_dir)?;
|
||||
let cli_settings = user_config::load_user_settings()?;
|
||||
let on_node: fabro_workflows::OnNodeCallback = Some({
|
||||
let short_id = super::short_run_id(&run_record.run_id).to_string();
|
||||
fabro_proctitle::set(&format!("fabro: {short_id}"));
|
||||
|
|
@ -35,11 +31,7 @@ pub(crate) async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bo
|
|||
let store = store::build_store(&run_record.settings.storage_dir())?;
|
||||
let run_store = open_or_hydrate_run(store.as_ref(), &run_dir).await?;
|
||||
|
||||
let github_app = shared::github::build_github_app_credentials(cli_settings.app_id());
|
||||
let git_author = GitAuthor::from_options(
|
||||
cli_settings.git_author().and_then(|a| a.name.clone()),
|
||||
cli_settings.git_author().and_then(|a| a.email.clone()),
|
||||
);
|
||||
let github_app = shared::github::build_github_app_credentials(run_record.settings.app_id());
|
||||
let runtime_state = RuntimeState::new(&run_dir);
|
||||
|
||||
let services = StartServices {
|
||||
|
|
@ -51,7 +43,6 @@ pub(crate) async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bo
|
|||
runtime_state.interview_claim_path(),
|
||||
)),
|
||||
run_store,
|
||||
git_author,
|
||||
github_app,
|
||||
on_node,
|
||||
registry_override: None,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,5 @@
|
|||
use base64::Engine as _;
|
||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||
use fabro_github::GitHubAppCredentials;
|
||||
|
||||
pub(crate) fn build_github_app_credentials(app_id: Option<&str>) -> Option<GitHubAppCredentials> {
|
||||
let app_id = app_id?;
|
||||
let raw = std::env::var("GITHUB_APP_PRIVATE_KEY").ok()?;
|
||||
let private_key_pem = if raw.starts_with("-----") {
|
||||
raw
|
||||
} else {
|
||||
let pem_bytes = BASE64_STANDARD.decode(&raw).ok()?;
|
||||
String::from_utf8(pem_bytes).ok()?
|
||||
};
|
||||
Some(GitHubAppCredentials {
|
||||
app_id: app_id.to_string(),
|
||||
private_key_pem,
|
||||
})
|
||||
GitHubAppCredentials::from_env(app_id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ jsonwebtoken.workspace = true
|
|||
chrono.workspace = true
|
||||
tracing.workspace = true
|
||||
tokio = { workspace = true }
|
||||
base64.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["test-util", "macros"] }
|
||||
base64.workspace = true
|
||||
|
|
|
|||
|
|
@ -53,6 +53,24 @@ pub struct GitHubAppCredentials {
|
|||
pub private_key_pem: String,
|
||||
}
|
||||
|
||||
impl GitHubAppCredentials {
|
||||
pub fn from_env(app_id: Option<&str>) -> Option<Self> {
|
||||
let app_id = app_id?;
|
||||
let raw = std::env::var("GITHUB_APP_PRIVATE_KEY").ok()?;
|
||||
let private_key_pem = if raw.starts_with("-----") {
|
||||
raw
|
||||
} else {
|
||||
let pem_bytes =
|
||||
base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &raw).ok()?;
|
||||
String::from_utf8(pem_bytes).ok()?
|
||||
};
|
||||
Some(Self {
|
||||
app_id: app_id.to_string(),
|
||||
private_key_pem,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP method used in GitHub API calls.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum HttpMethod {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ use std::sync::{Arc, RwLock};
|
|||
use std::time::Duration;
|
||||
|
||||
use fabro_config::server::{load_server_settings, resolve_storage_dir};
|
||||
use fabro_model::{Catalog, Provider};
|
||||
use fabro_util::terminal::Styles;
|
||||
use fabro_workflows::git::GitAuthor;
|
||||
use object_store::local::LocalFileSystem;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::time::interval;
|
||||
|
|
@ -21,7 +19,6 @@ use crate::server::{build_router, create_app_state_with_store, spawn_scheduler};
|
|||
use crate::tls::{ClientAuth, build_rustls_config, serve_tls};
|
||||
use fabro_llm::client::Client as LlmClient;
|
||||
use fabro_sandbox::SandboxProvider;
|
||||
use fabro_workflows::pipeline::LlmSpec;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct ServeArgs {
|
||||
|
|
@ -58,6 +55,27 @@ pub struct ServeArgs {
|
|||
pub config: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn apply_serve_overrides(
|
||||
base: &FabroSettings,
|
||||
args: &ServeArgs,
|
||||
dry_run_mode: bool,
|
||||
) -> FabroSettings {
|
||||
let mut settings = base.clone();
|
||||
if dry_run_mode {
|
||||
settings.dry_run = Some(true);
|
||||
}
|
||||
if let Some(ref model) = args.model {
|
||||
settings.llm.get_or_insert_default().model = Some(model.clone());
|
||||
}
|
||||
if let Some(ref provider) = args.provider {
|
||||
settings.llm.get_or_insert_default().provider = Some(provider.clone());
|
||||
}
|
||||
if let Some(sandbox) = args.sandbox {
|
||||
settings.sandbox.get_or_insert_default().provider = Some(sandbox.to_string());
|
||||
}
|
||||
settings
|
||||
}
|
||||
|
||||
/// Start the HTTP API server.
|
||||
///
|
||||
/// # Errors
|
||||
|
|
@ -93,33 +111,16 @@ pub async fn serve_command(
|
|||
};
|
||||
|
||||
// Initialize data directory and SQLite database
|
||||
let config_path = args.config;
|
||||
let server_settings = load_server_settings(config_path.as_deref())?;
|
||||
let data_dir = storage_dir_override.unwrap_or_else(|| resolve_storage_dir(&server_settings));
|
||||
let config_path = args.config.clone();
|
||||
let disk_settings = load_server_settings(config_path.as_deref())?;
|
||||
let data_dir = storage_dir_override.unwrap_or_else(|| resolve_storage_dir(&disk_settings));
|
||||
|
||||
// Shared config for live reloading
|
||||
let shared_settings = Arc::new(RwLock::new(server_settings));
|
||||
|
||||
// CLI overrides take precedence over config file values, even after reload
|
||||
let cli_model = args.model;
|
||||
let cli_provider = args.provider;
|
||||
|
||||
// Build registry factory that reads live config
|
||||
let settings_for_factory = Arc::clone(&shared_settings);
|
||||
let factory = move || {
|
||||
let (model, provider_enum) = resolve_model_provider(
|
||||
&settings_for_factory,
|
||||
cli_model.as_deref(),
|
||||
cli_provider.as_deref(),
|
||||
);
|
||||
LlmSpec {
|
||||
model,
|
||||
provider: provider_enum,
|
||||
fallback_chain: Vec::new(),
|
||||
mcp_servers: Vec::new(),
|
||||
dry_run: dry_run_mode,
|
||||
}
|
||||
};
|
||||
let shared_settings = Arc::new(RwLock::new(apply_serve_overrides(
|
||||
&disk_settings,
|
||||
&args,
|
||||
dry_run_mode,
|
||||
)));
|
||||
std::fs::create_dir_all(&data_dir)?;
|
||||
let db = fabro_db::connect(&data_dir.join("fabro.db")).await?;
|
||||
fabro_db::initialize_db(&db).await?;
|
||||
|
|
@ -141,18 +142,6 @@ pub async fn serve_command(
|
|||
(auth_mode, client_auth, max_concurrent_runs)
|
||||
};
|
||||
|
||||
let git_author = {
|
||||
let cfg = shared_settings.read().expect("config lock poisoned");
|
||||
let author = cfg.git_author();
|
||||
GitAuthor::from_options(
|
||||
author.and_then(|a| a.name.clone()),
|
||||
author.and_then(|a| a.email.clone()),
|
||||
)
|
||||
};
|
||||
let hooks = {
|
||||
let cfg = shared_settings.read().expect("config lock poisoned");
|
||||
cfg.hooks.clone()
|
||||
};
|
||||
let store_path = data_dir.join("store");
|
||||
std::fs::create_dir_all(&store_path)?;
|
||||
let object_store = Arc::new(LocalFileSystem::new_with_prefix(&store_path)?);
|
||||
|
|
@ -161,15 +150,8 @@ pub async fn serve_command(
|
|||
"",
|
||||
Duration::from_millis(5),
|
||||
));
|
||||
let state = create_app_state_with_store(
|
||||
db,
|
||||
factory,
|
||||
dry_run_mode,
|
||||
max_concurrent_runs,
|
||||
git_author,
|
||||
hooks,
|
||||
store,
|
||||
);
|
||||
let state =
|
||||
create_app_state_with_store(db, Arc::clone(&shared_settings), max_concurrent_runs, store);
|
||||
spawn_scheduler(Arc::clone(&state));
|
||||
let router = build_router(state, auth_mode);
|
||||
|
||||
|
|
@ -222,20 +204,32 @@ pub async fn serve_command(
|
|||
// Spawn config polling task
|
||||
let settings_for_poll = Arc::clone(&shared_settings);
|
||||
let config_path_for_poll = config_path.clone();
|
||||
let args_for_poll = ServeArgs {
|
||||
port: args.port,
|
||||
host: args.host.clone(),
|
||||
model: args.model.clone(),
|
||||
provider: args.provider.clone(),
|
||||
dry_run: args.dry_run,
|
||||
sandbox: args.sandbox,
|
||||
max_concurrent_runs: args.max_concurrent_runs,
|
||||
config: config_path.clone(),
|
||||
};
|
||||
tokio::spawn(async move {
|
||||
let mut interval = interval(Duration::from_secs(5));
|
||||
interval.tick().await; // skip first immediate tick
|
||||
loop {
|
||||
interval.tick().await;
|
||||
match load_server_settings(config_path_for_poll.as_deref()) {
|
||||
Ok(new_settings) => {
|
||||
Ok(new_disk_settings) => {
|
||||
let effective =
|
||||
apply_serve_overrides(&new_disk_settings, &args_for_poll, dry_run_mode);
|
||||
let changed = {
|
||||
let cfg = settings_for_poll.read().expect("config lock poisoned");
|
||||
*cfg != new_settings
|
||||
*cfg != effective
|
||||
};
|
||||
if changed {
|
||||
let mut cfg = settings_for_poll.write().expect("config lock poisoned");
|
||||
*cfg = new_settings;
|
||||
*cfg = effective;
|
||||
info!("Server config reloaded");
|
||||
}
|
||||
}
|
||||
|
|
@ -274,50 +268,6 @@ pub async fn serve_command(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve model and provider from shared config, with CLI overrides taking precedence.
|
||||
fn resolve_model_provider(
|
||||
shared_settings: &RwLock<FabroSettings>,
|
||||
cli_model: Option<&str>,
|
||||
cli_provider: Option<&str>,
|
||||
) -> (String, Provider) {
|
||||
let cfg = shared_settings.read().expect("config lock poisoned");
|
||||
let config_provider = cfg.llm.as_ref().and_then(|l| l.provider.as_deref());
|
||||
let config_model = cfg.llm.as_ref().and_then(|l| l.model.as_deref());
|
||||
|
||||
let provider_str = cli_provider.or(config_provider);
|
||||
let model = cli_model
|
||||
.map(std::string::ToString::to_string)
|
||||
.or_else(|| config_model.map(std::string::ToString::to_string))
|
||||
.unwrap_or_else(|| {
|
||||
// Look up default model from catalog for the given provider,
|
||||
// falling back to the best provider with an API key configured.
|
||||
provider_str
|
||||
.and_then(|s| s.parse::<Provider>().ok())
|
||||
.and_then(|p| Catalog::builtin().default_for_provider(p))
|
||||
.unwrap_or_else(|| Catalog::builtin().default_from_env())
|
||||
.id
|
||||
.clone()
|
||||
});
|
||||
|
||||
// Resolve model alias through catalog
|
||||
let (model, provider_str) = match Catalog::builtin().get(&model) {
|
||||
Some(info) => (
|
||||
info.id.clone(),
|
||||
provider_str
|
||||
.map(std::string::ToString::to_string)
|
||||
.or(Some(info.provider.to_string())),
|
||||
),
|
||||
None => (model, provider_str.map(std::string::ToString::to_string)),
|
||||
};
|
||||
|
||||
let provider_enum: Provider = provider_str
|
||||
.as_deref()
|
||||
.and_then(|s| s.parse::<Provider>().ok())
|
||||
.unwrap_or_else(Provider::default_from_env);
|
||||
|
||||
(model, provider_enum)
|
||||
}
|
||||
|
||||
/// Read the GitHub App private key from the environment, decoding base64 if needed.
|
||||
fn read_github_private_key() -> Option<String> {
|
||||
let raw = std::env::var("GITHUB_APP_PRIVATE_KEY").ok()?;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::time::Duration;
|
||||
|
||||
use axum::extract::{self as axum_extract, Path, Query, State};
|
||||
|
|
@ -9,19 +9,17 @@ use axum::response::sse::{Event, KeepAlive, Sse};
|
|||
use axum::response::{IntoResponse, Response};
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Json, Router};
|
||||
use fabro_config::sandbox::SandboxSettings;
|
||||
use fabro_config::FabroSettings;
|
||||
use fabro_llm::client::Client as LlmClient;
|
||||
use fabro_llm::generate::{GenerateParams, generate, generate_object};
|
||||
use fabro_llm::types::{
|
||||
ContentPart, FinishReason, Message as LlmMessage, Request as LlmRequest,
|
||||
Response as LlmResponse, Role, StreamEvent, ToolChoice, ToolDefinition, Usage,
|
||||
};
|
||||
use fabro_retro::retro;
|
||||
use fabro_retro::retro::Retro;
|
||||
use fabro_store::{InMemoryStore, Store};
|
||||
use fabro_util::redact::redact_jsonl_line;
|
||||
use fabro_workflows::error::FabroError;
|
||||
use fabro_workflows::git::GitAuthor;
|
||||
use fabro_workflows::handler::HandlerRegistry;
|
||||
use futures_util::stream;
|
||||
use tokio::sync::broadcast;
|
||||
|
|
@ -42,14 +40,11 @@ use crate::sessions as sessions_mod;
|
|||
use crate::sessions::{SessionStore, new_session_store};
|
||||
use fabro_interview::{Answer, Interviewer, QuestionType, WebInterviewer};
|
||||
use fabro_retro::RetroExt;
|
||||
use fabro_sandbox::SandboxSpec;
|
||||
use fabro_workflows::context::Context;
|
||||
use fabro_workflows::event::{EventEmitter, WorkflowRunEvent};
|
||||
use fabro_workflows::operations::{self, CreateRunInput, WorkflowInput};
|
||||
use fabro_workflows::pipeline::{self, InitOptions, LlmSpec, Persisted, SandboxEnvSpec};
|
||||
use fabro_workflows::pipeline::Persisted;
|
||||
use fabro_workflows::records::{Checkpoint, CheckpointExt};
|
||||
use fabro_workflows::run_options::LifecycleOptions;
|
||||
use fabro_workflows::run_options::RunOptions;
|
||||
|
||||
pub use fabro_api_types::{
|
||||
ApiQuestion, ApiQuestionOption, PaginatedRunList, PaginationMeta,
|
||||
|
|
@ -118,7 +113,6 @@ struct AggregateUsageTotals {
|
|||
by_model: HashMap<String, ModelUsageTotals>,
|
||||
}
|
||||
|
||||
type LlmSpecFactory = dyn Fn() -> LlmSpec + Send + Sync;
|
||||
type RegistryFactoryOverride = dyn Fn(Arc<dyn Interviewer>) -> HandlerRegistry + Send + Sync;
|
||||
|
||||
/// Shared application state for the server.
|
||||
|
|
@ -126,16 +120,19 @@ pub struct AppState {
|
|||
runs: Mutex<HashMap<String, ManagedRun>>,
|
||||
aggregate_usage: Mutex<AggregateUsageTotals>,
|
||||
store: Arc<dyn Store>,
|
||||
llm_spec_factory: Box<LlmSpecFactory>,
|
||||
registry_factory_override: Option<Box<RegistryFactoryOverride>>,
|
||||
pub dry_run: bool,
|
||||
pub db: sqlx::SqlitePool,
|
||||
max_concurrent_runs: usize,
|
||||
scheduler_notify: Notify,
|
||||
pub hooks: Vec<fabro_hooks::HookDefinition>,
|
||||
git_author: GitAuthor,
|
||||
pub sessions: SessionStore,
|
||||
llm_client: OnceCell<LlmClient>,
|
||||
settings: Arc<RwLock<FabroSettings>>,
|
||||
registry_factory_override: Option<Box<RegistryFactoryOverride>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub(crate) fn dry_run(&self) -> bool {
|
||||
self.settings.read().unwrap().dry_run_enabled()
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the axum Router with all run endpoints.
|
||||
|
|
@ -381,103 +378,65 @@ async fn get_aggregate_usage(
|
|||
}
|
||||
|
||||
/// Create an `AppState` with the given LLM spec factory and database pool.
|
||||
pub fn create_app_state(
|
||||
db: sqlx::SqlitePool,
|
||||
llm_spec_factory: impl Fn() -> LlmSpec + Send + Sync + 'static,
|
||||
) -> Arc<AppState> {
|
||||
create_app_state_with_options(
|
||||
db,
|
||||
llm_spec_factory,
|
||||
false,
|
||||
5,
|
||||
GitAuthor::default(),
|
||||
Vec::new(),
|
||||
)
|
||||
pub fn create_app_state(db: sqlx::SqlitePool) -> Arc<AppState> {
|
||||
create_app_state_with_options(db, FabroSettings::default(), 5)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn create_app_state_with_registry_factory(
|
||||
db: sqlx::SqlitePool,
|
||||
llm_spec_factory: impl Fn() -> LlmSpec + Send + Sync + 'static,
|
||||
registry_factory_override: impl Fn(Arc<dyn Interviewer>) -> HandlerRegistry + Send + Sync + 'static,
|
||||
) -> Arc<AppState> {
|
||||
build_app_state(
|
||||
db,
|
||||
Box::new(llm_spec_factory),
|
||||
Arc::new(RwLock::new(FabroSettings::default())),
|
||||
Some(Box::new(registry_factory_override)),
|
||||
false,
|
||||
5,
|
||||
GitAuthor::default(),
|
||||
Vec::new(),
|
||||
Arc::new(InMemoryStore::default()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create an `AppState` with the given database pool, LLM spec factory, dry-run flag, and concurrency limit.
|
||||
/// Create an `AppState` with the given database pool, settings, and concurrency limit.
|
||||
pub fn create_app_state_with_options(
|
||||
db: sqlx::SqlitePool,
|
||||
llm_spec_factory: impl Fn() -> LlmSpec + Send + Sync + 'static,
|
||||
dry_run: bool,
|
||||
settings: FabroSettings,
|
||||
max_concurrent_runs: usize,
|
||||
git_author: GitAuthor,
|
||||
hooks: Vec<fabro_hooks::HookDefinition>,
|
||||
) -> Arc<AppState> {
|
||||
create_app_state_with_store(
|
||||
db,
|
||||
llm_spec_factory,
|
||||
dry_run,
|
||||
Arc::new(RwLock::new(settings)),
|
||||
max_concurrent_runs,
|
||||
git_author,
|
||||
hooks,
|
||||
Arc::new(InMemoryStore::default()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_app_state_with_store(
|
||||
db: sqlx::SqlitePool,
|
||||
llm_spec_factory: impl Fn() -> LlmSpec + Send + Sync + 'static,
|
||||
dry_run: bool,
|
||||
settings: Arc<RwLock<FabroSettings>>,
|
||||
max_concurrent_runs: usize,
|
||||
git_author: GitAuthor,
|
||||
hooks: Vec<fabro_hooks::HookDefinition>,
|
||||
store: Arc<dyn Store>,
|
||||
) -> Arc<AppState> {
|
||||
build_app_state(
|
||||
db,
|
||||
Box::new(llm_spec_factory),
|
||||
None,
|
||||
dry_run,
|
||||
max_concurrent_runs,
|
||||
git_author,
|
||||
hooks,
|
||||
store,
|
||||
)
|
||||
build_app_state(db, settings, None, max_concurrent_runs, store)
|
||||
}
|
||||
|
||||
fn build_app_state(
|
||||
db: sqlx::SqlitePool,
|
||||
llm_spec_factory: Box<LlmSpecFactory>,
|
||||
settings: Arc<RwLock<FabroSettings>>,
|
||||
registry_factory_override: Option<Box<RegistryFactoryOverride>>,
|
||||
dry_run: bool,
|
||||
max_concurrent_runs: usize,
|
||||
git_author: GitAuthor,
|
||||
hooks: Vec<fabro_hooks::HookDefinition>,
|
||||
store: Arc<dyn Store>,
|
||||
) -> Arc<AppState> {
|
||||
Arc::new(AppState {
|
||||
runs: Mutex::new(HashMap::new()),
|
||||
aggregate_usage: Mutex::new(AggregateUsageTotals::default()),
|
||||
store,
|
||||
llm_spec_factory,
|
||||
registry_factory_override,
|
||||
dry_run,
|
||||
db,
|
||||
max_concurrent_runs,
|
||||
scheduler_notify: Notify::new(),
|
||||
hooks,
|
||||
git_author,
|
||||
sessions: new_session_store(),
|
||||
llm_client: OnceCell::new(),
|
||||
settings,
|
||||
registry_factory_override,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -539,15 +498,7 @@ async fn start_run(
|
|||
let run_id = ulid::Ulid::new().to_string();
|
||||
info!(run_id = %run_id, "Run queued");
|
||||
let run_dir = std::env::temp_dir().join(format!("fabro-{}", uuid::Uuid::new_v4()));
|
||||
let settings = fabro_config::FabroSettings {
|
||||
dry_run: Some(state.dry_run),
|
||||
hooks: state.hooks.clone(),
|
||||
sandbox: Some(SandboxSettings {
|
||||
provider: Some("local".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let settings = state.settings.read().unwrap().clone();
|
||||
let created = match operations::create(CreateRunInput {
|
||||
workflow: WorkflowInput::DotSource {
|
||||
source: req.dot_source.clone(),
|
||||
|
|
@ -626,7 +577,7 @@ async fn start_run(
|
|||
/// Execute a single run: transitions queued → starting → running → completed/failed/cancelled.
|
||||
async fn execute_run(state: Arc<AppState>, run_id: String) {
|
||||
// Transition to Starting and set up cancel infrastructure
|
||||
let (cancel_rx, run_dir) = {
|
||||
let (cancel_rx, run_dir, event_tx, cancel_token) = {
|
||||
let mut runs = state.runs.lock().expect("runs lock poisoned");
|
||||
let managed_run = match runs.get_mut(&run_id) {
|
||||
Some(r) if r.status == RunStatus::Queued => r,
|
||||
|
|
@ -645,38 +596,23 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
|
|||
managed_run.cancel_token = Some(Arc::clone(&cancel_token));
|
||||
managed_run.event_tx = Some(event_tx);
|
||||
|
||||
(cancel_rx, run_dir)
|
||||
(
|
||||
cancel_rx,
|
||||
run_dir,
|
||||
managed_run.event_tx.clone(),
|
||||
cancel_token,
|
||||
)
|
||||
};
|
||||
|
||||
// Create interviewer, sandbox, engine (this is the "provisioning" phase)
|
||||
// Create interviewer and event plumbing (this is the "provisioning" phase)
|
||||
let interviewer = Arc::new(WebInterviewer::new());
|
||||
let context = Context::new();
|
||||
|
||||
let event_tx = {
|
||||
let runs = state.runs.lock().expect("runs lock poisoned");
|
||||
runs.get(&run_id).and_then(|r| r.event_tx.clone())
|
||||
};
|
||||
|
||||
let emitter = EventEmitter::new();
|
||||
if let Some(tx_clone) = event_tx {
|
||||
emitter.on_event(move |event| {
|
||||
let _ = tx_clone.send(event.clone());
|
||||
});
|
||||
}
|
||||
|
||||
let cancel_token = {
|
||||
let runs = state.runs.lock().expect("runs lock poisoned");
|
||||
runs.get(&run_id).and_then(|r| r.cancel_token.clone())
|
||||
};
|
||||
let Some(cancel_token) = cancel_token else {
|
||||
return;
|
||||
};
|
||||
|
||||
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
|
||||
let sandbox = SandboxSpec::Local {
|
||||
working_directory: cwd,
|
||||
};
|
||||
let llm = (state.llm_spec_factory)();
|
||||
let registry_override = state
|
||||
.registry_factory_override
|
||||
.as_ref()
|
||||
|
|
@ -726,118 +662,46 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
|
|||
return;
|
||||
}
|
||||
};
|
||||
let run_record = persisted.run_record().clone();
|
||||
let run_options = RunOptions {
|
||||
settings: run_record.settings,
|
||||
run_dir: run_dir.clone(),
|
||||
cancel_token: Some(cancel_token),
|
||||
run_id: run_id.clone(),
|
||||
labels: run_record.labels,
|
||||
git_author: state.git_author.clone(),
|
||||
workflow_slug: run_record.workflow_slug,
|
||||
github_app: None,
|
||||
base_branch: run_record.base_branch,
|
||||
display_base_sha: None,
|
||||
host_repo_path: run_record.host_repo_path.map(Into::into),
|
||||
git: None,
|
||||
let github_app =
|
||||
fabro_github::GitHubAppCredentials::from_env(persisted.run_record().settings.app_id());
|
||||
let services = operations::StartServices {
|
||||
cancel_token: Some(Arc::clone(&cancel_token)),
|
||||
emitter: Arc::clone(&emitter),
|
||||
interviewer: Arc::clone(&interviewer) as Arc<dyn Interviewer>,
|
||||
run_store: Arc::clone(&run_store),
|
||||
github_app,
|
||||
on_node: None,
|
||||
registry_override,
|
||||
};
|
||||
|
||||
let execution = {
|
||||
let emitter = Arc::clone(&emitter);
|
||||
let interviewer = Arc::clone(&interviewer) as Arc<dyn Interviewer>;
|
||||
let run_id = run_id.clone();
|
||||
let run_options = run_options.clone();
|
||||
let run_store = Arc::clone(&run_store);
|
||||
let hooks = state.hooks.clone();
|
||||
let dry_run = state.dry_run;
|
||||
async move {
|
||||
let initialized = pipeline::initialize(
|
||||
persisted,
|
||||
InitOptions {
|
||||
run_id,
|
||||
run_store,
|
||||
dry_run,
|
||||
emitter,
|
||||
sandbox,
|
||||
llm,
|
||||
interviewer,
|
||||
lifecycle: LifecycleOptions {
|
||||
setup_commands: Vec::new(),
|
||||
setup_command_timeout_ms: 300_000,
|
||||
devcontainer_phases: Vec::new(),
|
||||
},
|
||||
run_options,
|
||||
hooks: fabro_hooks::HookConfig { hooks },
|
||||
sandbox_env: SandboxEnvSpec {
|
||||
devcontainer_env: HashMap::new(),
|
||||
toml_env: HashMap::new(),
|
||||
github_permissions: None,
|
||||
origin_url: None,
|
||||
},
|
||||
devcontainer: None,
|
||||
git: None,
|
||||
worktree_mode: None,
|
||||
registry_override,
|
||||
checkpoint: None,
|
||||
seed_context: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok::<_, FabroError>(pipeline::execute(initialized).await)
|
||||
}
|
||||
};
|
||||
|
||||
let (result, final_context) = tokio::select! {
|
||||
result = execution => match result {
|
||||
Ok(executed) => (executed.outcome, Some(executed.final_context)),
|
||||
Err(err) => (Err(err), None),
|
||||
},
|
||||
let result = tokio::select! {
|
||||
result = operations::start(&run_dir, services) => result,
|
||||
_ = cancel_rx => {
|
||||
let mut runs = state.runs.lock().expect("runs lock poisoned");
|
||||
if let Some(managed_run) = runs.get_mut(&run_id) {
|
||||
managed_run.status = RunStatus::Cancelled;
|
||||
managed_run.event_tx = None;
|
||||
}
|
||||
state.scheduler_notify.notify_one();
|
||||
return;
|
||||
cancel_token.store(true, Ordering::SeqCst);
|
||||
Err(FabroError::Cancelled)
|
||||
}
|
||||
};
|
||||
|
||||
// Save final checkpoint
|
||||
let checkpoint = match run_store.get_checkpoint().await {
|
||||
Ok(checkpoint) => checkpoint
|
||||
.or_else(|| Checkpoint::load(&run_options.run_dir.join("checkpoint.json")).ok()),
|
||||
Ok(checkpoint) => {
|
||||
checkpoint.or_else(|| Checkpoint::load(&run_dir.join("checkpoint.json")).ok())
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(run_id = %run_id, error = %err, "Failed to load checkpoint from store");
|
||||
Checkpoint::load(&run_options.run_dir.join("checkpoint.json")).ok()
|
||||
Checkpoint::load(&run_dir.join("checkpoint.json")).ok()
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-derive retro and accumulate aggregate usage
|
||||
// Accumulate aggregate usage after execution completes.
|
||||
if let Some(ref cp) = checkpoint {
|
||||
let failed = result.is_err();
|
||||
let completed_stages = fabro_workflows::build_completed_stages(cp, failed);
|
||||
let stage_durations = match run_store.list_events().await {
|
||||
Ok(events) => fabro_workflows::extract_stage_durations_from_events(&events),
|
||||
Err(err) => {
|
||||
tracing::warn!(run_id = %run_id, error = %err, "Failed to load run events from store");
|
||||
retro::extract_stage_durations(&run_options.run_dir)
|
||||
fabro_retro::retro::extract_stage_durations(&run_dir)
|
||||
}
|
||||
};
|
||||
let retro = retro::derive_retro(
|
||||
&run_id,
|
||||
"workflow",
|
||||
"",
|
||||
completed_stages,
|
||||
0,
|
||||
&stage_durations,
|
||||
);
|
||||
let _ = retro.save(&run_options.run_dir);
|
||||
if let Err(err) = run_store.put_retro(&retro).await {
|
||||
tracing::warn!(run_id = %run_id, error = %err, "Failed to save retro to store");
|
||||
}
|
||||
|
||||
// Accumulate aggregate usage
|
||||
let mut agg = state
|
||||
.aggregate_usage
|
||||
.lock()
|
||||
|
|
@ -860,11 +724,22 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
|
|||
|
||||
let mut runs = state.runs.lock().expect("runs lock poisoned");
|
||||
if let Some(managed_run) = runs.get_mut(&run_id) {
|
||||
match result {
|
||||
Ok(_) => {
|
||||
info!(run_id = %run_id, "Run completed");
|
||||
managed_run.status = RunStatus::Completed;
|
||||
}
|
||||
match &result {
|
||||
Ok(started) => match &started.finalized.outcome {
|
||||
Ok(_) => {
|
||||
info!(run_id = %run_id, "Run completed");
|
||||
managed_run.status = RunStatus::Completed;
|
||||
}
|
||||
Err(FabroError::Cancelled) => {
|
||||
info!(run_id = %run_id, "Run cancelled");
|
||||
managed_run.status = RunStatus::Cancelled;
|
||||
}
|
||||
Err(e) => {
|
||||
error!(run_id = %run_id, error = %e, "Run failed");
|
||||
managed_run.status = RunStatus::Failed;
|
||||
managed_run.error = Some(e.to_string());
|
||||
}
|
||||
},
|
||||
Err(FabroError::Cancelled) => {
|
||||
info!(run_id = %run_id, "Run cancelled");
|
||||
managed_run.status = RunStatus::Cancelled;
|
||||
|
|
@ -876,11 +751,15 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
|
|||
}
|
||||
}
|
||||
managed_run.checkpoint = checkpoint;
|
||||
if let Some(ctx) = final_context {
|
||||
managed_run.context = Some(ctx);
|
||||
if let Ok(started) = &result {
|
||||
if let Some(ctx) = &started.final_context {
|
||||
managed_run.context = Some(ctx.clone());
|
||||
}
|
||||
}
|
||||
managed_run.run_dir = Some(run_options.run_dir.clone());
|
||||
managed_run.run_dir = Some(run_dir);
|
||||
managed_run.event_tx = None;
|
||||
managed_run.cancel_tx = None;
|
||||
managed_run.cancel_token = None;
|
||||
}
|
||||
drop(runs);
|
||||
state.scheduler_notify.notify_one();
|
||||
|
|
@ -1230,7 +1109,7 @@ async fn test_model(
|
|||
return ApiError::not_found(format!("Model not found: {id}")).into_response();
|
||||
};
|
||||
|
||||
if state.dry_run {
|
||||
if state.dry_run() {
|
||||
return Json(serde_json::json!({
|
||||
"model_id": id,
|
||||
"status": "ok",
|
||||
|
|
@ -1408,7 +1287,7 @@ async fn create_completion(
|
|||
let use_stream = req.stream && req.schema.is_none();
|
||||
|
||||
// Dry-run mode returns a stub response
|
||||
if state.dry_run {
|
||||
if state.dry_run() {
|
||||
let msg_id = ulid::Ulid::new().to_string();
|
||||
if use_stream {
|
||||
let finish_event = StreamEvent::finish(
|
||||
|
|
@ -1646,6 +1525,7 @@ mod tests {
|
|||
use super::*;
|
||||
use axum::body::Body;
|
||||
use axum::http::Request;
|
||||
use fabro_workflows::records::{RunRecord, RunRecordExt};
|
||||
use tower::ServiceExt;
|
||||
|
||||
const MINIMAL_DOT: &str = r#"digraph Test {
|
||||
|
|
@ -1655,16 +1535,25 @@ mod tests {
|
|||
start -> exit
|
||||
}"#;
|
||||
|
||||
fn test_llm_spec() -> LlmSpec {
|
||||
LlmSpec {
|
||||
model: "test-model".to_string(),
|
||||
provider: fabro_llm::Provider::Anthropic,
|
||||
fallback_chain: Vec::new(),
|
||||
mcp_servers: Vec::new(),
|
||||
dry_run: true,
|
||||
fn dry_run_settings() -> FabroSettings {
|
||||
FabroSettings {
|
||||
dry_run: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn command_dot(command: &str) -> String {
|
||||
format!(
|
||||
r#"digraph Test {{
|
||||
graph [goal="Test"]
|
||||
start [shape=Mdiamond]
|
||||
exit [shape=Msquare]
|
||||
command [shape=parallelogram, tool_command="{command}"]
|
||||
start -> command -> exit
|
||||
}}"#
|
||||
)
|
||||
}
|
||||
|
||||
async fn test_db() -> sqlx::SqlitePool {
|
||||
let pool = fabro_db::connect_memory().await.unwrap();
|
||||
fabro_db::initialize_db(&pool).await.unwrap();
|
||||
|
|
@ -1672,7 +1561,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn test_app_with(db: sqlx::SqlitePool) -> Router {
|
||||
let state = create_app_state(db, test_llm_spec);
|
||||
let state = create_app_state(db);
|
||||
build_router(state, AuthMode::Disabled)
|
||||
}
|
||||
|
||||
|
|
@ -1722,14 +1611,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_model_dry_run_returns_ok() {
|
||||
let state = create_app_state_with_options(
|
||||
test_db().await,
|
||||
test_llm_spec,
|
||||
true,
|
||||
5,
|
||||
GitAuthor::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
let state = create_app_state_with_options(test_db().await, dry_run_settings(), 5);
|
||||
let app = build_router(state, AuthMode::Disabled);
|
||||
|
||||
let req = Request::builder()
|
||||
|
|
@ -1749,14 +1631,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_model_dry_run_unknown_returns_404() {
|
||||
let state = create_app_state_with_options(
|
||||
test_db().await,
|
||||
test_llm_spec,
|
||||
true,
|
||||
5,
|
||||
GitAuthor::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
let state = create_app_state_with_options(test_db().await, dry_run_settings(), 5);
|
||||
let app = build_router(state, AuthMode::Disabled);
|
||||
|
||||
let req = Request::builder()
|
||||
|
|
@ -1810,7 +1685,7 @@ mod tests {
|
|||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn get_run_status_returns_status() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = test_app_with_scheduler(state);
|
||||
|
||||
// Start a run
|
||||
|
|
@ -1868,7 +1743,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn get_questions_returns_empty_list() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(Arc::clone(&state), AuthMode::Disabled);
|
||||
|
||||
// Start a run
|
||||
|
|
@ -1933,7 +1808,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn get_checkpoint_returns_null_initially() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(Arc::clone(&state), AuthMode::Disabled);
|
||||
|
||||
// Start a run
|
||||
|
|
@ -1963,7 +1838,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn get_context_returns_map() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(Arc::clone(&state), AuthMode::Disabled);
|
||||
|
||||
// Start a run
|
||||
|
|
@ -1996,7 +1871,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn cancel_run_succeeds() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(Arc::clone(&state), AuthMode::Disabled);
|
||||
|
||||
// Start a run
|
||||
|
|
@ -2045,7 +1920,7 @@ mod tests {
|
|||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn get_events_returns_sse_stream() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = test_app_with_scheduler(state);
|
||||
|
||||
// Start a run
|
||||
|
|
@ -2096,7 +1971,7 @@ mod tests {
|
|||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn run_completes_and_status_is_completed() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = test_app_with_scheduler(state);
|
||||
|
||||
// Start a run
|
||||
|
|
@ -2135,7 +2010,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn get_graph_returns_svg() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(Arc::clone(&state), AuthMode::Disabled);
|
||||
|
||||
// Start a run
|
||||
|
|
@ -2203,7 +2078,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn list_runs_returns_started_run() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(Arc::clone(&state), AuthMode::Disabled);
|
||||
|
||||
// List should be empty initially
|
||||
|
|
@ -2252,7 +2127,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn get_aggregate_usage_returns_zeros_initially() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(Arc::clone(&state), AuthMode::Disabled);
|
||||
|
||||
let req = Request::builder()
|
||||
|
|
@ -2275,7 +2150,7 @@ mod tests {
|
|||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn aggregate_usage_increments_after_run_completes() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = test_app_with_scheduler(state);
|
||||
|
||||
// Start a run
|
||||
|
|
@ -2326,7 +2201,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn post_runs_returns_queued_status() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(state, AuthMode::Disabled);
|
||||
|
||||
let req = Request::builder()
|
||||
|
|
@ -2355,9 +2230,120 @@ mod tests {
|
|||
assert_eq!(body["status"].as_str().unwrap(), "queued");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn start_run_persists_full_settings_snapshot() {
|
||||
let settings = FabroSettings {
|
||||
dry_run: Some(true),
|
||||
llm: Some(fabro_config::run::LlmSettings {
|
||||
model: Some("claude-sonnet-4-5".to_string()),
|
||||
provider: Some("anthropic".to_string()),
|
||||
fallbacks: None,
|
||||
}),
|
||||
sandbox: Some(fabro_config::sandbox::SandboxSettings {
|
||||
provider: Some("local".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
hooks: vec![fabro_hooks::HookDefinition {
|
||||
name: Some("snapshot-hook".to_string()),
|
||||
event: fabro_hooks::HookEvent::RunStart,
|
||||
command: Some("echo snapshot".to_string()),
|
||||
hook_type: None,
|
||||
matcher: None,
|
||||
blocking: Some(false),
|
||||
timeout_ms: Some(1_000),
|
||||
sandbox: Some(false),
|
||||
}],
|
||||
git: Some(fabro_config::server::GitSettings {
|
||||
app_id: Some("12345".to_string()),
|
||||
author: fabro_config::server::GitAuthorSettings {
|
||||
name: Some("Snapshot Bot".to_string()),
|
||||
email: Some("snapshot@example.com".to_string()),
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
web: Some(fabro_config::server::WebSettings {
|
||||
url: "http://example.test".to_string(),
|
||||
..Default::default()
|
||||
}),
|
||||
api: Some(fabro_config::server::ApiSettings {
|
||||
base_url: "http://api.example.test".to_string(),
|
||||
..Default::default()
|
||||
}),
|
||||
log: Some(fabro_config::server::LogSettings {
|
||||
level: Some("debug".to_string()),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let state = create_app_state_with_options(test_db().await, settings.clone(), 5);
|
||||
let app = build_router(Arc::clone(&state), AuthMode::Disabled);
|
||||
|
||||
let req = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/runs")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(
|
||||
serde_json::to_string(&serde_json::json!({"dot_source": MINIMAL_DOT})).unwrap(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::CREATED);
|
||||
let body = body_json(response.into_body()).await;
|
||||
let run_id = body["id"].as_str().unwrap().to_string();
|
||||
|
||||
let run_dir = {
|
||||
let runs = state.runs.lock().expect("runs lock poisoned");
|
||||
runs.get(&run_id)
|
||||
.and_then(|run| run.run_dir.clone())
|
||||
.expect("run_dir should be recorded")
|
||||
};
|
||||
let run_record = RunRecord::load(&run_dir).unwrap();
|
||||
let mut expected_settings = settings;
|
||||
expected_settings.goal = Some("Test".to_string());
|
||||
|
||||
assert_eq!(run_record.settings, expected_settings);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn config_change_after_submission_does_not_affect_execution() {
|
||||
let output_dir = tempfile::tempdir().unwrap();
|
||||
let output_path = output_dir.path().join("executed.txt");
|
||||
let dot = command_dot(&format!("printf snapshot > {}", output_path.display()));
|
||||
let initial_settings = dry_run_settings();
|
||||
let state = create_app_state_with_options(test_db().await, initial_settings.clone(), 5);
|
||||
let app = build_router(Arc::clone(&state), AuthMode::Disabled);
|
||||
|
||||
let req = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/runs")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(
|
||||
serde_json::to_string(&serde_json::json!({ "dot_source": dot })).unwrap(),
|
||||
))
|
||||
.unwrap();
|
||||
let response = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::CREATED);
|
||||
let body = body_json(response.into_body()).await;
|
||||
let run_id = body["id"].as_str().unwrap().to_string();
|
||||
|
||||
*state.settings.write().unwrap() = FabroSettings::default();
|
||||
|
||||
execute_run(Arc::clone(&state), run_id.clone()).await;
|
||||
|
||||
let runs = state.runs.lock().expect("runs lock poisoned");
|
||||
let managed_run = runs.get(&run_id).expect("run should still exist");
|
||||
assert_eq!(managed_run.status, RunStatus::Completed);
|
||||
drop(runs);
|
||||
|
||||
assert!(
|
||||
!output_path.exists(),
|
||||
"run should still use snapshotted dry-run settings"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cancel_queued_run_succeeds() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(state, AuthMode::Disabled);
|
||||
|
||||
// Submit a run (no scheduler, stays queued)
|
||||
|
|
@ -2396,9 +2382,86 @@ mod tests {
|
|||
assert_eq!(body["status"].as_str().unwrap(), "cancelled");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn cancel_during_startup_persists_cancelled_reason() {
|
||||
let settings = FabroSettings {
|
||||
setup: Some(fabro_config::run::SetupSettings {
|
||||
commands: vec!["sleep 5".to_string()],
|
||||
timeout_ms: Some(30_000),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let state = create_app_state_with_options(test_db().await, settings, 5);
|
||||
let app = build_router(Arc::clone(&state), AuthMode::Disabled);
|
||||
|
||||
let req = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/runs")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(
|
||||
serde_json::to_string(&serde_json::json!({"dot_source": MINIMAL_DOT})).unwrap(),
|
||||
))
|
||||
.unwrap();
|
||||
let response = app.clone().oneshot(req).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::CREATED);
|
||||
let body = body_json(response.into_body()).await;
|
||||
let run_id = body["id"].as_str().unwrap().to_string();
|
||||
|
||||
let runner = tokio::spawn(execute_run(Arc::clone(&state), run_id.clone()));
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
|
||||
{
|
||||
let mut runs = state.runs.lock().expect("runs lock poisoned");
|
||||
let managed_run = runs.get_mut(&run_id).expect("run should exist");
|
||||
if let Some(token) = &managed_run.cancel_token {
|
||||
token.store(true, Ordering::SeqCst);
|
||||
}
|
||||
if let Some(cancel_tx) = managed_run.cancel_tx.take() {
|
||||
let _ = cancel_tx.send(());
|
||||
}
|
||||
}
|
||||
|
||||
runner.await.unwrap();
|
||||
|
||||
let runs = state.runs.lock().expect("runs lock poisoned");
|
||||
let managed_run = runs.get(&run_id).expect("run should exist");
|
||||
assert_eq!(managed_run.status, RunStatus::Cancelled);
|
||||
drop(runs);
|
||||
|
||||
let run_store = state
|
||||
.store
|
||||
.open_run_reader(&run_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.expect("run store should exist");
|
||||
|
||||
let mut status_record = None;
|
||||
for _ in 0..50 {
|
||||
if let Some(record) = run_store.get_status().await.unwrap() {
|
||||
if record.status == fabro_workflows::run_status::RunStatus::Failed
|
||||
&& record.reason == Some(fabro_workflows::run_status::StatusReason::Cancelled)
|
||||
{
|
||||
status_record = Some(record);
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
}
|
||||
|
||||
let status_record = status_record.expect("status record should be persisted");
|
||||
assert_eq!(
|
||||
status_record.status,
|
||||
fabro_workflows::run_status::RunStatus::Failed
|
||||
);
|
||||
assert_eq!(
|
||||
status_record.reason,
|
||||
Some(fabro_workflows::run_status::StatusReason::Cancelled)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn queue_position_reported_for_queued_runs() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(state, AuthMode::Disabled);
|
||||
|
||||
// Submit two runs (no scheduler, both stay queued)
|
||||
|
|
@ -2440,14 +2503,7 @@ mod tests {
|
|||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn concurrency_limit_respected() {
|
||||
let state = create_app_state_with_options(
|
||||
test_db().await,
|
||||
test_llm_spec,
|
||||
false,
|
||||
1,
|
||||
GitAuthor::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
let state = create_app_state_with_options(test_db().await, FabroSettings::default(), 1);
|
||||
let app = test_app_with_scheduler(state);
|
||||
|
||||
// Submit two runs with max_concurrent_runs=1
|
||||
|
|
@ -2496,7 +2552,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn submit_answer_to_queued_run_returns_conflict() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(state, AuthMode::Disabled);
|
||||
|
||||
let req = Request::builder()
|
||||
|
|
@ -2528,14 +2584,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn create_completion_non_streaming_returns_json() {
|
||||
let state = create_app_state_with_options(
|
||||
test_db().await,
|
||||
test_llm_spec,
|
||||
true,
|
||||
5,
|
||||
GitAuthor::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
let state = create_app_state_with_options(test_db().await, dry_run_settings(), 5);
|
||||
let app = build_router(state, AuthMode::Disabled);
|
||||
|
||||
let req = Request::builder()
|
||||
|
|
@ -2565,14 +2614,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn create_completion_streaming_returns_sse() {
|
||||
let state = create_app_state_with_options(
|
||||
test_db().await,
|
||||
test_llm_spec,
|
||||
true,
|
||||
5,
|
||||
GitAuthor::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
let state = create_app_state_with_options(test_db().await, dry_run_settings(), 5);
|
||||
let app = build_router(state, AuthMode::Disabled);
|
||||
|
||||
let req = Request::builder()
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ pub async fn create_session(
|
|||
store.insert(session_id, session);
|
||||
}
|
||||
|
||||
spawn_generation(Arc::clone(&state.sessions), session_id, state.dry_run, 1);
|
||||
spawn_generation(Arc::clone(&state.sessions), session_id, state.dry_run(), 1);
|
||||
|
||||
(
|
||||
StatusCode::CREATED,
|
||||
|
|
@ -307,7 +307,7 @@ pub async fn send_message(
|
|||
}
|
||||
};
|
||||
|
||||
spawn_generation(Arc::clone(&state.sessions), id, state.dry_run, seq);
|
||||
spawn_generation(Arc::clone(&state.sessions), id, state.dry_run(), seq);
|
||||
|
||||
(
|
||||
StatusCode::ACCEPTED,
|
||||
|
|
@ -432,18 +432,6 @@ mod tests {
|
|||
use crate::jwt_auth::AuthMode;
|
||||
use crate::server::{build_router, create_app_state_with_options};
|
||||
|
||||
use fabro_workflows::pipeline::LlmSpec;
|
||||
|
||||
fn test_llm_spec() -> LlmSpec {
|
||||
LlmSpec {
|
||||
model: "test-model".to_string(),
|
||||
provider: fabro_llm::Provider::Anthropic,
|
||||
fallback_chain: Vec::new(),
|
||||
mcp_servers: Vec::new(),
|
||||
dry_run: true,
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_db() -> sqlx::SqlitePool {
|
||||
let pool = fabro_db::connect_memory().await.unwrap();
|
||||
fabro_db::initialize_db(&pool).await.unwrap();
|
||||
|
|
@ -454,11 +442,11 @@ mod tests {
|
|||
let db = test_db().await;
|
||||
let state = create_app_state_with_options(
|
||||
db,
|
||||
test_llm_spec,
|
||||
true,
|
||||
fabro_config::FabroSettings {
|
||||
dry_run: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
5,
|
||||
fabro_workflows::git::GitAuthor::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
build_router(state, AuthMode::Disabled)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,19 +12,8 @@ mod mtls_e2e {
|
|||
use fabro_server::server::{build_router, create_app_state};
|
||||
use fabro_server::server_config::TlsSettings;
|
||||
use fabro_server::tls::{ClientAuth, build_rustls_config};
|
||||
use fabro_workflows::pipeline::LlmSpec;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
fn test_llm_spec() -> LlmSpec {
|
||||
LlmSpec {
|
||||
model: "test-model".to_string(),
|
||||
provider: fabro_llm::Provider::Anthropic,
|
||||
fallback_chain: Vec::new(),
|
||||
mcp_servers: Vec::new(),
|
||||
dry_run: true,
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_db() -> sqlx::SqlitePool {
|
||||
let pool = fabro_db::connect_memory().await.unwrap();
|
||||
fabro_db::initialize_db(&pool).await.unwrap();
|
||||
|
|
@ -191,7 +180,7 @@ mod mtls_e2e {
|
|||
let rustls_config = build_rustls_config(tls_settings, client_auth);
|
||||
let tls_acceptor = tokio_rustls::TlsAcceptor::from(rustls_config);
|
||||
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let router = build_router(state, auth_mode);
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
|
@ -422,7 +411,7 @@ mod mtls_e2e {
|
|||
// ===========================================================================
|
||||
|
||||
mod server_lifecycle {
|
||||
use super::super::helpers::{test_db, test_llm_spec};
|
||||
use super::super::helpers::test_db;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -469,8 +458,7 @@ mod server_lifecycle {
|
|||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn full_http_lifecycle_approve_and_complete() {
|
||||
let state =
|
||||
create_app_state_with_registry_factory(test_db().await, test_llm_spec, gate_registry);
|
||||
let state = create_app_state_with_registry_factory(test_db().await, gate_registry);
|
||||
fabro_server::server::spawn_scheduler(Arc::clone(&state));
|
||||
let app = build_router(
|
||||
Arc::clone(&state),
|
||||
|
|
@ -569,8 +557,7 @@ mod server_lifecycle {
|
|||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn full_http_lifecycle_cancel() {
|
||||
let state =
|
||||
create_app_state_with_registry_factory(test_db().await, test_llm_spec, gate_registry);
|
||||
let state = create_app_state_with_registry_factory(test_db().await, gate_registry);
|
||||
fabro_server::server::spawn_scheduler(Arc::clone(&state));
|
||||
let app = build_router(
|
||||
Arc::clone(&state),
|
||||
|
|
@ -621,7 +608,7 @@ mod server_lifecycle {
|
|||
// ===========================================================================
|
||||
|
||||
mod sse_events {
|
||||
use super::super::helpers::{test_db, test_llm_spec};
|
||||
use super::super::helpers::test_db;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -641,7 +628,7 @@ mod sse_events {
|
|||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn sse_stream_contains_expected_event_types() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
fabro_server::server::spawn_scheduler(Arc::clone(&state));
|
||||
let app = build_router(
|
||||
Arc::clone(&state),
|
||||
|
|
@ -768,7 +755,7 @@ mod sse_events {
|
|||
// ===========================================================================
|
||||
|
||||
mod serve_dry_run {
|
||||
use super::super::helpers::{test_db, test_llm_spec};
|
||||
use super::super::helpers::test_db;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -786,7 +773,7 @@ mod serve_dry_run {
|
|||
|
||||
/// Build the router exactly as `serve_command` does in dry-run mode.
|
||||
async fn dry_run_app() -> axum::Router {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
fabro_server::server::spawn_scheduler(Arc::clone(&state));
|
||||
build_router(state, fabro_server::jwt_auth::AuthMode::Disabled)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,3 @@
|
|||
use fabro_workflows::pipeline::LlmSpec;
|
||||
|
||||
pub(crate) fn test_llm_spec() -> LlmSpec {
|
||||
LlmSpec {
|
||||
model: "test-model".to_string(),
|
||||
provider: fabro_llm::Provider::Anthropic,
|
||||
fallback_chain: Vec::new(),
|
||||
mcp_servers: Vec::new(),
|
||||
dry_run: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn test_db() -> sqlx::SqlitePool {
|
||||
let pool = fabro_db::connect_memory().await.unwrap();
|
||||
fabro_db::initialize_db(&pool).await.unwrap();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Conformance tests: spec ↔ router ↔ Rust struct consistency.
|
||||
|
||||
use super::helpers::{test_db, test_llm_spec};
|
||||
use super::helpers::test_db;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use axum::body::Body;
|
||||
|
|
@ -58,7 +58,7 @@ fn methods_for_path_item(item: &openapiv3::PathItem) -> Vec<Method> {
|
|||
#[tokio::test]
|
||||
async fn all_spec_routes_are_routable() {
|
||||
let spec = load_spec();
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(state, AuthMode::Disabled);
|
||||
|
||||
let mut checked = 0;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Tests that paginated list endpoints return `{ data, meta: { has_more } }`.
|
||||
|
||||
use super::helpers::{test_db, test_llm_spec};
|
||||
use super::helpers::test_db;
|
||||
use axum::body::Body;
|
||||
use axum::http::{Request, StatusCode};
|
||||
use fabro_server::jwt_auth::AuthMode;
|
||||
|
|
@ -91,7 +91,7 @@ const ENDPOINTS: &[PaginatedEndpoint] = &[
|
|||
|
||||
#[tokio::test]
|
||||
async fn paginated_endpoints_return_correct_shape() {
|
||||
let state = create_app_state(test_db().await, test_llm_spec);
|
||||
let state = create_app_state(test_db().await);
|
||||
let app = build_router(state, AuthMode::Disabled);
|
||||
|
||||
for ep in ENDPOINTS {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ use std::fmt::Write;
|
|||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
use fabro_config::FabroSettings;
|
||||
use fabro_config::server::GitAuthorSettings;
|
||||
use fabro_git_storage::branchstore::BranchStore;
|
||||
use fabro_git_storage::gitobj::Store;
|
||||
use git2::{Repository, Signature};
|
||||
|
|
@ -64,6 +66,19 @@ impl GitAuthor {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&GitAuthorSettings> for GitAuthor {
|
||||
fn from(value: &GitAuthorSettings) -> Self {
|
||||
Self::from_options(value.name.clone(), value.email.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn git_author_from_settings(settings: &FabroSettings) -> GitAuthor {
|
||||
settings
|
||||
.git_author()
|
||||
.map(GitAuthor::from)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn git_error(msg: impl Into<String>) -> FabroError {
|
||||
FabroError::engine(msg.into())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,17 +162,12 @@ impl Handler for SubWorkflowHandler {
|
|||
let cancel_token = Arc::new(AtomicBool::new(false));
|
||||
let child_cancel = Arc::clone(&cancel_token);
|
||||
|
||||
let git_state = services.git_state();
|
||||
let child_run_options = RunOptions {
|
||||
settings: fabro_config::FabroSettings::default(),
|
||||
run_dir: child_logs,
|
||||
cancel_token: Some(cancel_token),
|
||||
run_id: format!("{parent_run_id}_child_{}", node.id),
|
||||
labels: HashMap::new(),
|
||||
git_author: git_state
|
||||
.as_ref()
|
||||
.map(|gs| gs.git_author.clone())
|
||||
.unwrap_or_default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ impl RunLifecycle<WorkflowGraph> for GitLifecycle {
|
|||
.and_then(|g| g.meta_branch.as_ref()),
|
||||
self.run_options.host_repo_path.as_ref(),
|
||||
) {
|
||||
let store = MetadataStore::new(repo_path, &self.run_options.git_author);
|
||||
let git_author = self.run_options.git_author();
|
||||
let store = MetadataStore::new(repo_path, &git_author);
|
||||
let run_json = self
|
||||
.run_store
|
||||
.get_run()
|
||||
|
|
@ -131,7 +132,8 @@ impl RunLifecycle<WorkflowGraph> for GitLifecycle {
|
|||
.and_then(|g| g.meta_branch.as_ref()),
|
||||
self.run_options.host_repo_path.as_ref(),
|
||||
) {
|
||||
let store = MetadataStore::new(repo_path, &self.run_options.git_author);
|
||||
let git_author = self.run_options.git_author();
|
||||
let store = MetadataStore::new(repo_path, &git_author);
|
||||
// Build checkpoint JSON for shadow branch
|
||||
self.run_store
|
||||
.get_checkpoint()
|
||||
|
|
@ -178,6 +180,7 @@ impl RunLifecycle<WorkflowGraph> for GitLifecycle {
|
|||
|
||||
// Run branch commit via sandbox
|
||||
let completed_count = state.completed_nodes.len();
|
||||
let git_author = self.run_options.git_author();
|
||||
let commit_result = git_checkpoint(
|
||||
&*self.sandbox,
|
||||
&self.run_id,
|
||||
|
|
@ -186,7 +189,7 @@ impl RunLifecycle<WorkflowGraph> for GitLifecycle {
|
|||
completed_count,
|
||||
shadow_sha,
|
||||
self.run_options.checkpoint_exclude_globs(),
|
||||
&self.run_options.git_author,
|
||||
&git_author,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
|
|
@ -20,7 +20,6 @@ use crate::event::{
|
|||
EventEmitter, RunNoticeLevel, StoreProgressLogger, WorkflowRunEvent, append_progress_event,
|
||||
build_redacted_event_payload,
|
||||
};
|
||||
use crate::git::GitAuthor;
|
||||
use crate::handler::HandlerRegistry;
|
||||
use crate::outcome::{Outcome, StageStatus};
|
||||
use crate::pipeline::{
|
||||
|
|
@ -51,7 +50,6 @@ struct RunSession {
|
|||
devcontainer: Option<DevcontainerSpec>,
|
||||
seed_context: Option<Context>,
|
||||
run_store: Arc<dyn RunStore>,
|
||||
git_author: GitAuthor,
|
||||
git: Option<GitCheckpointOptions>,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
worktree_mode: Option<WorktreeMode>,
|
||||
|
|
@ -69,7 +67,6 @@ pub struct StartServices {
|
|||
pub emitter: Arc<EventEmitter>,
|
||||
pub interviewer: Arc<dyn Interviewer>,
|
||||
pub run_store: Arc<dyn RunStore>,
|
||||
pub git_author: GitAuthor,
|
||||
pub github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
pub on_node: crate::OnNodeCallback,
|
||||
pub registry_override: Option<Arc<HandlerRegistry>>,
|
||||
|
|
@ -77,6 +74,7 @@ pub struct StartServices {
|
|||
|
||||
pub struct Started {
|
||||
pub finalized: Finalized,
|
||||
pub final_context: Option<Context>,
|
||||
pub retro: Option<Retro>,
|
||||
pub retro_duration: Duration,
|
||||
}
|
||||
|
|
@ -117,6 +115,7 @@ pub(super) async fn execute_persisted_run(
|
|||
checkpoint: Option<Checkpoint>,
|
||||
mut services: StartServices,
|
||||
) -> Result<Started, FabroError> {
|
||||
let cancel_token = services.cancel_token.clone();
|
||||
let inner_store = Arc::clone(&services.run_store);
|
||||
let projection_run_dir = run_dir.to_path_buf();
|
||||
services.run_store = Arc::new(
|
||||
|
|
@ -169,7 +168,8 @@ pub(super) async fn execute_persisted_run(
|
|||
return Err(error);
|
||||
}
|
||||
|
||||
let mut bootstrap_guard = DetachedRunBootstrapGuard::arm(run_dir, Arc::clone(&run_store));
|
||||
let mut bootstrap_guard =
|
||||
DetachedRunBootstrapGuard::arm(run_dir, Arc::clone(&run_store), cancel_token.clone());
|
||||
|
||||
let persisted = match Persisted::load_from_store(services.run_store.as_ref(), run_dir).await {
|
||||
Ok(persisted) => persisted,
|
||||
|
|
@ -204,7 +204,8 @@ pub(super) async fn execute_persisted_run(
|
|||
};
|
||||
|
||||
bootstrap_guard.defuse();
|
||||
let mut completion_guard = DetachedRunCompletionGuard::arm(run_dir, Arc::clone(&run_store));
|
||||
let mut completion_guard =
|
||||
DetachedRunCompletionGuard::arm(run_dir, Arc::clone(&run_store), cancel_token);
|
||||
let run_start = Instant::now();
|
||||
let started = Box::pin(session.run(persisted, checkpoint)).await;
|
||||
|
||||
|
|
@ -394,7 +395,6 @@ impl RunSession {
|
|||
devcontainer,
|
||||
seed_context: None,
|
||||
run_store: services.run_store,
|
||||
git_author: services.git_author,
|
||||
git: None,
|
||||
github_app: services.github_app.clone(),
|
||||
worktree_mode: Some(resolve_worktree_mode(&settings)),
|
||||
|
|
@ -480,7 +480,6 @@ impl RunSession {
|
|||
cancel_token: self.cancel_token,
|
||||
run_id: record.run_id.clone(),
|
||||
labels: record.labels.clone(),
|
||||
git_author: self.git_author,
|
||||
workflow_slug: record.workflow_slug.clone(),
|
||||
github_app: self.github_app.clone(),
|
||||
host_repo_path: record.host_repo_path.as_deref().map(PathBuf::from),
|
||||
|
|
@ -548,6 +547,7 @@ impl RunSession {
|
|||
|
||||
let executed = pipeline::execute(initialized).await;
|
||||
store_progress_logger.flush().await;
|
||||
let final_context = Some(executed.final_context.clone());
|
||||
let failed = !matches!(
|
||||
executed.outcome.as_ref().map(|outcome| &outcome.status),
|
||||
Ok(StageStatus::Success | StageStatus::PartialSuccess)
|
||||
|
|
@ -600,6 +600,7 @@ impl RunSession {
|
|||
|
||||
Ok(Started {
|
||||
finalized,
|
||||
final_context,
|
||||
retro,
|
||||
retro_duration,
|
||||
})
|
||||
|
|
@ -609,11 +610,16 @@ impl RunSession {
|
|||
struct DetachedRunBootstrapGuard {
|
||||
run_dir: PathBuf,
|
||||
run_store: Arc<dyn RunStore>,
|
||||
cancel_token: Option<Arc<AtomicBool>>,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl DetachedRunBootstrapGuard {
|
||||
fn arm(run_dir: &Path, run_store: Arc<dyn RunStore>) -> Self {
|
||||
fn arm(
|
||||
run_dir: &Path,
|
||||
run_store: Arc<dyn RunStore>,
|
||||
cancel_token: Option<Arc<AtomicBool>>,
|
||||
) -> Self {
|
||||
run_status::write_run_status(
|
||||
run_dir,
|
||||
RunStatus::Starting,
|
||||
|
|
@ -622,6 +628,7 @@ impl DetachedRunBootstrapGuard {
|
|||
Self {
|
||||
run_dir: run_dir.to_path_buf(),
|
||||
run_store,
|
||||
cancel_token,
|
||||
active: true,
|
||||
}
|
||||
}
|
||||
|
|
@ -634,18 +641,23 @@ impl DetachedRunBootstrapGuard {
|
|||
impl Drop for DetachedRunBootstrapGuard {
|
||||
fn drop(&mut self) {
|
||||
if self.active {
|
||||
run_status::write_run_status(
|
||||
&self.run_dir,
|
||||
RunStatus::Failed,
|
||||
Some(StatusReason::SandboxInitFailed),
|
||||
);
|
||||
let cancelled = self
|
||||
.cancel_token
|
||||
.as_ref()
|
||||
.is_some_and(|token| token.load(Ordering::SeqCst));
|
||||
let reason = if cancelled {
|
||||
StatusReason::Cancelled
|
||||
} else {
|
||||
StatusReason::SandboxInitFailed
|
||||
};
|
||||
run_status::write_run_status(&self.run_dir, RunStatus::Failed, Some(reason));
|
||||
let run_store = Arc::clone(&self.run_store);
|
||||
if let Ok(handle) = Handle::try_current() {
|
||||
handle.spawn(async move {
|
||||
let _ = run_store
|
||||
.put_status(&run_status::RunStatusRecord::new(
|
||||
RunStatus::Failed,
|
||||
Some(StatusReason::SandboxInitFailed),
|
||||
Some(reason),
|
||||
))
|
||||
.await;
|
||||
});
|
||||
|
|
@ -655,20 +667,27 @@ impl Drop for DetachedRunBootstrapGuard {
|
|||
}
|
||||
|
||||
const POSTRUN_ABORTED_MESSAGE: &str = "Run aborted before post-run finalization completed.";
|
||||
const POSTRUN_CANCELLED_MESSAGE: &str = "Run cancelled before post-run finalization completed.";
|
||||
|
||||
struct DetachedRunCompletionGuard {
|
||||
run_dir: PathBuf,
|
||||
run_store: Arc<dyn RunStore>,
|
||||
run_id: Option<String>,
|
||||
cancel_token: Option<Arc<AtomicBool>>,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl DetachedRunCompletionGuard {
|
||||
fn arm(run_dir: &Path, run_store: Arc<dyn RunStore>) -> Self {
|
||||
fn arm(
|
||||
run_dir: &Path,
|
||||
run_store: Arc<dyn RunStore>,
|
||||
cancel_token: Option<Arc<AtomicBool>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
run_dir: run_dir.to_path_buf(),
|
||||
run_store,
|
||||
run_id: load_run_id(run_dir),
|
||||
cancel_token,
|
||||
active: true,
|
||||
}
|
||||
}
|
||||
|
|
@ -684,17 +703,29 @@ impl Drop for DetachedRunCompletionGuard {
|
|||
return;
|
||||
}
|
||||
|
||||
run_status::write_run_status(
|
||||
&self.run_dir,
|
||||
RunStatus::Failed,
|
||||
Some(StatusReason::WorkflowError),
|
||||
);
|
||||
let cancelled = self
|
||||
.cancel_token
|
||||
.as_ref()
|
||||
.is_some_and(|token| token.load(Ordering::SeqCst));
|
||||
let reason = if cancelled {
|
||||
StatusReason::Cancelled
|
||||
} else {
|
||||
StatusReason::WorkflowError
|
||||
};
|
||||
let message = if cancelled {
|
||||
POSTRUN_CANCELLED_MESSAGE
|
||||
} else {
|
||||
POSTRUN_ABORTED_MESSAGE
|
||||
};
|
||||
let code = if cancelled {
|
||||
"postrun_cancelled"
|
||||
} else {
|
||||
"postrun_aborted"
|
||||
};
|
||||
|
||||
run_status::write_run_status(&self.run_dir, RunStatus::Failed, Some(reason));
|
||||
if !self.run_dir.join("conclusion.json").exists() {
|
||||
let _ = write_failure_conclusion(
|
||||
&self.run_dir,
|
||||
POSTRUN_ABORTED_MESSAGE,
|
||||
Some(StatusReason::WorkflowError),
|
||||
);
|
||||
let _ = write_failure_conclusion(&self.run_dir, message, Some(reason));
|
||||
}
|
||||
if let Some(run_id) = load_run_id(&self.run_dir) {
|
||||
let _ = append_progress_event(
|
||||
|
|
@ -702,8 +733,8 @@ impl Drop for DetachedRunCompletionGuard {
|
|||
&run_id,
|
||||
&WorkflowRunEvent::RunNotice {
|
||||
level: RunNoticeLevel::Error,
|
||||
code: "postrun_aborted".to_string(),
|
||||
message: POSTRUN_ABORTED_MESSAGE.to_string(),
|
||||
code: code.to_string(),
|
||||
message: message.to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -714,11 +745,11 @@ impl Drop for DetachedRunCompletionGuard {
|
|||
let _ = run_store
|
||||
.put_status(&run_status::RunStatusRecord::new(
|
||||
RunStatus::Failed,
|
||||
Some(StatusReason::WorkflowError),
|
||||
Some(reason),
|
||||
))
|
||||
.await;
|
||||
if let Err(err) = run_store
|
||||
.put_conclusion(&build_failure_conclusion(POSTRUN_ABORTED_MESSAGE))
|
||||
.put_conclusion(&build_failure_conclusion(message))
|
||||
.await
|
||||
{
|
||||
tracing::warn!(
|
||||
|
|
@ -729,8 +760,8 @@ impl Drop for DetachedRunCompletionGuard {
|
|||
if let Some(run_id) = run_id {
|
||||
let event = WorkflowRunEvent::RunNotice {
|
||||
level: RunNoticeLevel::Error,
|
||||
code: "postrun_aborted".to_string(),
|
||||
message: POSTRUN_ABORTED_MESSAGE.to_string(),
|
||||
code: code.to_string(),
|
||||
message: message.to_string(),
|
||||
};
|
||||
match build_redacted_event_payload(&event, &run_id) {
|
||||
Ok(payload) => {
|
||||
|
|
@ -938,7 +969,6 @@ mod tests {
|
|||
run_store: crate::operations::open_or_hydrate_run(&InMemoryStore::default(), run_dir)
|
||||
.await
|
||||
.unwrap(),
|
||||
git_author: crate::git::GitAuthor::default(),
|
||||
github_app: None,
|
||||
on_node: None,
|
||||
registry_override: Some(registry),
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ pub async fn execute(init: Initialized) -> Executed {
|
|||
run_branch: git.run_branch.clone(),
|
||||
meta_branch: git.meta_branch.clone(),
|
||||
checkpoint_exclude_globs: run_options.checkpoint_exclude_globs().to_vec(),
|
||||
git_author: run_options.git_author.clone(),
|
||||
git_author: run_options.git_author(),
|
||||
}))
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ fn test_run_options(run_dir: &Path, run_id: &str) -> RunOptions {
|
|||
host_repo_path: None,
|
||||
labels: HashMap::new(),
|
||||
github_app: None,
|
||||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
display_base_sha: None,
|
||||
workflow_slug: None,
|
||||
|
|
|
|||
|
|
@ -277,7 +277,8 @@ pub async fn write_finalize_commit(
|
|||
return;
|
||||
};
|
||||
|
||||
let store = MetadataStore::new(repo_path, &run_options.git_author);
|
||||
let git_author = run_options.git_author();
|
||||
let store = MetadataStore::new(repo_path, &git_author);
|
||||
let mut entries = scan_node_files(run_dir);
|
||||
let retro_bytes = match run_store.get_retro().await {
|
||||
Ok(Some(retro)) => serde_json::to_vec_pretty(&retro).ok(),
|
||||
|
|
@ -452,7 +453,6 @@ mod tests {
|
|||
cancel_token: None,
|
||||
run_id: "run-test".to_string(),
|
||||
labels: HashMap::new(),
|
||||
git_author: crate::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
host_repo_path: None,
|
||||
|
|
|
|||
|
|
@ -690,7 +690,6 @@ mod tests {
|
|||
cancel_token: None,
|
||||
run_id: "run-test".to_string(),
|
||||
labels: HashMap::new(),
|
||||
git_author: crate::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
host_repo_path: None,
|
||||
|
|
|
|||
|
|
@ -228,7 +228,6 @@ mod tests {
|
|||
cancel_token: None,
|
||||
run_id: "run-test".to_string(),
|
||||
labels: HashMap::new(),
|
||||
git_author: crate::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
host_repo_path: None,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ use std::sync::atomic::AtomicBool;
|
|||
use fabro_config::FabroSettings;
|
||||
use fabro_config::run::PullRequestSettings;
|
||||
|
||||
use crate::git::GitAuthor;
|
||||
|
||||
/// Git checkpoint options for a workflow run.
|
||||
#[derive(Clone)]
|
||||
pub struct GitCheckpointOptions {
|
||||
|
|
@ -26,8 +24,6 @@ pub struct RunOptions {
|
|||
pub run_id: String,
|
||||
/// User-defined key-value labels for this run.
|
||||
pub labels: HashMap<String, String>,
|
||||
/// Git author identity for checkpoint commits.
|
||||
pub git_author: GitAuthor,
|
||||
/// Workflow directory slug (e.g. "smoke" from `fabro/workflows/smoke/`).
|
||||
pub workflow_slug: Option<String>,
|
||||
/// GitHub App credentials for pushing metadata branches to origin.
|
||||
|
|
@ -51,6 +47,10 @@ impl RunOptions {
|
|||
&self.settings.checkpoint.exclude_globs
|
||||
}
|
||||
|
||||
pub fn git_author(&self) -> crate::git::GitAuthor {
|
||||
crate::git::git_author_from_settings(&self.settings)
|
||||
}
|
||||
|
||||
/// PR config (already normalized — disabled entries stripped at construction).
|
||||
pub fn pull_request(&self) -> Option<&PullRequestSettings> {
|
||||
self.settings.pull_request.as_ref()
|
||||
|
|
|
|||
|
|
@ -396,7 +396,6 @@ async fn daytona_pipeline_artifact_offload_and_sync() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -587,7 +586,6 @@ async fn daytona_git_checkpoint_remote_emits_events() {
|
|||
cancel_token: None,
|
||||
run_id: "git-cp-test".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -773,7 +771,6 @@ async fn daytona_parallel_git_branching_e2e() {
|
|||
cancel_token: None,
|
||||
run_id: run_id.clone(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -1149,7 +1146,6 @@ async fn daytona_git_checkpoint_with_shadow_branch() {
|
|||
cancel_token: None,
|
||||
run_id: run_id.clone(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -1294,7 +1290,6 @@ async fn daytona_asset_collection() {
|
|||
cancel_token: None,
|
||||
run_id: "asset-test-daytona".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -1540,7 +1535,6 @@ async fn daytona_git_push_run_branch_to_origin() {
|
|||
cancel_token: None,
|
||||
run_id: run_id.clone(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
|
|||
|
|
@ -200,7 +200,6 @@ async fn end_to_end_linear_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -341,7 +340,6 @@ async fn end_to_end_branching_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -461,7 +459,6 @@ async fn end_to_end_human_gate_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -557,7 +554,6 @@ async fn human_gate_aborted_input_fails_closed_without_fail_route() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -668,7 +664,6 @@ async fn human_gate_aborted_input_routes_via_outcome_fail_condition() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -781,7 +776,6 @@ async fn goal_gate_routes_to_retry_target_on_failure() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -902,7 +896,6 @@ async fn goal_gate_routes_to_retry_target_when_present() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -1214,7 +1207,6 @@ async fn retry_on_failure_then_succeed() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -1289,7 +1281,6 @@ async fn pipeline_with_many_nodes() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -1611,7 +1602,6 @@ async fn smoke_test_with_mock_codergen_backend() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -1712,7 +1702,6 @@ async fn end_to_end_parallel_fan_out_fan_in() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -1825,7 +1814,6 @@ async fn resume_from_checkpoint_completes_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -1924,7 +1912,6 @@ async fn resume_from_checkpoint_preserves_goal_gate_outcomes() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -1967,7 +1954,6 @@ async fn graph_goal_in_context() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2003,7 +1989,6 @@ async fn event_streaming_lifecycle() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2097,7 +2082,6 @@ async fn context_flow_between_stages() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2150,7 +2134,6 @@ async fn tool_handler_e2e() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2222,7 +2205,6 @@ async fn auto_approve_interviewer_e2e() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2259,7 +2241,6 @@ async fn codergen_without_backend_simulated() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2364,7 +2345,6 @@ async fn branching_loop_back_on_failure() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2447,7 +2427,6 @@ async fn human_gate_loops_back() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2505,7 +2484,6 @@ async fn scenario_ship_a_feature() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2595,7 +2573,6 @@ async fn scenario_parallel_expert_review() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2679,7 +2656,6 @@ async fn scenario_node_retries_on_retry_status() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2741,7 +2717,6 @@ async fn scenario_loop_restart_resets_context() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2809,7 +2784,6 @@ async fn scenario_bug_triage_router() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2868,7 +2842,6 @@ async fn scenario_crash_recovery() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -2977,7 +2950,6 @@ async fn manager_loop_stop_condition_satisfied_e2e() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3056,7 +3028,6 @@ async fn manager_loop_max_cycles_exceeded_e2e() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3194,7 +3165,6 @@ async fn conditional_branching_success_fail_paths() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3247,7 +3217,6 @@ async fn edge_selection_condition_match_wins_over_weight() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3294,7 +3263,6 @@ async fn edge_selection_weight_breaks_ties() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3333,7 +3301,6 @@ async fn edge_selection_lexical_tiebreak() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3391,7 +3358,6 @@ async fn context_updates_visible_across_nodes() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3435,7 +3401,6 @@ async fn stylesheet_applies_model_override() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3491,7 +3456,6 @@ async fn custom_handler_registration_and_execution() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3562,7 +3526,6 @@ async fn integration_smoke_plan_implement_review_done() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3672,7 +3635,6 @@ async fn manager_loop_runs_child_engine_e2e() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3806,7 +3768,6 @@ async fn manager_loop_context_flows_e2e() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3879,7 +3840,6 @@ async fn manager_loop_child_dotfile_e2e() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -3981,7 +3941,6 @@ async fn import_e2e_through_engine() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4135,7 +4094,6 @@ async fn fidelity_default_is_compact() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4192,7 +4150,6 @@ async fn fidelity_graph_default_applied() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4245,7 +4202,6 @@ async fn fidelity_node_overrides_graph_default() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4304,7 +4260,6 @@ async fn fidelity_edge_overrides_node_and_graph() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4353,7 +4308,6 @@ async fn fidelity_full_produces_empty_preamble() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4412,7 +4366,6 @@ async fn fidelity_truncate_preamble_minimal() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4484,7 +4437,6 @@ async fn fidelity_summary_low_mode() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4551,7 +4503,6 @@ async fn fidelity_summary_medium_mode() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4618,7 +4569,6 @@ async fn fidelity_summary_high_mode() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4678,7 +4628,6 @@ async fn fidelity_full_sets_thread_id_in_context() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4749,7 +4698,6 @@ async fn fidelity_full_nodes_share_thread_id() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4830,7 +4778,6 @@ async fn fidelity_resume_degrades_full_to_summary_high() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -4927,7 +4874,6 @@ async fn fidelity_resume_degrade_only_affects_first_hop() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5011,7 +4957,6 @@ async fn fidelity_resume_no_degrade_when_not_full() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5053,7 +4998,6 @@ async fn fidelity_stored_in_checkpoint_context() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5139,7 +5083,6 @@ async fn fidelity_precedence_multi_node_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5207,7 +5150,6 @@ async fn fidelity_compact_preamble_includes_completed_stages_and_context() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5282,7 +5224,6 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5349,7 +5290,6 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5420,7 +5360,6 @@ async fn fidelity_thread_id_fallback_to_previous_node_in_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5474,7 +5413,6 @@ async fn fidelity_thread_id_from_node_class_in_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5531,7 +5469,6 @@ async fn fidelity_edge_thread_id_override_in_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5589,7 +5526,6 @@ async fn fidelity_full_without_explicit_thread_id_uses_previous_node() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5657,7 +5593,6 @@ async fn fidelity_from_parsed_dot_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5705,7 +5640,6 @@ async fn fidelity_checkpoint_roundtrip_preserves_fidelity() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5775,7 +5709,6 @@ async fn fidelity_node_thread_id_overrides_edge_thread_id_in_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -5862,7 +5795,6 @@ async fn fidelity_resume_preserves_context_values_across_checkpoint() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -6077,7 +6009,6 @@ mod real_llm {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -6191,7 +6122,6 @@ mod real_llm {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -6330,7 +6260,6 @@ mod real_llm {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -6437,7 +6366,6 @@ mod real_llm {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -6533,7 +6461,6 @@ async fn human_gate_freeform_only_routes_text() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -6663,7 +6590,6 @@ async fn human_gate_freeform_with_fixed_choice_match() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -6778,7 +6704,6 @@ async fn human_gate_freeform_fallback_on_unmatched_text() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -6906,7 +6831,6 @@ async fn human_gate_freeform_sets_allow_freeform_on_question() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -7014,7 +6938,6 @@ async fn human_gate_without_freeform_sets_allow_freeform_false() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -7299,7 +7222,6 @@ fn make_run_options(dir: &std::path::Path) -> RunOptions {
|
|||
cancel_token: None,
|
||||
run_id: "hook-test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -8405,7 +8327,6 @@ async fn arc_e2e_with_real_llm() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -8533,7 +8454,6 @@ async fn run_fidelity_prompt_pipeline(fidelity: &str) -> String {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -8732,7 +8652,6 @@ async fn large_context_values_are_offloaded_to_artifact_store() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -8947,7 +8866,6 @@ async fn artifact_pointers_rewritten_for_remote_sandbox() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -9077,7 +8995,6 @@ async fn node_dir_uses_visit_count_on_revisit() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -10051,7 +9968,6 @@ async fn full_pipeline_with_cli_backend_node() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -10182,7 +10098,6 @@ async fn stylesheet_backend_property_routes_to_cli() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -10461,7 +10376,6 @@ async fn git_checkpoint_host_emits_events_and_diff_patch() {
|
|||
cancel_token: None,
|
||||
run_id: "test-docker".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -10664,7 +10578,6 @@ async fn git_checkpoint_host_writes_shadow_branch() {
|
|||
cancel_token: None,
|
||||
run_id: run_id.into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -10862,7 +10775,6 @@ async fn parallel_git_branching_host_e2e() {
|
|||
cancel_token: None,
|
||||
run_id: run_id.into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -11125,7 +11037,6 @@ async fn git_checkpoint_host_skips_empty_diff_patch() {
|
|||
cancel_token: None,
|
||||
run_id: "empty-diff".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -11507,7 +11418,6 @@ async fn e2e_circuit_breaker_deterministic_self_loop() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-circuit-breaker".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -11554,7 +11464,6 @@ async fn e2e_circuit_breaker_custom_limit() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-custom-limit".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -11594,7 +11503,6 @@ async fn e2e_circuit_breaker_ignores_transient_failures() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-transient-no-breaker".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -11641,7 +11549,6 @@ async fn e2e_circuit_breaker_different_reasons_separate_counters() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-varying-reasons".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -11681,7 +11588,6 @@ async fn e2e_circuit_breaker_loop_restart() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-restart-breaker".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -11743,7 +11649,6 @@ async fn e2e_failure_signature_persisted_in_context() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-sig-context".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -11807,7 +11712,6 @@ async fn e2e_failure_signature_hint_overrides_reason_in_context() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-sig-hint".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -11863,7 +11767,6 @@ async fn e2e_signature_maps_persist_in_checkpoint() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-sig-persist".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -11990,7 +11893,6 @@ async fn e2e_circuit_breaker_emits_events_before_abort() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-events".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12057,7 +11959,6 @@ async fn e2e_circuit_breaker_does_not_fire_below_limit() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-below-limit".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12153,7 +12054,6 @@ async fn e2e_circuit_breaker_multi_stage_impl_verify_cycle() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-impl-verify-cycle".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12250,7 +12150,6 @@ async fn e2e_loop_restart_blocked_for_deterministic_failure() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-restart-blocked-det".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12290,7 +12189,6 @@ async fn e2e_loop_restart_blocked_for_structural_failure() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-restart-blocked-struct".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12330,7 +12228,6 @@ async fn e2e_loop_restart_blocked_for_budget_exhausted_failure() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-restart-blocked-budget".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12370,7 +12267,6 @@ async fn e2e_loop_restart_blocked_for_canceled_failure() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-restart-blocked-canceled".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12407,7 +12303,6 @@ async fn e2e_loop_restart_blocked_for_compilation_loop_failure() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-restart-blocked-comploop".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12448,7 +12343,6 @@ async fn e2e_loop_restart_allowed_for_transient_infra() {
|
|||
cancel_token: None,
|
||||
run_id: "e2e-restart-allowed-transient".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12552,7 +12446,6 @@ async fn e2e_stall_watchdog_triggers_from_dot_parsed_pipeline() {
|
|||
cancel_token: None,
|
||||
run_id: "stall-e2e".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12608,7 +12501,6 @@ async fn e2e_stall_watchdog_kept_alive_by_handler_events() {
|
|||
cancel_token: None,
|
||||
run_id: "stall-alive-e2e".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12654,7 +12546,6 @@ async fn e2e_stall_watchdog_disabled_with_zero_timeout() {
|
|||
cancel_token: None,
|
||||
run_id: "stall-disabled-e2e".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12719,7 +12610,6 @@ async fn e2e_stall_watchdog_with_explicit_timeout_override() {
|
|||
cancel_token: None,
|
||||
run_id: "stall-override-e2e".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12855,7 +12745,6 @@ async fn asset_collection_local_sandbox_success() {
|
|||
cancel_token: None,
|
||||
run_id: "asset-test-local".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -12981,7 +12870,6 @@ async fn asset_collection_local_sandbox_on_failure() {
|
|||
cancel_token: None,
|
||||
run_id: "asset-test-fail".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -13073,7 +12961,6 @@ async fn asset_collection_docker_sandbox() {
|
|||
cancel_token: None,
|
||||
run_id: "asset-test-docker".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
@ -13137,7 +13024,6 @@ async fn wait_timer_e2e() {
|
|||
cancel_token: None,
|
||||
run_id: "test-run".into(),
|
||||
labels: std::collections::HashMap::new(),
|
||||
git_author: fabro_workflows::git::GitAuthor::default(),
|
||||
workflow_slug: None,
|
||||
github_app: None,
|
||||
base_branch: None,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue