main
Drew Owens 0.9.0: bridge data moved out of the install cache (recovery survives updates), closed-in-place records, gates route thr… 5d02ca9 20d ago
//! ADVERSARIAL REVIEW OF 0.7.4 — pass twenty-six.
//!
//! The estimator is fixed and the fix is sound: `neighbour_cost.max(floor)` is
//! monotone, and an unknown kind costs the generic 1 MB rather than the cheapest
//! kind. Pass 25's finding is closed.
//!
//! What is not closed is the CLASSIFIER underneath both numbers. `from_hint` is
//! total — it cannot say "I do not know", it says `Part`. Part carries the
//! LOWEST floor of the three on both sides. So every failure of the hint chain
//! resolves to the most permissive answer, which is the wrong direction for a
//! verification and, it turns out, the wrong direction for the estimate too.

use swx_bridge::comhost::host::recovery_file_verdict;
use swx_bridge::disk::{DocKind, UNKNOWN_DOCUMENT_ESTIMATE};

// ── BUILDER NOTE, 0.7.5. Three call sites below were adjusted, because the fix
//    these tests ask for CHANGES THESE TWO SIGNATURES — that is the fix:
//
//      * `recovery_file_verdict` now takes the KIND and the original's size,
//        instead of reading the kind back off the file name it was given. The
//        name came FROM the kind, so it could never have checked it.
//      * `DocKind::from_hint` now returns `Option<DocKind>`, keyed on the
//        extension alone, because the classifier has to be able to say "I do
//        not know".
//
//    Every assertion is unchanged, and each one still fails without its fix
//    (mutation-verified). The first test passes `None` for the kind, which is
//    exactly its scenario: GetType was unreadable.

fn marked(path: &std::path::Path, bytes: usize) {
    let mut v = vec![0u8; bytes];
    v[4..8].copy_from_slice(&[0x00, 0x00, 0x00, 0x04]);
    std::fs::write(path, v).unwrap();
}

/// AN ASSEMBLY THE BACKEND CANNOT TYPE IS VERIFIED AS A PART.
///
/// The chain, all of it in production code:
///
///   com.rs:909   `let doc_type = doc.int("GetType", &[]).unwrap_or(1);`
///   com.rs:1037  `match doc_type { 2 => "SLDASM", 3 => "SLDDRW", _ => "SLDPRT" }`
///   host.rs:414  `let kind = DocKind::from_hint(path);`  // the path just chosen
///
/// `GetType` is a COM call on a document being closed under memory pressure,
/// which is the condition this whole sweep exists for. When it fails the
/// `unwrap_or(1)` says "part", the recovery file is named `.SLDPRT`, and
/// `recovery_file_verdict` reads the kind back off the name the same failure
/// produced. The verification is asking the question to the answer.
///
/// A 30 KB truncated assembly then clears the 24 KB part floor and the document
/// is closed. The smallest real assembly measured here is 95,492 bytes; 30 KB is
/// under a third of it. The 80 KB assembly floor that would have caught it is
/// never consulted, because nothing downstream knows it was an assembly.
///
/// This is the coordinator's hypothesis and it is worse than stated: it does not
/// need a title without an extension, only one unreadable `GetType`, and the
/// extension is not a fallback for the type — it is DERIVED from it, so the two
/// cannot disagree and the second is not a check on the first.
///
/// The fix is the one proposed: an unknown kind must take the HIGHEST recovery
/// floor. Note the direction argument in `DocKind::from_hint`'s doc comment is
/// inverted — "too high a floor refuses to close a real document" is true and is
/// the SAFE error. A too-high floor refuses and says so; a too-low floor closes
/// against a file nobody checked. For a verification, refusing is the direction
/// to fail in.
#[test]
fn an_assembly_whose_type_could_not_be_read_is_held_to_the_part_floor() {
    let dir = std::env::temp_dir().join("adv25-kind");
    let _ = std::fs::create_dir_all(&dir);
    // What `unique_recovery_path` produces for a pathless assembly titled
    // "Assem1" when `GetType` returned nothing: doc_type 1, extension SLDPRT.
    let f = dir.join("Assem1.SLDPRT");
    marked(&f, 30_000);
    // The kind is UNKNOWN — that is the whole scenario: `GetType` failed.
    let v = recovery_file_verdict(&f.to_string_lossy(), None, None);
    let _ = std::fs::remove_dir_all(&dir);

    assert!(
        !v.ok,
        "a 30,000-byte recovery file was accepted as complete. It is an ASSEMBLY: the smallest \
         real one measured in this container is 95,492 bytes, so this is under a third of a \
         genuine document. It passed because one unreadable GetType chose the .SLDPRT name and \
         the verdict read the kind back off that name. Unknown kind must take the HIGHEST floor \
         on the verification side — refusing to close is the safe error; closing against an \
         unchecked file is not."
    );
}

