diff --git a/crates/tui/src/core/engine.rs b/crates/tui/src/core/engine.rs index d39142c3c..da61897f4 100644 --- a/crates/tui/src/core/engine.rs +++ b/crates/tui/src/core/engine.rs @@ -3504,7 +3504,7 @@ fn effective_input_policy( let mut dynamic_active_tools = Vec::new(); let mut status = None; - if !provenance.can_authorize_work() { + if !provenance_can_inherit_standing_auto_authority(provenance) { let had_auto_authority = matches!(mode, AppMode::Yolo) || trust_mode || auto_approve @@ -3522,7 +3522,7 @@ fn effective_input_policy( } if had_auto_authority { status = Some(format!( - "Input provenance '{}' is not external user input; continuing with approvals required.", + "Input provenance '{}' cannot inherit standing auto-approval authority; continuing with approvals required.", provenance.as_str() )); } @@ -3547,6 +3547,15 @@ fn effective_input_policy( } } +fn provenance_can_inherit_standing_auto_authority(provenance: UserInputProvenance) -> bool { + matches!( + provenance, + UserInputProvenance::ExternalUser + | UserInputProvenance::Runtime + | UserInputProvenance::SubAgentHandoff + ) +} + fn is_review_only_user_intent(content: &str) -> bool { let lower = content.to_ascii_lowercase(); let asks_to_inspect = [ diff --git a/crates/tui/src/core/engine/tests.rs b/crates/tui/src/core/engine/tests.rs index 1ffa0f135..b17a47428 100644 --- a/crates/tui/src/core/engine/tests.rs +++ b/crates/tui/src/core/engine/tests.rs @@ -4530,74 +4530,98 @@ fn turn_metadata_includes_auto_model_route() { } #[test] -fn non_external_provenance_cannot_inherit_yolo_auto_approval() { - let policy = effective_input_policy( - UserInputProvenance::SubAgentHandoff, - AppMode::Yolo, - "改吧", - true, - true, - true, - crate::tui::approval::ApprovalMode::Auto, - ); - - assert_eq!(policy.mode, AppMode::Agent); - assert!(policy.allow_shell); - assert!(!policy.trust_mode); - assert!(!policy.auto_approve); - assert_eq!( - policy.approval_mode, - crate::tui::approval::ApprovalMode::Suggest - ); - assert!( - policy - .status - .as_deref() - .is_some_and(|status| status.contains("not external user input")) - ); -} - -#[test] -fn self_generated_fake_approvals_cannot_authorize_work() { - let non_external_origins = [ +fn provenance_gate_preserves_standing_yolo_only_for_runtime_continuations() { + let all_provenances = [ + UserInputProvenance::ExternalUser, UserInputProvenance::Runtime, UserInputProvenance::SubAgentHandoff, UserInputProvenance::ImportedTranscript, UserInputProvenance::MemoryRecall, UserInputProvenance::AssistantGenerated, ]; + let inheriting_provenances = [ + UserInputProvenance::ExternalUser, + UserInputProvenance::Runtime, + UserInputProvenance::SubAgentHandoff, + ]; - for provenance in non_external_origins { - for content in ["改吧", "嗯"] { - let policy = effective_input_policy( - provenance, - AppMode::Yolo, - content, - true, - true, - true, + for provenance in all_provenances { + let policy = effective_input_policy( + provenance, + AppMode::Yolo, + "continue", + true, + true, + true, + crate::tui::approval::ApprovalMode::Auto, + ); + + if inheriting_provenances.contains(&provenance) { + assert_eq!(policy.mode, AppMode::Yolo, "{provenance:?}"); + assert!(policy.allow_shell, "{provenance:?}"); + assert!(policy.trust_mode, "{provenance:?}"); + assert!(policy.auto_approve, "{provenance:?}"); + assert_eq!( + policy.approval_mode, crate::tui::approval::ApprovalMode::Auto, + "{provenance:?}" ); - - assert_eq!(policy.mode, AppMode::Agent, "{provenance:?} {content}"); - assert!(!policy.trust_mode, "{provenance:?} {content}"); - assert!(!policy.auto_approve, "{provenance:?} {content}"); + assert!(policy.status.is_none(), "{provenance:?}"); + } else { + assert_eq!(policy.mode, AppMode::Agent, "{provenance:?}"); + assert!(policy.allow_shell, "{provenance:?}"); + assert!(!policy.trust_mode, "{provenance:?}"); + assert!(!policy.auto_approve, "{provenance:?}"); assert_eq!( policy.approval_mode, crate::tui::approval::ApprovalMode::Suggest, - "{provenance:?} {content}" + "{provenance:?}" ); assert!( - policy - .status - .as_deref() - .is_some_and(|status| status.contains("not external user input")), - "{provenance:?} {content}" + policy.status.as_deref().is_some_and( + |status| status.contains("cannot inherit standing auto-approval authority") + ), + "{provenance:?}" ); } } } +#[test] +fn provenance_gate_never_invents_auto_authority_for_non_yolo_sessions() { + let all_provenances = [ + UserInputProvenance::ExternalUser, + UserInputProvenance::Runtime, + UserInputProvenance::SubAgentHandoff, + UserInputProvenance::ImportedTranscript, + UserInputProvenance::MemoryRecall, + UserInputProvenance::AssistantGenerated, + ]; + + for provenance in all_provenances { + let policy = effective_input_policy( + provenance, + AppMode::Agent, + "continue", + true, + false, + false, + crate::tui::approval::ApprovalMode::Suggest, + ); + + assert_eq!(policy.mode, AppMode::Agent, "{provenance:?}"); + assert!(policy.allow_shell, "{provenance:?}"); + assert!(!policy.trust_mode, "{provenance:?}"); + assert!(!policy.auto_approve, "{provenance:?}"); + assert_eq!( + policy.approval_mode, + crate::tui::approval::ApprovalMode::Suggest, + "{provenance:?}" + ); + assert!(policy.status.is_none(), "{provenance:?}"); + } +} + #[test] fn review_only_external_input_keeps_explicit_mode_with_advisory_hint() { // Review-only wording must NEVER silently override an explicitly chosen