fix(TASK-046): keep inspection leniency off mutating and writing flags
The look-don't-touch bypass was keyed on load_with_interpolation's pre-existing bool, which mutating early paths (--authenticate, --auth-mcp, mcp manage, vault flags) already passed as true meaning only 'skip vault interpolation' — so they silently lost their first-run config creation. Split the parameter: skip_interpolation keeps the historical meaning, a distinct inspection flag alone gates the missing-config default/wizard bypass. inspection_only is now computed from an explicit read-only whitelist: --sync-models is excluded (it downloads and writes models-override.yaml) and returns to the bootstrap/strict path, keeping only its historical interpolation-skip and vault leniency. mcp/vault inspection sub-conditions are hoisted and shared so mutating flag combos never inherit the bypass. Coverage: sandbox vault contract tests, a sync-models bootstrap-path pin, a --sync-models scenario in usage-probe-inspection.sh, and a PTY wizard regression probe (scripts/usage-probe-wizard.sh).
This commit is contained in:
6 files changed
+269
-32
No files matched your search
+30
-20
@@ -113,6 +113,19 @@ async fn main() -> Result<()> {
|
||||
|| cli.list_macros
|
||||
|| cli.list_skills
|
||||
|| cli.list_sessions;
|
||||
// Read-only whitelist: --sync-models is excluded because it downloads
|
||||
// and writes models-override.yaml; combined with a readout flag the run
|
||||
// still writes (run() handles --sync-models first), so it disqualifies
|
||||
// the whole invocation.
|
||||
let readout_flag = (cli.info
|
||||
|| cli.list_models
|
||||
|| cli.list_roles
|
||||
|| cli.list_agents
|
||||
|| cli.list_rags
|
||||
|| cli.list_macros
|
||||
|| cli.list_skills
|
||||
|| cli.list_sessions)
|
||||
&& !cli.sync_models;
|
||||
let vault_flags = cli.add_secret.is_some()
|
||||
|| cli.get_secret.is_some()
|
||||
|| cli.update_secret.is_some()
|
||||
@@ -144,9 +157,15 @@ async fn main() -> Result<()> {
|
||||
// bootstrap so a pristine config dir stays empty. On such a dir the
|
||||
// list flags list nothing because nothing is installed, by design.
|
||||
// Agent/role runs still bootstrap: they load builtin tool definitions.
|
||||
let inspection_only = (info_flag || cli.list_secrets || cli.mcp_list)
|
||||
&& cli.agent.is_none()
|
||||
&& cli.role.is_none();
|
||||
let mcp_inspect =
|
||||
cli.mcp_list && cli.mcp_get.is_none() && cli.mcp_remove.is_none() && cli.mcp_add.is_none();
|
||||
let vault_inspect = cli.list_secrets
|
||||
&& cli.add_secret.is_none()
|
||||
&& cli.get_secret.is_none()
|
||||
&& cli.update_secret.is_none()
|
||||
&& cli.delete_secret.is_none();
|
||||
let inspection_only =
|
||||
(readout_flag || vault_inspect || mcp_inspect) && cli.agent.is_none() && cli.role.is_none();
|
||||
if !inspection_only {
|
||||
install_builtins()?;
|
||||
}
|
||||
@@ -173,7 +192,7 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
if let Some(client_arg) = &cli.authenticate {
|
||||
let cfg = Config::load_with_interpolation(true).await?;
|
||||
let cfg = Config::load_with_interpolation(true, false).await?;
|
||||
let app_config = AppConfig::from_config(cfg)?;
|
||||
let (client_name, provider) =
|
||||
resolve_oauth_client(client_arg.as_deref(), &app_config.clients)?;
|
||||
@@ -182,7 +201,7 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
if let Some(server_name) = &cli.auth_mcp {
|
||||
let cfg = Config::load_with_interpolation(true).await?;
|
||||
let cfg = Config::load_with_interpolation(true, false).await?;
|
||||
let app_config = AppConfig::from_config(cfg)?;
|
||||
let vault = Vault::init(&app_config)?;
|
||||
let mcp_path = paths::mcp_config_file();
|
||||
@@ -234,12 +253,8 @@ async fn main() -> Result<()> {
|
||||
let mcp_action =
|
||||
cli.mcp_list || cli.mcp_get.is_some() || cli.mcp_remove.is_some() || cli.mcp_add.is_some();
|
||||
if mcp_action {
|
||||
let cfg = Config::load_with_interpolation(true).await?;
|
||||
let app_config = if cli.mcp_list
|
||||
&& cli.mcp_get.is_none()
|
||||
&& cli.mcp_remove.is_none()
|
||||
&& cli.mcp_add.is_none()
|
||||
{
|
||||
let cfg = Config::load_with_interpolation(true, mcp_inspect).await?;
|
||||
let app_config = if mcp_inspect {
|
||||
AppConfig::from_config_lenient(cfg)?
|
||||
} else {
|
||||
AppConfig::from_config(cfg)?
|
||||
@@ -252,13 +267,8 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
if vault_flags {
|
||||
let cfg = Config::load_with_interpolation(true).await?;
|
||||
let app_config = if cli.list_secrets
|
||||
&& cli.add_secret.is_none()
|
||||
&& cli.get_secret.is_none()
|
||||
&& cli.update_secret.is_none()
|
||||
&& cli.delete_secret.is_none()
|
||||
{
|
||||
let cfg = Config::load_with_interpolation(true, vault_inspect).await?;
|
||||
let app_config = if vault_inspect {
|
||||
AppConfig::from_config_lenient(cfg)?
|
||||
} else {
|
||||
AppConfig::from_config(cfg)?
|
||||
@@ -269,8 +279,8 @@ async fn main() -> Result<()> {
|
||||
|
||||
let abort_signal = create_abort_signal();
|
||||
let start_mcp_servers = cli.agent.is_none() && cli.role.is_none();
|
||||
let cfg = Config::load_with_interpolation(info_flag).await?;
|
||||
let mut app_config = if info_flag {
|
||||
let cfg = Config::load_with_interpolation(info_flag, readout_flag).await?;
|
||||
let mut app_config = if readout_flag {
|
||||
AppConfig::from_config_lenient(cfg)?
|
||||
} else {
|
||||
AppConfig::from_config(cfg)?
|
||||
|
||||
Reference in new issue
Block a user