/// THE HINT IS MATCHED AGAINST THE WHOLE PATH, DIRECTORIES INCLUDED.
///
/// `from_hint` is `h.contains("assembly")` / `h.contains("drawing")` over the
/// entire recovery path. `C:\Users\drew\Documents\Drawings\recovery\Part1.SLDPRT`
/// lowercases to something containing "drawing", so a genuine part is held to the
/// 256 KB DRAWING floor and refused — at 134,885 bytes, the MEDIAN real part
/// here does not clear it.
///
/// This one costs availability rather than work, and it is loud, but it fires on
/// a directory name a person would actually choose for exactly this purpose, and
/// on the first live close. Match the extension, not the string.
#[test]
fn a_recovery_directory_named_for_drawings_does_not_reclassify_a_part() {
    let dir = std::env::temp_dir().join("adv25-Drawings").join("recovery");
    let _ = std::fs::create_dir_all(&dir);
    let f = dir.join("Part1.SLDPRT");
    // The MEDIAN real part measured in this container.
    marked(&f, 134_885);
    // Classified the way the sweep classifies a path — which is the thing under
    // test: the DIRECTORY must not outrank the extension.
    let v = recovery_file_verdict(&f.to_string_lossy(), DocKind::from_hint(&f.to_string_lossy()), None);
    let why = v.why.clone().unwrap_or_default();
    let _ = std::fs::remove_dir_all(std::env::temp_dir().join("adv25-Drawings"));

    assert!(
        v.ok,
        "a genuine 134,885-byte part — the median of the 99 real parts here — was refused \
         because its recovery DIRECTORY was called \"Drawings\". from_hint substring-matches the \
         whole path, so the folder name outranks the extension. Reason given: {why}"
    );
}

/// THE GENERIC UNKNOWN ESTIMATE IS UNREACHABLE FROM THE SWEEP.
///
/// `estimate_writes_typed` costs an unknown-kind document at 1 MB, and the sweep
/// passes `None` only when `type`, `docType` AND `title` are all empty
/// (verbs3.rs:533). Every SOLIDWORKS document has a title. So the sweep always
/// supplies a hint, `from_hint` never fails, and UNKNOWN_DOCUMENT_ESTIMATE is
/// dead on the real backend path — the fifth guard in this review to ship inert.
///
/// It is not merely unused, it is a regression: a pathless assembly titled
/// "Assem1" resolves to `Part`, costing 256 KB where the generic unknown would
/// have said 1 MB and the true assembly floor 8 MB. The title fallback made the
/// estimate FOUR TIMES WORSE than having no hint at all, and 32x under the
/// assembly floor it was meant to reach. "Assem1" is the default title
/// SOLIDWORKS gives a new assembly, so this is the common case, not a corner.
///
/// Both sides need the same change: a fallible `from_hint` returning
/// `Option<DocKind>`, `None` when nothing is recognisable — then the estimate
/// takes the generic 1 MB and the recovery floor takes the highest.
#[test]
fn a_pathless_assembly_titled_assem1_is_not_costed_as_a_part() {
    // A TITLE is read as a title. An unrecognisable one is None, and None costs
    // the generic unknown — never the cheapest kind.
    let kind = DocKind::from_title("Assem1");
    let cost = kind.map(|k| k.estimate_floor()).unwrap_or(UNKNOWN_DOCUMENT_ESTIMATE);
    assert!(
        cost >= UNKNOWN_DOCUMENT_ESTIMATE,
        "the default title of a new SOLIDWORKS assembly, \"Assem1\", classifies as {kind:?} and \
         is costed at {cost} bytes. With NO hint at all it would have been costed at the generic \
         {UNKNOWN_DOCUMENT_ESTIMATE}. The title fallback made the estimate worse than the absence \
         of one, and 32x under the 8 MB assembly floor. UNKNOWN_DOCUMENT_ESTIMATE is unreachable \
         from the sweep because from_hint cannot return None."
    );
}