mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 08:04:25 -10:00
runtime: move policy decisions below the UI boundary
Move the existing redaction confirmation decision and receipt write onto Config, retaining the same config fields and codewhale-config authority/persistence helpers. Move workspace trust next to its existing config owner with the same legacy marker rule. Move the unchanged0.9.4 constitution checkpoint and existing documentation to prompts. Migrate every caller and remove old public function/constant paths without re-exports. Reconcile the roadmap with the actual0.10.2 source graph; the later engine/exec-agent test remains separate. No new tests or code comments. On35e3204870 plus16 frozen paths: "Summary [ 2.643s] 86 tests run: 86 passed, 15225 skipped" covering redaction decisions/refusal, workspace trust and profile/local constitution requests. The same cases were passing in the prior full TUI run. Strict all-targets/all-features TUI clippy passes38.63s with the existing CI three allowances; first unused onboarding import diagnostic retained, removed before final check. All16 hashes unchanged. Source ratchet PASS at production0/test0/late1/uilib0/doc0 over118closuremodules; no old decision paths remain. Cargo crate-boundary, fmt/diff gates pass. Blocking538sites/165files and dead-code220 remain within budget. Fresh required npm1312passed0failed7skipped and check:webcompleted. Visibility/authority: the Config methods expose only the same existing decisions and persistence contract; they add no confirmation authority or alternate store. Workspace trust remains crate-private. The checkpoint value and model-facing bytes are unchanged. Full hosted/shared-process release qualification remains separate. Refs CW-7160 Refs #6938
This commit is contained in:
1 parent
35e3204870
commit
3126fe397b
16 files changed
+94
-93
No files matched your search
@@ -797,17 +797,17 @@
|
||||
.unwrap(),
|
||||
custom.canonicalize().unwrap()
|
||||
);
|
||||
assert!(crate::tui::redaction_gate::confirmation_required(&config));
|
||||
assert!(config.redaction_confirmation_required());
|
||||
let client = CodewhaleClient::new(&config).unwrap();
|
||||
let prepared =
|
||||
client.prepare_model_bound_request(request_with_tool_result(tool_output.clone()));
|
||||
assert!(!tool_result_content(&prepared).contains(CONFIG_SECRET_SENTINELS[6]));
|
||||
}
|
||||
let config = Config::load(None, None).unwrap();
|
||||
crate::tui::redaction_gate::record_confirmation(&config).unwrap();
|
||||
config.record_redaction_confirmation().unwrap();
|
||||
for explicit in [Some(custom.clone()), None] {
|
||||
let config = Config::load(explicit, None).unwrap();
|
||||
assert!(!crate::tui::redaction_gate::confirmation_required(&config));
|
||||
assert!(!config.redaction_confirmation_required());
|
||||
let client = CodewhaleClient::new(&config).unwrap();
|
||||
let prepared =
|
||||
client.prepare_model_bound_request(request_with_tool_result(tool_output.clone()));
|
||||
|
||||
@@ -7926,6 +7926,25 @@ impl Config {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Whether the startup gate must ask before the current config's
|
||||
/// `[redaction] model_bound` request can take effect.
|
||||
pub fn redaction_confirmation_required(&self) -> bool {
|
||||
codewhale_config::redaction::confirmation_required(
|
||||
self.model_bound_redaction(),
|
||||
self.loaded_config_path.as_deref(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Persist the confirmation and return the written receipt path. Called after
|
||||
/// the user picks the explicit "confirm" action.
|
||||
pub fn record_redaction_confirmation(&self) -> anyhow::Result<std::path::PathBuf> {
|
||||
let path = self.loaded_config_path.as_deref().ok_or_else(|| {
|
||||
anyhow::anyhow!("No loaded config file can receive this confirmation")
|
||||
})?;
|
||||
codewhale_config::redaction::record_model_bound_disabled_confirmation(path)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
/// Return the configured DeepSeek reasoning-effort tier, if any.
|
||||
#[must_use]
|
||||
pub fn reasoning_effort(&self) -> Option<&str> {
|
||||
@@ -8192,6 +8211,18 @@ pub(crate) fn workspace_trust_config_candidate_paths() -> Vec<PathBuf> {
|
||||
]
|
||||
}
|
||||
|
||||
pub(crate) fn needs_trust(workspace: &Path) -> bool {
|
||||
if is_workspace_trusted(workspace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let markers = [
|
||||
workspace.join(".deepseek").join("trusted"),
|
||||
workspace.join(".deepseek").join("trust.json"),
|
||||
];
|
||||
!markers.iter().any(|path| path.exists())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn is_workspace_trusted(workspace: &Path) -> bool {
|
||||
let config_path = match default_config_path() {
|
||||
|
||||
@@ -4379,7 +4379,7 @@ fn workspace_trust_round_trips_through_global_config() -> Result<()> {
|
||||
|
||||
assert_eq!(saved, temp_root.join(".deepseek").join("config.toml"));
|
||||
assert!(is_workspace_trusted(&workspace));
|
||||
assert!(!crate::tui::onboarding::needs_trust(&workspace));
|
||||
assert!(!crate::config::needs_trust(&workspace));
|
||||
assert!(
|
||||
!workspace.join(".deepseek").exists(),
|
||||
"trust persistence must not create a project-local .deepseek directory"
|
||||
@@ -4422,7 +4422,7 @@ fn workspace_trust_reads_existing_projects_table() -> Result<()> {
|
||||
)?;
|
||||
|
||||
assert!(is_workspace_trusted(&workspace));
|
||||
assert!(!crate::tui::onboarding::needs_trust(&workspace));
|
||||
assert!(!crate::config::needs_trust(&workspace));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+18
-18
@@ -7072,7 +7072,7 @@ fn doctor_inherited_setup_facts(
|
||||
language: None,
|
||||
has_provider_route: !config.default_model().trim().is_empty(),
|
||||
has_credentials_or_local_runtime: doctor_has_credentials_or_local_runtime(config),
|
||||
trust_chosen: !crate::tui::onboarding::needs_trust(workspace),
|
||||
trust_chosen: !crate::config::needs_trust(workspace),
|
||||
has_expert_override,
|
||||
has_user_constitution,
|
||||
user_constitution_validity,
|
||||
@@ -7100,7 +7100,7 @@ fn print_doctor_setup_report(
|
||||
// Ordinary doctor deliberately does not read environment values or the
|
||||
// durable secret store, so `not_probed` must not erase a completed lane.
|
||||
let first_run_ready = state.first_run_ready();
|
||||
let update_ready = state.update_ready(crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION);
|
||||
let update_ready = state.update_ready(crate::prompts::CONSTITUTION_CHECKPOINT_VERSION);
|
||||
let operate_ready = state.operate_ready();
|
||||
let first_run_icon = if first_run_ready {
|
||||
"✓".truecolor(ok_rgb.0, ok_rgb.1, ok_rgb.2)
|
||||
@@ -7135,7 +7135,7 @@ fn print_doctor_setup_report(
|
||||
if first_run_ready {
|
||||
println!(
|
||||
" {update_icon} update checkpoint {}: {}",
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
doctor_ready_label(update_ready)
|
||||
);
|
||||
}
|
||||
@@ -7365,7 +7365,7 @@ fn doctor_runtime_posture_line(config: &Config, workspace: &Path) -> String {
|
||||
} else {
|
||||
"default"
|
||||
};
|
||||
let trust = if crate::tui::onboarding::needs_trust(workspace) {
|
||||
let trust = if crate::config::needs_trust(workspace) {
|
||||
"workspace not elevated"
|
||||
} else {
|
||||
"workspace trusted"
|
||||
@@ -7824,7 +7824,7 @@ fn doctor_setup_report_json(config: &Config, workspace: &Path) -> serde_json::Va
|
||||
"default"
|
||||
};
|
||||
let (telemetry_value, telemetry_source) = doctor_runtime_telemetry(config);
|
||||
let workspace_trusted = !crate::tui::onboarding::needs_trust(workspace);
|
||||
let workspace_trusted = !crate::config::needs_trust(workspace);
|
||||
let credential = resolve_credential_diagnostic(config);
|
||||
let credential_ready = credential.availability.certifies_ready();
|
||||
let steps: Vec<_> = codewhale_config::SetupStep::ALL
|
||||
@@ -7845,9 +7845,9 @@ fn doctor_setup_report_json(config: &Config, workspace: &Path) -> serde_json::Va
|
||||
"source": source,
|
||||
"schema_version": state.schema_version,
|
||||
"inherited": state.inherited,
|
||||
"checkpoint_version": crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
"checkpoint_version": crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
"first_run_ready": state.first_run_ready(),
|
||||
"update_ready": state.update_ready(crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION),
|
||||
"update_ready": state.update_ready(crate::prompts::CONSTITUTION_CHECKPOINT_VERSION),
|
||||
"operate_ready": state.operate_ready(),
|
||||
"credential": {
|
||||
"ready": credential_ready,
|
||||
@@ -15729,7 +15729,7 @@ mod doctor_setup_state_tests {
|
||||
assert_eq!(report["next_actions"]["persistence"], "/setup persistence");
|
||||
assert_eq!(
|
||||
report["checkpoint_version"],
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION
|
||||
);
|
||||
assert_eq!(report["update_ready"], false);
|
||||
assert_eq!(report["operate_ready"], false);
|
||||
@@ -16022,7 +16022,7 @@ mod doctor_setup_state_tests {
|
||||
codewhale_config::StepEntry::new(
|
||||
codewhale_config::StepStatus::Verified,
|
||||
true,
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
),
|
||||
);
|
||||
state.set_step(
|
||||
@@ -16030,7 +16030,7 @@ mod doctor_setup_state_tests {
|
||||
codewhale_config::StepEntry::new(
|
||||
codewhale_config::StepStatus::Verified,
|
||||
true,
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
)
|
||||
.with_result("deepseek/deepseek-chat"),
|
||||
);
|
||||
@@ -16039,12 +16039,12 @@ mod doctor_setup_state_tests {
|
||||
codewhale_config::StepEntry::new(
|
||||
codewhale_config::StepStatus::Verified,
|
||||
true,
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
),
|
||||
);
|
||||
state
|
||||
.complete_constitution_checkpoint(
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
codewhale_config::ConstitutionChoice::Bundled,
|
||||
)
|
||||
.set_step(
|
||||
@@ -16052,7 +16052,7 @@ mod doctor_setup_state_tests {
|
||||
codewhale_config::StepEntry::new(
|
||||
codewhale_config::StepStatus::Verified,
|
||||
true,
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
),
|
||||
);
|
||||
state.runtime_posture_source = codewhale_config::RuntimePostureSource::Confirmed;
|
||||
@@ -16084,7 +16084,7 @@ mod doctor_setup_state_tests {
|
||||
assert_eq!(report["constitution"]["choice"], "bundled");
|
||||
assert_eq!(
|
||||
report["constitution"]["checkpoint_completed_for"],
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION
|
||||
);
|
||||
assert_eq!(report["constitution"]["autonomy_preference"], "inactive");
|
||||
assert_eq!(report["runtime_posture_source"], "confirmed");
|
||||
@@ -16243,7 +16243,7 @@ mod doctor_setup_state_tests {
|
||||
codewhale_config::StepEntry::new(
|
||||
codewhale_config::StepStatus::Verified,
|
||||
true,
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
),
|
||||
);
|
||||
state.set_step(
|
||||
@@ -16251,12 +16251,12 @@ mod doctor_setup_state_tests {
|
||||
codewhale_config::StepEntry::new(
|
||||
codewhale_config::StepStatus::Verified,
|
||||
true,
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
),
|
||||
);
|
||||
state.runtime_posture_source = codewhale_config::RuntimePostureSource::Confirmed;
|
||||
state.complete_constitution_checkpoint(
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
codewhale_config::ConstitutionChoice::Bundled,
|
||||
);
|
||||
state.set_step(
|
||||
@@ -16264,7 +16264,7 @@ mod doctor_setup_state_tests {
|
||||
codewhale_config::StepEntry::new(
|
||||
codewhale_config::StepStatus::Verified,
|
||||
false,
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
)
|
||||
.with_result(
|
||||
"provider=ready, runtime=ready, roster=ready, concurrency=plan limit not probed",
|
||||
|
||||
@@ -682,6 +682,11 @@ pub fn set_base_prompt_override(s: String) -> Result<(), String> {
|
||||
// This keeps replacing the global Constitution a deliberate, auditable act
|
||||
// rather than something a stray file can do.
|
||||
|
||||
/// Target lane for the once-per-version constitution checkpoint. Bumped per
|
||||
/// release when the bundled constitution materially changes, so existing users
|
||||
/// re-acknowledge it once. 0.9.4 re-ships the Fleet/operate constitution.
|
||||
pub const CONSTITUTION_CHECKPOINT_VERSION: &str = "0.9.4";
|
||||
|
||||
/// Relative path, under the config directory, of the optional base-prompt
|
||||
/// (constitution) override file.
|
||||
pub const CONSTITUTION_OVERRIDE_FILE: &str = "prompts/constitution.md";
|
||||
@@ -2694,7 +2699,7 @@ mod tests {
|
||||
|
||||
let mut state = codewhale_config::SetupState::default();
|
||||
state.complete_constitution_checkpoint(
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
codewhale_config::ConstitutionChoice::Bundled,
|
||||
);
|
||||
state
|
||||
|
||||
@@ -561,7 +561,7 @@ impl App {
|
||||
// YOLO is a permission change. A locked policy must not be sidestepped
|
||||
// by --yolo, default_mode=yolo, /zidong, or Alt+Y.
|
||||
let yolo_compat = yolo_requested && !approval_policy_locked;
|
||||
let needs_workspace_trust = !yolo_compat && crate::tui::onboarding::needs_trust(&workspace);
|
||||
let needs_workspace_trust = !yolo_compat && crate::config::needs_trust(&workspace);
|
||||
// The language screen is required only when the locale cannot be
|
||||
// confidently inferred from settings or the environment; returning
|
||||
// users never see it.
|
||||
|
||||
@@ -109,7 +109,7 @@ fn required_progress(app: &App) -> Option<(usize, usize)> {
|
||||
}
|
||||
|
||||
fn trust_decision_required(app: &App) -> bool {
|
||||
!app.trust_mode && needs_trust(&app.workspace)
|
||||
!app.trust_mode && crate::config::needs_trust(&app.workspace)
|
||||
}
|
||||
|
||||
fn action_hints(app: &App) -> Vec<ActionHint> {
|
||||
@@ -392,18 +392,6 @@ fn mark_onboarded_at_path(path: PathBuf) -> std::io::Result<PathBuf> {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn needs_trust(workspace: &Path) -> bool {
|
||||
if crate::config::is_workspace_trusted(workspace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let markers = [
|
||||
workspace.join(".deepseek").join("trusted"),
|
||||
workspace.join(".deepseek").join("trust.json"),
|
||||
];
|
||||
!markers.iter().any(|path| path.exists())
|
||||
}
|
||||
|
||||
pub fn mark_trusted(workspace: &Path) -> anyhow::Result<PathBuf> {
|
||||
crate::config::save_workspace_trust(workspace)
|
||||
}
|
||||
|
||||
@@ -27,15 +27,6 @@ use crate::tui::views::{ActionHint, render_modal_footer, render_underwater_surfa
|
||||
use codewhale_localization::MessageId;
|
||||
use codewhale_palette as palette;
|
||||
|
||||
/// Whether the startup gate must ask before the current config's
|
||||
/// `[redaction] model_bound` request can take effect.
|
||||
pub fn confirmation_required(config: &crate::config::Config) -> bool {
|
||||
codewhale_config::redaction::confirmation_required(
|
||||
config.model_bound_redaction(),
|
||||
config.loaded_config_path.as_deref(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Render the gate. Callers (the frame compositor) invoke this only while
|
||||
/// `app.redaction_gate` is set. The gate has two stages: the first stage
|
||||
/// explains the opt-out and its risk; pressing the confirm key moves to the
|
||||
@@ -182,17 +173,6 @@ fn wrap_body_muted(lines: &mut Vec<Line<'static>>, app: &App, id: MessageId, wid
|
||||
}
|
||||
}
|
||||
|
||||
/// Persist the confirmation and return the written receipt path. Called after
|
||||
/// the user picks the explicit "confirm" action.
|
||||
pub fn record_confirmation(config: &crate::config::Config) -> anyhow::Result<std::path::PathBuf> {
|
||||
let path = config
|
||||
.loaded_config_path
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("No loaded config file can receive this confirmation"))?;
|
||||
codewhale_config::redaction::record_model_bound_disabled_confirmation(path)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
// The "keep masking" answer persists nothing and rewrites no file: the
|
||||
// current launch stays on the safe default, and because the config field
|
||||
// still requests `"disabled"`, the gate asks again on the next launch until
|
||||
@@ -368,8 +348,8 @@ mod tests {
|
||||
let config = Config::load(Some(path.clone()), None).unwrap();
|
||||
// An existing directory makes the write fail on every supported OS.
|
||||
std::fs::create_dir(codewhale_config::redaction::model_bound_state_path(&path)).unwrap();
|
||||
assert!(record_confirmation(&config).is_err());
|
||||
assert!(confirmation_required(&config));
|
||||
assert!(config.record_redaction_confirmation().is_err());
|
||||
assert!(config.redaction_confirmation_required());
|
||||
let mut app = app_fixture();
|
||||
let notice = app.tr(MessageId::RedactionGateSaveFailed).into_owned();
|
||||
app.push_status_toast_record(
|
||||
|
||||
@@ -24,7 +24,6 @@ use crate::prompts::{
|
||||
BASE_PROMPT_OVERRIDE_OPT_IN_ENV, CONSTITUTION_OVERRIDE_FILE, base_prompt_override_opt_in,
|
||||
};
|
||||
use crate::tui::app::{App, AppModeUi};
|
||||
use crate::tui::onboarding;
|
||||
use crate::tui::views::{
|
||||
ActionHint, ModalKind, ModalView, ViewAction, ViewEvent, render_modal_footer,
|
||||
render_panel_scroll_rail, render_underwater_surface,
|
||||
@@ -53,10 +52,7 @@ use persistence::SetupPersistenceFacts;
|
||||
pub(crate) use provider::record_configured_route;
|
||||
use remote::SetupRemoteFacts;
|
||||
|
||||
/// Target lane for the once-per-version constitution checkpoint. Bumped per
|
||||
/// release when the bundled constitution materially changes, so existing users
|
||||
/// re-acknowledge it once. 0.9.4 re-ships the Fleet/operate constitution.
|
||||
pub const CONSTITUTION_CHECKPOINT_VERSION: &str = "0.9.4";
|
||||
use crate::prompts::CONSTITUTION_CHECKPOINT_VERSION;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SetupCommitKind {
|
||||
@@ -5441,7 +5437,7 @@ fn inherited_facts_for_app(app: &App, config: &Config) -> InheritedConfigFacts {
|
||||
language: Some(app.ui_locale.tag().to_string()),
|
||||
has_provider_route: !config.default_model().trim().is_empty(),
|
||||
has_credentials_or_local_runtime: has_api_key(config),
|
||||
trust_chosen: app.trust_mode || !onboarding::needs_trust(&app.workspace),
|
||||
trust_chosen: app.trust_mode || !crate::config::needs_trust(&app.workspace),
|
||||
has_expert_override: expert_override.is_active(),
|
||||
has_user_constitution,
|
||||
user_constitution_validity,
|
||||
|
||||
@@ -36,7 +36,7 @@ pub(crate) async fn record_configured_route(app: &App) -> anyhow::Result<()> {
|
||||
}
|
||||
let entry = step_entry(
|
||||
StepStatus::Configured,
|
||||
super::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
format!(
|
||||
"provider={}, model={}; configured, not checked",
|
||||
app.provider_identity_for_persistence(),
|
||||
|
||||
@@ -1018,7 +1018,7 @@ pub(crate) struct SteerPausedSnapshot {
|
||||
fn use_bundled_constitution(app: &mut App, config: &Config) {
|
||||
let mut state = crate::tui::setup::load_setup_state_for_app(app, config);
|
||||
state.complete_constitution_checkpoint(
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
codewhale_config::ConstitutionChoice::Bundled,
|
||||
);
|
||||
state.constitution_source = codewhale_config::ConstitutionSource::Bundled;
|
||||
@@ -1029,7 +1029,7 @@ fn use_bundled_constitution(app: &mut App, config: &Config) {
|
||||
codewhale_config::StepEntry::new(
|
||||
codewhale_config::StepStatus::Verified,
|
||||
true,
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
)
|
||||
.with_result("bundled/default constitution"),
|
||||
);
|
||||
|
||||
@@ -1142,7 +1142,7 @@ pub async fn run_tui(
|
||||
// masking boundary only after an explicit one-time confirmation on this
|
||||
// startup gate. Arm the gate before the engine spawns so it owns the first
|
||||
// screen; answering it rebuilds the engine with the confirmed mode.
|
||||
app.redaction_gate = crate::tui::redaction_gate::confirmation_required(config);
|
||||
app.redaction_gate = config.redaction_confirmation_required();
|
||||
|
||||
// Restore before admitting initial input, including resumed conversations.
|
||||
let engine_handle = spawn_tui_engine_with_session(&mut app, config).await?;
|
||||
@@ -5439,7 +5439,7 @@ pub(crate) async fn run_event_loop(
|
||||
app.redaction_gate_confirming = true;
|
||||
app.redaction_gate_scroll.set(0);
|
||||
} else {
|
||||
match crate::tui::redaction_gate::record_confirmation(config) {
|
||||
match config.record_redaction_confirmation() {
|
||||
Ok(_) => {
|
||||
// The engine already spawned with masking on
|
||||
// (the unconfirmed safe default). Rebuild it so
|
||||
|
||||
@@ -8652,7 +8652,7 @@ fn setup_checkpoint_waits_for_onboarding_and_skip_flag() {
|
||||
.expect("setup state");
|
||||
assert_eq!(
|
||||
state.constitution_checkpoint_completed_for.as_deref(),
|
||||
Some(crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION)
|
||||
Some(crate::prompts::CONSTITUTION_CHECKPOINT_VERSION)
|
||||
);
|
||||
assert_eq!(
|
||||
state.constitution_choice,
|
||||
@@ -8693,7 +8693,7 @@ fn setup_runtime_preset_apply_persists_settings_config_and_state() {
|
||||
codewhale_config::StepEntry::new(
|
||||
codewhale_config::StepStatus::Verified,
|
||||
true,
|
||||
crate::tui::setup::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
crate::prompts::CONSTITUTION_CHECKPOINT_VERSION,
|
||||
)
|
||||
.with_result(preset.result_summary()),
|
||||
);
|
||||
@@ -20294,7 +20294,7 @@ async fn redaction_confirmation_restores_history_before_first_local_provider_req
|
||||
);
|
||||
assert!(server.received_requests().await.unwrap().is_empty());
|
||||
}
|
||||
crate::tui::redaction_gate::record_confirmation(&config).unwrap();
|
||||
config.record_redaction_confirmation().unwrap();
|
||||
engine.send(Op::Shutdown).await.unwrap();
|
||||
let replacement = spawn_tui_engine_with_session(&mut app, &config)
|
||||
.await
|
||||
@@ -20308,7 +20308,7 @@ async fn redaction_confirmation_restores_history_before_first_local_provider_req
|
||||
app.current_session_id.as_deref(),
|
||||
Some("local-consent-resumed-session")
|
||||
);
|
||||
assert!(!crate::tui::redaction_gate::confirmation_required(&config));
|
||||
assert!(!config.redaction_confirmation_required());
|
||||
app.redaction_gate = false;
|
||||
app.redaction_gate_confirming = false;
|
||||
submit_initial_input_if_ready(&mut app, &config, &replacement)
|
||||
@@ -21325,7 +21325,7 @@ fn trust_directory_continue_untrusted_advances_without_recording_trust() {
|
||||
assert!(!app.trust_mode);
|
||||
assert_eq!(app.onboarding, OnboardingState::Ready);
|
||||
assert!(
|
||||
crate::tui::onboarding::needs_trust(&app.workspace),
|
||||
crate::config::needs_trust(&app.workspace),
|
||||
"declining trust must not write a trusted marker"
|
||||
);
|
||||
let notice = app.status_message.expect("restricted-mode notice");
|
||||
|
||||
@@ -16,9 +16,9 @@ to a date: at the observed cadence (0.10.0 on 2026-09-22, 0.10.1 on
|
||||
- The A/B decision comes first, because it decides which carve-outs are on the critical path. If the SCC is moved atomically (Option A), X1, X4, X7, X8, P1, P2 and settings are not prerequisites. They only shrink a cycle that moves as one unit anyway. What A needs is: prod edges 0 (X2), test edges 0, late 0, uilib 0, and a lower test-support. If the founder picks the design doc's slice-only path (Option B), those carve-outs are the work. The sequencer put 9 carve-out releases ahead of the decision. This plan puts the shared prefix first and the decision at 0.10.5.
|
||||
- One structural move per patch release. A move is a rename-dominated relocation or one seam-introducing slice with a named gate. Changes that do not alter the crate graph ride along: test-only edge cuts, packaging, docs, the guard, ratchet baseline updates. A release that carries both a seam and its move is two moves. P2 and X8 are split accordingly.
|
||||
- Two gate tiers. Slice gate (per landing on main): batched edits, one compile, `module_graph.py --check`, the existing tests for the touched filters with real 'N passed' (N > 0), clippy with CI's flags. Release gate (per patch release, once): `scripts/release/publish-crates.sh dry-run` (multi-package `cargo publish --dry-run`, which includes check-crate-sizes.py), validate-crate-publish-order.py, and green hosted CI (macOS and Windows) on the exact SHA. Per-slice `cargo package` of a single crate does not work for unpublished 0.10.x workspace dependencies. Only the multi-package dry-run resolves them, and it builds every tarball, so it runs at the release cut, not per slice.
|
||||
- Reuse before add. Existing homes first: execpolicy, codewhale-secrets, codewhale-models (it owns codewhale_models::Tool, the provider request types and the client contract), codewhale-config, codewhale-runtime. X4 therefore lands in codewhale-models, not in crates/tools (which depends only on protocol and publishes before config and models) and not in a new util-schema package. A new package name is a founder gate (test-support is publish=false and exempt).
|
||||
- Reuse before add. Existing homes first: execpolicy, codewhale-secrets, codewhale-models (it owns codewhale_models::Tool, the provider request types and the client contract), codewhale-config, codewhale-runtime. X4 therefore lands in codewhale-models, not in crates/tools (which depends only on protocol and publishes before config and models) and not in a new util-schema package. A new package name is a founder gate; RS-11 uses the existing Runtime test-support feature.
|
||||
- Every move leaves a `pub use` shim at the old path (3,017 crate::config refs, 330 test_support importers, 165 session_manager refs). The shim is deleted in a later cleanup, never in the move. A glob `pub(crate) use` shim for a test-only crate is fine.
|
||||
- The ratchet only goes down, and the baseline in scripts/runtime-boundary-baseline.json (prod 11, test 35, late 1, uilib 1, verified today as PASS over 117 modules) is updated in the same commit that moves the code. The sequencer's ratchet_targets kept test 35, late 1, uilib 1 through every release and then demanded 'test <= 34' at 0.11.0. Nothing in the old plan lowered test edges (tools|tui 12, test_support|tui 7, client|tui 3, session_manager|tui 3, fleet|lib.rs 3, config|tui 2, core|tui 2, runtime_api|commands 2, prompts|tui 1). The late edge (core/engine/tests/test_cases_04.rs:1197 uses crate::exec_agent) and the uilib edge (tools/subagent/tests/roster_routes.rs:798 uses crossterm) are tests. Each has to reach 0 before any new crate that must never depend on tui or crossterm (crates/runtime/Cargo.toml says so) can hold that code.
|
||||
- The ratchet only goes down. The original sequencer snapshot recorded production 11, test 35, late 1 and UI-library 1; those are historical counts. X2 and RS-11 bring the current 0.10.2 source baseline to production 0, test 0, late 1 and UI-library 0. The remaining late reference is the engine/exec-agent test and must reach 0 before that code moves into a runtime-only crate.
|
||||
- Test edges and test code are not in the tarball: tests/**, **/tests.rs, **/tests/**, *_tests.rs and test_cases_*.rs are excluded by crates/tui/Cargo.toml. Moving tests therefore frees 0 bytes of tarball. Inline `#[cfg(test)]` blocks in non-excluded files (schema_sanitize.rs from line 470, settings.rs from line 2372, test_support.rs from line 657) do ship.
|
||||
- Cadence is the unmeasured cost. v0.10.0 is dated 2026-09-22 and v0.10.1 2026-10-07 (15 days, measured from tags). The sequencer's 10 patch releases to 0.11.0 is about five months at that rate, with no calendar stated. The shared prefix here is 0.10.2 to 0.10.5, roughly six weeks. Track B items are explicit, optional and uncalendared.
|
||||
- Never widen a trust boundary silently. Every `pub(crate)` to `pub` change (try_lock_file_exclusive, test-support items, execpolicy rule types, credentials) gets a one-paragraph visibility review in the commit message.
|
||||
@@ -44,7 +44,8 @@ moved them to 0.10.3/0.10.4 is superseded.
|
||||
environment/support helpers behind the existing `codewhale-runtime`
|
||||
`test-support` feature, keep one environment mutex, and move UI fixtures and
|
||||
UI-typed assertions to their UI owner. No new test-support crate is needed.
|
||||
RS-11 is still pending here; its final test-edge count is recorded when it lands.
|
||||
The completed source moves lower tests 30 -> 0 and terminal-library 1 -> 0.
|
||||
Production stays 0; late stays 1 for the later engine/exec-agent test slice.
|
||||
- Slice gate: one frozen native batch, focused existing checks with actual
|
||||
nonzero counts, clippy using CI’s flags, and the lowered generated ratchet.
|
||||
Preserve exact command JSON through a local before/after inspection.
|
||||
@@ -59,17 +60,17 @@ moved them to 0.10.3/0.10.4 is superseded.
|
||||
The removed X2/test-support deferral is not permission to invent replacement
|
||||
work or alter the larger A/B decision. Later carve-outs retain their gates below.
|
||||
|
||||
### 0.10.5: Test-edge rehoming and the A/B decision release. Non-graph cleanup, no crate moves.
|
||||
### 0.10.5: Remaining late edge and the A/B decision. No crate moves.
|
||||
|
||||
Slices:
|
||||
- Rehome the remaining test edges so no #[cfg(test)] code in the runtime half names crate::tui, commands or lib.rs: tools|tui 12, client|tui 3, session_manager|tui 3, config|tui 2, core|tui 2, prompts|tui 1, plus late 1 and uilib 1. Mechanism: move each such test to a tui-side integration module or replace the tui fixture with a runtime-side one. Split across several landings on main, owned per directory.
|
||||
- Resolve the remaining late engine/exec-agent test reference. RS-11 already removes the Runtime-to-UI test references and terminal-library import in 0.10.2; do not repeat those moves or introduce another test-support crate.
|
||||
- Record the founder's A/B decision (open_decisions[0]) before any Track B release starts.
|
||||
|
||||
- **Entry gate:** RS-11 in 0.10.2 is verified. Per-landing: the test files edited are not dirty. Mapping of the 32 edges to files regenerated with `module_graph.py --report` at the start of the release and posted in the Linear issue.
|
||||
- **Entry gate:** RS-11 in 0.10.2 is verified. Per-landing: the test files edited are not dirty. Regenerate the current graph report, whose RS-11 baseline is production 0, test 0, late 1 and UI-library 0, and claim its remaining exact paths in Linear.
|
||||
- **Exit gate:** module_graph.py --check at prod 0, test 0, late 0, uilib 0, baseline JSON updated. Existing tests for each touched file run with identical pass counts (quote before and after). No production file changed except visibility needed by a moved fixture. The A/B decision is recorded in docs/design/TUI_DECONSTRUCTION.md.
|
||||
- **Ratchet target:** prod 0, test 30 -> 0, late 1 -> 0, uilib 1 -> 0.
|
||||
- **Ratchet target:** prod 0, test 0, late 1 -> 0, uilib 0.
|
||||
- **Size budget:** No change. Test files are excluded from the tarball.
|
||||
- **Risks:** Test relocation is not rename-dominated and can silently drop coverage: gate on equal before/after pass counts per directory. tools tests are 152k production lines of neighbours, so collisions are likely; land per-directory.
|
||||
- **Risks:** The remaining test crosses the engine/exec-agent launch boundary. Preserve its launch and lifecycle assertions and gate on the existing nonzero cases before and after the move.
|
||||
- **Rollback:** Per-landing revert. No published state touched.
|
||||
|
||||
### 0.10.6: [Track B only; skipped if Option A] X1: fslock into crates/runtime.
|
||||
@@ -234,7 +235,7 @@ Record at every release, on one machine, at a named source commit with a dirty-f
|
||||
- **0.10.2**: 0.10.2: ship candidate with no structural move; record per-crate tarball table and ratatui 0.1.2 receipt. No move in the candidate. Add real per-crate tarball sizes to the release notes (baseline for later releases) and the codewhale-ratatui 0.1.2 publication receipt (out of band, not in crates.sh). Gate: publish-crates.sh dry-run, validate-crate-publish-order.py, hosted CI green on the SHA.
|
||||
- **0.10.2 (founder, 2026-10-12)**: X2 finishes RS-13 (#6940) through the shared command_catalog leaf; every direct consumer is migrated. Tests capture a catalog instead of mutating a global authority. Ratchet production 11 -> 0, tests 32 -> 30; RS-11 lowers the remaining test edges through the existing runtime test-support feature. Linear remains the claim owner.
|
||||
- **0.10.2 RS-11 environment helpers**: supersedes the former 0.10.4 test-support crate proposal. The UI-free helpers and process environment barrier move into codewhale-runtime's existing test-support feature; the TUI shim retains its resolver fences and App fixtures. One barrier serves both TUI tests and the directly included shell harness. No new crate or workspace member.
|
||||
- **0.10.5**: 0.10.5 Test-edge rehoming: test 32 -> 0, late 1 -> 0, uilib 1 -> 0. Rehome tests naming crate::tui, commands or lib.rs (tools|tui 12, client|tui 3, session_manager|tui 3, config|tui 2, core|tui 2, prompts|tui 1), plus test_cases_04.rs:1197 (exec_agent) and roster_routes.rs:798 (crossterm). Test-only; equal pass counts before and after per directory. Prerequisite for any new runtime crate that must not depend on tui or crossterm.
|
||||
- **0.10.5**: resolve the remaining late engine/exec-agent test edge (late 1 -> 0) and record the A/B decision. X2/RS-11 already leave production, test and UI-library references at 0 in 0.10.2. Existing nonzero before/after lifecycle checks remain required.
|
||||
- **0.10.6**: 0.10.6 X1 [Track B]: fslock into crates/runtime. Move try_lock_file_exclusive plus RuntimeProcessOwnerLock::try_lock_exclusive/is_contention (runtime_threads.rs:6592, 6855) to crates/runtime. Seam-introducing, not pure rename. Blocked until runtime_threads.rs and its tests are committed. Visibility review in the commit message; revert immediately on any lock regression.
|
||||
- **0.10.7**: 0.10.7 X7 [Track B]: tool rules into execpolicy. Reconcile the moved set (design table: tool_denied, tool_matches_any_rule, requires_raw_shell, policy_tool_aliases; AutoReviewPolicy lives in core/authority/auto_review) then move to crates/execpolicy with re-exports. Approval trust boundary: no matching or visibility change. Blocked by dirty core/authority.rs.
|
||||
- **0.10.8**: 0.10.8 X4 [Track B]: schema_sanitize/schema_canonicalize production half into codewhale-models. Move schema_sanitize.rs lines 1-469 and schema_canonicalize.rs to codewhale-models (owns Tool). Tests stay in tui. No new package name. Assert byte-identical sanitized output on existing fixtures (KV-cache prefix).
|
||||
|
||||
@@ -342,8 +342,12 @@ Workflow panel assertions to its existing history test and removes duplicated
|
||||
coordination pager checks, leaving 18 test references. The history split moves
|
||||
persistence/render tests to history and uses the runtime MCP decoder in the
|
||||
engine, leaving 13 test references. Moving the two unchanged App fixtures to
|
||||
their UI owner and migrating all114 consumers leaves6 test references.
|
||||
The current baseline is the authority.
|
||||
their UI owner and migrating all 114 consumers leaves 6 test references.
|
||||
Moving redaction decisions onto Config, workspace trust into config, and the
|
||||
unchanged constitution checkpoint into prompts removes the final 6. RS-11
|
||||
ends at production 0, test 0, late 1, UI-library 0 and doc 0. The late
|
||||
engine/exec-agent test reference belongs to a later slice. The current baseline
|
||||
is the authority.
|
||||
`crates/runtime` must never depend on `tui`, `cli`, ratatui, crossterm,
|
||||
`ansi-to-tui` or a terminal component kit; the runtime reaches the terminal
|
||||
only through `host_terminal`.
|
||||
|
||||
@@ -2,18 +2,14 @@
|
||||
"_comment": "Runtime -> UI boundary ratchet (docs/design/TUI_DECONSTRUCTION.md, runtime split). Counts may only go down. Regenerate with python3 scripts/split/module_graph.py --update after removing references; never raise a count by hand.",
|
||||
"totals": {
|
||||
"prod": 0,
|
||||
"test": 6,
|
||||
"test": 0,
|
||||
"late": 1,
|
||||
"uilib": 0,
|
||||
"doc": 0
|
||||
},
|
||||
"counts": {
|
||||
"prod": {},
|
||||
"test": {
|
||||
"client|tui": 3,
|
||||
"config|tui": 2,
|
||||
"prompts|tui": 1
|
||||
},
|
||||
"test": {},
|
||||
"late": {
|
||||
"core|exec_agent": 1
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user