opencrabs 0.3.38

The autonomous, self-improving AI agent. Single Rust binary. Every channel. Install with: cargo install opencrabs
Documentation
//! Pins the section-resolver invariant for `save_provider_settings` in
//! `src/tui/app/dialogs.rs`.
//!
//! Regression: 2026-06-04. User opens `/models`, cursor sits on the
//! currently-active custom provider (`dialagram`). They navigate to the
//! "+ Add new custom" row. `reload_model_selector_custom_fields` correctly
//! clears `self.ps.custom_name = ""`. They start typing the new entry's
//! `base_url` field. Every keystroke fires `save_provider_settings` with
//! `close_dialog=false` (per-field merge save).
//!
//! Pre-fix, the section resolver's `""` arm rescued empty `custom_name`
//! with `config.providers.active_custom()` — which still pointed at
//! `dialagram`. The keystroke-driven save then did
//! `Config::write_key("providers.custom.dialagram", "base_url", <new-url>)`,
//! silently corrupting dialagram's section. The model field survived
//! because `write_key` is a per-key TOML merge.
//!
//! Concrete corruption observed in user's config.toml:
//! ```toml
//! [providers.custom.dialagram]
//! base_url = "https://api-inference.modelscope.ai/v1"   # overwritten
//! default_model = "qwen-3.7-max-thinking"               # untouched
//! ```
//!
//! The fix: never fall back to `active_custom()` in the section resolver.
//! Empty `custom_name` means "user is mid-draft, no name typed yet" — the
//! only safe action is to skip the write.

const DIALOGS_SRC_RAW: &str = include_str!("../tui/app/dialogs.rs");

/// Strip `//` line comments so source-level invariant scans don't false-
/// match against the regression doc-comments that describe the bug they're
/// guarding against. Same approach as the
/// `approval_requests_are_not_routed_through_session_state_mut` sentinel
/// in `background_session_test.rs`.
fn dialogs_src_code() -> String {
    DIALOGS_SRC_RAW
        .lines()
        .map(|line| {
            if let Some(idx) = line.find("//") {
                let before = &line[..idx];
                let quote_count = before.matches('"').count();
                if quote_count % 2 == 0 {
                    return before.trim_end().to_string();
                }
            }
            line.to_string()
        })
        .collect::<Vec<_>>()
        .join("\n")
}

#[test]
fn pre_fix_active_custom_fallback_pattern_is_absent() {
    // Exact code shape of the pre-fix bug: an `else-if-let` arm pulling
    // `active_custom()` and feeding it into the section name. This
    // specific pattern is what corrupted dialagram on 2026-06-04. Banning
    // the exact shape is brittle to formatting but a fair regression
    // sentinel — if a future refactor needs `active_custom()` for an
    // unrelated read it won't trip this, but a re-introduction of the
    // fallback would land textually-identical or near-identical code.
    let src = dialogs_src_code();
    let forbidden_signatures = [
        "else if let Some((name, _)) = config.providers.active_custom()",
        "else if let Some((name, _)) = self.providers.active_custom()",
        ".active_custom() {\n                    name.to_string()",
    ];
    for sig in forbidden_signatures {
        assert!(
            !src.contains(sig),
            "save_provider_settings: forbidden fallback pattern re-introduced.\n\
             Signature: {sig:?}\n\
             This is the 2026-06-04 dialagram-corruption pattern. See \
             custom_provider_section_resolver_test doc for the full repro and the \
             reasoning behind banning the fallback."
        );
    }
}

#[test]
fn empty_custom_name_guard_precedes_section_format_in_resolver_arm() {
    // The resolver's "" arm has exactly one assignment of the form
    // `custom_section = format!("providers.custom.{}", self.ps.custom_name);`
    // — the line that builds the per-key write target. The empty-name
    // guard MUST appear in the same arm, before that assignment, and end
    // with `return Ok(())`. Anchor on the assignment string and walk
    // backwards within a tight window.
    let src = dialogs_src_code();
    let format_marker = "custom_section = format!(\"providers.custom.{}\", self.ps.custom_name)";
    let format_idx = src.find(format_marker).unwrap_or_else(|| {
        panic!(
            "expected resolver's section-format line `{format_marker}` in dialogs.rs — \
             either the format string moved or the resolver was restructured; update \
             this test if intentional"
        )
    });

    // Window of ~600 chars preceding the format assignment, bounded at
    // the previous match-arm divider so we don't scan into unrelated code.
    let window_start = format_idx.saturating_sub(600);
    let preceding = &src[window_start..format_idx];

    assert!(
        preceding.contains("self.ps.custom_name.is_empty()"),
        "empty-custom_name guard must precede the section-format assignment in the \
         resolver \"\" arm. Without the guard, an empty custom_name falls through to \
         a section format with `self.ps.custom_name = \"\"`, producing the literal \
         section name `providers.custom.` (TOML write to an empty-named subkey) — \
         arguably less harmful than the active_custom() fallback but still corruption."
    );
    assert!(
        preceding.contains("return Ok(())"),
        "empty-custom_name guard must early-return — without the return, the format \
         assignment still runs against `self.ps.custom_name = \"\"`."
    );
}