mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Make asset collection opt-in via [assets] config globs
Asset collection previously ran a full `find` scan before and after every stage (~30s per scan), even when no assets were needed. This makes it opt-in: only workflows with an `[assets] include = [...]` section in their TOML config will run asset collection, and only with user-specified globs. - Add AssetsConfig struct and wire through WorkflowRunConfig, RunDefaults, and RunConfig - Remove baseline snapshot approach; single post-execution scan using user globs filtered by mtime and size budgets - Delete hardcoded pattern constants, is_asset_candidate, matches_simple_glob, FileFingerprint, and snapshot() — replaced by configurable globs - Skip all asset work when asset_globs is empty (zero overhead default) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
686d6bbdb0
commit
7400a9d5a1
9 changed files with 334 additions and 276 deletions
|
|
@ -1293,6 +1293,7 @@ mod runs {
|
|||
hooks: vec![],
|
||||
checkpoint: Default::default(),
|
||||
pull_request: None,
|
||||
assets: None,
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
|
@ -1443,6 +1444,7 @@ mod workflows {
|
|||
hooks: vec![],
|
||||
checkpoint: Default::default(),
|
||||
pull_request: None,
|
||||
assets: None,
|
||||
}),
|
||||
graph: r#"digraph fix_build {
|
||||
graph [
|
||||
|
|
@ -1510,6 +1512,7 @@ mod workflows {
|
|||
hooks: vec![],
|
||||
checkpoint: Default::default(),
|
||||
pull_request: None,
|
||||
assets: None,
|
||||
}),
|
||||
graph: r#"digraph implement {
|
||||
graph [
|
||||
|
|
@ -1589,6 +1592,7 @@ mod workflows {
|
|||
hooks: vec![],
|
||||
checkpoint: Default::default(),
|
||||
pull_request: None,
|
||||
assets: None,
|
||||
}),
|
||||
graph: r#"digraph sync {
|
||||
graph [
|
||||
|
|
@ -1657,6 +1661,7 @@ mod workflows {
|
|||
hooks: vec![],
|
||||
checkpoint: Default::default(),
|
||||
pull_request: None,
|
||||
assets: None,
|
||||
}),
|
||||
graph: r#"digraph expand {
|
||||
graph [
|
||||
|
|
@ -3097,6 +3102,7 @@ mod settings {
|
|||
vars: None,
|
||||
checkpoint: Default::default(),
|
||||
pull_request: None,
|
||||
assets: None,
|
||||
},
|
||||
hook_config: arc_workflows::hook::HookConfig { hooks: vec![] },
|
||||
})
|
||||
|
|
|
|||
|
|
@ -608,6 +608,7 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
|
|||
git_author: state.git_author.clone(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = tokio::select! {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,8 @@
|
|||
use arc_agent::Sandbox;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
/// Fingerprint of a file for change detection between snapshots.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FileFingerprint {
|
||||
pub size: u64,
|
||||
pub mtime_epoch_secs: f64,
|
||||
}
|
||||
|
||||
/// A file discovered by the find command.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DiscoveredFile {
|
||||
|
|
@ -29,17 +21,6 @@ pub struct AssetCollectionSummary {
|
|||
pub copied_paths: Vec<String>,
|
||||
}
|
||||
|
||||
/// Directory path segments that identify asset directories.
|
||||
const DIRECTORY_SEGMENTS: &[&str] = &[
|
||||
"playwright-report",
|
||||
"test-results",
|
||||
"cypress/videos",
|
||||
"cypress/screenshots",
|
||||
];
|
||||
|
||||
/// Filename glob patterns for individual asset files.
|
||||
const FILENAME_GLOBS: &[&str] = &["junit*.xml", "*.trace.zip"];
|
||||
|
||||
/// Directories to exclude from the find search.
|
||||
const EXCLUDE_DIRS: &[&str] = &[
|
||||
".git",
|
||||
|
|
@ -51,17 +32,18 @@ const EXCLUDE_DIRS: &[&str] = &[
|
|||
"__pycache__",
|
||||
];
|
||||
|
||||
/// Path segments that indicate excluded tool cache directories.
|
||||
const EXCLUDE_SEGMENTS: &[&str] = &[".cache/ms-playwright", "playwright/.cache", ".yarn/cache"];
|
||||
|
||||
/// Maximum size for a single file (10 MB).
|
||||
const MAX_FILE_SIZE: u64 = 10 * 1024 * 1024;
|
||||
|
||||
/// Maximum total size for all collected files (50 MB).
|
||||
const MAX_TOTAL_SIZE: u64 = 50 * 1024 * 1024;
|
||||
|
||||
/// Build a platform-aware find command to discover asset files.
|
||||
pub fn build_find_command(root: &str, platform: &str) -> String {
|
||||
/// Build a platform-aware find command to discover asset files matching the given globs.
|
||||
///
|
||||
/// Globs without `/` are treated as filename patterns (`-name`).
|
||||
/// Globs with `/` are treated as directory patterns: the trailing `/**` (if any) is stripped
|
||||
/// and the remainder is matched via `-path '*/{dir}/*'`.
|
||||
pub fn build_find_command(root: &str, platform: &str, globs: &[String]) -> String {
|
||||
let mut cmd = format!("find {root}");
|
||||
|
||||
// Prune excluded directories
|
||||
|
|
@ -73,17 +55,21 @@ pub fn build_find_command(root: &str, platform: &str) -> String {
|
|||
cmd.push_str(&prune_parts.join(" -o "));
|
||||
cmd.push_str(" \\) -prune -o");
|
||||
|
||||
// Match conditions: not a symlink, is a file, matches asset patterns
|
||||
// Match conditions: not a symlink, is a file, matches user globs
|
||||
cmd.push_str(" -not -type l -type f \\(");
|
||||
|
||||
let mut path_conditions: Vec<String> = Vec::new();
|
||||
for segment in DIRECTORY_SEGMENTS {
|
||||
path_conditions.push(format!(" -path '*/{segment}/*'"));
|
||||
let mut conditions: Vec<String> = Vec::new();
|
||||
for glob in globs {
|
||||
if glob.contains('/') {
|
||||
// Directory-style glob: strip trailing /** and match as path
|
||||
let dir = glob.trim_end_matches("/**").trim_end_matches("/*");
|
||||
conditions.push(format!(" -path '*/{dir}/*'"));
|
||||
} else {
|
||||
// Filename glob
|
||||
conditions.push(format!(" -name '{glob}'"));
|
||||
}
|
||||
}
|
||||
for glob in FILENAME_GLOBS {
|
||||
path_conditions.push(format!(" -name '{glob}'"));
|
||||
}
|
||||
cmd.push_str(&path_conditions.join(" -o"));
|
||||
cmd.push_str(&conditions.join(" -o"));
|
||||
cmd.push_str(" \\)");
|
||||
|
||||
// Platform-specific output format
|
||||
|
|
@ -177,94 +163,14 @@ fn parse_find_output_darwin(output: &str) -> Vec<DiscoveredFile> {
|
|||
files
|
||||
}
|
||||
|
||||
/// Check whether a path matches known asset patterns.
|
||||
pub fn is_asset_candidate(path: &str) -> bool {
|
||||
// Check excluded segments first
|
||||
for seg in EXCLUDE_SEGMENTS {
|
||||
if path.contains(seg) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check directory segments — must appear as a complete path segment
|
||||
for segment in DIRECTORY_SEGMENTS {
|
||||
// segment may contain a slash (e.g., "cypress/videos"), so check
|
||||
// that it appears bounded by / or start/end of string
|
||||
if let Some(pos) = path.find(segment) {
|
||||
let before_ok = pos == 0 || path.as_bytes()[pos - 1] == b'/';
|
||||
let after_pos = pos + segment.len();
|
||||
let after_ok = after_pos >= path.len() || path.as_bytes()[after_pos] == b'/';
|
||||
if before_ok && after_ok {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check filename globs against the last path component
|
||||
if let Some(filename) = path.rsplit('/').next() {
|
||||
for glob_pattern in FILENAME_GLOBS {
|
||||
if matches_simple_glob(glob_pattern, filename) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Also check at root level (no slash in path)
|
||||
if !path.contains('/') {
|
||||
for glob_pattern in FILENAME_GLOBS {
|
||||
if matches_simple_glob(glob_pattern, path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Simple glob matching supporting only `*` wildcard.
|
||||
fn matches_simple_glob(pattern: &str, text: &str) -> bool {
|
||||
let parts: Vec<&str> = pattern.split('*').collect();
|
||||
if parts.len() == 1 {
|
||||
return pattern == text;
|
||||
}
|
||||
|
||||
// Check prefix
|
||||
if !text.starts_with(parts[0]) {
|
||||
return false;
|
||||
}
|
||||
// Check suffix
|
||||
if !text.ends_with(parts[parts.len() - 1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For patterns like "junit*.xml", verify the middle parts appear in order
|
||||
let mut pos = parts[0].len();
|
||||
for part in &parts[1..parts.len() - 1] {
|
||||
if let Some(found) = text[pos..].find(part) {
|
||||
pos += found + part.len();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Select which files should be collected based on fingerprint changes, timing, and size budgets.
|
||||
/// Select which files should be collected based on timing and size budgets.
|
||||
pub fn select_files_to_collect(
|
||||
discovered: &[DiscoveredFile],
|
||||
baseline: &HashMap<String, FileFingerprint>,
|
||||
command_start_epoch: f64,
|
||||
) -> Vec<DiscoveredFile> {
|
||||
let mut candidates: Vec<DiscoveredFile> = discovered
|
||||
.iter()
|
||||
.filter(|f| {
|
||||
// Skip files that haven't changed since baseline
|
||||
if let Some(fp) = baseline.get(&f.relative_path) {
|
||||
if fp.size == f.size && (fp.mtime_epoch_secs - f.mtime_epoch_secs).abs() < 0.01 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip files older than command start
|
||||
if f.mtime_epoch_secs < command_start_epoch {
|
||||
return false;
|
||||
|
|
@ -326,62 +232,27 @@ fn normalize_paths(discovered: Vec<DiscoveredFile>, root: &str) -> Vec<Discovere
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Take a snapshot of current asset files in the sandbox.
|
||||
/// Returns a fingerprint map of discovered files.
|
||||
pub async fn snapshot(sandbox: &dyn Sandbox) -> Result<HashMap<String, FileFingerprint>, String> {
|
||||
let root = sandbox.working_directory();
|
||||
let platform = sandbox.platform();
|
||||
let cmd = build_find_command(root, platform);
|
||||
|
||||
debug!("Taking asset snapshot");
|
||||
let result = sandbox
|
||||
.exec_command(&cmd, FIND_TIMEOUT_MS, None, None, None)
|
||||
.await?;
|
||||
|
||||
// Ignore non-zero exit codes — find may return 1 if some dirs are unreadable
|
||||
let discovered = parse_find_output(&result.stdout, platform);
|
||||
let discovered = normalize_paths(discovered, root);
|
||||
|
||||
let mut fingerprints = HashMap::new();
|
||||
for f in discovered {
|
||||
if is_asset_candidate(&f.relative_path) {
|
||||
fingerprints.insert(
|
||||
f.relative_path,
|
||||
FileFingerprint {
|
||||
size: f.size,
|
||||
mtime_epoch_secs: f.mtime_epoch_secs,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(fingerprints)
|
||||
}
|
||||
|
||||
/// Collect asset files that changed since the baseline snapshot.
|
||||
/// Collect asset files matching the configured globs that were created during this stage.
|
||||
pub async fn collect_assets(
|
||||
sandbox: &dyn Sandbox,
|
||||
stage_dir: &Path,
|
||||
baseline: &HashMap<String, FileFingerprint>,
|
||||
globs: &[String],
|
||||
command_start_epoch: f64,
|
||||
) -> Result<AssetCollectionSummary, String> {
|
||||
let root = sandbox.working_directory();
|
||||
let platform = sandbox.platform();
|
||||
let cmd = build_find_command(root, platform);
|
||||
let cmd = build_find_command(root, platform, globs);
|
||||
|
||||
debug!(cmd = cmd.as_str(), "Collecting assets");
|
||||
let result = sandbox
|
||||
.exec_command(&cmd, FIND_TIMEOUT_MS, None, None, None)
|
||||
.await?;
|
||||
|
||||
let discovered = parse_find_output(&result.stdout, platform);
|
||||
let discovered = normalize_paths(discovered, root);
|
||||
let candidates: Vec<DiscoveredFile> = discovered
|
||||
.into_iter()
|
||||
.filter(|f| is_asset_candidate(&f.relative_path))
|
||||
.collect();
|
||||
|
||||
let total_discovered = candidates.len();
|
||||
let to_collect = select_files_to_collect(&candidates, baseline, command_start_epoch);
|
||||
let total_discovered = discovered.len();
|
||||
let to_collect = select_files_to_collect(&discovered, command_start_epoch);
|
||||
let files_skipped = total_discovered - to_collect.len();
|
||||
|
||||
let mut files_copied: usize = 0;
|
||||
|
|
@ -437,6 +308,7 @@ pub async fn collect_assets(
|
|||
mod tests {
|
||||
use super::*;
|
||||
use arc_agent::sandbox::ExecResult;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Minimal mock sandbox for asset_snapshot tests.
|
||||
struct AssetMockSandbox {
|
||||
|
|
@ -546,50 +418,6 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_asset_candidate_matches_directory_segments() {
|
||||
assert!(is_asset_candidate("playwright-report/index.html"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_asset_candidate_matches_nested_segments() {
|
||||
assert!(is_asset_candidate("frontend/playwright-report/index.html"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_asset_candidate_rejects_partial_segments() {
|
||||
assert!(!is_asset_candidate("playwright-reporter/index.html"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_asset_candidate_matches_filename_globs() {
|
||||
assert!(is_asset_candidate("junit-report.xml"));
|
||||
assert!(is_asset_candidate("some/dir/junit.xml"));
|
||||
assert!(is_asset_candidate("output/results.trace.zip"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_asset_candidate_rejects_excluded_paths() {
|
||||
assert!(!is_asset_candidate(
|
||||
".cache/ms-playwright/chromium/file.txt"
|
||||
));
|
||||
assert!(!is_asset_candidate("playwright/.cache/some-file"));
|
||||
assert!(!is_asset_candidate(".yarn/cache/something.zip"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_asset_candidate_matches_cypress_directories() {
|
||||
assert!(is_asset_candidate("cypress/videos/test.mp4"));
|
||||
assert!(is_asset_candidate("cypress/screenshots/fail.png"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_asset_candidate_rejects_unrelated_paths() {
|
||||
assert!(!is_asset_candidate("src/main.rs"));
|
||||
assert!(!is_asset_candidate("package.json"));
|
||||
assert!(!is_asset_candidate("report.xml"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_find_output_linux() {
|
||||
let output = "1024\t1709312400.0\ttest-results/r.xml\n";
|
||||
|
|
@ -620,25 +448,6 @@ mod tests {
|
|||
assert_eq!(files[0].relative_path, "test-results/good.xml");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_files_skips_unchanged() {
|
||||
let discovered = vec![DiscoveredFile {
|
||||
relative_path: "test-results/r.xml".to_string(),
|
||||
size: 1024,
|
||||
mtime_epoch_secs: 1000.0,
|
||||
}];
|
||||
let mut baseline = HashMap::new();
|
||||
baseline.insert(
|
||||
"test-results/r.xml".to_string(),
|
||||
FileFingerprint {
|
||||
size: 1024,
|
||||
mtime_epoch_secs: 1000.0,
|
||||
},
|
||||
);
|
||||
let selected = select_files_to_collect(&discovered, &baseline, 500.0);
|
||||
assert_eq!(selected.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_files_skips_old_mtime() {
|
||||
let discovered = vec![DiscoveredFile {
|
||||
|
|
@ -646,8 +455,7 @@ mod tests {
|
|||
size: 1024,
|
||||
mtime_epoch_secs: 500.0,
|
||||
}];
|
||||
let baseline = HashMap::new();
|
||||
let selected = select_files_to_collect(&discovered, &baseline, 1000.0);
|
||||
let selected = select_files_to_collect(&discovered, 1000.0);
|
||||
assert_eq!(selected.len(), 0);
|
||||
}
|
||||
|
||||
|
|
@ -658,8 +466,7 @@ mod tests {
|
|||
size: MAX_FILE_SIZE + 1,
|
||||
mtime_epoch_secs: 2000.0,
|
||||
}];
|
||||
let baseline = HashMap::new();
|
||||
let selected = select_files_to_collect(&discovered, &baseline, 1000.0);
|
||||
let selected = select_files_to_collect(&discovered, 1000.0);
|
||||
assert_eq!(selected.len(), 0);
|
||||
}
|
||||
|
||||
|
|
@ -682,8 +489,7 @@ mod tests {
|
|||
mtime_epoch_secs: 2000.0,
|
||||
},
|
||||
];
|
||||
let baseline = HashMap::new();
|
||||
let selected = select_files_to_collect(&discovered, &baseline, 1000.0);
|
||||
let selected = select_files_to_collect(&discovered, 1000.0);
|
||||
assert_eq!(selected.len(), 3);
|
||||
assert_eq!(selected[0].size, 1000);
|
||||
assert_eq!(selected[1].size, 2000);
|
||||
|
|
@ -699,28 +505,46 @@ mod tests {
|
|||
mtime_epoch_secs: 2000.0,
|
||||
})
|
||||
.collect();
|
||||
let baseline = HashMap::new();
|
||||
let selected = select_files_to_collect(&discovered, &baseline, 1000.0);
|
||||
let selected = select_files_to_collect(&discovered, 1000.0);
|
||||
// 50 MB budget / 9 MB each = 5 fit (45 MB), 6th would be 54 MB
|
||||
assert_eq!(selected.len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_find_command_linux() {
|
||||
let cmd = build_find_command("/workspace", "linux");
|
||||
fn build_find_command_filename_glob() {
|
||||
let globs = vec!["*.trace.zip".to_string()];
|
||||
let cmd = build_find_command("/workspace", "linux", &globs);
|
||||
assert!(cmd.contains("-name '*.trace.zip'"));
|
||||
assert!(cmd.contains("-printf"));
|
||||
assert!(cmd.contains("playwright-report"));
|
||||
assert!(cmd.contains("junit*.xml"));
|
||||
assert!(cmd.contains("-prune"));
|
||||
assert!(cmd.contains("node_modules"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_find_command_directory_glob() {
|
||||
let globs = vec!["test-results/**".to_string()];
|
||||
let cmd = build_find_command("/workspace", "linux", &globs);
|
||||
assert!(cmd.contains("-path '*/test-results/*'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_find_command_mixed_globs() {
|
||||
let globs = vec![
|
||||
"test-results/**".to_string(),
|
||||
"playwright-report/**".to_string(),
|
||||
"*.trace.zip".to_string(),
|
||||
];
|
||||
let cmd = build_find_command("/workspace", "linux", &globs);
|
||||
assert!(cmd.contains("-path '*/test-results/*'"));
|
||||
assert!(cmd.contains("-path '*/playwright-report/*'"));
|
||||
assert!(cmd.contains("-name '*.trace.zip'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_find_command_darwin() {
|
||||
let cmd = build_find_command("/workspace", "darwin");
|
||||
let globs = vec!["test-results/**".to_string()];
|
||||
let cmd = build_find_command("/workspace", "darwin", &globs);
|
||||
assert!(cmd.contains("-exec stat -f"));
|
||||
assert!(cmd.contains("playwright-report"));
|
||||
assert!(cmd.contains("junit*.xml"));
|
||||
assert!(!cmd.contains("-printf"));
|
||||
}
|
||||
|
||||
|
|
@ -749,21 +573,6 @@ mod tests {
|
|||
assert_eq!(normalized[2].relative_path, "test-results/t.xml");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn snapshot_uses_exec_command_and_parses() {
|
||||
let mock = AssetMockSandbox::new(
|
||||
HashMap::new(),
|
||||
"1024\t2000.0\ttest-results/r.xml\n512\t2000.0\tsrc/main.rs\n",
|
||||
"linux",
|
||||
);
|
||||
|
||||
let fingerprints = snapshot(&mock).await.unwrap();
|
||||
// Only test-results/r.xml is an asset candidate, src/main.rs is not
|
||||
assert_eq!(fingerprints.len(), 1);
|
||||
assert!(fingerprints.contains_key("test-results/r.xml"));
|
||||
assert_eq!(fingerprints["test-results/r.xml"].size, 1024);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_assets_downloads_and_writes_manifest() {
|
||||
let stage_dir = tempfile::tempdir().unwrap();
|
||||
|
|
@ -773,8 +582,8 @@ mod tests {
|
|||
|
||||
let mock = AssetMockSandbox::new(files, "1024\t2000.0\ttest-results/r.xml\n", "linux");
|
||||
|
||||
let baseline = HashMap::new();
|
||||
let summary = collect_assets(&mock, stage_dir.path(), &baseline, 1000.0)
|
||||
let globs = vec!["test-results/**".to_string()];
|
||||
let summary = collect_assets(&mock, stage_dir.path(), &globs, 1000.0)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -795,25 +604,17 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_assets_skips_unchanged_files() {
|
||||
async fn collect_assets_skips_old_files() {
|
||||
let stage_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let mut files = HashMap::new();
|
||||
files.insert("test-results/r.xml".to_string(), "<test/>".to_string());
|
||||
|
||||
let mock = AssetMockSandbox::new(files, "1024\t2000.0\ttest-results/r.xml\n", "linux");
|
||||
// File mtime (500.0) is before command_start_epoch (1000.0)
|
||||
let mock = AssetMockSandbox::new(files, "1024\t500.0\ttest-results/r.xml\n", "linux");
|
||||
|
||||
// Provide a baseline with the same fingerprint
|
||||
let mut baseline = HashMap::new();
|
||||
baseline.insert(
|
||||
"test-results/r.xml".to_string(),
|
||||
FileFingerprint {
|
||||
size: 1024,
|
||||
mtime_epoch_secs: 2000.0,
|
||||
},
|
||||
);
|
||||
|
||||
let summary = collect_assets(&mock, stage_dir.path(), &baseline, 1000.0)
|
||||
let globs = vec!["test-results/**".to_string()];
|
||||
let summary = collect_assets(&mock, stage_dir.path(), &globs, 1000.0)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -831,8 +632,8 @@ mod tests {
|
|||
"linux",
|
||||
);
|
||||
|
||||
let baseline = HashMap::new();
|
||||
let summary = collect_assets(&mock, stage_dir.path(), &baseline, 1000.0)
|
||||
let globs = vec!["test-results/**".to_string()];
|
||||
let summary = collect_assets(&mock, stage_dir.path(), &globs, 1000.0)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -770,6 +770,11 @@ pub async fn run_command(
|
|||
.as_ref()
|
||||
.and_then(|c| c.pull_request.as_ref())
|
||||
.is_some_and(|p| p.enabled),
|
||||
asset_globs: run_cfg
|
||||
.as_ref()
|
||||
.and_then(|c| c.assets.as_ref())
|
||||
.map(|a| a.include.clone())
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
|
||||
let run_start = Instant::now();
|
||||
|
|
@ -1204,6 +1209,7 @@ async fn run_from_branch(
|
|||
git_author,
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let run_start = Instant::now();
|
||||
|
|
@ -1764,6 +1770,7 @@ mod tests {
|
|||
hooks: Vec::new(),
|
||||
checkpoint: Default::default(),
|
||||
pull_request: None,
|
||||
assets: None,
|
||||
};
|
||||
let (model, provider) = resolve_model_provider(
|
||||
Some("gpt-5.2"),
|
||||
|
|
@ -1806,6 +1813,7 @@ mod tests {
|
|||
hooks: Vec::new(),
|
||||
checkpoint: Default::default(),
|
||||
pull_request: None,
|
||||
assets: None,
|
||||
};
|
||||
let (model, provider) = resolve_model_provider(None, None, Some(&cfg), &defaults, &graph);
|
||||
assert_eq!(model, "toml-model");
|
||||
|
|
@ -1883,6 +1891,7 @@ mod tests {
|
|||
hooks: Vec::new(),
|
||||
checkpoint: Default::default(),
|
||||
pull_request: None,
|
||||
assets: None,
|
||||
};
|
||||
let (model, provider) = resolve_model_provider(None, None, Some(&cfg), &defaults, &graph);
|
||||
assert_eq!(model, "toml-model");
|
||||
|
|
@ -1909,6 +1918,7 @@ mod tests {
|
|||
hooks: Vec::new(),
|
||||
checkpoint: Default::default(),
|
||||
pull_request: None,
|
||||
assets: None,
|
||||
};
|
||||
let defaults = RunDefaults::default();
|
||||
assert!(resolve_preserve_sandbox(true, Some(&cfg), &defaults));
|
||||
|
|
@ -1934,6 +1944,7 @@ mod tests {
|
|||
hooks: Vec::new(),
|
||||
checkpoint: Default::default(),
|
||||
pull_request: None,
|
||||
assets: None,
|
||||
};
|
||||
let defaults = RunDefaults {
|
||||
sandbox: Some(run_config::SandboxConfig {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@ pub struct PullRequestConfig {
|
|||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
pub struct AssetsConfig {
|
||||
#[serde(default)]
|
||||
pub include: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WorkflowRunConfig {
|
||||
|
|
@ -38,6 +44,7 @@ pub struct WorkflowRunConfig {
|
|||
#[serde(default)]
|
||||
pub checkpoint: CheckpointConfig,
|
||||
pub pull_request: Option<PullRequestConfig>,
|
||||
pub assets: Option<AssetsConfig>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
|
|
@ -76,6 +83,7 @@ pub struct RunDefaults {
|
|||
#[serde(default)]
|
||||
pub checkpoint: CheckpointConfig,
|
||||
pub pull_request: Option<PullRequestConfig>,
|
||||
pub assets: Option<AssetsConfig>,
|
||||
}
|
||||
|
||||
impl WorkflowRunConfig {
|
||||
|
|
@ -176,6 +184,10 @@ impl WorkflowRunConfig {
|
|||
if self.pull_request.is_none() {
|
||||
self.pull_request = defaults.pull_request.clone();
|
||||
}
|
||||
|
||||
if self.assets.is_none() {
|
||||
self.assets = defaults.assets.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1790,4 +1802,77 @@ enabled = true
|
|||
let defaults: RunDefaults = toml::from_str(toml).unwrap();
|
||||
assert!(defaults.pull_request.unwrap().enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_toml_with_assets() {
|
||||
let toml = r#"
|
||||
version = 1
|
||||
goal = "Run tests"
|
||||
graph = "workflow.dot"
|
||||
|
||||
[assets]
|
||||
include = ["test-results/**", "playwright-report/**", "*.trace.zip"]
|
||||
"#;
|
||||
let config = parse_run_config(toml).unwrap();
|
||||
let assets = config.assets.unwrap();
|
||||
assert_eq!(
|
||||
assets.include,
|
||||
vec!["test-results/**", "playwright-report/**", "*.trace.zip"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_toml_without_assets() {
|
||||
let toml = r#"
|
||||
version = 1
|
||||
graph = "workflow.dot"
|
||||
"#;
|
||||
let config = parse_run_config(toml).unwrap();
|
||||
assert!(config.assets.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_defaults_inherits_assets() {
|
||||
let mut cfg = parse_run_config(
|
||||
r#"
|
||||
version = 1
|
||||
goal = "test"
|
||||
graph = "w.dot"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let defaults = RunDefaults {
|
||||
assets: Some(AssetsConfig {
|
||||
include: vec!["test-results/**".into()],
|
||||
}),
|
||||
..RunDefaults::default()
|
||||
};
|
||||
cfg.apply_defaults(&defaults);
|
||||
let assets = cfg.assets.unwrap();
|
||||
assert_eq!(assets.include, vec!["test-results/**"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_defaults_task_assets_wins() {
|
||||
let mut cfg = parse_run_config(
|
||||
r#"
|
||||
version = 1
|
||||
goal = "test"
|
||||
graph = "w.dot"
|
||||
|
||||
[assets]
|
||||
include = ["playwright-report/**"]
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let defaults = RunDefaults {
|
||||
assets: Some(AssetsConfig {
|
||||
include: vec!["test-results/**".into()],
|
||||
}),
|
||||
..RunDefaults::default()
|
||||
};
|
||||
cfg.apply_defaults(&defaults);
|
||||
let assets = cfg.assets.unwrap();
|
||||
assert_eq!(assets.include, vec!["playwright-report/**"]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -855,6 +855,8 @@ pub struct RunConfig {
|
|||
pub base_branch: Option<String>,
|
||||
/// Whether to auto-create a PR on successful completion.
|
||||
pub pull_request_enabled: bool,
|
||||
/// Glob patterns for asset collection. Empty = no asset collection.
|
||||
pub asset_globs: Vec<String>,
|
||||
}
|
||||
|
||||
/// The workflow run execution engine.
|
||||
|
|
@ -983,20 +985,13 @@ impl WorkflowRunEngine {
|
|||
policy: &RetryPolicy,
|
||||
stage_index: usize,
|
||||
visit: usize,
|
||||
asset_globs: &[String],
|
||||
) -> Result<(Outcome, u32)> {
|
||||
let handler = self.services.registry.resolve(node);
|
||||
|
||||
let node_timeout = node.timeout();
|
||||
|
||||
for attempt in 1..=policy.max_attempts {
|
||||
// Take baseline asset snapshot before handler execution
|
||||
let baseline = match asset_snapshot::snapshot(self.services.sandbox.as_ref()).await {
|
||||
Ok(fp) => fp,
|
||||
Err(e) => {
|
||||
tracing::warn!(node = %node.id, error = %e, "Asset baseline snapshot failed");
|
||||
std::collections::HashMap::new()
|
||||
}
|
||||
};
|
||||
// Floor to integer seconds: macOS stat reports mtime as integer seconds,
|
||||
// so a fractional epoch would reject files created in the same second.
|
||||
let command_start_epoch = std::time::SystemTime::now()
|
||||
|
|
@ -1038,8 +1033,8 @@ impl WorkflowRunEngine {
|
|||
}
|
||||
};
|
||||
|
||||
// Collect assets after handler completes (both success and error)
|
||||
{
|
||||
// Collect assets after handler completes (only when globs are configured)
|
||||
if !asset_globs.is_empty() {
|
||||
let node_slug = if visit <= 1 {
|
||||
node.id.clone()
|
||||
} else {
|
||||
|
|
@ -1053,7 +1048,7 @@ impl WorkflowRunEngine {
|
|||
match asset_snapshot::collect_assets(
|
||||
self.services.sandbox.as_ref(),
|
||||
&assets_dir,
|
||||
&baseline,
|
||||
asset_globs,
|
||||
command_start_epoch,
|
||||
)
|
||||
.await
|
||||
|
|
@ -1636,7 +1631,7 @@ impl WorkflowRunEngine {
|
|||
let (mut outcome, attempts_used) = if let Some((ref token, _)) = stall_token {
|
||||
tokio::select! {
|
||||
result = self.execute_with_retry(
|
||||
node, &context, graph, &config.logs_root, &retry_policy, stage_index, visit,
|
||||
node, &context, graph, &config.logs_root, &retry_policy, stage_index, visit, &config.asset_globs,
|
||||
) => result?,
|
||||
() = token.cancelled() => {
|
||||
let idle_secs = graph.stall_timeout().map_or(0, |d| d.as_secs());
|
||||
|
|
@ -1659,6 +1654,7 @@ impl WorkflowRunEngine {
|
|||
&retry_policy,
|
||||
stage_index,
|
||||
visit,
|
||||
&config.asset_globs,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
|
@ -2878,6 +2874,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&g, &config).await.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -2904,6 +2901,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&g, &config).await.unwrap();
|
||||
let checkpoint_path = dir.path().join("checkpoint.json");
|
||||
|
|
@ -2938,6 +2936,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -2968,6 +2967,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -2994,6 +2994,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -3033,6 +3034,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&g, &config).await.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -3096,6 +3098,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -3186,6 +3189,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -3219,6 +3223,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -3247,6 +3252,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -3275,6 +3281,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -3307,6 +3314,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -3467,6 +3475,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -3510,6 +3519,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -3571,6 +3581,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -3635,6 +3646,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
|
||||
|
|
@ -3703,6 +3715,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_ok());
|
||||
|
|
@ -3760,6 +3773,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&g, &config).await.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -3818,6 +3832,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -3851,6 +3866,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&g, &config).await.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -3880,6 +3896,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -3908,6 +3925,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&g, &config).await.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -3949,6 +3967,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
// Set cancel after a short delay (while the slow handler is running)
|
||||
|
|
@ -4027,6 +4046,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -4058,6 +4078,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -4091,6 +4112,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -4129,6 +4151,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -4165,6 +4188,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -4198,6 +4222,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -4292,6 +4317,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
// The engine returns Err because the Fail outcome has no outgoing fail edge,
|
||||
|
|
@ -4501,6 +4527,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -4537,6 +4564,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -4580,6 +4608,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -4663,6 +4692,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -4757,6 +4787,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let result = engine.run(&g, &config).await;
|
||||
assert!(result.is_err());
|
||||
|
|
@ -4828,6 +4859,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&g, &config).await.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -4886,6 +4918,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&g, &config).await.unwrap();
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -4945,6 +4978,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let _outcome = engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
@ -5031,6 +5065,7 @@ mod tests {
|
|||
git_author: crate::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&g, &config).await.unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ impl Handler for SubWorkflowHandler {
|
|||
.unwrap_or_default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
// Clone parent context for child; inject parent preamble
|
||||
|
|
|
|||
|
|
@ -400,6 +400,7 @@ async fn daytona_pipeline_artifact_offload_and_sync() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -596,6 +597,7 @@ async fn daytona_git_checkpoint_remote_emits_events() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -784,6 +786,7 @@ async fn daytona_parallel_git_branching_e2e() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -1160,6 +1163,7 @@ async fn daytona_git_checkpoint_with_shadow_branch() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -1302,6 +1306,7 @@ async fn daytona_asset_collection() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -1558,6 +1563,7 @@ async fn daytona_git_push_run_branch_to_origin() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ async fn end_to_end_linear_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -346,6 +347,7 @@ async fn end_to_end_branching_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -470,6 +472,7 @@ async fn end_to_end_human_gate_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -584,6 +587,7 @@ async fn goal_gate_routes_to_retry_target_on_failure() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -708,6 +712,7 @@ async fn goal_gate_routes_to_retry_target_when_present() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -1019,6 +1024,7 @@ async fn retry_on_failure_then_succeed() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -1097,6 +1103,7 @@ async fn pipeline_with_many_nodes() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -1425,6 +1432,7 @@ async fn smoke_test_with_mock_codergen_backend() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -1529,6 +1537,7 @@ async fn end_to_end_parallel_fan_out_fan_in() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -1645,6 +1654,7 @@ async fn resume_from_checkpoint_completes_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -1747,6 +1757,7 @@ async fn resume_from_checkpoint_preserves_goal_gate_outcomes() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
// This should succeed because goal gate for gated_work is satisfied
|
||||
|
|
@ -1793,6 +1804,7 @@ async fn graph_goal_in_context() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -1831,6 +1843,7 @@ async fn event_streaming_lifecycle() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -1913,6 +1926,7 @@ async fn context_flow_between_stages() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -1968,6 +1982,7 @@ async fn tool_handler_e2e() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -2040,6 +2055,7 @@ async fn auto_approve_interviewer_e2e() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -2079,6 +2095,7 @@ async fn codergen_without_backend_simulated() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -2186,6 +2203,7 @@ async fn branching_loop_back_on_failure() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -2273,6 +2291,7 @@ async fn human_gate_loops_back() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -2333,6 +2352,7 @@ async fn scenario_ship_a_feature() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -2421,6 +2441,7 @@ async fn scenario_parallel_expert_review() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -2503,6 +2524,7 @@ async fn scenario_node_retries_on_retry_status() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -2567,6 +2589,7 @@ async fn scenario_loop_restart_resets_context() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -2637,6 +2660,7 @@ async fn scenario_bug_triage_router() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -2698,6 +2722,7 @@ async fn scenario_crash_recovery() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine
|
||||
.run_from_checkpoint(&graph, &config, &checkpoint)
|
||||
|
|
@ -2809,6 +2834,7 @@ async fn manager_loop_stop_condition_satisfied_e2e() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -2888,6 +2914,7 @@ async fn manager_loop_max_cycles_exceeded_e2e() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -3026,6 +3053,7 @@ async fn conditional_branching_success_fail_paths() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -3081,6 +3109,7 @@ async fn edge_selection_condition_match_wins_over_weight() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -3130,6 +3159,7 @@ async fn edge_selection_weight_breaks_ties() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -3171,6 +3201,7 @@ async fn edge_selection_lexical_tiebreak() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -3231,6 +3262,7 @@ async fn context_updates_visible_across_nodes() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -3277,6 +3309,7 @@ async fn stylesheet_applies_model_override() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -3335,6 +3368,7 @@ async fn custom_handler_registration_and_execution() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -3408,6 +3442,7 @@ async fn integration_smoke_plan_implement_review_done() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
@ -3514,6 +3549,7 @@ async fn manager_loop_runs_child_engine_e2e() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -3651,6 +3687,7 @@ async fn manager_loop_context_flows_e2e() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
|
|
@ -3727,6 +3764,7 @@ async fn manager_loop_child_dotfile_e2e() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
|
|
@ -3843,6 +3881,7 @@ async fn graph_merge_e2e_through_engine() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -3996,6 +4035,7 @@ async fn fidelity_default_is_compact() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -4055,6 +4095,7 @@ async fn fidelity_graph_default_applied() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -4110,6 +4151,7 @@ async fn fidelity_node_overrides_graph_default() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -4171,6 +4213,7 @@ async fn fidelity_edge_overrides_node_and_graph() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -4222,6 +4265,7 @@ async fn fidelity_full_produces_empty_preamble() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -4283,6 +4327,7 @@ async fn fidelity_truncate_preamble_minimal() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -4357,6 +4402,7 @@ async fn fidelity_summary_low_mode() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -4426,6 +4472,7 @@ async fn fidelity_summary_medium_mode() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -4495,6 +4542,7 @@ async fn fidelity_summary_high_mode() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -4557,6 +4605,7 @@ async fn fidelity_full_sets_thread_id_in_context() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -4630,6 +4679,7 @@ async fn fidelity_full_nodes_share_thread_id() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -4713,6 +4763,7 @@ async fn fidelity_resume_degrades_full_to_summary_high() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine
|
||||
.run_from_checkpoint(&graph, &config, &checkpoint)
|
||||
|
|
@ -4812,6 +4863,7 @@ async fn fidelity_resume_degrade_only_affects_first_hop() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine
|
||||
.run_from_checkpoint(&graph, &config, &checkpoint)
|
||||
|
|
@ -4898,6 +4950,7 @@ async fn fidelity_resume_no_degrade_when_not_full() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine
|
||||
.run_from_checkpoint(&graph, &config, &checkpoint)
|
||||
|
|
@ -4942,6 +4995,7 @@ async fn fidelity_stored_in_checkpoint_context() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -5030,6 +5084,7 @@ async fn fidelity_precedence_multi_node_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -5100,6 +5155,7 @@ async fn fidelity_compact_preamble_includes_completed_stages_and_context() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -5178,6 +5234,7 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine_low
|
||||
.run(&graph_low, &config_low)
|
||||
|
|
@ -5248,6 +5305,7 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine_med
|
||||
.run(&graph_med, &config_med)
|
||||
|
|
@ -5321,6 +5379,7 @@ async fn fidelity_thread_id_fallback_to_previous_node_in_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -5377,6 +5436,7 @@ async fn fidelity_thread_id_from_node_class_in_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -5436,6 +5496,7 @@ async fn fidelity_edge_thread_id_override_in_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -5496,6 +5557,7 @@ async fn fidelity_full_without_explicit_thread_id_uses_previous_node() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -5566,6 +5628,7 @@ async fn fidelity_from_parsed_dot_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -5616,6 +5679,7 @@ async fn fidelity_checkpoint_roundtrip_preserves_fidelity() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -5688,6 +5752,7 @@ async fn fidelity_node_thread_id_overrides_edge_thread_id_in_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine.run(&graph, &config).await.expect("run");
|
||||
|
||||
|
|
@ -5777,6 +5842,7 @@ async fn fidelity_resume_preserves_context_values_across_checkpoint() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
engine
|
||||
.run_from_checkpoint(&graph, &config, &checkpoint)
|
||||
|
|
@ -5990,6 +6056,7 @@ mod real_llm {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = tokio::time::timeout(
|
||||
|
|
@ -6107,6 +6174,7 @@ mod real_llm {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = tokio::time::timeout(
|
||||
|
|
@ -6249,6 +6317,7 @@ mod real_llm {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = tokio::time::timeout(
|
||||
|
|
@ -6359,6 +6428,7 @@ mod real_llm {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = tokio::time::timeout(
|
||||
|
|
@ -6458,6 +6528,7 @@ async fn human_gate_freeform_only_routes_text() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -6592,6 +6663,7 @@ async fn human_gate_freeform_with_fixed_choice_match() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -6710,6 +6782,7 @@ async fn human_gate_freeform_fallback_on_unmatched_text() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -6842,6 +6915,7 @@ async fn human_gate_freeform_sets_allow_freeform_on_question() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -6954,6 +7028,7 @@ async fn human_gate_without_freeform_sets_allow_freeform_false() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -7217,6 +7292,7 @@ fn make_run_config(dir: &std::path::Path) -> RunConfig {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -8356,6 +8432,7 @@ async fn arc_e2e_with_real_llm() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -8487,6 +8564,7 @@ async fn run_fidelity_prompt_pipeline(fidelity: &str) -> String {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
engine
|
||||
|
|
@ -8689,6 +8767,7 @@ async fn large_context_values_are_offloaded_to_artifact_store() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -8904,6 +8983,7 @@ async fn artifact_pointers_rewritten_for_remote_sandbox() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -9037,6 +9117,7 @@ async fn node_dir_uses_visit_count_on_revisit() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -9993,6 +10074,7 @@ async fn full_pipeline_with_cli_backend_node() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -10125,6 +10207,7 @@ async fn stylesheet_backend_property_routes_to_cli() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -10410,6 +10493,7 @@ async fn git_checkpoint_host_emits_events_and_diff_patch() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
// 5. Run pipeline
|
||||
|
|
@ -10602,6 +10686,7 @@ async fn git_checkpoint_host_writes_shadow_branch() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
// 5. Run pipeline
|
||||
|
|
@ -10799,6 +10884,7 @@ async fn parallel_git_branching_host_e2e() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
// 5. Run pipeline
|
||||
|
|
@ -11064,6 +11150,7 @@ async fn git_checkpoint_host_skips_empty_diff_patch() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -11449,6 +11536,7 @@ async fn e2e_circuit_breaker_deterministic_self_loop() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -11499,6 +11587,7 @@ async fn e2e_circuit_breaker_custom_limit() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -11542,6 +11631,7 @@ async fn e2e_circuit_breaker_ignores_transient_failures() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -11592,6 +11682,7 @@ async fn e2e_circuit_breaker_different_reasons_separate_counters() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -11635,6 +11726,7 @@ async fn e2e_circuit_breaker_loop_restart() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -11700,6 +11792,7 @@ async fn e2e_failure_signature_persisted_in_context() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine.run(&graph, &config).await.unwrap();
|
||||
|
|
@ -11767,6 +11860,7 @@ async fn e2e_failure_signature_hint_overrides_reason_in_context() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let _outcome = engine.run(&graph, &config).await.unwrap();
|
||||
|
|
@ -11826,6 +11920,7 @@ async fn e2e_signature_maps_persist_in_checkpoint() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine.run(&graph, &config).await.unwrap();
|
||||
|
|
@ -11956,6 +12051,7 @@ async fn e2e_circuit_breaker_emits_events_before_abort() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -12026,6 +12122,7 @@ async fn e2e_circuit_breaker_does_not_fire_below_limit() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine.run(&graph, &config).await.unwrap();
|
||||
|
|
@ -12125,6 +12222,7 @@ async fn e2e_circuit_breaker_multi_stage_impl_verify_cycle() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -12224,6 +12322,7 @@ async fn e2e_loop_restart_blocked_for_deterministic_failure() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -12267,6 +12366,7 @@ async fn e2e_loop_restart_blocked_for_structural_failure() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -12310,6 +12410,7 @@ async fn e2e_loop_restart_blocked_for_budget_exhausted_failure() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -12353,6 +12454,7 @@ async fn e2e_loop_restart_blocked_for_canceled_failure() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -12393,6 +12495,7 @@ async fn e2e_loop_restart_blocked_for_compilation_loop_failure() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -12437,6 +12540,7 @@ async fn e2e_loop_restart_allowed_for_transient_infra() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -12544,6 +12648,7 @@ async fn e2e_stall_watchdog_triggers_from_dot_parsed_pipeline() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let result = engine.run(&graph, &config).await;
|
||||
|
|
@ -12603,6 +12708,7 @@ async fn e2e_stall_watchdog_kept_alive_by_handler_events() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -12652,6 +12758,7 @@ async fn e2e_stall_watchdog_disabled_with_zero_timeout() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -12720,6 +12827,7 @@ async fn e2e_stall_watchdog_with_explicit_timeout_override() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
|
@ -12853,6 +12961,7 @@ async fn asset_collection_local_sandbox_success() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -12964,6 +13073,7 @@ async fn asset_collection_local_sandbox_on_failure() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -13058,6 +13168,7 @@ async fn asset_collection_docker_sandbox() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
|
||||
let outcome = engine
|
||||
|
|
@ -13130,6 +13241,7 @@ async fn wait_timer_e2e() {
|
|||
git_author: arc_workflows::git::GitAuthor::default(),
|
||||
base_branch: None,
|
||||
pull_request_enabled: false,
|
||||
asset_globs: Vec::new(),
|
||||
};
|
||||
let outcome = engine.run(&graph, &config).await.expect("run");
|
||||
assert_eq!(outcome.status, StageStatus::Success);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue