Add configurable git author identity for checkpoint commits

Users can now configure the git author name/email used for checkpoint
commits via [git.author] in server.toml (default) and cli.toml (override).
Defaults to "arc" / "arc@local" preserving current behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-07 17:02:57 -05:00
parent 02a10b2f8d
commit 99ee007572
13 changed files with 484 additions and 27 deletions

View file

@ -2661,6 +2661,7 @@ mod settings {
app_id: Some("12345".into()),
client_id: Some("Iv1.abc123".into()),
slug: Some("arc-dev".into()),
author: Default::default(),
},
feature_flags: FeatureFlags {
session_sandboxes: false,

View file

@ -136,7 +136,14 @@ pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow::
(auth_mode, client_auth, max_concurrent_runs)
};
let state = crate::server::create_app_state_with_options(db, factory, dry_run_mode, max_concurrent_runs);
let (git_author_name, git_author_email) = {
let cfg = shared_config.read().expect("config lock poisoned");
(
cfg.git.author.name.clone().unwrap_or_else(|| "arc".into()),
cfg.git.author.email.clone().unwrap_or_else(|| "arc@local".into()),
)
};
let state = crate::server::create_app_state_with_options(db, factory, dry_run_mode, max_concurrent_runs, git_author_name, git_author_email);
crate::server::spawn_scheduler(Arc::clone(&state));
let router = build_router(state, auth_mode);

View file

