import sys, os
sys.path.insert(0, os.path.dirname(__file__))
from crlfpatch import patch

P = 'crates/spt/tests/serving_in_brain_e2e.rs'
raw = open(P, 'rb').read().decode('utf-8').replace('\r\n', '\n')
new_test = r'''

/// THE FIELD TRANSITION ARM (doyle gate, 2026-09-25). The W9 set is BRAIN_ONLY,
/// so every field node first runs a W9 brain on a PRE-W9 broker that still holds
/// the docs port. The brain's bind then fails, and that failure must stay a
/// logged degradation: if it ever leaked into the ready stretch, every node's
/// brain-only apply would fail its readiness trial and roll back, and nothing
/// in the set would land.
///
/// The test's own `TcpListener` stands in for the old broker. Asserted, each
/// READ rather than inferred: (1) the brain stamps `brain.ready` for its
/// generation — the exact stamp the supervisor's trial gate reads — both at
/// start and after a refresh, and does not crash-loop afterwards; (2)
/// `DOCS_SERVER_BIND_FAIL` names the port in the log; (3) DocsStatus reports
/// NO port (`DOCS_LISTENER_UNAVAILABLE`), never a guess; (4) the OS still says
/// the foreign listener owns the port.
#[test]
fn a_brain_whose_docs_port_is_held_still_readies_and_reports_no_port() {
    let home = tempfile::tempdir().unwrap();
    std::env::set_var("SPT_HOME", home.path());
    let identity_dir = home.path().join("identity");
    std::fs::create_dir_all(&identity_dir).unwrap();
    std::fs::write(identity_dir.join("node.key"), "not-a-valid-seed").unwrap();
    let docs = home.path().join("docs");
    std::fs::create_dir_all(&docs).unwrap();
    std::fs::write(docs.join("index.html"), format!("<html>{MARKER}</html>")).unwrap();
    let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));

    // The stand-in for the pre-W9 broker's listener, held for the whole test.
    let foreign = std::net::TcpListener::bind(("127.0.0.1", 0)).expect("hold a docs port");
    let port = foreign.local_addr().expect("local addr").port();

    let daemon_log = home.path().join("daemon.stderr.log");
    let log_file = std::fs::File::create(&daemon_log).expect("create daemon stderr log");
    let mut broker: Child = Command::new(&spt_bin)
        .no_window()
        .args(["daemon", "run"])
        .env("SPT_HOME", home.path())
        .env(spt_daemon::docshost::DOCS_PORT_ENV, port.to_string())
        .env_remove(spt_daemon::docshost::TEST_EPHEMERAL_ADVISORY_PORTS_ENV)
        .stdout(Stdio::null())
        .stderr(Stdio::from(log_file))
        .spawn()
        .expect("spawn spt daemon run (broker process)");
    let broker_pid = broker.id();
    let ready_path = home.path().join("brain.ready");

    let before = wait_ready_not(&ready_path, None, Duration::from_secs(60));
    let refresh = {
        let mut cmd = Command::new(&spt_bin);
        cmd.no_window().args(["daemon", "refresh"]).env("SPT_HOME", home.path());
        common::output_bounded(cmd, Duration::from_secs(30))
    };
    let cycled = before.and_then(|(pid, _)| wait_ready_not(&ready_path, Some(pid), Duration::from_secs(60)));
    // Steady state: a brain that readied and then died would be replaced by a
    // new generation; hold long enough for the supervisor's crash backoff
    // (2 s floor) to show one.
    std::thread::sleep(Duration::from_secs(5));
    let steady = read_ready(&ready_path);
    let steady_alive = steady.is_some_and(|(pid, _)| spt_store::proc::is_process_alive(pid));

    let docs_url = {
        let mut cmd = Command::new(&spt_bin);
        cmd.no_window().args(["docs", "url"]).env("SPT_HOME", home.path());
        common::output_bounded(cmd, Duration::from_secs(20))
    };
    let docs_url_stdout = String::from_utf8_lossy(&docs_url.stdout).into_owned();
    let docs_url_stderr = String::from_utf8_lossy(&docs_url.stderr).into_owned();
    let owner = listener_owner(port);
    let log = format!(
        "{}\n{}",
        std::fs::read_to_string(&daemon_log).unwrap_or_default(),
        std::fs::read_to_string(spt_daemon::stderrlog::sink_path(home.path())).unwrap_or_default()
    );
    let bind_fail = format!("DOCS_SERVER_BIND_FAIL: port {port}:");
    let bind_fails = log.lines().filter(|line| line.contains(&bind_fail)).count();
    let daemon_panel = common::daemon_stderr_panel(&daemon_log);
    eprintln!(
        "=== W9 field-transition: os={} port={port} broker_pid={broker_pid} before={before:?} \
         cycled={cycled:?} steady={steady:?} steady_alive={steady_alive} owner={owner:?} \
         test_pid={} bind_fails={bind_fails} docs_url_exit={:?} ===\n\
         --- docs url stdout ---\n{docs_url_stdout}\n--- docs url stderr ---\n{docs_url_stderr}\n{daemon_panel}",
        std::env::consts::OS,
        std::process::id(),
        docs_url.status.code(),
    );

    let _ = {
        let mut cmd = Command::new(&spt_bin);
        cmd.no_window().args(["daemon", "stop", "--force"]).env("SPT_HOME", home.path());
        common::output_bounded(cmd, Duration::from_secs(20))
    };
    for pid in [before, cycled, steady].into_iter().flatten().map(|(pid, _)| pid) {
        kill_pid(pid);
    }
    let _ = broker.kill();
    let _ = broker.wait();

    // (1) the brain readies despite the held port — cold AND after refresh.
    let (brain_before, gen_before) = before.unwrap_or_else(|| {
        panic!("FIELD TRANSITION (1): with the docs port held, the cold brain never stamped brain.ready.\n{daemon_panel}")
    });
    assert!(
        refresh.status.success(),
        "`spt daemon refresh` must exit 0.\n{daemon_panel}"
    );
    let (brain_after, gen_after) = cycled.unwrap_or_else(|| {
        panic!(
            "FIELD TRANSITION (1): after refresh no new brain stamped brain.ready (pid stayed \
             {brain_before}) — a held docs port must never fail readiness.\n{daemon_panel}"
        )
    });
    assert!(gen_after > gen_before, "the generation must advance across the refresh");
    assert_eq!(
        steady,
        Some((brain_after, gen_after)),
        "FIELD TRANSITION (1): the readied brain must STAY up — no crash-loop respawn.\n{daemon_panel}"
    );
    assert!(steady_alive, "the readied brain pid {brain_after} must be alive.\n{daemon_panel}");
    // (2) the failure is loud, once per generation.
    assert!(
        bind_fails >= 2,
        "FIELD TRANSITION (2): each generation must log `{bind_fail}` (saw {bind_fails}).\n{daemon_panel}"
    );
    // (3) DocsStatus reports no port rather than a guess.
    assert!(
        !docs_url.status.success()
            && docs_url_stderr.contains("DOCS_LISTENER_UNAVAILABLE")
            && !docs_url_stdout.contains(&format!("localhost:{port}")),
        "FIELD TRANSITION (3): with no brain-owned listener DocsStatus must report NO port \
         (stdout {docs_url_stdout:?}, stderr {docs_url_stderr:?}).\n{daemon_panel}"
    );
    // (4) the foreign listener (the old broker's stand-in) still owns the port.
    assert_eq!(
        owner,
        Some(std::process::id()),
        "FIELD TRANSITION (4): the held port must still belong to its holder.\n{daemon_panel}"
    );
    drop(foreign);
}
'''
assert raw.endswith('}\n'), 'file tail'
raw = raw + new_test.lstrip('\n').join(['\n', '']) if False else raw + new_test
# header tag note: the int tags at the top cover the binary; nothing else to move.
crlf = b'\r\n' in open(P, 'rb').read()
out = raw.replace('\n', '\r\n') if crlf else raw
open(P, 'wb').write(out.encode('utf-8'))
print('appended field arm, crlf=', crlf)
