diff --git a/apps/arc-web/app/routes/run-compare.tsx b/apps/arc-web/app/routes/run-compare.tsx index 617549996..1f3590765 100644 --- a/apps/arc-web/app/routes/run-compare.tsx +++ b/apps/arc-web/app/routes/run-compare.tsx @@ -14,7 +14,7 @@ import type { Route } from "./+types/run-compare"; export const handle = { wide: true }; export async function loader({ request, params }: Route.LoaderArgs) { - const data = await apiJson(`/runs/${params.id}/compare?checkpoint=all`, { request }); + const data = await apiJson(`/runs/${params.id}/compare`, { request }); return data; } diff --git a/crates/arc-api/src/demo/mod.rs b/crates/arc-api/src/demo/mod.rs index 5afb6e857..ca5175a79 100644 --- a/crates/arc-api/src/demo/mod.rs +++ b/crates/arc-api/src/demo/mod.rs @@ -946,7 +946,6 @@ mod runs { pub fn compare() -> RunCompare { RunCompare { checkpoints: vec![ - FileCheckpoint { id: "all".into(), label: "All changes".into() }, FileCheckpoint { id: "cp-4".into(), label: "Checkpoint 4 — Apply Changes".into() }, FileCheckpoint { id: "cp-3".into(), label: "Checkpoint 3 — Review Changes".into() }, FileCheckpoint { id: "cp-2".into(), label: "Checkpoint 2 — Propose Changes".into() }, @@ -1454,7 +1453,7 @@ mod verifications { name: &'static str, slug: &'static str, description: &'static str, - type_: Option, + type_: VerificationType, mode: VerificationMode, f1: Option, pass_at_1: Option, @@ -1640,7 +1639,7 @@ mod verifications { ControlDef { name: "Motivation", slug: "motivation", description: "Origin of proposal identified", - type_: Some(VerificationType::Ai), mode: VerificationMode::Active, + type_: VerificationType::Ai, mode: VerificationMode::Active, f1: Some(0.87), pass_at_1: Some(0.82), evaluations: &[P, P, F, P, P, P, P, F, P, P], run_status: VerificationResult::Pass, @@ -1653,7 +1652,7 @@ mod verifications { ControlDef { name: "Specifications", slug: "specifications", description: "Requirements written down", - type_: Some(VerificationType::Ai), mode: VerificationMode::Active, + type_: VerificationType::Ai, mode: VerificationMode::Active, f1: Some(0.83), pass_at_1: Some(0.78), evaluations: &[P, F, P, P, P, F, P, P, P, P], run_status: VerificationResult::Pass, @@ -1666,7 +1665,7 @@ mod verifications { ControlDef { name: "Documentation", slug: "documentation", description: "Developer and user docs added", - type_: Some(VerificationType::Ai), mode: VerificationMode::Active, + type_: VerificationType::Ai, mode: VerificationMode::Active, f1: Some(0.79), pass_at_1: Some(0.74), evaluations: &[P, P, P, F, P, P, F, P, P, F], run_status: VerificationResult::Pass, @@ -1679,7 +1678,7 @@ mod verifications { ControlDef { name: "Minimization", slug: "minimization", description: "No extraneous changes", - type_: Some(VerificationType::Ai), mode: VerificationMode::Evaluate, + type_: VerificationType::Ai, mode: VerificationMode::Evaluate, f1: Some(0.72), pass_at_1: Some(0.68), evaluations: &[P, F, P, F, P, P, F, P, P, P], run_status: VerificationResult::Pass, @@ -1698,7 +1697,7 @@ mod verifications { ControlDef { name: "Formatting", slug: "formatting", description: "Code layout matches standard", - type_: Some(VerificationType::Automated), mode: VerificationMode::Active, + type_: VerificationType::Automated, mode: VerificationMode::Active, f1: Some(0.99), pass_at_1: Some(0.98), evaluations: &[P, P, P, P, P, P, P, P, P, P], run_status: VerificationResult::Pass, @@ -1711,7 +1710,7 @@ mod verifications { ControlDef { name: "Linting", slug: "linting", description: "Linter issues resolved", - type_: Some(VerificationType::Automated), mode: VerificationMode::Active, + type_: VerificationType::Automated, mode: VerificationMode::Active, f1: Some(0.98), pass_at_1: Some(0.97), evaluations: &[P, P, P, P, P, P, P, P, F, P], run_status: VerificationResult::Pass, @@ -1724,7 +1723,7 @@ mod verifications { ControlDef { name: "Style", slug: "style", description: "House style applied", - type_: Some(VerificationType::Ai), mode: VerificationMode::Active, + type_: VerificationType::Ai, mode: VerificationMode::Active, f1: Some(0.81), pass_at_1: Some(0.76), evaluations: &[P, F, P, P, P, P, F, P, P, P], run_status: VerificationResult::Pass, @@ -1743,7 +1742,7 @@ mod verifications { ControlDef { name: "Completeness", slug: "completeness", description: "Implementation covers requirements", - type_: Some(VerificationType::Ai), mode: VerificationMode::Active, + type_: VerificationType::Ai, mode: VerificationMode::Active, f1: Some(0.76), pass_at_1: Some(0.71), evaluations: &[P, P, F, P, F, P, P, P, F, P], run_status: VerificationResult::Pass, @@ -1756,7 +1755,7 @@ mod verifications { ControlDef { name: "Defects", slug: "defects", description: "Potential or likely bugs remediated", - type_: Some(VerificationType::AiAnalysis), mode: VerificationMode::Active, + type_: VerificationType::AiAnalysis, mode: VerificationMode::Active, f1: Some(0.84), pass_at_1: Some(0.79), evaluations: &[P, P, P, F, P, P, P, P, P, F], run_status: VerificationResult::Pass, @@ -1769,7 +1768,7 @@ mod verifications { ControlDef { name: "Performance", slug: "performance", description: "Hot path impact identified", - type_: Some(VerificationType::Ai), mode: VerificationMode::Evaluate, + type_: VerificationType::Ai, mode: VerificationMode::Evaluate, f1: Some(0.69), pass_at_1: Some(0.63), evaluations: &[F, P, P, F, P, F, P, P, F, P], run_status: VerificationResult::Pass, @@ -1788,7 +1787,7 @@ mod verifications { ControlDef { name: "Test Coverage", slug: "test-coverage", description: "Production code exercised by unit tests", - type_: Some(VerificationType::Analysis), mode: VerificationMode::Active, + type_: VerificationType::Analysis, mode: VerificationMode::Active, f1: Some(0.95), pass_at_1: Some(0.93), evaluations: &[P, P, P, P, P, P, F, P, P, P], run_status: VerificationResult::Pass, @@ -1801,7 +1800,7 @@ mod verifications { ControlDef { name: "Test Quality", slug: "test-quality", description: "Tests are robust and clear", - type_: Some(VerificationType::Ai), mode: VerificationMode::Evaluate, + type_: VerificationType::Ai, mode: VerificationMode::Evaluate, f1: Some(0.71), pass_at_1: Some(0.65), evaluations: &[P, F, F, P, P, F, P, F, P, P], run_status: VerificationResult::Fail, @@ -1814,7 +1813,7 @@ mod verifications { ControlDef { name: "E2E Coverage", slug: "e2e-coverage", description: "Browser automation exercises UX", - type_: Some(VerificationType::Analysis), mode: VerificationMode::Active, + type_: VerificationType::Analysis, mode: VerificationMode::Active, f1: Some(0.91), pass_at_1: Some(0.88), evaluations: &[P, P, P, F, P, P, P, P, P, P], run_status: VerificationResult::Na, @@ -1833,7 +1832,7 @@ mod verifications { ControlDef { name: "Architecture", slug: "architecture", description: "Layering and dependency graph meets design", - type_: Some(VerificationType::Analysis), mode: VerificationMode::Active, + type_: VerificationType::Analysis, mode: VerificationMode::Active, f1: Some(0.88), pass_at_1: Some(0.84), evaluations: &[P, P, P, P, F, P, P, P, P, P], run_status: VerificationResult::Pass, @@ -1846,7 +1845,7 @@ mod verifications { ControlDef { name: "Interfaces", slug: "interfaces", description: "", - type_: None, mode: VerificationMode::Disabled, + type_: VerificationType::Ai, mode: VerificationMode::Disabled, f1: None, pass_at_1: None, evaluations: &[], run_status: VerificationResult::Pass, @@ -1859,7 +1858,7 @@ mod verifications { ControlDef { name: "Duplication", slug: "duplication", description: "Similar and identical code blocks identified", - type_: Some(VerificationType::Analysis), mode: VerificationMode::Active, + type_: VerificationType::Analysis, mode: VerificationMode::Active, f1: Some(0.96), pass_at_1: Some(0.94), evaluations: &[P, P, P, P, P, P, P, F, P, P], run_status: VerificationResult::Pass, @@ -1872,7 +1871,7 @@ mod verifications { ControlDef { name: "Simplicity", slug: "simplicity", description: "Extra review for reducing complexity", - type_: Some(VerificationType::Ai), mode: VerificationMode::Active, + type_: VerificationType::Ai, mode: VerificationMode::Active, f1: Some(0.74), pass_at_1: Some(0.69), evaluations: &[P, F, P, P, F, P, P, F, P, P], run_status: VerificationResult::Pass, @@ -1885,7 +1884,7 @@ mod verifications { ControlDef { name: "Dead Code", slug: "dead-code", description: "Unexecuted code and dependencies removed", - type_: Some(VerificationType::Analysis), mode: VerificationMode::Active, + type_: VerificationType::Analysis, mode: VerificationMode::Active, f1: Some(0.93), pass_at_1: Some(0.90), evaluations: &[P, P, P, P, P, F, P, P, P, P], run_status: VerificationResult::Pass, @@ -1904,7 +1903,7 @@ mod verifications { ControlDef { name: "Vulnerabilities", slug: "vulnerabilities", description: "Security issues are remediated", - type_: Some(VerificationType::AiAnalysis), mode: VerificationMode::Active, + type_: VerificationType::AiAnalysis, mode: VerificationMode::Active, f1: Some(0.86), pass_at_1: Some(0.81), evaluations: &[P, P, F, P, P, P, P, P, F, P], run_status: VerificationResult::Pass, @@ -1917,7 +1916,7 @@ mod verifications { ControlDef { name: "IaC Scanning", slug: "iac-scanning", description: "", - type_: None, mode: VerificationMode::Disabled, + type_: VerificationType::Automated, mode: VerificationMode::Disabled, f1: None, pass_at_1: None, evaluations: &[], run_status: VerificationResult::Pass, @@ -1930,7 +1929,7 @@ mod verifications { ControlDef { name: "Dependency Alerts", slug: "dependency-alerts", description: "Known CVEs are patched", - type_: Some(VerificationType::Analysis), mode: VerificationMode::Active, + type_: VerificationType::Analysis, mode: VerificationMode::Active, f1: Some(0.97), pass_at_1: Some(0.95), evaluations: &[P, P, P, P, P, P, P, P, P, F], run_status: VerificationResult::Pass, @@ -1943,7 +1942,7 @@ mod verifications { ControlDef { name: "Security Controls", slug: "security-controls", description: "Organization standards applied", - type_: Some(VerificationType::Ai), mode: VerificationMode::Active, + type_: VerificationType::Ai, mode: VerificationMode::Active, f1: Some(0.80), pass_at_1: Some(0.75), evaluations: &[P, P, F, P, P, F, P, P, P, P], run_status: VerificationResult::Pass, @@ -1962,7 +1961,7 @@ mod verifications { ControlDef { name: "Compatibility", slug: "compatibility", description: "Breaking changes are avoided", - type_: Some(VerificationType::Analysis), mode: VerificationMode::Active, + type_: VerificationType::Analysis, mode: VerificationMode::Active, f1: Some(0.89), pass_at_1: Some(0.85), evaluations: &[P, P, P, P, F, P, P, P, P, P], run_status: VerificationResult::Pass, @@ -1975,7 +1974,7 @@ mod verifications { ControlDef { name: "Rollout / Rollback", slug: "rollout-rollback", description: "Known rollback plan if deploy fails", - type_: Some(VerificationType::Ai), mode: VerificationMode::Evaluate, + type_: VerificationType::Ai, mode: VerificationMode::Evaluate, f1: Some(0.66), pass_at_1: Some(0.60), evaluations: &[F, P, F, P, F, P, P, F, P, F], run_status: VerificationResult::Fail, @@ -1988,7 +1987,7 @@ mod verifications { ControlDef { name: "Observability", slug: "observability", description: "Logging, metrics, tracing instrumented", - type_: Some(VerificationType::Ai), mode: VerificationMode::Evaluate, + type_: VerificationType::Ai, mode: VerificationMode::Evaluate, f1: Some(0.73), pass_at_1: Some(0.67), evaluations: &[P, F, P, F, P, P, F, P, F, P], run_status: VerificationResult::Fail, @@ -2001,7 +2000,7 @@ mod verifications { ControlDef { name: "Cost", slug: "cost", description: "Tech ops costs estimated", - type_: Some(VerificationType::Analysis), mode: VerificationMode::Evaluate, + type_: VerificationType::Analysis, mode: VerificationMode::Evaluate, f1: Some(0.78), pass_at_1: Some(0.72), evaluations: &[P, P, F, P, F, P, P, F, P, P], run_status: VerificationResult::Pass, @@ -2020,7 +2019,7 @@ mod verifications { ControlDef { name: "Change Control", slug: "change-control", description: "Separation of Duties policy met", - type_: Some(VerificationType::Analysis), mode: VerificationMode::Active, + type_: VerificationType::Analysis, mode: VerificationMode::Active, f1: Some(0.94), pass_at_1: Some(0.91), evaluations: &[P, P, P, P, P, P, P, P, F, P], run_status: VerificationResult::Pass, @@ -2033,7 +2032,7 @@ mod verifications { ControlDef { name: "AI Governance", slug: "ai-governance", description: "AI involvement was acceptable", - type_: Some(VerificationType::Analysis), mode: VerificationMode::Active, + type_: VerificationType::Analysis, mode: VerificationMode::Active, f1: Some(0.85), pass_at_1: Some(0.80), evaluations: &[P, P, P, F, P, P, P, P, P, P], run_status: VerificationResult::Pass, @@ -2046,7 +2045,7 @@ mod verifications { ControlDef { name: "Privacy", slug: "privacy", description: "PII is identified and handled to standards", - type_: Some(VerificationType::Ai), mode: VerificationMode::Active, + type_: VerificationType::Ai, mode: VerificationMode::Active, f1: Some(0.77), pass_at_1: Some(0.72), evaluations: &[P, F, P, P, P, F, P, P, P, F], run_status: VerificationResult::Pass, @@ -2059,7 +2058,7 @@ mod verifications { ControlDef { name: "Accessibility", slug: "accessibility", description: "Software meets accessibility requirements", - type_: Some(VerificationType::Analysis), mode: VerificationMode::Active, + type_: VerificationType::Analysis, mode: VerificationMode::Active, f1: Some(0.90), pass_at_1: Some(0.87), evaluations: &[P, P, P, P, P, F, P, P, P, P], run_status: VerificationResult::Pass, @@ -2072,7 +2071,7 @@ mod verifications { ControlDef { name: "Licensing", slug: "licensing", description: "Supply chain meets IP policy", - type_: Some(VerificationType::Analysis), mode: VerificationMode::Active, + type_: VerificationType::Analysis, mode: VerificationMode::Active, f1: Some(0.96), pass_at_1: Some(0.93), evaluations: &[P, P, P, P, P, P, P, P, P, P], run_status: VerificationResult::Pass, @@ -2155,7 +2154,7 @@ mod verifications { .map(|(_, s)| SiblingControl { name: s.name.into(), slug: s.slug.into(), - type_: s.type_, + type_: Some(s.type_), mode: Some(s.mode), }) .collect(); @@ -2170,7 +2169,7 @@ mod verifications { name: ctrl.name.into(), slug: ctrl.slug.into(), description: ctrl.description.into(), - type_: ctrl.type_, + type_: Some(ctrl.type_), category: CategoryReference { name: cat.name.into() }, }, performance: ControlPerformance { @@ -2206,6 +2205,7 @@ mod verifications { .iter() .map(|c| RunVerificationControl { name: c.name.into(), + slug: c.slug.into(), description: c.description.into(), type_: c.type_, status: c.run_status, diff --git a/crates/arc-api/src/server.rs b/crates/arc-api/src/server.rs index 2bbfcfc96..a82bae2d5 100644 --- a/crates/arc-api/src/server.rs +++ b/crates/arc-api/src/server.rs @@ -767,30 +767,42 @@ async fn submit_answer( .into_response(); } }; - let answer = match &req.selected_option_key { - Some(key) => { - let option = interviewer - .pending_questions() - .iter() - .find(|pq| pq.id == qid) - .and_then(|pq| pq.question.options.iter().find(|o| o.key == *key)) - .cloned(); - match option { - Some(opt) => Answer::selected(key.clone(), opt), + let answer = if let Some(key) = &req.selected_option_key { + let option = interviewer + .pending_questions() + .iter() + .find(|pq| pq.id == qid) + .and_then(|pq| pq.question.options.iter().find(|o| o.key == *key)) + .cloned(); + match option { + Some(opt) => Answer::selected(key.clone(), opt), + None => { + return ApiError::bad_request("Invalid option key.").into_response(); + } + } + } else if !req.selected_option_keys.is_empty() { + let pending = interviewer.pending_questions(); + let pq = pending.iter().find(|pq| pq.id == qid); + let mut options = Vec::new(); + for key in &req.selected_option_keys { + let opt = pq.and_then(|pq| { + pq.question.options.iter().find(|o| o.key == *key).cloned() + }); + match opt { + Some(o) => options.push(o), None => { return ApiError::bad_request("Invalid option key.").into_response(); } } } - None => match req.value { - Some(v) => Answer::text(v), - None => { - return ApiError::bad_request( - "Either value or selected_option_key is required.", - ) - .into_response(); - } - }, + Answer::multi_selected(req.selected_option_keys, options) + } else if let Some(v) = req.value { + Answer::text(v) + } else { + return ApiError::bad_request( + "One of value, selected_option_key, or selected_option_keys is required.", + ) + .into_response(); }; let accepted = interviewer.submit_answer(&qid, answer); if accepted { diff --git a/crates/arc-slack/examples/slack_e2e.rs b/crates/arc-slack/examples/slack_e2e.rs index 260db568a..3c0f91d0a 100644 --- a/crates/arc-slack/examples/slack_e2e.rs +++ b/crates/arc-slack/examples/slack_e2e.rs @@ -86,6 +86,7 @@ fn format_answer(answer: &Answer) -> String { k.clone() } } + AnswerValue::MultiSelected(keys) => keys.join(", "), AnswerValue::Skipped => "Skipped".to_string(), AnswerValue::Timeout => "Timed out".to_string(), } diff --git a/crates/arc-workflows/src/handler/human.rs b/crates/arc-workflows/src/handler/human.rs index 8a6b6bb0b..9ab1b16d2 100644 --- a/crates/arc-workflows/src/handler/human.rs +++ b/crates/arc-workflows/src/handler/human.rs @@ -255,6 +255,7 @@ fn answer_text(answer: &Answer) -> String { match &answer.value { AnswerValue::Text(t) => t.clone(), AnswerValue::Selected(s) => s.clone(), + AnswerValue::MultiSelected(keys) => keys.join(", "), AnswerValue::Yes => "yes".to_string(), AnswerValue::No => "no".to_string(), AnswerValue::Skipped => "skipped".to_string(), diff --git a/crates/arc-workflows/src/interviewer/auto_approve.rs b/crates/arc-workflows/src/interviewer/auto_approve.rs index 13034def3..e647f36e6 100644 --- a/crates/arc-workflows/src/interviewer/auto_approve.rs +++ b/crates/arc-workflows/src/interviewer/auto_approve.rs @@ -16,6 +16,7 @@ impl Interviewer for AutoApproveInterviewer { |first| Answer { value: AnswerValue::Selected(first.key.clone()), selected_option: Some(first.clone()), + selected_options: Vec::new(), text: None, }, ) diff --git a/crates/arc-workflows/src/interviewer/console.rs b/crates/arc-workflows/src/interviewer/console.rs index e1f60a0f7..acfaab4e1 100644 --- a/crates/arc-workflows/src/interviewer/console.rs +++ b/crates/arc-workflows/src/interviewer/console.rs @@ -28,6 +28,7 @@ fn find_matching_option(response: &str, options: &[super::QuestionOption]) -> Op return Some(Answer { value: AnswerValue::Selected(opt.key.clone()), selected_option: Some(opt.clone()), + selected_options: Vec::new(), text: None, }); } @@ -39,6 +40,7 @@ fn find_matching_option(response: &str, options: &[super::QuestionOption]) -> Op return Some(Answer { value: AnswerValue::Selected(opt.key.clone()), selected_option: Some(opt.clone()), + selected_options: Vec::new(), text: None, }); } @@ -89,6 +91,7 @@ fn ask_select_interactive(question: &Question) -> Answer { Answer { value: AnswerValue::Selected(opt.key.clone()), selected_option: Some(opt.clone()), + selected_options: Vec::new(), text: None, } } @@ -111,13 +114,9 @@ fn ask_multi_select_interactive(question: &Question) -> Answer { match selection { Ok(Some(indices)) if !indices.is_empty() => { - let idx = indices[0]; - let opt = &question.options[idx]; - Answer { - value: AnswerValue::Selected(opt.key.clone()), - selected_option: Some(opt.clone()), - text: None, - } + let keys: Vec = indices.iter().map(|&i| question.options[i].key.clone()).collect(); + let options: Vec<_> = indices.iter().map(|&i| question.options[i].clone()).collect(); + Answer::multi_selected(keys, options) } _ => Answer::skipped(), } diff --git a/crates/arc-workflows/src/interviewer/mod.rs b/crates/arc-workflows/src/interviewer/mod.rs index 615231916..74566f7f0 100644 --- a/crates/arc-workflows/src/interviewer/mod.rs +++ b/crates/arc-workflows/src/interviewer/mod.rs @@ -76,6 +76,7 @@ pub enum AnswerValue { Skipped, Timeout, Selected(String), + MultiSelected(Vec), Text(String), } @@ -84,42 +85,48 @@ pub enum AnswerValue { pub struct Answer { pub value: AnswerValue, pub selected_option: Option, + #[serde(default)] + pub selected_options: Vec, pub text: Option, } impl Answer { #[must_use] - pub const fn yes() -> Self { + pub fn yes() -> Self { Self { value: AnswerValue::Yes, selected_option: None, + selected_options: Vec::new(), text: None, } } #[must_use] - pub const fn no() -> Self { + pub fn no() -> Self { Self { value: AnswerValue::No, selected_option: None, + selected_options: Vec::new(), text: None, } } #[must_use] - pub const fn skipped() -> Self { + pub fn skipped() -> Self { Self { value: AnswerValue::Skipped, selected_option: None, + selected_options: Vec::new(), text: None, } } #[must_use] - pub const fn timeout() -> Self { + pub fn timeout() -> Self { Self { value: AnswerValue::Timeout, selected_option: None, + selected_options: Vec::new(), text: None, } } @@ -129,6 +136,16 @@ impl Answer { Self { value: AnswerValue::Selected(key), selected_option: Some(option), + selected_options: Vec::new(), + text: None, + } + } + + pub fn multi_selected(keys: Vec, options: Vec) -> Self { + Self { + value: AnswerValue::MultiSelected(keys), + selected_option: None, + selected_options: options, text: None, } } @@ -138,6 +155,7 @@ impl Answer { Self { value: AnswerValue::Text(t.clone()), selected_option: None, + selected_options: Vec::new(), text: Some(t), } } diff --git a/crates/arc-workflows/tests/integration.rs b/crates/arc-workflows/tests/integration.rs index 61e5679ec..15be3e4a1 100644 --- a/crates/arc-workflows/tests/integration.rs +++ b/crates/arc-workflows/tests/integration.rs @@ -435,6 +435,7 @@ async fn end_to_end_human_gate_pipeline() { let answers = VecDeque::from([Answer { value: AnswerValue::Selected("R".to_string()), selected_option: None, + selected_options: Vec::new(), text: None, }]); let interviewer = Arc::new(QueueInterviewer::new(answers)); @@ -2151,11 +2152,13 @@ async fn human_gate_loops_back() { Answer { value: AnswerValue::Selected("F".to_string()), selected_option: None, + selected_options: Vec::new(), text: None, }, Answer { value: AnswerValue::Selected("A".to_string()), selected_option: None, + selected_options: Vec::new(), text: None, }, ]); @@ -6193,6 +6196,7 @@ async fn human_gate_freeform_with_fixed_choice_match() { let answers = VecDeque::from([Answer { value: AnswerValue::Selected("A".to_string()), selected_option: None, + selected_options: Vec::new(), text: None, }]); let interviewer = Arc::new(QueueInterviewer::new(answers)); @@ -6434,6 +6438,7 @@ async fn human_gate_freeform_sets_allow_freeform_on_question() { let answers = VecDeque::from([Answer { value: AnswerValue::Selected("A".to_string()), selected_option: None, + selected_options: Vec::new(), text: None, }]); let inner = QueueInterviewer::new(answers); @@ -6542,6 +6547,7 @@ async fn human_gate_without_freeform_sets_allow_freeform_false() { let answers = VecDeque::from([Answer { value: AnswerValue::Selected("A".to_string()), selected_option: None, + selected_options: Vec::new(), text: None, }]); let inner = QueueInterviewer::new(answers); diff --git a/docs/api-reference/arc-api.yaml b/docs/api-reference/arc-api.yaml index e7fba8910..58aa7cf5e 100644 --- a/docs/api-reference/arc-api.yaml +++ b/docs/api-reference/arc-api.yaml @@ -438,9 +438,9 @@ paths: /runs/{id}/compare: get: - operationId: retrieveRunDiff + operationId: retrieveRunCompare tags: [Run Outputs] - summary: List Run Compare + summary: Retrieve Run Compare description: Returns file-level diffs produced by the run, optionally filtered to a specific checkpoint. parameters: - $ref: "#/components/parameters/RunId" @@ -550,6 +550,12 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorResponse" + "409": + description: Run is not in a steerable state + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" /runs/{id}/preview: post: @@ -578,6 +584,12 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorResponse" + "409": + description: Run has no active sandbox + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" # ── Workflows ───────────────────────────────────────────────────────── @@ -1174,10 +1186,9 @@ components: name: checkpoint in: query required: false - description: Filter file diffs to a specific checkpoint. Defaults to all changes. + description: Filter to a specific checkpoint ID. Omit to include all changes. schema: type: string - default: "all" example: cp-3 PageLimit: @@ -1506,7 +1517,9 @@ components: - confirmation SubmitAnswerRequest: - description: Request body for submitting an answer to a pending question. + description: > + Request body for submitting an answer to a pending question. + At least one of `value`, `selected_option_key`, or `selected_option_keys` must be provided. type: object properties: value: @@ -1515,8 +1528,14 @@ components: example: "Yes, proceed with the changes." selected_option_key: type: string - description: Key of the selected option (for multiple-choice questions). + description: Key of the selected option (for single-select multiple-choice questions). example: option_a + selected_option_keys: + type: array + items: + type: string + description: Keys of selected options (for multi-select questions). + example: ["option_a", "option_b"] ErrorResponseEntry: description: A single error entry in an error response. @@ -1676,6 +1695,8 @@ components: CodeLocation: description: A file and line location in the codebase. type: object + required: + - file properties: file: type: string @@ -2228,7 +2249,10 @@ components: # ── Verification Schemas ───────────────────────────────────────────── VerificationResult: - description: Outcome of a verification control evaluation. + description: > + Outcome of a verification control evaluation. + `skip`: evaluation was intentionally skipped (e.g., control is disabled). + `na`: control does not apply to this run (e.g., Python lint on a Rust-only change). type: string enum: - pass @@ -2250,13 +2274,19 @@ components: type: object required: - name + - slug - description + - type - status properties: name: type: string description: Human-readable control name. example: Motivation + slug: + type: string + description: URL-safe slug for linking to verification detail page. + example: motivation description: type: string description: Short description of what the control verifies. @@ -2318,6 +2348,8 @@ components: expires_in_secs: type: integer description: Time-to-live for the preview URL in seconds. + minimum: 1 + maximum: 86400 example: 3600 PreviewUrlResponse: @@ -2409,6 +2441,7 @@ components: - name - slug - description + - type properties: name: type: string diff --git a/packages/arc-api-client/src/api/run-outputs-api.ts b/packages/arc-api-client/src/api/run-outputs-api.ts index 1f98199de..da546999f 100644 --- a/packages/arc-api-client/src/api/run-outputs-api.ts +++ b/packages/arc-api-client/src/api/run-outputs-api.ts @@ -87,15 +87,15 @@ export const RunOutputsApiAxiosParamCreator = function (configuration?: Configur }, /** * Returns file-level diffs produced by the run, optionally filtered to a specific checkpoint. - * @summary List Run Compare + * @summary Retrieve Run Compare * @param {string} id Unique run identifier (ULID). - * @param {string} [checkpoint] Filter file diffs to a specific checkpoint. Defaults to all changes. + * @param {string} [checkpoint] Filter to a specific checkpoint ID. Omit to include all changes. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - retrieveRunDiff: async (id: string, checkpoint?: string, options: RawAxiosRequestConfig = {}): Promise => { + retrieveRunCompare: async (id: string, checkpoint?: string, options: RawAxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined - assertParamExists('retrieveRunDiff', 'id', id) + assertParamExists('retrieveRunCompare', 'id', id) const localVarPath = `/runs/{id}/compare` .replace(`{${"id"}}`, encodeURIComponent(String(id))); // use dummy base URL string because the URL constructor only accepts absolute URLs. @@ -198,16 +198,16 @@ export const RunOutputsApiFp = function(configuration?: Configuration) { }, /** * Returns file-level diffs produced by the run, optionally filtered to a specific checkpoint. - * @summary List Run Compare + * @summary Retrieve Run Compare * @param {string} id Unique run identifier (ULID). - * @param {string} [checkpoint] Filter file diffs to a specific checkpoint. Defaults to all changes. + * @param {string} [checkpoint] Filter to a specific checkpoint ID. Omit to include all changes. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async retrieveRunDiff(id: string, checkpoint?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveRunDiff(id, checkpoint, options); + async retrieveRunCompare(id: string, checkpoint?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveRunCompare(id, checkpoint, options); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; - const localVarOperationServerBasePath = operationServerMap['RunOutputsApi.retrieveRunDiff']?.[localVarOperationServerIndex]?.url; + const localVarOperationServerBasePath = operationServerMap['RunOutputsApi.retrieveRunCompare']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, /** @@ -246,14 +246,14 @@ export const RunOutputsApiFactory = function (configuration?: Configuration, bas }, /** * Returns file-level diffs produced by the run, optionally filtered to a specific checkpoint. - * @summary List Run Compare + * @summary Retrieve Run Compare * @param {string} id Unique run identifier (ULID). - * @param {string} [checkpoint] Filter file diffs to a specific checkpoint. Defaults to all changes. + * @param {string} [checkpoint] Filter to a specific checkpoint ID. Omit to include all changes. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - retrieveRunDiff(id: string, checkpoint?: string, options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.retrieveRunDiff(id, checkpoint, options).then((request) => request(axios, basePath)); + retrieveRunCompare(id: string, checkpoint?: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.retrieveRunCompare(id, checkpoint, options).then((request) => request(axios, basePath)); }, /** * Returns token and cost usage broken down by stage and model for a specific run. @@ -287,14 +287,14 @@ export class RunOutputsApi extends BaseAPI { /** * Returns file-level diffs produced by the run, optionally filtered to a specific checkpoint. - * @summary List Run Compare + * @summary Retrieve Run Compare * @param {string} id Unique run identifier (ULID). - * @param {string} [checkpoint] Filter file diffs to a specific checkpoint. Defaults to all changes. + * @param {string} [checkpoint] Filter to a specific checkpoint ID. Omit to include all changes. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - public retrieveRunDiff(id: string, checkpoint?: string, options?: RawAxiosRequestConfig) { - return RunOutputsApiFp(this.configuration).retrieveRunDiff(id, checkpoint, options).then((request) => request(this.axios, this.basePath)); + public retrieveRunCompare(id: string, checkpoint?: string, options?: RawAxiosRequestConfig) { + return RunOutputsApiFp(this.configuration).retrieveRunCompare(id, checkpoint, options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/packages/arc-api-client/src/models/code-location.ts b/packages/arc-api-client/src/models/code-location.ts index 11122868e..16baa0d79 100644 --- a/packages/arc-api-client/src/models/code-location.ts +++ b/packages/arc-api-client/src/models/code-location.ts @@ -21,7 +21,7 @@ export interface CodeLocation { /** * File path. */ - 'file'?: string; + 'file': string; /** * Line number in the file. */ diff --git a/packages/arc-api-client/src/models/run-verification-control.ts b/packages/arc-api-client/src/models/run-verification-control.ts index 8a4477379..53d43d3ae 100644 --- a/packages/arc-api-client/src/models/run-verification-control.ts +++ b/packages/arc-api-client/src/models/run-verification-control.ts @@ -28,11 +28,15 @@ export interface RunVerificationControl { * Human-readable control name. */ 'name': string; + /** + * URL-safe slug for linking to verification detail page. + */ + 'slug': string; /** * Short description of what the control verifies. */ 'description': string; - 'type'?: VerificationType; + 'type': VerificationType; 'status': VerificationResult; } diff --git a/packages/arc-api-client/src/models/submit-answer-request.ts b/packages/arc-api-client/src/models/submit-answer-request.ts index 673f73a08..2b00ea9ef 100644 --- a/packages/arc-api-client/src/models/submit-answer-request.ts +++ b/packages/arc-api-client/src/models/submit-answer-request.ts @@ -15,7 +15,7 @@ /** - * Request body for submitting an answer to a pending question. + * Request body for submitting an answer to a pending question. At least one of `value`, `selected_option_key`, or `selected_option_keys` must be provided. */ export interface SubmitAnswerRequest { /** @@ -23,8 +23,12 @@ export interface SubmitAnswerRequest { */ 'value'?: string; /** - * Key of the selected option (for multiple-choice questions). + * Key of the selected option (for single-select multiple-choice questions). */ 'selected_option_key'?: string; + /** + * Keys of selected options (for multi-select questions). + */ + 'selected_option_keys'?: Array; } diff --git a/packages/arc-api-client/src/models/verification-control.ts b/packages/arc-api-client/src/models/verification-control.ts index 5f944b997..1fee53142 100644 --- a/packages/arc-api-client/src/models/verification-control.ts +++ b/packages/arc-api-client/src/models/verification-control.ts @@ -39,7 +39,7 @@ export interface VerificationControl { * Short description of what the control verifies. */ 'description': string; - 'type'?: VerificationType; + 'type': VerificationType; 'mode'?: VerificationMode; /** * F1 score of the control\'s AI evaluator. diff --git a/packages/arc-api-client/src/models/verification-result.ts b/packages/arc-api-client/src/models/verification-result.ts index 14afa674b..360ac0954 100644 --- a/packages/arc-api-client/src/models/verification-result.ts +++ b/packages/arc-api-client/src/models/verification-result.ts @@ -15,7 +15,7 @@ /** - * Outcome of a verification control evaluation. + * Outcome of a verification control evaluation. `skip`: evaluation was intentionally skipped (e.g., control is disabled). `na`: control does not apply to this run (e.g., Python lint on a Rust-only change). */ export const VerificationResult = {