@ -106,6 +106,8 @@ pub struct AppState {
max_concurrent_runs: usize,
scheduler_notify: tokio::sync::Notify,
pub hook_config: arc_workflows::hook::HookConfig,
git_author_name: String,
git_author_email: String,
}
/// Build the axum Router with all run endpoints.
@ -365,7 +367,7 @@ pub fn create_app_state(
db: sqlx::SqlitePool,
registry_factory: impl Fn(Arc<dyn Interviewer>) -> HandlerRegistry + Send + Sync + 'static,
) -> Arc<AppState> {
create_app_state_with_options(db, registry_factory, false, 5)
create_app_state_with_options(db, registry_factory, false, 5, "arc".into(), "arc@local".into())
}
/// Create an `AppState` with the given database pool, registry factory, dry-run flag, and concurrency limit.
@ -374,6 +376,8 @@ pub fn create_app_state_with_options(
registry_factory: impl Fn(Arc<dyn Interviewer>) -> HandlerRegistry + Send + Sync + 'static,
dry_run: bool,
max_concurrent_runs: usize,
git_author_name: String,
git_author_email: String,
) -> Arc<AppState> {
Arc::new(AppState {
runs: Mutex::new(HashMap::new()),
@ -384,6 +388,8 @@ pub fn create_app_state_with_options(
max_concurrent_runs,
scheduler_notify: tokio::sync::Notify::new(),
hook_config: arc_workflows::hook::HookConfig::default(),
git_author_name,
git_author_email,
})
}
@ -579,6 +585,8 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: state.git_author_name.clone(),
git_author_email: state.git_author_email.clone(),
};
let result = tokio::select! {
@ -1135,7 +1143,7 @@ mod tests {
#[tokio::test]
async fn test_model_dry_run_returns_ok() {
let state = create_app_state_with_options(test_db().await, test_registry, true, 5);
let state = create_app_state_with_options(test_db().await, test_registry, true, 5, "arc".into(), "arc@local".into());
let app = build_router(state, AuthMode::Disabled);
let req = Request::builder()
@ -1155,7 +1163,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_registry, true, 5);
let state = create_app_state_with_options(test_db().await, test_registry, true, 5, "arc".into(), "arc@local".into());
let app = build_router(state, AuthMode::Disabled);
let req = Request::builder()
@ -1836,7 +1844,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_registry, false, 1);
let state = create_app_state_with_options(test_db().await, test_registry, false, 1, "arc".into(), "arc@local".into());
let app = test_app_with_scheduler(state);
// Submit two runs with max_concurrent_runs=1

View file

@ -65,6 +65,12 @@ pub enum GitProvider {
Github,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
pub struct GitAuthorConfig {
pub name: Option<String>,
pub email: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
pub struct GitConfig {
#[serde(default)]
@ -72,6 +78,8 @@ pub struct GitConfig {
pub app_id: Option<String>,
pub client_id: Option<String>,
pub slug: Option<String>,
#[serde(default)]
pub author: GitAuthorConfig,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
@ -262,6 +270,34 @@ client_id = "Iv1.abc123"
assert_eq!(config.git.provider, GitProvider::Github);
assert_eq!(config.git.app_id, None);
assert_eq!(config.git.client_id, None);
assert_eq!(config.git.author.name, None);
assert_eq!(config.git.author.email, None);
}
#[test]
fn parse_git_author_config() {
let toml = r#"
[git.author]
name = "arc-bot"
email = "arc-bot@company.com"
"#;
let config: ServerConfig = toml::from_str(toml).unwrap();
assert_eq!(config.git.author.name.as_deref(), Some("arc-bot"));
assert_eq!(
config.git.author.email.as_deref(),
Some("arc-bot@company.com")
);
}
#[test]
fn parse_git_author_partial() {
let toml = r#"
[git.author]
name = "custom-name"
"#;
let config: ServerConfig = toml::from_str(toml).unwrap();
assert_eq!(config.git.author.name.as_deref(), Some("custom-name"));
assert_eq!(config.git.author.email, None);
}
#[test]

View file

@ -38,12 +38,19 @@ pub struct LlmDefaults {
pub model: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct CliGitConfig {
#[serde(default)]
pub author: arc_api::server_config::GitAuthorConfig,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct CliConfig {
pub mode: Option<ExecutionMode>,
pub server: Option<ServerDefaults>,
pub agent: Option<AgentDefaults>,
pub llm: Option<LlmDefaults>,
pub git: Option<CliGitConfig>,
}
#[derive(Debug, PartialEq)]
@ -324,6 +331,25 @@ ca = "~/.arc/tls/ca.crt"
assert_eq!(resolved.server_base_url, "https://cli.example.com");
}
#[test]
fn parse_git_author_config() {
let toml = r#"
[git.author]
name = "my-arc"
email = "me@local"
"#;
let config: CliConfig = toml::from_str(toml).unwrap();
let git = config.git.unwrap();
assert_eq!(git.author.name.as_deref(), Some("my-arc"));
assert_eq!(git.author.email.as_deref(), Some("me@local"));
}
#[test]
fn parse_git_author_absent() {
let config: CliConfig = toml::from_str("").unwrap();
assert_eq!(config.git, None);
}
#[test]
fn resolve_mode_tls_from_config() {
let tls = ClientTlsConfig {

View file

@ -179,12 +179,28 @@ async fn main() -> Result<()> {
let styles: &'static arc_util::terminal::Styles =
Box::leak(Box::new(arc_util::terminal::Styles::detect_stderr()));
let server_config = arc_api::server_config::load_server_config(None)?;
let cli_config = cli_config::load_cli_config(None)?;
let github_app = build_github_app_credentials(&server_config);
let cli_author = cli_config.git.as_ref().map(|g| &g.author);
let git_author_name = cli_author
.and_then(|a| a.name.as_deref())
.or(server_config.git.author.name.as_deref())
.unwrap_or("arc")
.to_string();
let git_author_email = cli_author
.and_then(|a| a.email.as_deref())
.or(server_config.git.author.email.as_deref())
.unwrap_or("arc@local")
.to_string();
arc_workflows::cli::run::run_command(
args,
server_config.run_defaults,
styles,
github_app,
git_author_name,
git_author_email,
)
.await?;
}

View file

@ -181,10 +181,12 @@ pub async fn run_command(
run_defaults: RunDefaults,
styles: &'static Styles,
github_app: Option<crate::github_app::GitHubAppCredentials>,
git_author_name: String,
git_author_email: String,
) -> anyhow::Result<()> {
// Handle --run-branch resume: read everything from git metadata
if let Some(branch) = args.run_branch.clone() {
return run_from_branch(args, &branch, styles).await;
return run_from_branch(args, &branch, styles, git_author_name, git_author_email).await;
}
let workflow_path = args
@ -695,6 +697,8 @@ pub async fn run_command(
.collect(),
checkpoint_exclude_globs,
github_app: github_app.clone(),
git_author_name,
git_author_email,
};
let run_start = Instant::now();
@ -926,6 +930,8 @@ async fn run_from_branch(
args: RunArgs,
run_branch: &str,
styles: &'static Styles,
git_author_name: String,
git_author_email: String,
) -> anyhow::Result<()> {
// Extract run_id from branch name: "arc/run/{run_id}" -> "{run_id}"
let run_id = run_branch
@ -1062,6 +1068,8 @@ async fn run_from_branch(
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name,
git_author_email,
};
let run_start = Instant::now();

View file

@ -521,6 +521,8 @@ pub struct GitState {
pub run_branch: Option<String>,
pub meta_branch: Option<String>,
pub checkpoint_exclude_globs: Vec<String>,
pub git_author_name: String,
pub git_author_email: String,
}
/// How git checkpointing should be performed for a workflow run.
@ -542,6 +544,8 @@ pub async fn git_checkpoint_host(
completed_count: usize,
shadow_sha: Option<String>,
exclude_globs: Vec<String>,
author_name: String,
author_email: String,
) -> Option<String> {
match tokio::task::spawn_blocking(move || {
crate::git::checkpoint_commit(
@ -552,6 +556,8 @@ pub async fn git_checkpoint_host(
completed_count,
shadow_sha.as_deref(),
&exclude_globs,
&author_name,
&author_email,
)
})
.await
@ -587,6 +593,8 @@ pub async fn git_checkpoint_remote(
completed_count: usize,
shadow_sha: Option<String>,
exclude_globs: &[String],
author_name: &str,
author_email: &str,
) -> Option<String> {
// Stage everything (with optional excludes)
let add_cmd = if exclude_globs.is_empty() {
@ -636,9 +644,9 @@ pub async fn git_checkpoint_remote(
return None;
}
// Commit with arc identity using the message file
// Commit with configured identity using the message file
let commit_cmd = format!(
"{GIT_REMOTE} -c user.name=arc -c user.email=arc@local commit --allow-empty -F /tmp/arc-commit-msg"
"{GIT_REMOTE} -c user.name={author_name} -c user.email={author_email} commit --allow-empty -F /tmp/arc-commit-msg"
);
let commit_result = sandbox
.exec_command(&commit_cmd, 30_000, None, None, None)
@ -830,6 +838,10 @@ pub struct RunConfig {
pub checkpoint_exclude_globs: Vec<String>,
/// GitHub App credentials for pushing metadata branches to origin.
pub github_app: Option<crate::github_app::GitHubAppCredentials>,
/// Git author name for checkpoint commits.
pub git_author_name: String,
/// Git author email for checkpoint commits.
pub git_author_email: String,
}
/// The workflow run execution engine.
@ -1208,6 +1220,8 @@ impl WorkflowRunEngine {
run_branch: config.run_branch.clone(),
meta_branch: config.meta_branch.clone(),
checkpoint_exclude_globs: config.checkpoint_exclude_globs.clone(),
git_author_name: config.git_author_name.clone(),
git_author_email: config.git_author_email.clone(),
})),
_ => None,
};
@ -1258,7 +1272,11 @@ impl WorkflowRunEngine {
None => None,
};
if let Some(repo_path) = store_path {
let store = crate::git::MetadataStore::new(repo_path);
let store = crate::git::MetadataStore::new(
repo_path,
&config.git_author_name,
&config.git_author_email,
);
let manifest_bytes = serde_json::to_vec_pretty(&manifest).unwrap_or_default();
let dot_source =
std::fs::read(config.logs_root.join("graph.dot")).unwrap_or_default();
@ -1882,7 +1900,11 @@ impl WorkflowRunEngine {
let repo_path = match mode {
GitCheckpointMode::Host(ref p) | GitCheckpointMode::Remote(ref p) => p,
};
let store = crate::git::MetadataStore::new(repo_path);
let store = crate::git::MetadataStore::new(
repo_path,
&config.git_author_name,
&config.git_author_email,
);
serde_json::to_vec_pretty(&checkpoint)
.ok()
.and_then(|cp_json| {
@ -1931,6 +1953,8 @@ impl WorkflowRunEngine {
completed_count,
shadow_sha,
config.checkpoint_exclude_globs.clone(),
config.git_author_name.clone(),
config.git_author_email.clone(),
)
.await
}
@ -1943,6 +1967,8 @@ impl WorkflowRunEngine {
completed_count,
shadow_sha,
&config.checkpoint_exclude_globs,
&config.git_author_name,
&config.git_author_email,
)
.await
}
@ -2810,6 +2836,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&g, &config).await.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -2833,6 +2861,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&g, &config).await.unwrap();
let checkpoint_path = dir.path().join("checkpoint.json");
@ -2864,6 +2894,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&g, &config).await.unwrap();
@ -2891,6 +2923,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -2914,6 +2948,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&g, &config).await.unwrap();
@ -2950,6 +2986,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&g, &config).await.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -3010,6 +3048,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&g, &config).await.unwrap();
@ -3097,6 +3137,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&g, &config).await.unwrap();
@ -3127,6 +3169,8 @@ mod tests {
labels: HashMap::from([("env".into(), "test".into())]),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&g, &config).await.unwrap();
@ -3153,6 +3197,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&g, &config).await.unwrap();
@ -3179,6 +3225,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&g, &config).await.unwrap();
@ -3208,6 +3256,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&g, &config).await.unwrap();
@ -3365,6 +3415,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&g, &config).await.unwrap();
@ -3405,6 +3457,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&g, &config).await.unwrap();
@ -3463,6 +3517,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&g, &config).await.unwrap();
@ -3524,6 +3580,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
@ -3589,6 +3647,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_ok());
@ -3643,6 +3703,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&g, &config).await.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -3698,6 +3760,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&g, &config).await.unwrap();
@ -3728,6 +3792,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&g, &config).await.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -3754,6 +3820,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -3779,6 +3847,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&g, &config).await.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -3817,6 +3887,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
// Set cancel after a short delay (while the slow handler is running)
@ -3892,6 +3964,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -3920,6 +3994,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -3950,6 +4026,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -3985,6 +4063,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -4018,6 +4098,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -4048,6 +4130,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -4139,6 +4223,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
// The engine returns Err because the Fail outcome has no outgoing fail edge,
@ -4345,6 +4431,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -4378,6 +4466,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -4418,6 +4508,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -4498,6 +4590,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -4589,6 +4683,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&g, &config).await;
assert!(result.is_err());
@ -4657,6 +4753,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&g, &config).await.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -4712,6 +4810,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&g, &config).await.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -4768,6 +4868,8 @@ mod tests {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let _outcome = engine.run(&g, &config).await.unwrap();

View file

@ -145,6 +145,8 @@ pub fn checkpoint_commit(
completed_count: usize,
shadow_sha: Option<&str>,
excludes: &[String],
author_name: &str,
author_email: &str,
) -> Result<String> {
tracing::debug!(path = %work_dir.display(), node_id, "Creating git checkpoint commit");
// Stage everything (with optional excludes)
@ -183,13 +185,15 @@ pub fn checkpoint_commit(
}
let message = trailerlink::format_message(&subject, "", &trailers);
// Commit with arc identity (works even if user.name/email not configured)
// Commit with configured identity (works even if user.name/email not configured)
let name_cfg = format!("user.name={author_name}");
let email_cfg = format!("user.email={author_email}");
let output = git_cmd(work_dir)
.args([
"-c",
"user.name=arc",
&name_cfg,
"-c",
"user.email=arc@local",
&email_cfg,
"commit",
"--allow-empty",
"-m",
@ -283,12 +287,16 @@ pub fn sanitize_ref_component(s: &str) -> String {
/// (`arc/{run_id}`) so that runs can be resumed from git alone.
pub struct MetadataStore {
repo_path: std::path::PathBuf,
author_name: String,
author_email: String,
}
impl MetadataStore {
pub fn new(repo_path: impl Into<std::path::PathBuf>) -> Self {
pub fn new(repo_path: impl Into<std::path::PathBuf>, author_name: &str, author_email: &str) -> Self {
Self {
repo_path: repo_path.into(),
author_name: author_name.to_string(),
author_email: author_email.to_string(),
}
}
@ -301,7 +309,7 @@ impl MetadataStore {
let repo = Repository::discover(&self.repo_path)
.map_err(|e| git_error(format!("failed to open repo: {e}")))?;
let store = Store::new(repo);
let sig = Signature::now("arc", "arc@local")
let sig = Signature::now(&self.author_name, &self.author_email)
.map_err(|e| git_error(format!("failed to create signature: {e}")))?;
Ok((store, sig))
}
@ -518,7 +526,7 @@ mod tests {
let wt = dir.path().join("ff-wt");
add_worktree(dir.path(), &wt, "ff-branch").unwrap();
fs::write(wt.join("new.txt"), "data").unwrap();
checkpoint_commit(&wt, "run", "node", "ok", 1, None, &[]).unwrap();
checkpoint_commit(&wt, "run", "node", "ok", 1, None, &[], "arc", "arc@local").unwrap();
let advanced_sha = head_sha(&wt).unwrap();
remove_worktree(dir.path(), &wt).unwrap();
@ -590,7 +598,7 @@ mod tests {
// Simulate a shadow commit SHA
let shadow_sha = "abcdef1234567890abcdef1234567890abcdef12";
let sha =
checkpoint_commit(&wt_path, "run1", "nodeA", "success", 3, Some(shadow_sha), &[]).unwrap();
checkpoint_commit(&wt_path, "run1", "nodeA", "success", 3, Some(shadow_sha), &[], "arc", "arc@local").unwrap();
assert_eq!(sha.len(), 40);
assert!(sha.chars().all(|c| c.is_ascii_hexdigit()));
@ -629,7 +637,7 @@ mod tests {
let wt_path = dir.path().join("worktree");
add_worktree(dir.path(), &wt_path, "run-branch2").unwrap();
let sha = checkpoint_commit(&wt_path, "run2", "nodeB", "completed", 1, None, &[]).unwrap();
let sha = checkpoint_commit(&wt_path, "run2", "nodeB", "completed", 1, None, &[], "arc", "arc@local").unwrap();
assert_eq!(sha.len(), 40);
// Verify Arc-Completed trailer present but no Arc-Meta
@ -673,7 +681,7 @@ mod tests {
let wt_path = dir.path().join("worktree");
add_worktree(dir.path(), &wt_path, "fallback-branch").unwrap();
let sha = checkpoint_commit(&wt_path, "run2", "nodeB", "completed", 0, None, &[]).unwrap();
let sha = checkpoint_commit(&wt_path, "run2", "nodeB", "completed", 0, None, &[], "arc", "arc@local").unwrap();
assert_eq!(sha.len(), 40);
remove_worktree(dir.path(), &wt_path).unwrap();
@ -727,7 +735,7 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
init_repo(dir.path());
let store = MetadataStore::new(dir.path());
let store = MetadataStore::new(dir.path(), "arc", "arc@local");
let manifest = br#"{"run_id":"RUN1","workflow_name":"test","goal":"g","start_time":"2025-01-01T00:00:00Z","node_count":2,"edge_count":1}"#;
let dot = b"digraph { start -> end }";
store.init_run("RUN1", manifest, dot).unwrap();
@ -749,7 +757,7 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
init_repo(dir.path());
let store = MetadataStore::new(dir.path());
let store = MetadataStore::new(dir.path(), "arc", "arc@local");
store.init_run("RUN2", b"{}", b"digraph {}").unwrap();
let ctx = crate::context::Context::new();
@ -784,7 +792,7 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
init_repo(dir.path());
let store = MetadataStore::new(dir.path());
let store = MetadataStore::new(dir.path(), "arc", "arc@local");
store.init_run("RUN3", b"{}", b"digraph {}").unwrap();
let ctx = crate::context::Context::new();
@ -835,7 +843,7 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
init_repo(dir.path());
let store = MetadataStore::new(dir.path());
let store = MetadataStore::new(dir.path(), "arc", "arc@local");
store.init_run("RUN4", b"{}", b"digraph {}").unwrap();
let artifact_data = br#"{"large_output":"some data"}"#;
@ -958,7 +966,7 @@ mod tests {
fs::write(wt_path.join("node_modules/pkg/index.js"), "module").unwrap();
let excludes = vec!["**/node_modules/**".to_string()];
checkpoint_commit(&wt_path, "run", "node", "ok", 1, None, &excludes).unwrap();
checkpoint_commit(&wt_path, "run", "node", "ok", 1, None, &excludes, "arc", "arc@local").unwrap();
// Verify kept.txt was committed
let output = Command::new("git")
@ -991,14 +999,14 @@ mod tests {
// Create and commit a file in the excluded dir first
fs::create_dir_all(wt_path.join(".cache")).unwrap();
fs::write(wt_path.join(".cache/data.bin"), "v1").unwrap();
checkpoint_commit(&wt_path, "run", "setup", "ok", 0, None, &[]).unwrap();
checkpoint_commit(&wt_path, "run", "setup", "ok", 0, None, &[], "arc", "arc@local").unwrap();
// Now modify the tracked excluded file and add a new non-excluded file
fs::write(wt_path.join(".cache/data.bin"), "v2").unwrap();
fs::write(wt_path.join("result.txt"), "done").unwrap();
let excludes = vec!["**/.cache/**".to_string()];
checkpoint_commit(&wt_path, "run", "step", "ok", 1, None, &excludes).unwrap();
checkpoint_commit(&wt_path, "run", "step", "ok", 1, None, &excludes, "arc", "arc@local").unwrap();
let output = Command::new("git")
.args(["show", "--name-only", "--format=", "HEAD"])

View file

@ -137,6 +137,7 @@ 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_config = RunConfig {
logs_root: child_logs,
cancel_token: Some(cancel_token),
@ -149,6 +150,8 @@ impl Handler for SubWorkflowHandler {
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: git_state.as_ref().map(|gs| gs.git_author_name.clone()).unwrap_or_else(|| "arc".into()),
git_author_email: git_state.as_ref().map(|gs| gs.git_author_email.clone()).unwrap_or_else(|| "arc@local".into()),
};
// Clone parent context for child; inject parent preamble

View file

@ -244,6 +244,8 @@ impl Handler for ParallelHandler {
0,
None,
gs.checkpoint_exclude_globs.clone(),
gs.git_author_name.clone(),
gs.git_author_email.clone(),
)
.await
}
@ -256,6 +258,8 @@ impl Handler for ParallelHandler {
0,
None,
&gs.checkpoint_exclude_globs,
&gs.git_author_name,
&gs.git_author_email,
)
.await
}
@ -397,6 +401,8 @@ impl Handler for ParallelHandler {
let sem = Arc::clone(&semaphore);
let has_git = git_state.is_some();
let run_id = git_state.as_ref().map(|gs| gs.run_id.clone());
let git_author_name = git_state.as_ref().map(|gs| gs.git_author_name.clone()).unwrap_or_else(|| "arc".into());
let git_author_email = git_state.as_ref().map(|gs| gs.git_author_email.clone()).unwrap_or_else(|| "arc@local".into());
let handle = tokio::spawn(async move {
let _permit = sem
@ -462,7 +468,7 @@ impl Handler for ParallelHandler {
if add_result.as_ref().is_ok_and(|r| r.exit_code == 0) {
let msg = format!("arc({rid}): {nid} ({status_str})");
let commit_cmd = format!(
"{git_r} -c user.name=arc -c user.email=arc@local commit --allow-empty -m '{msg}'"
"{git_r} -c user.name={git_author_name} -c user.email={git_author_email} commit --allow-empty -m '{msg}'"
);
let _ = setup
.sandbox

View file

@ -333,6 +333,8 @@ async fn daytona_pipeline_artifact_offload_and_sync() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -526,6 +528,8 @@ async fn daytona_git_checkpoint_remote_emits_events() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -708,6 +712,8 @@ async fn daytona_parallel_git_branching_e2e() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -1035,6 +1041,8 @@ async fn daytona_git_checkpoint_with_shadow_branch() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -1174,6 +1182,8 @@ async fn daytona_asset_collection() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -1406,6 +1416,8 @@ async fn daytona_git_push_run_branch_to_origin() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine

View file

@ -201,6 +201,8 @@ async fn end_to_end_linear_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -340,6 +342,8 @@ async fn end_to_end_branching_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -463,6 +467,8 @@ async fn end_to_end_human_gate_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -574,6 +580,8 @@ async fn goal_gate_routes_to_retry_target_on_failure() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -695,6 +703,8 @@ async fn goal_gate_routes_to_retry_target_when_present() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -1003,6 +1013,8 @@ async fn retry_on_failure_then_succeed() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -1078,6 +1090,8 @@ async fn pipeline_with_many_nodes() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -1401,6 +1415,8 @@ async fn smoke_test_with_mock_codergen_backend() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -1502,6 +1518,8 @@ async fn end_to_end_parallel_fan_out_fan_in() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -1614,6 +1632,8 @@ async fn resume_from_checkpoint_completes_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -1712,6 +1732,8 @@ async fn resume_from_checkpoint_preserves_goal_gate_outcomes() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
// This should succeed because goal gate for gated_work is satisfied
@ -1755,6 +1777,8 @@ async fn graph_goal_in_context() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -1790,6 +1814,8 @@ async fn event_streaming_lifecycle() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -1869,6 +1895,8 @@ async fn context_flow_between_stages() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -1921,6 +1949,8 @@ async fn tool_handler_e2e() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);
@ -1992,6 +2022,8 @@ async fn auto_approve_interviewer_e2e() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);
@ -2028,6 +2060,8 @@ async fn codergen_without_backend_simulated() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -2132,6 +2166,8 @@ async fn branching_loop_back_on_failure() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);
@ -2218,6 +2254,8 @@ async fn human_gate_loops_back() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);
@ -2275,6 +2313,8 @@ async fn scenario_ship_a_feature() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);
@ -2360,6 +2400,8 @@ async fn scenario_parallel_expert_review() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);
@ -2439,6 +2481,8 @@ async fn scenario_node_retries_on_retry_status() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);
@ -2500,6 +2544,8 @@ async fn scenario_loop_restart_resets_context() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);
@ -2567,6 +2613,8 @@ async fn scenario_bug_triage_router() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);
@ -2624,6 +2672,8 @@ async fn scenario_crash_recovery() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
.run_from_checkpoint(&graph, &config, &checkpoint)
@ -2732,6 +2782,8 @@ async fn manager_loop_stop_condition_satisfied_e2e() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
@ -2808,6 +2860,8 @@ async fn manager_loop_max_cycles_exceeded_e2e() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
@ -2943,6 +2997,8 @@ async fn conditional_branching_success_fail_paths() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);
@ -2995,6 +3051,8 @@ async fn edge_selection_condition_match_wins_over_weight() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -3041,6 +3099,8 @@ async fn edge_selection_weight_breaks_ties() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -3079,6 +3139,8 @@ async fn edge_selection_lexical_tiebreak() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -3136,6 +3198,8 @@ async fn context_updates_visible_across_nodes() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -3179,6 +3243,8 @@ async fn stylesheet_applies_model_override() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);
@ -3234,6 +3300,8 @@ async fn custom_handler_registration_and_execution() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -3304,6 +3372,8 @@ async fn integration_smoke_plan_implement_review_done() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);
@ -3407,6 +3477,8 @@ async fn manager_loop_runs_child_engine_e2e() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -3541,6 +3613,8 @@ async fn manager_loop_context_flows_e2e() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
@ -3614,6 +3688,8 @@ async fn manager_loop_child_dotfile_e2e() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
@ -3727,6 +3803,8 @@ async fn graph_merge_e2e_through_engine() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -3877,6 +3955,8 @@ async fn fidelity_default_is_compact() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -3933,6 +4013,8 @@ async fn fidelity_graph_default_applied() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -3985,6 +4067,8 @@ async fn fidelity_node_overrides_graph_default() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -4043,6 +4127,8 @@ async fn fidelity_edge_overrides_node_and_graph() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -4091,6 +4177,8 @@ async fn fidelity_full_produces_empty_preamble() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -4149,6 +4237,8 @@ async fn fidelity_truncate_preamble_minimal() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -4220,6 +4310,8 @@ async fn fidelity_summary_low_mode() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -4286,6 +4378,8 @@ async fn fidelity_summary_medium_mode() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -4352,6 +4446,8 @@ async fn fidelity_summary_high_mode() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -4411,6 +4507,8 @@ async fn fidelity_full_sets_thread_id_in_context() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -4481,6 +4579,8 @@ async fn fidelity_full_nodes_share_thread_id() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -4560,6 +4660,8 @@ async fn fidelity_resume_degrades_full_to_summary_high() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine
.run_from_checkpoint(&graph, &config, &checkpoint)
@ -4655,6 +4757,8 @@ async fn fidelity_resume_degrade_only_affects_first_hop() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine
.run_from_checkpoint(&graph, &config, &checkpoint)
@ -4737,6 +4841,8 @@ async fn fidelity_resume_no_degrade_when_not_full() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine
.run_from_checkpoint(&graph, &config, &checkpoint)
@ -4778,6 +4884,8 @@ async fn fidelity_stored_in_checkpoint_context() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -4863,6 +4971,8 @@ async fn fidelity_precedence_multi_node_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -4930,6 +5040,8 @@ async fn fidelity_compact_preamble_includes_completed_stages_and_context() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -5005,6 +5117,8 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine_low
.run(&graph_low, &config_low)
@ -5072,6 +5186,8 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine_med
.run(&graph_med, &config_med)
@ -5142,6 +5258,8 @@ async fn fidelity_thread_id_fallback_to_previous_node_in_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -5195,6 +5313,8 @@ async fn fidelity_thread_id_from_node_class_in_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -5251,6 +5371,8 @@ async fn fidelity_edge_thread_id_override_in_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -5308,6 +5430,8 @@ async fn fidelity_full_without_explicit_thread_id_uses_previous_node() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -5375,6 +5499,8 @@ async fn fidelity_from_parsed_dot_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -5422,6 +5548,8 @@ async fn fidelity_checkpoint_roundtrip_preserves_fidelity() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -5491,6 +5619,8 @@ async fn fidelity_node_thread_id_overrides_edge_thread_id_in_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine.run(&graph, &config).await.expect("run");
@ -5576,6 +5706,8 @@ async fn fidelity_resume_preserves_context_values_across_checkpoint() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine
.run_from_checkpoint(&graph, &config, &checkpoint)
@ -5769,6 +5901,8 @@ mod real_llm {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = tokio::time::timeout(
@ -5883,6 +6017,8 @@ mod real_llm {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = tokio::time::timeout(
@ -6024,6 +6160,8 @@ mod real_llm {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = tokio::time::timeout(
@ -6131,6 +6269,8 @@ mod real_llm {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = tokio::time::timeout(
@ -6229,6 +6369,8 @@ async fn human_gate_freeform_only_routes_text() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -6362,6 +6504,8 @@ async fn human_gate_freeform_with_fixed_choice_match() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -6479,6 +6623,8 @@ async fn human_gate_freeform_fallback_on_unmatched_text() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -6610,6 +6756,8 @@ async fn human_gate_freeform_sets_allow_freeform_on_question() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -6721,6 +6869,8 @@ async fn human_gate_without_freeform_sets_allow_freeform_false() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -6980,6 +7130,8 @@ fn make_run_config(dir: &std::path::Path) -> RunConfig {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
}
}
@ -8073,6 +8225,8 @@ async fn arc_e2e_with_real_llm() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -8201,6 +8355,8 @@ async fn run_fidelity_prompt_pipeline(fidelity: &str) -> String {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
engine
@ -8400,6 +8556,8 @@ async fn large_context_values_are_offloaded_to_artifact_store() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -8612,6 +8770,8 @@ async fn artifact_pointers_rewritten_for_remote_sandbox() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -8742,6 +8902,8 @@ async fn node_dir_uses_visit_count_on_revisit() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -9660,6 +9822,8 @@ async fn full_pipeline_with_cli_backend_node() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -9789,6 +9953,8 @@ async fn stylesheet_backend_property_routes_to_cli() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -10070,6 +10236,8 @@ async fn git_checkpoint_host_emits_events_and_diff_patch() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
// 5. Run pipeline
@ -10255,6 +10423,8 @@ async fn git_checkpoint_host_writes_shadow_branch() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
// 5. Run pipeline
@ -10449,6 +10619,8 @@ async fn parallel_git_branching_host_e2e() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
// 5. Run pipeline
@ -10711,6 +10883,8 @@ async fn git_checkpoint_host_skips_empty_diff_patch() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -11093,6 +11267,8 @@ async fn e2e_circuit_breaker_deterministic_self_loop() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -11140,6 +11316,8 @@ async fn e2e_circuit_breaker_custom_limit() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -11180,6 +11358,8 @@ async fn e2e_circuit_breaker_ignores_transient_failures() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -11227,6 +11407,8 @@ async fn e2e_circuit_breaker_different_reasons_separate_counters() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -11267,6 +11449,8 @@ async fn e2e_circuit_breaker_loop_restart() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -11329,6 +11513,8 @@ async fn e2e_failure_signature_persisted_in_context() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.unwrap();
@ -11393,6 +11579,8 @@ async fn e2e_failure_signature_hint_overrides_reason_in_context() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let _outcome = engine.run(&graph, &config).await.unwrap();
@ -11449,6 +11637,8 @@ async fn e2e_signature_maps_persist_in_checkpoint() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.unwrap();
@ -11575,6 +11765,8 @@ async fn e2e_circuit_breaker_emits_events_before_abort() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -11642,6 +11834,8 @@ async fn e2e_circuit_breaker_does_not_fire_below_limit() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.unwrap();
@ -11738,6 +11932,8 @@ async fn e2e_circuit_breaker_multi_stage_impl_verify_cycle() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -11834,6 +12030,8 @@ async fn e2e_loop_restart_blocked_for_deterministic_failure() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -11874,6 +12072,8 @@ async fn e2e_loop_restart_blocked_for_structural_failure() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -11914,6 +12114,8 @@ async fn e2e_loop_restart_blocked_for_budget_exhausted_failure() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -11954,6 +12156,8 @@ async fn e2e_loop_restart_blocked_for_canceled_failure() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -11991,6 +12195,8 @@ async fn e2e_loop_restart_blocked_for_compilation_loop_failure() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -12032,6 +12238,8 @@ async fn e2e_loop_restart_allowed_for_transient_infra() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -12136,6 +12344,8 @@ async fn e2e_stall_watchdog_triggers_from_dot_parsed_pipeline() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let result = engine.run(&graph, &config).await;
@ -12192,6 +12402,8 @@ async fn e2e_stall_watchdog_kept_alive_by_handler_events() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -12238,6 +12450,8 @@ async fn e2e_stall_watchdog_disabled_with_zero_timeout() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -12303,6 +12517,8 @@ async fn e2e_stall_watchdog_with_explicit_timeout_override() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let start = std::time::Instant::now();
@ -12433,6 +12649,8 @@ async fn asset_collection_local_sandbox_success() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -12541,6 +12759,8 @@ async fn asset_collection_local_sandbox_on_failure() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -12632,6 +12852,8 @@ async fn asset_collection_docker_sandbox() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine
@ -12701,6 +12923,8 @@ async fn wait_timer_e2e() {
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author_name: "arc".into(),
git_author_email: "arc@local".into(),
};
let outcome = engine.run(&graph, &config).await.expect("run");
assert_eq!(outcome.status, StageStatus::Success);