From c098effac6adaefbc788003c6963f1c3d9719664 Mon Sep 17 00:00:00 2001 From: Fini Date: Sat, 23 May 2026 07:52:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(orchestrator):=20types=20=E2=80=94=20visio?= =?UTF-8?q?n=20validation=20traits=20+=20Progress=20+=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the S3c type layer: 3 injected traits (PreValidator / ScreenshotProvider / VisionLlmClient), supporting types (PreValidationResult / VisionCallRequest / VisionResponse), 5 new Progress::Validation* variants, DesignRequest.validation_enabled field (serde default true), and validation_config.rs constants faithful to ai-runtime-config.ts:119-123 (node-count threshold 30, max rounds 3, quality threshold 8, timeout 180 000 ms). Stub impls (Skipped*) added to test_support.rs. All 35 existing DesignRequest literals updated with the new field. 366 tests pass. --- .../op-orchestrator/src/concurrent_tests.rs | 1 + .../src/concurrent_tests_b2.rs | 1 + crates/op-orchestrator/src/lib.rs | 1 + crates/op-orchestrator/src/plan.rs | 1 + crates/op-orchestrator/src/plan_normalize.rs | 2 + .../op-orchestrator/src/plan_repair_tests.rs | 2 + crates/op-orchestrator/src/prompt.rs | 8 + crates/op-orchestrator/src/run_tests.rs | 3 + crates/op-orchestrator/src/run_tests_b4.rs | 4 + crates/op-orchestrator/src/run_tests_c2.rs | 2 + crates/op-orchestrator/src/run_tests_c3.rs | 2 + crates/op-orchestrator/src/subagent.rs | 1 + crates/op-orchestrator/src/test_support.rs | 37 ++- crates/op-orchestrator/src/types.rs | 233 +++++++++++++++++- .../op-orchestrator/src/validation_config.rs | 29 +++ crates/op-orchestrator/src/variables.rs | 1 + 16 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 crates/op-orchestrator/src/validation_config.rs diff --git a/crates/op-orchestrator/src/concurrent_tests.rs b/crates/op-orchestrator/src/concurrent_tests.rs index d7ffb2a0a..5269eb99d 100644 --- a/crates/op-orchestrator/src/concurrent_tests.rs +++ b/crates/op-orchestrator/src/concurrent_tests.rs @@ -80,6 +80,7 @@ fn make_req() -> crate::types::DesignRequest { design_md: None, concurrency: 2, append_context: None, + validation_enabled: true, } } diff --git a/crates/op-orchestrator/src/concurrent_tests_b2.rs b/crates/op-orchestrator/src/concurrent_tests_b2.rs index 34f25dd47..03000f53d 100644 --- a/crates/op-orchestrator/src/concurrent_tests_b2.rs +++ b/crates/op-orchestrator/src/concurrent_tests_b2.rs @@ -22,6 +22,7 @@ fn make_req() -> crate::types::DesignRequest { design_md: None, concurrency: 2, append_context: None, + validation_enabled: true, } } diff --git a/crates/op-orchestrator/src/lib.rs b/crates/op-orchestrator/src/lib.rs index ac7d4d7a9..023654a08 100644 --- a/crates/op-orchestrator/src/lib.rs +++ b/crates/op-orchestrator/src/lib.rs @@ -22,6 +22,7 @@ pub mod retry; pub mod style_guide_context; pub mod timeouts; pub mod types; +pub mod validation_config; pub mod variables; pub mod append; diff --git a/crates/op-orchestrator/src/plan.rs b/crates/op-orchestrator/src/plan.rs index 5ebc4d9c4..c9a000ccd 100644 --- a/crates/op-orchestrator/src/plan.rs +++ b/crates/op-orchestrator/src/plan.rs @@ -218,6 +218,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, } } diff --git a/crates/op-orchestrator/src/plan_normalize.rs b/crates/op-orchestrator/src/plan_normalize.rs index 8a990fd63..96f817983 100644 --- a/crates/op-orchestrator/src/plan_normalize.rs +++ b/crates/op-orchestrator/src/plan_normalize.rs @@ -83,6 +83,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, } } @@ -175,6 +176,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, } } diff --git a/crates/op-orchestrator/src/plan_repair_tests.rs b/crates/op-orchestrator/src/plan_repair_tests.rs index 7c2829ed3..ce6dd3aaf 100644 --- a/crates/op-orchestrator/src/plan_repair_tests.rs +++ b/crates/op-orchestrator/src/plan_repair_tests.rs @@ -244,6 +244,7 @@ fn req(prompt: &str) -> DesignRequest { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, } } @@ -268,6 +269,7 @@ fn req_with_design_md(prompt: &str) -> DesignRequest { }), concurrency: 1, append_context: None, + validation_enabled: true, } } diff --git a/crates/op-orchestrator/src/prompt.rs b/crates/op-orchestrator/src/prompt.rs index b3cc66714..66c7c4ea8 100644 --- a/crates/op-orchestrator/src/prompt.rs +++ b/crates/op-orchestrator/src/prompt.rs @@ -251,6 +251,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, } } @@ -396,6 +397,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, }; let full_cr = build_subagent_prompt(&st, &plan(), &basic_req, AbortFlag::new(), false, false); @@ -485,6 +487,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, }; let long_prompt = "x".repeat(5000); // >= 4200 chars let long_req = DesignRequest { @@ -494,6 +497,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, }; let short_pp = build_orchestrator_prompt(&short_req, PlanningMode::Rich, AbortFlag::new()); let long_pp = build_orchestrator_prompt(&long_req, PlanningMode::Rich, AbortFlag::new()); @@ -514,6 +518,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, }; let pp = build_orchestrator_prompt(&ds_req, PlanningMode::Rich, AbortFlag::new()); // Short bucket base: 300_000ms × 2.0 = 600_000ms @@ -571,6 +576,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, }; let long_req = DesignRequest { prompt: "x".repeat(5000), @@ -579,6 +585,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, }; let short_cr = build_subagent_prompt( &subtask(), @@ -612,6 +619,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, }; let cr = build_subagent_prompt( &subtask(), diff --git a/crates/op-orchestrator/src/run_tests.rs b/crates/op-orchestrator/src/run_tests.rs index 942d446d2..3dec3a950 100644 --- a/crates/op-orchestrator/src/run_tests.rs +++ b/crates/op-orchestrator/src/run_tests.rs @@ -14,6 +14,7 @@ fn req() -> DesignRequest { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, } } @@ -27,6 +28,7 @@ fn req_standard() -> DesignRequest { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, } } @@ -40,6 +42,7 @@ fn req_basic() -> DesignRequest { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, } } diff --git a/crates/op-orchestrator/src/run_tests_b4.rs b/crates/op-orchestrator/src/run_tests_b4.rs index 63cdd5134..35d698f1b 100644 --- a/crates/op-orchestrator/src/run_tests_b4.rs +++ b/crates/op-orchestrator/src/run_tests_b4.rs @@ -76,6 +76,7 @@ fn req_append(live_target_id: &str) -> DesignRequest { existing_section_labels: vec!["Hero".into(), "Features".into()], is_mobile: false, }), + validation_enabled: true, } } @@ -92,6 +93,7 @@ fn req_append_concurrent(live_target_id: &str) -> DesignRequest { existing_section_labels: vec![], is_mobile: false, }), + validation_enabled: true, } } @@ -334,6 +336,7 @@ fn non_append_mode_takes_normal_sequential_path() { design_md: None, concurrency: 1, append_context: None, // no append context + validation_enabled: true, }; let summary = futures::executor::block_on(Orchestrator::new().run( @@ -425,6 +428,7 @@ fn append_mode_wins_over_dashboard_branch() { existing_section_labels: vec!["Hero".into()], is_mobile: false, }), + validation_enabled: true, }; let llm = ScriptedLlm::new(vec![ diff --git a/crates/op-orchestrator/src/run_tests_c2.rs b/crates/op-orchestrator/src/run_tests_c2.rs index b739e2a52..ce4f6e662 100644 --- a/crates/op-orchestrator/src/run_tests_c2.rs +++ b/crates/op-orchestrator/src/run_tests_c2.rs @@ -58,6 +58,7 @@ fn req_concurrent() -> DesignRequest { design_md: None, concurrency: 2, append_context: None, + validation_enabled: true, } } @@ -71,6 +72,7 @@ fn req_sequential() -> DesignRequest { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, } } diff --git a/crates/op-orchestrator/src/run_tests_c3.rs b/crates/op-orchestrator/src/run_tests_c3.rs index 464fce950..dc89475a8 100644 --- a/crates/op-orchestrator/src/run_tests_c3.rs +++ b/crates/op-orchestrator/src/run_tests_c3.rs @@ -57,6 +57,7 @@ fn req_dashboard() -> DesignRequest { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, } } @@ -68,6 +69,7 @@ fn req_non_dashboard() -> DesignRequest { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, } } diff --git a/crates/op-orchestrator/src/subagent.rs b/crates/op-orchestrator/src/subagent.rs index aaac891c5..eaf631842 100644 --- a/crates/op-orchestrator/src/subagent.rs +++ b/crates/op-orchestrator/src/subagent.rs @@ -106,6 +106,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, } } diff --git a/crates/op-orchestrator/src/test_support.rs b/crates/op-orchestrator/src/test_support.rs index ca585bee6..e16490f96 100644 --- a/crates/op-orchestrator/src/test_support.rs +++ b/crates/op-orchestrator/src/test_support.rs @@ -1,6 +1,9 @@ //! 测试桩 —— crate 内各测试模块共用。仅在 `cfg(test)` 下编译。 -use crate::types::{CallRequest, DocSink, LlmChunk, LlmClient, LlmError}; +use crate::types::{ + CallRequest, DocSink, LlmChunk, LlmClient, LlmError, PreValidationResult, PreValidator, + ScreenshotProvider, VisionCallRequest, VisionLlmClient, VisionResponse, +}; use futures::stream::BoxStream; use futures::Stream; use op_editor_core::{EditorCommand, EditorState}; @@ -205,3 +208,35 @@ impl LlmClient for CountingLlm { }) } } + +// ── S3c: vision-validation stub impls ───────────────────────────────────────── + +/// Stub `PreValidator` —— always returns zero fixes; no side effects. +/// Used by all tests until S1 op-design-lint detectors are wired. +pub(crate) struct SkippedPreValidator; + +impl PreValidator for SkippedPreValidator { + fn run_pre_validation_fixes(&self, _sink: &mut dyn DocSink) -> PreValidationResult { + PreValidationResult::default() + } +} + +/// Stub `ScreenshotProvider` —— always returns `None` (no screenshot available). +/// Causes the vision loop to exit after pre-validation with zero vision rounds. +pub(crate) struct SkippedScreenshotProvider; + +impl ScreenshotProvider for SkippedScreenshotProvider { + fn capture_root_frame(&self) -> Option { + None + } +} + +/// Stub `VisionLlmClient` —— always returns `VisionResponse::Skipped`. +/// Used until a real multimodal LLM client is wired host-side. +pub(crate) struct SkippedVisionLlmClient; + +impl VisionLlmClient for SkippedVisionLlmClient { + fn validate(&self, _req: VisionCallRequest) -> VisionResponse { + VisionResponse::Skipped { reason: None } + } +} diff --git a/crates/op-orchestrator/src/types.rs b/crates/op-orchestrator/src/types.rs index 9e84eeff0..35952873e 100644 --- a/crates/op-orchestrator/src/types.rs +++ b/crates/op-orchestrator/src/types.rs @@ -6,6 +6,7 @@ use futures::stream::BoxStream; use op_editor_core::{EditorCommand, EditorState}; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; @@ -65,6 +66,80 @@ pub struct LlmError { pub aborted: bool, } +// ── S3c: Vision-validation traits + supporting types ────────────────────────── + +/// 预校验(pre-validation)出口。host 实现把 TS `runPreValidationFixes` +/// 的七个 pass 注入;stub 实现直接返回零修复计数。 +/// +/// Port of `design-pre-validation.ts:38-45` shape + spec §4.1. +pub trait PreValidator: Send + Sync { + /// 在文档 sink 上原地运行所有预校验修复 pass,并返回修复统计。 + fn run_pre_validation_fixes(&self, sink: &mut dyn DocSink) -> PreValidationResult; +} + +/// 截图出口。host 实现调用 `SkiaEngine.captureRegion`;stub 返回 `None` +/// 表示"跳过视觉轮次"。 +/// +/// Port of `design-screenshot.ts` shape + spec §4.1. +pub trait ScreenshotProvider: Send + Sync { + /// 捕捉画布根帧截图,返回 base64 PNG;`None` 表示不可用 / 跳过。 + fn capture_root_frame(&self) -> Option; +} + +/// 视觉 LLM 调用出口。host 实现使用多模态 LLM;stub 返回 +/// `VisionResponse::Skipped`。 +/// +/// Port of `validateDesignScreenshot` shape in `design-validation.ts:137-228` +/// + spec §4.1. +pub trait VisionLlmClient: Send + Sync { + /// 执行一次同步视觉校验调用并返回结果。 + fn validate(&self, req: VisionCallRequest) -> VisionResponse; +} + +/// `PreValidator::run_pre_validation_fixes` 的返回值 —— 修复统计。 +/// +/// Port of the TS `{ total, byCategory }` shape. +#[derive(Debug, Clone, Default)] +pub struct PreValidationResult { + /// 本次运行所有 pass 合计修复的节点数。 + pub total: usize, + /// 按 pass 类别细分的修复计数(key = pass 名称)。 + pub by_category: BTreeMap, +} + +/// `VisionLlmClient::validate` 的输入 —— 单轮视觉校验请求。 +/// +/// Port of the call-site params in `validateDesignScreenshot` +/// (`design-validation.ts:137`). +#[derive(Debug, Clone)] +pub struct VisionCallRequest { + /// 视觉 LLM system prompt。 + pub system: String, + /// 携带节点树 dump 与修复指令的 user message。 + pub message: String, + /// 画布截图 base64 PNG。 + pub image_base64: String, + /// 覆盖模型名称;`None` 表示由 host 决定。 + pub model: Option, + /// 覆盖 provider;`None` 表示由 host 决定。 + pub provider: Option, + /// 本轮调用的超时时间。 + pub timeout: Duration, +} + +/// `VisionLlmClient::validate` 的返回值。 +/// +/// `Text` = 模型返回了 JSON 文本;`Skipped` = stub / 截图不可用 / host 选择跳过。 +#[derive(Debug, Clone)] +pub enum VisionResponse { + /// 模型返回了完整文本(JSON 格式,待 `parse_validation_response` 解析)。 + Text(String), + /// 本轮跳过;可选地附带跳过原因(用于日志)。 + Skipped { reason: Option }, +} + +// ── S3c end ─────────────────────────────────────────────────────────────────── + /// 廉价可克隆的中止句柄(`Arc` 语义)。 #[derive(Debug, Clone, Default)] pub struct AbortFlag(Arc); @@ -114,10 +189,41 @@ pub enum Intent { pub enum Progress { Planning, ScaffoldDone, - SubtaskStarted { id: String, label: String }, - SubtaskDone { id: String, node_count: usize }, - SubtaskFailed { id: String, error: String }, + SubtaskStarted { + id: String, + label: String, + }, + SubtaskDone { + id: String, + node_count: usize, + }, + SubtaskFailed { + id: String, + error: String, + }, CleanupDone, + // ── S3c: Vision-validation progress variants ───────────────────────────── + /// 视觉校验阶段开始(pre-validation 将在此之后立即运行)。 + ValidationStarted, + /// Pre-validation 完成;`applied` = 总修复数,`by_category` = 分类明细。 + ValidationPreCheckDone { + applied: usize, + by_category: BTreeMap, + }, + /// 某轮视觉 LLM 调用开始。`round` ∈ [1, MAX_VALIDATION_ROUNDS]。 + ValidationRoundStarted { + round: u8, + }, + /// 某轮视觉 LLM 调用完成并已应用修复。 + ValidationRoundDone { + round: u8, + applied: usize, + quality_score: u8, + }, + /// 整个视觉校验阶段完成。`total_applied` = pre + 所有轮次之和。 + ValidationDone { + total_applied: usize, + }, } /// 单个 subtask 的执行结果。`error` 带值但 `node_count > 0` 表示 @@ -199,6 +305,16 @@ pub struct DesignRequest { /// Port of `AIDesignRequest.context.appendContext` in `ai-types.ts:51`. #[serde(default, skip_serializing_if = "Option::is_none")] pub append_context: Option, + /// 是否启用后生成视觉校验循环(S3c)。 + /// 对应 TS `VALIDATION_ENABLED` flag(默认 `true`)。 + /// host 可将其设为 `false` 以跳过整个视觉校验阶段。 + /// Port of `VALIDATION_ENABLED` in `ai-runtime-config.ts:109`. + #[serde(default = "default_validation_enabled")] + pub validation_enabled: bool, +} + +fn default_validation_enabled() -> bool { + true } #[cfg(test)] @@ -268,6 +384,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, }; assert!(req.append_context.is_none()); } @@ -288,6 +405,7 @@ mod tests { design_md: None, concurrency: 1, append_context: Some(ctx), + validation_enabled: true, }; assert!(req.append_context.is_some()); } @@ -302,6 +420,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, }; let json = serde_json::to_string(&req).expect("serialize"); assert!( @@ -309,4 +428,112 @@ mod tests { "appendContext should be omitted when None, got: {json}" ); } + + // ── Task A1 (S3c): vision validation types ──────────────────────────────── + + /// `PreValidator` stub returns zero fixes. + #[test] + fn skipped_pre_validator_returns_empty_result() { + use crate::test_support::SkippedPreValidator; + let mut sink = VecDocSink::new(); + let result = SkippedPreValidator.run_pre_validation_fixes(&mut sink); + assert_eq!(result.total, 0); + assert!(result.by_category.is_empty()); + } + + /// `ScreenshotProvider` stub returns `None`. + #[test] + fn skipped_screenshot_provider_returns_none() { + use crate::test_support::SkippedScreenshotProvider; + assert!(SkippedScreenshotProvider.capture_root_frame().is_none()); + } + + /// `VisionLlmClient` stub returns `VisionResponse::Skipped`. + #[test] + fn skipped_vision_llm_client_returns_skipped() { + use crate::test_support::SkippedVisionLlmClient; + let req = VisionCallRequest { + system: "sys".into(), + message: "msg".into(), + image_base64: "img".into(), + model: None, + provider: None, + timeout: std::time::Duration::from_millis(5_000), + }; + let resp = SkippedVisionLlmClient.validate(req); + assert!(matches!(resp, VisionResponse::Skipped { .. })); + } + + /// `Progress::Validation*` variants all compile and pattern-match. + #[test] + fn progress_validation_variants_compile() { + use std::collections::BTreeMap; + let variants = vec![ + Progress::ValidationStarted, + Progress::ValidationPreCheckDone { + applied: 3, + by_category: BTreeMap::new(), + }, + Progress::ValidationRoundStarted { round: 1 }, + Progress::ValidationRoundDone { + round: 1, + applied: 2, + quality_score: 7, + }, + Progress::ValidationDone { total_applied: 5 }, + ]; + // Verify all variants are matchable + for v in variants { + match v { + Progress::ValidationStarted => {} + Progress::ValidationPreCheckDone { .. } => {} + Progress::ValidationRoundStarted { .. } => {} + Progress::ValidationRoundDone { .. } => {} + Progress::ValidationDone { .. } => {} + _ => {} + } + } + } + + /// `DesignRequest.validation_enabled` defaults to `true` when omitted from JSON. + #[test] + fn design_request_validation_enabled_defaults_true() { + // JSON without `validationEnabled` field + let json = r#"{"prompt":"test","concurrency":1}"#; + let req: DesignRequest = serde_json::from_str(json).expect("deserialize"); + assert!( + req.validation_enabled, + "validation_enabled should default to true" + ); + } + + /// `DesignRequest.validation_enabled` round-trips via serde. + #[test] + fn design_request_validation_enabled_serde_roundtrip() { + let req = DesignRequest { + prompt: "test".into(), + model: None, + provider: None, + design_md: None, + concurrency: 1, + append_context: None, + validation_enabled: false, + }; + let json = serde_json::to_string(&req).expect("serialize"); + let back: DesignRequest = serde_json::from_str(&json).expect("deserialize"); + assert!(!back.validation_enabled); + } + + /// Config consts have the values faithful to `ai-runtime-config.ts:119-123`. + #[test] + fn validation_config_consts_match_ts() { + use crate::validation_config::{ + MAX_VALIDATION_ROUNDS, VALIDATION_NODE_COUNT_THRESHOLD, VALIDATION_QUALITY_THRESHOLD, + VALIDATION_TIMEOUT_MS, + }; + assert_eq!(VALIDATION_NODE_COUNT_THRESHOLD, 30); + assert_eq!(MAX_VALIDATION_ROUNDS, 3); + assert_eq!(VALIDATION_QUALITY_THRESHOLD, 8); + assert_eq!(VALIDATION_TIMEOUT_MS, 180_000); + } } diff --git a/crates/op-orchestrator/src/validation_config.rs b/crates/op-orchestrator/src/validation_config.rs new file mode 100644 index 000000000..7ba082d0c --- /dev/null +++ b/crates/op-orchestrator/src/validation_config.rs @@ -0,0 +1,29 @@ +//! Configuration constants for the vision-validation pipeline. +//! +//! Faithful port of `apps/web/src/services/ai/ai-runtime-config.ts:119-123`. + +/// Minimum total node count in the active page to trigger the vision LLM +/// validation loop. Designs producing fewer nodes than this threshold +/// skip vision (saves +30-90s + vision API tokens) — pre-validation +/// heuristics still run regardless. Composite designs (multi-section +/// dashboards, full-page mockups) easily clear this; atomic single-tool +/// outputs (one badge, one chart) stay fast. +/// +/// Port of `VALIDATION_NODE_COUNT_THRESHOLD = 30` in `ai-runtime-config.ts`. +pub const VALIDATION_NODE_COUNT_THRESHOLD: usize = 30; + +/// Maximum number of vision-LLM validation rounds per generation. +/// +/// Port of `MAX_VALIDATION_ROUNDS = 3` in `ai-runtime-config.ts`. +pub const MAX_VALIDATION_ROUNDS: u8 = 3; + +/// Quality score (0–10) at or above which the validation loop exits early +/// without running the next round. +/// +/// Port of `VALIDATION_QUALITY_THRESHOLD = 8` in `ai-runtime-config.ts`. +pub const VALIDATION_QUALITY_THRESHOLD: u8 = 8; + +/// Per-round vision LLM call timeout in milliseconds. +/// +/// Port of `VALIDATION_TIMEOUT_MS = 180_000` in `ai-runtime-config.ts`. +pub const VALIDATION_TIMEOUT_MS: u64 = 180_000; diff --git a/crates/op-orchestrator/src/variables.rs b/crates/op-orchestrator/src/variables.rs index 05ffd2bfe..4386f6262 100644 --- a/crates/op-orchestrator/src/variables.rs +++ b/crates/op-orchestrator/src/variables.rs @@ -48,6 +48,7 @@ mod tests { design_md: None, concurrency: 1, append_context: None, + validation_enabled: true, }); assert!(seed_commands(&plan).is_empty()); let sink = VecDocSink::new();