def load(p):
    raw = open(p, encoding='utf-8', newline='').read()
    return raw.replace('\r\n', '\n'), '\r\n' in raw

def save(p, s, crlf):
    open(p, 'w', encoding='utf-8', newline='').write(s.replace('\n', '\r\n') if crlf else s)

def once(s, old, new):
    assert s.count(old) == 1, (old[:80], s.count(old))
    return s.replace(old, new)

p = 'crates/spt/src/adapterlist.rs'
s, c = load(p)
s = once(s, '''    out.sort_by(|a, b| a.name.cmp(&b.name).then_with(|| a.version.cmp(&b.version)));
    out
}''', '''    out.sort_by(|a, b| a.name.cmp(&b.name).then_with(|| version_order(&a.version, &b.version)));
    out
}

/// Versions in NUMERIC order (0.9.0 before 0.10.0), the numeric model the
/// tree already uses for floors and freshness: split on `.`, each component a
/// number (missing or non-numeric reads 0), first difference decides. Two
/// versions that tie numerically fall back to their text so the order stays
/// total and stable.
// [impl->REQ-ADAPTER-LIST-SURFACE]
fn version_order(a: &str, b: &str) -> std::cmp::Ordering {
    let parts = |s: &str| -> Vec<u64> { s.split('.').map(|c| c.trim().parse::<u64>().unwrap_or(0)).collect() };
    let (x, y) = (parts(a), parts(b));
    (0..x.len().max(y.len()))
        .map(|i| x.get(i).copied().unwrap_or(0).cmp(&y.get(i).copied().unwrap_or(0)))
        .find(|o| o.is_ne())
        .unwrap_or_else(|| a.cmp(b))
}''')
anchor = '''    // [unit->REQ-ADAPTER-LIST-SURFACE] an adapter this node has is not'''
s = once(s, anchor, '''    // [unit->REQ-ADAPTER-LIST-SURFACE] versions of one adapter order
    // NUMERICALLY: 0.9.0 before 0.10.0, which a string sort reverses.
    #[test]
    fn available_versions_sort_numerically_not_as_text() {
        let rows = vec![
            ("p".to_string(), "x".to_string(), "0.10.0".to_string(), AdapterGroup::Harness),
            ("p".to_string(), "x".to_string(), "0.9.0".to_string(), AdapterGroup::Harness),
            ("p".to_string(), "x".to_string(), "0.9.1".to_string(), AdapterGroup::Harness),
        ];
        let order: Vec<String> = available(&[], &rows).into_iter().map(|a| a.version).collect();
        assert_eq!(order, vec!["0.9.0", "0.9.1", "0.10.0"]);
        assert_eq!(version_order("1.0", "1.0.0"), std::cmp::Ordering::Less, "a numeric tie falls back to text");
    }

''' + anchor)
save(p, s, c)
print('ok')
