//! Extracted from `write.rs` (large-module split). See parent module for context.

use super::*;
use agent_doc_diff as diff;
use agent_doc_diff::semantic::semantic_diff_summary;
use agent_doc_frontmatter::frontmatter;
use agent_doc_preflight_io::{
    PendingMaintenanceReport, PreflightResponseContract, QueueState, check_linked_docs,
    checkpoint_baseline_content, enforce_no_dropped_backlog, enforce_no_shadow_open_backlog,
    explicit_backlog_target_requirements, inspect_queue_state, read_and_truncate_claims,
    read_claims, resolve_pipeline_state, run_gate_verify, run_pending_maintenance,
    run_queue_maintenance,
    sweep::{current_sweep_owner, log_and_skip_foreign_owned_sweep_if_needed, sweep_owner_for_doc},
};
use agent_doc_preflight_runtime_io::{
    relocate_out_of_exchange_prompt_before_diff,
    remove_duplicate_answered_exchange_prompt_tail_for_preflight,
    remove_post_exchange_duplicate_prompt_comments_for_preflight,
    resolve_current_preflight_document,
};
use agent_doc_prompt_contract::{push_unique_prompt_bearing_changes, push_unique_strings};
use agent_doc_queue::queue_convergence::{
    queue_body_diff_is_non_selected_future_state, realign_baseline_to_converged_queue,
};
use agent_doc_run_context_io::AgentDocContextExt;
use agent_doc_state_backbone::DocumentScope;
use agent_doc_state_backbone::preflight::{
    PreflightEffect, PreflightEffectState, PreflightQueueProjection, PreflightReadFacts,
    PreflightReadState, PreflightRelatedDocumentProjection, PreflightTierProjection,
};
use agent_doc_turn::drain_stall::{StallFacts, StallVerdict, classify_stall};
use agent_doc_turn::op_log::OpsLogEvent;
use agent_doc_workflow::session_cycle::{compute_user_intent_prompt_changes, derive_turn_scope};
use anyhow::Context;
use std::io::Write;

/// Injects the lazily reactive CRDT model as the diff's `current` source
/// (#preflight-lazily-diff-feed). Lives here (not in `agent-doc-diff-io`)
/// because the diff crate is a leaf beneath the relay crate and cannot depend
/// on the reactive model without a dependency cycle.
struct ReactiveLiveCurrentSource;

impl agent_doc_diff_io::LiveCurrentSource for ReactiveLiveCurrentSource {
    fn live_current(&self, doc: &Path, disk: &str) -> Option<String> {
        // `durable_buffer_state` returns `Some` only when a live editor buffer
        // (CRDT relay / controller model) has diverged from disk. That read is
        // already quiescence-gated by the commit barrier (#preflight-lazily-diff-feed
        // Phase 3): the relay surfaces `CurrentText::Current` only once the
        // canonical replica *covers every live editor's ops*, so the returned
        // text is prompt-complete by construction — not a mid-typing partial.
        // (`delivery_converged` is a downstream-ACK signal, a different
        // direction, and is intentionally NOT gated on: it would reject complete
        // canonical text over a pending fan-out ACK.) Otherwise `None`, so the
        // diff keeps its disk-sourced content.
        agent_doc_document_realtime_io::durable_buffer_state(doc, disk).map(|state| state.content)
    }
}

/// Options controlling a `preflight` invocation.
#[derive(Debug, Clone, Copy, Default)]
pub struct PreflightOptions {
    /// Pure inspection probe (`#preflight-probe-side-effect-free`): compute and
    /// emit the same JSON, but do NOT open a `preflight_started` cycle. A
    /// diagnostic preflight is not dispatch/response-bound, so opening a cycle
    /// only leaves open state that later wedges `session-check`.
    pub probe: bool,
}

pub(crate) fn response_contract_for_content(content: &str) -> Option<PreflightResponseContract> {
    frontmatter::parse(content)
        .ok()
        .filter(|(fm, _)| fm.resolve_mode().is_template())
        .map(|_| PreflightResponseContract::strict_template())
}

#[derive(Debug, Clone, PartialEq, Eq)]
enum ActivePromptMarkerProjection {
    Unchanged,
    Applied { delivery_converged: bool },
    SkippedDetached,
    SkippedError(String),
}

/// Publish the cosmetic active-prompt marker without entering the durable
/// serialized-write pipeline. A delayed editor ACK must not turn a transient UI
/// hint into a closeout-blocking document-write intent.
fn project_active_prompt_marker_with(
    authoritative: &str,
    marked: &str,
    project: impl FnOnce(&str, &str) -> Result<Option<bool>>,
) -> ActivePromptMarkerProjection {
    if authoritative == marked {
        return ActivePromptMarkerProjection::Unchanged;
    }
    match project(authoritative, marked) {
        Ok(Some(delivery_converged)) => {
            ActivePromptMarkerProjection::Applied { delivery_converged }
        }
        Ok(None) => ActivePromptMarkerProjection::SkippedDetached,
        Err(err) => ActivePromptMarkerProjection::SkippedError(err.to_string()),
    }
}

fn closeout_cycle_is_open(file: &Path) -> Result<bool> {
    let projection = agent_doc_cycle_state_io::load_closeout_projection(file)?;
    if let Some(state) = agent_doc_cycle_state_io::load_with_closeout_projection(file)? {
        if let Some(projection) = projection.as_ref()
            && !projection.matches_cycle(&state.cycle_id)
            && projection.phase_is_open().unwrap_or(false)
        {
            return Ok(true);
        }
        return Ok(state.is_open());
    }
    Ok(projection
        .and_then(|projection| projection.phase_is_open())
        .unwrap_or(false))
}

/// Run preflight with default (dispatch/response-bound) options.
pub fn run(file: &Path) -> Result<()> {
    run_with_options(file, PreflightOptions::default())
}

fn maybe_record_preflight_terminal_closeout_proof(file: &Path, did_commit: bool) {
    let Ok(agent_doc_session_check_io::SessionCheckStatus::Ok(_)) =
        agent_doc_session_check_io::inspect(
            file,
            &agent_doc_closeout_runtime_io::session_check_effects(),
        )
    else {
        return;
    };
    if let Err(err) = agent_doc_controller_io::project_controller::persist_session_actor_closeout(
        file,
    )
    .and_then(|_| {
        agent_doc_flow_io::closeout::record_terminal_closeout_proof(
            file,
            did_commit,
            &agent_doc_closeout_runtime_io::closeout_effects(),
            agent_doc_flow_io::closeout::CompleteRequiredCloseoutOptions::default(),
        )
    }) {
        eprintln!("[preflight] terminal proof warning: {err}");
    }
}

pub fn run_with_options(file: &Path, options: PreflightOptions) -> Result<()> {
    let stdout = std::io::stdout();
    let mut stdout = stdout.lock();
    run_with_options_to_writer(file, options, &mut stdout)
}

/// Run preflight while directing its single JSON result to `output`.
///
/// Hook adapters use this boundary to wrap the preflight contract in their
/// harness-specific JSON envelope without ever writing a partial or second JSON
/// document to process stdout. The ordinary CLI path above still writes the
/// exact same preflight JSON directly to stdout.
pub fn run_with_options_to_writer(
    file: &Path,
    options: PreflightOptions,
    writer: &mut dyn Write,
) -> Result<()> {
    if !file.exists() {
        anyhow::bail!("file not found: {}", file.display());
    }
    // Schedule a known-stale supervisor before any document resolution or
    // integrity gate can abort this stage. The request is idempotent and is
    // honored only at the supervisor's safe idle boundary.
    let _ = agent_doc_controller_io::project_controller::recycle_stale_supervisor_for_turn_stage(
        file,
        "preflight_entry",
    );

    let rc = agent_doc_run_context_io::cycle_context(file.to_path_buf());
    // #rtwwire (rung 3): classify against the realtime document model. When an
    // editor is active, the CRDT relay is the authority and disk is not read as
    // a substitute; with no editor attached, disk is the fallback replica.
    let mut content = resolve_current_preflight_document(file, "initial")?;
    // A retained response replay may transiently duplicate only response cells
    // and/or protocol boundary markers. Normalize that evidence-backed shape
    // through Lazily before the generic integrity gate; otherwise the gate
    // blocks the recovery that can safely collapse it.
    if let Some(normalized) =
        agent_doc_document_realtime_io::normalize_recoverable_response_replay_duplication_for_file(
            file,
            &content,
            "preflight_response_replay_dedup",
        )?
    {
        let disk = agent_doc_document_realtime_io::resolve_disk_current_document_content(
            file,
            "preflight_response_replay_dedup_trusted_disk",
        )?;
        let exact_doubled_disk_projection = normalized == disk
            && !disk.is_empty()
            && content.len() == disk.len() * 2
            && content
                .strip_prefix(&disk)
                .is_some_and(|suffix| suffix == disk);
        if exact_doubled_disk_projection {
            agent_doc_document_realtime_io::retire_redundant_doubled_document_write_intents(
                file,
                &content,
                &disk,
                "preflight_response_replay_dedup",
            )?;
        }
        retain_preflight_controller_projection(
            file,
            &normalized,
            &content,
            "preflight_response_replay_dedup",
        )?;
        content = resolve_current_preflight_document(file, "after_response_replay_dedup")?;
        anyhow::ensure!(
            content == normalized,
            "response-replay semantic recovery did not converge live authority to the exact target (target_hash={}, observed_hash={})",
            agent_doc_hash::content_hash(&normalized),
            agent_doc_hash::content_hash(&content),
        );
        agent_doc_ops_log_io::log_op(
            file,
            &format!(
                "preflight_response_replay_duplication_self_healed file={} content_hash={}",
                file.display(),
                agent_doc_hash::content_hash(&content),
            ),
        );
    }
    // A retired retained intent can still have one already-queued editor effect.
    // If that effect concatenates two complete committed projections, there is no
    // pending intent left for the file-aware recovery above to use. HEAD is still
    // an exact safe proof, so restore it before singleton-component validation.
    if let Some(head) = rc.head_content()
        && agent_doc_turn::response_replay::classify_late_ipc_response_overapplication(
            &content, &head,
        )
        .is_some()
    {
        retain_preflight_controller_projection(
            file,
            &head,
            &content,
            "preflight_committed_overapplication_repair",
        )?;
        content =
            resolve_current_preflight_document(file, "after_committed_overapplication_repair")?;
        agent_doc_snapshot_io::checkpoint_document_baseline(
            file,
            &head,
            agent_doc_ops_log_io::log_op,
        )?;
        agent_doc_ops_log_io::log_op(
            file,
            &format!(
                "preflight_committed_overapplication_self_healed file={} content_hash={}",
                file.display(),
                agent_doc_hash::content_hash(&content),
            ),
        );
    }
    // A preflight may perform recovery and queue/backlog maintenance. Refuse
    // every mutation when the authoritative document is already structurally
    // invalid; recovery must never normalize corruption into a new baseline.
    agent_doc_lint_io::validate_integrity_on_content_with_logger(
        file,
        &content,
        agent_doc_ops_log_io::log_op,
    )?;
    // Phase 1 lossless-tree shadow projection (#lzlosstree): build the lossless
    // tree from the authoritative current text, render it, and log whether it
    // round-trips. Pure and non-blocking — the flat CRDT stays the authority and
    // this never influences any closeout decision. A `match=false` line is the
    // signal to investigate the adapter before later phases can trust the tree.
    if !options.probe {
        agent_doc_ops_log_io::log_op(
            file,
            &agent_doc_markdown_lossless::shadow_audit_ops_log_line(&content, "preflight_initial"),
        );
    }
    // `#preflightreactive`: this scope owns the derived read projection and the
    // effect gate for the whole open document observation. The command remains
    // the IO adapter; ordering is now a graph dependency, not a comment/line
    // number contract in this function.
    let preflight_scope = DocumentScope::new();
    let preflight_reads = PreflightReadState::new_in(&preflight_scope);
    let preflight_effects = PreflightEffectState::new_in(&preflight_scope);
    preflight_effects.observe_authority_current(true);
    let (initial_frontmatter, _) = agent_doc_frontmatter_io::session::parse_for_file_with_context(
        &content,
        file,
        &rc.ssh_context(),
    )?;
    let active_harness = rc.harness();
    let mut warnings = Vec::new();
    for warning in agent_doc_preflight_io::warnings::initial_warnings(
        file,
        initial_frontmatter.agent.as_deref(),
        &active_harness,
        initial_frontmatter.codex_network_access.is_some(),
    ) {
        eprintln!("[preflight] warning: {}", warning.message);
        warnings.push(warning);
    }

    // Step 0a: Auto-GC (at most once per day, tracked in state.db).
    if !options.probe {
        agent_doc_preflight_io::gc::run_preflight_auto_gc(file);
    }

    // Pre-mutation debounce: recovery, pending maintenance, commit, and
    // duplicate-residue cleanup all mutate authoritative or recovery state.
    // Do not let those paths race an editor buffer that is still publishing a
    // prompt.
    if !options.probe {
        agent_doc_preflight_io::debounce::wait_for_lazily_current_before_mutation(file)?;
        if agent_doc_document_realtime::write_policy::coalesce_exact_document_replay(&content)
            .is_some()
        {
            let (settled_content, replay_copies) =
                converge_exact_document_replay_before_preflight(file)?;
            content = settled_content;
            if let Some(copies) = replay_copies {
                warnings.push(PreflightWarning {
                        code: "exact_document_replay_coalesced".to_string(),
                        message: format!(
                            "coalesced {copies} whole-document replay projections through the CRDT authority before preflight while retaining monotonic live additions"
                        ),
                    document_agent: None,
                    active_harness: None,
                });
            }
        }
    }
    let pre_mutation_unresolved_exchange_prompt =
        agent_doc_turn::exchange_tail::unresolved_exchange_prompt_in_content(&content);
    // Observe owner-turn liveness before repair can close the prior cycle. This
    // is a derived input to later affectedness classification; using the
    // post-repair lifecycle would forget that a sibling queue edit arrived
    // during an already-open owner turn.
    let entry_cycle_was_open = closeout_cycle_is_open(file).unwrap_or(false);

    // Repair is the first signal-gated effect. Everything through pending
    // maintenance belongs to this effect boundary; commit cannot start until
    // this boundary is settled.
    preflight_effects
        .require(PreflightEffect::Repair)
        .context("preflight repair effect gate")?;
    // Interrupted-cycle guard (#cyc1). Use exact persisted cycle state instead
    // of inferring solely from `ops.log`.
    let (recovered_prior, committed_prior) = if options.probe {
        (false, false)
    } else {
        enforce_cycle_completion(file)?
    };

    let response_contract = response_contract_for_content(&content);
    if response_contract.is_some() {
        eprintln!(
            "[preflight] response contract: strict template; persist with `agent-doc finalize --template <FILE>` (or `write --template --commit`) and wrap stdin in matching `<!-- patch:exchange -->` / `<!-- /patch:exchange -->` markers"
        );
    }

    // Step 0: Check tmux layout health.
    eprintln!("[preflight] step 0: layout check");
    let mut layout_issues = check_layout();
    for issue in &layout_issues {
        eprintln!("[preflight] layout issue: {}", issue);
    }

    // Step 0b (#a014): Session drift auto-resync — when drift is detected on
    // consecutive preflights, auto-run `resync --fix` to clean the registry.
    // State lives in the project `state.db` so we only auto-fix after
    // the second consecutive detection (one false positive is tolerated).
    if !options.probe {
        maybe_auto_resync_on_drift(file, &layout_issues);
    }

    // Step 0c: Auto-repair base-index compliance — when window index 0 is
    // missing, run repair_layout immediately so this preflight reports the
    // post-repair layout state.
    if !options.probe && maybe_auto_repair_base_index(file, &layout_issues) {
        layout_issues = check_layout();
        if layout_issues.is_empty() {
            eprintln!("[preflight] layout repair cleared base-index issues");
        } else {
            for issue in &layout_issues {
                eprintln!("[preflight] layout issue after repair: {}", issue);
            }
        }
    }

    // Step 0d: Fail closed on out-of-band closeout drift before transcript
    // repair can normalize a dirty response body into prompt-looking lines.
    // Open cycles still go through repair first so interrupted write/commit
    // boundaries can recover normally. A proven route/queue snapshot commit
    // boundary is safe recovery and must run before the fresh-prompt exemption;
    // the live prompt belongs to the next cycle, while the queued dispatch
    // snapshot belongs to the prior boundary.
    if !options.probe {
        agent_doc_preflight_runtime_io::recover_route_queue_snapshot_commit_boundary(file, &rc)?;
    }
    let open_cycle = closeout_cycle_is_open(file)?;
    if !options.probe
        && !open_cycle
        && agent_doc_session_check_io::detect_unstarted_prompt_bearing_diff(file)?.is_none()
    {
        agent_doc_preflight_runtime_io::enforce_no_uncommitted_closeout_drift(
            file,
            &rc,
            &agent_doc_closeout_runtime_io::session_check_effects(),
        )?;
    }

    // Step 1: Recover orphaned pending responses.
    eprintln!("[preflight] step 1: repair");
    // #queue-active-deprecated-line-stuck: drop a legacy `queue_active:` line that
    // is stuck in the document because the diff layer classifies it as managed
    // state (so its removal never reads as a diff and is never committed) and the
    // byte-precise hot path never re-serializes frontmatter through `write()`
    // (which would drop it). Strip it directly on disk + snapshot, but ONLY when
    // the canonical `queue:` control is present so no queue state is lost. Idempotent.
    if !options.probe {
        let migrated = frontmatter::strip_deprecated_queue_active_line(&content);
        if migrated != content {
            match agent_doc_document_realtime_io::atomic_write_through_authority(file, &migrated) {
                Ok(()) => {
                    if let Err(err) = agent_doc_snapshot_io::checkpoint_document_baseline(
                        file,
                        &migrated,
                        agent_doc_ops_log_io::log_op,
                    ) {
                        eprintln!(
                            "[preflight] warning: dropped deprecated queue_active line but failed to update snapshot for {}: {err}",
                            file.display()
                        );
                    }
                    agent_doc_ops_log_io::log_op(
                        file,
                        &format!(
                            "deprecated_queue_active_line_dropped file={}",
                            file.display()
                        ),
                    );
                    eprintln!(
                        "[preflight] dropped deprecated `queue_active:` line (canonical `queue:` retained) for {}",
                        file.display()
                    );
                }
                Err(err) => {
                    eprintln!(
                        "[preflight] warning: failed to drop deprecated queue_active line for {}: {err}",
                        file.display()
                    );
                }
            }
        }
    }
    // Detect the stuck-captured-cycle wedge: cycle_state advanced to Committed
    // while the active capture body never landed in HEAD. Emit as a non-blocking
    // warning so the harness can take a recovery path (e.g. force write --commit)
    // instead of silently retrying the same finalize.
    // See tasks/agent-doc/plan-stuck-cycle-causes-duplicated-uncommitted-response.md.
    // #stuck-capture-compact-false-positive: first durably settle any
    // committed-cycle capture whose response is absent from HEAD only because
    // `compact` archived it. This converts the per-pass archive-suppression into
    // a one-time terminal `Discarded`, so a later archive GC cannot resurface the
    // false-positive stuck warning. After this, `stuck_captured_cycle` sees a
    // discarded capture and returns None for the same case.
    if !options.probe {
        match agent_doc_flow_io::closeout::reconcile_compacted_committed_capture(file) {
            Ok(true) => {
                eprintln!(
                    "[preflight] reconciled compacted committed capture for {}",
                    file.display()
                );
            }
            Ok(false) => {}
            Err(err) => {
                eprintln!(
                    "[preflight] warning: failed to reconcile compacted committed capture for {}: {err}",
                    file.display()
                );
            }
        }
    }
    if let Some(info) = agent_doc_flow_io::closeout::stuck_captured_cycle(file) {
        warnings.push(PreflightWarning {
            code: "stuck_captured_cycle".to_string(),
            message: format!(
                "Cycle {} reached state `committed` but the captured response body ({} bytes, capture {}, state `{}`) is not present in HEAD for {}. Recover via `agent-doc write --commit {}` once the visible response body is final.",
                info.cycle_id,
                info.response_body_len,
                info.capture_id,
                info.capture_state,
                file.display(),
                file.display()
            ),
            document_agent: None,
            active_harness: None,
        });
    }
    let mut recovered = recovered_prior
        || if options.probe {
            false
        } else {
            match agent_doc_repair_io::run(
                agent_doc_repair_runtime_io::repair_coordinator_effects(
                    &agent_doc_write_runtime_io::REPAIR_REPLAY_WRITE_EFFECTS,
                ),
                file,
            ) {
                Ok(outcome) => outcome.repaired(),
                // A failed response recovery is never advisory. Continuing into the
                // generic commit stage can commit the stale pre-response projection
                // that caused the recovery attempt, permanently rolling back an
                // already-committed answer. Abort before any commit or queue mutation.
                Err(e) => return Err(e.context("preflight response recovery failed closed")),
            }
        };

    // Step 1b: Ensure document is initialized (snapshot + git baseline).
    // If no snapshot exists, creates one and commits the file.
    if !options.probe
        && let Err(e) = agent_doc_workflow_io::document_init::ensure_initialized(
            file,
            agent_doc_commit_io::commit,
            agent_doc_ops_log_io::log_op,
        )
    {
        eprintln!("[preflight] warning: auto-init failed: {}", e);
    }

    // Step 1b2: Fail closed on out-of-band closeout drift before this preflight
    // mutates backlog state or runs the generic commit path. Otherwise a
    // snapshot/file pair that already contains a visible response could be
    // normalized into a misleading `no_changes` result. Preserve the same
    // fresh-prompt exemption as step 0d: repair may have reaped completed
    // backlog state into the snapshot while leaving the operator's live prompt
    // only in the visible document. That binary-owned maintenance is committed
    // with the response cycle; it is not orphaned response drift.
    if !options.probe
        && agent_doc_session_check_io::detect_unstarted_prompt_bearing_diff(file)?.is_none()
    {
        agent_doc_preflight_runtime_io::enforce_no_uncommitted_closeout_drift(
            file,
            &rc,
            &agent_doc_closeout_runtime_io::session_check_effects(),
        )?;
    }

    // Step 1c: Pending component maintenance — lazy backfill, reap, archive, and
    // reorder detection. MUST run BEFORE step 2 commit so the single step-2
    // commit bundles the pending mutations with the previous-cycle response,
    // producing exactly one HEAD advance per preflight. Running after step 2
    // caused #64mb (double commit_staging: step 2 committed, then maintenance
    // mutated and committed again).
    //
    // Maintenance applies its mutations to BOTH the working tree file AND the
    // snapshot (surgically, via component replace), so the upcoming step-2
    // commit which stages from snapshot picks them up atomically.
    let pending_report = if options.probe {
        PendingMaintenanceReport::default()
    } else {
        run_pending_maintenance(file, &PREFLIGHT_MAINTENANCE_WRITE_EFFECTS)?
    };
    let backlog_reordered = pending_report.reordered;
    let backlog_gated_count = pending_report.backlog_gated_count;

    // `#optverify`: opportunistic gated-review auto-verification. Runs before the
    // step-2 commit so any opt-in `[/]→[x]` flip is staged atomically (the
    // mutation touches both the working-tree file and the snapshot). Default off
    // — without the opt-in the gate status is only surfaced, never flipped.
    let gate_autoverify_optin = initial_frontmatter
        .gate_autoverify
        .or(rc.project_config().agent_doc_gate_autoverify)
        .unwrap_or(false);
    let gate_verify_results = if options.probe {
        Vec::new()
    } else {
        match run_gate_verify(
            file,
            gate_autoverify_optin,
            &PREFLIGHT_MAINTENANCE_WRITE_EFFECTS,
        ) {
            Ok(results) => results,
            Err(e) => {
                eprintln!("[preflight] optverify: scan skipped: {}", e);
                Vec::new()
            }
        }
    };
    if pending_report.legacy_gated_in_backlog_count > 0 {
        warnings.push(PreflightWarning {
            code: "legacy_gated_in_backlog".to_string(),
            message: format!(
                "{} gated item(s) still live in agent:backlog; run `agent-doc migrate {}` to move them into agent:review.",
                pending_report.legacy_gated_in_backlog_count,
                file.display()
            ),
            document_agent: None,
            active_harness: None,
        });
    }
    enforce_no_shadow_open_backlog(file)?;
    enforce_no_dropped_backlog(file, rc.head_content().as_deref().map(String::as_str))?;
    if !options.probe && remove_duplicate_answered_exchange_prompt_tail_for_preflight(file)? {
        recovered = true;
    }
    if !options.probe && remove_post_exchange_duplicate_prompt_comments_for_preflight(file, &rc)? {
        recovered = true;
    }

    preflight_effects
        .settle(PreflightEffect::Repair)
        .context("preflight repair effect settlement")?;

    // Commit of the previous cycle is ready because the repair/maintenance
    // signal settled, not because this call happens to be below it.
    preflight_effects
        .require(PreflightEffect::PriorCycleCommit)
        .context("preflight prior-cycle commit effect gate")?;
    eprintln!("[preflight] step 2: commit");
    let mut did_commit_this_preflight = committed_prior;
    let committed = committed_prior
        || if options.probe {
            false
        } else {
            match commit_previous_cycle_for_preflight(file) {
                Ok(did_commit) => {
                    if did_commit {
                        rc.invalidate_head_content();
                        did_commit_this_preflight = true;
                    }
                    did_commit
                }
                Err(e) => {
                    eprintln!("[preflight] commit warning: {}", e);
                    false
                }
            }
        };
    if !options.probe && committed {
        maybe_record_preflight_terminal_closeout_proof(
            file,
            did_commit_this_preflight || committed_prior,
        );
    }
    preflight_effects
        .settle(PreflightEffect::PriorCycleCommit)
        .context("preflight prior-cycle commit effect settlement")?;

    if !options.probe && relocate_out_of_exchange_prompt_before_diff(file)? {
        recovered = true;
    }
    if !options.probe && remove_duplicate_answered_exchange_prompt_tail_for_preflight(file)? {
        recovered = true;
    }
    if !options.probe && remove_post_exchange_duplicate_prompt_comments_for_preflight(file, &rc)? {
        recovered = true;
    }

    // Step 2d: Cross-document sweep (Fix 5) — commit any other tracked docs in the same
    // project that have uncommitted snapshot content. Turns preflight into a catch-all
    // backstop: even if a previous session's commit was skipped, the next preflight
    // from any document in the project will pick it up.
    if !options.probe {
        let canonical = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
        if let Some(root) = agent_doc_project_root_io::project_root_containing(&canonical)
            && let Ok(registry) = agent_doc_session_registry_io::load_in(&root)
        {
            let current_owner = current_sweep_owner(file, &root, &registry, &canonical);
            for (registry_key, entry) in &registry {
                let tracked_file = if entry.file.trim().is_empty() {
                    registry_key.as_str()
                } else {
                    entry.file.as_str()
                };
                if tracked_file.trim().is_empty() {
                    continue;
                }
                let doc_path = {
                    let path = Path::new(tracked_file);
                    let joined = if path.is_absolute() {
                        path.to_path_buf()
                    } else {
                        root.join(path)
                    };
                    std::fs::canonicalize(&joined).unwrap_or(joined)
                };
                if doc_path == canonical {
                    continue;
                } // already committed in step 2
                if !doc_path.exists() {
                    continue;
                }
                // State.db is the only lifecycle/baseline authority. Commit is
                // idempotent, so inspect every live sibling document instead of
                // consulting a snapshot-sidecar mtime as a hidden phase signal.
                {
                    let sibling_owner = sweep_owner_for_doc(file, &root, &registry, &doc_path);
                    if log_and_skip_foreign_owned_sweep_if_needed(
                        file,
                        &doc_path,
                        current_owner.as_ref(),
                        sibling_owner.as_ref(),
                    ) {
                        continue;
                    }
                    // Guard: don't sweep-commit if the document has user additions
                    // that the agent hasn't responded to yet. For inline mode this
                    // checks ## User / ## Assistant blocks; for template mode it
                    // falls through to a content-equality check.
                    if let (Ok(Some(baseline_content)), Ok(doc_content)) = (
                        agent_doc_snapshot_io::load_document_baseline(&doc_path),
                        std::fs::read_to_string(&doc_path),
                    ) && !agent_doc_diff::is_stale_snapshot(&baseline_content, &doc_content)
                    {
                        // Not a stale inline snapshot — check content equality
                        // (covers template mode where is_stale_snapshot always returns false)
                        let snap_stripped = agent_doc_diff::strip_comments(&baseline_content);
                        let doc_stripped = agent_doc_diff::strip_comments(&doc_content);
                        if snap_stripped.trim() != doc_stripped.trim() {
                            eprintln!(
                                "[preflight] sweep: skipping {} (unresponded user content)",
                                doc_path.display()
                            );
                            continue;
                        }
                    }
                    // Freshness gate: skip if another session committed this doc
                    // within the last 5s. This is a valid fast-path — a
                    // concurrent commit that just ran will have advanced HEAD's
                    // commit time, so we avoid re-spawning git (~10ms) for
                    // nothing. The gate only closes races when paired with Git's
                    // index lock and HEAD update-ref compare-and-swap in
                    // git::commit (there is no bespoke commit flock; #lazily-hot-path).
                    let fresh = agent_doc_git_io::revision::last_commit_mtime(&doc_path)
                        .ok()
                        .flatten()
                        .and_then(|t| t.elapsed().ok())
                        .is_some_and(|e| e.as_secs() < 5);
                    if fresh {
                        eprintln!(
                            "[preflight] sweep: skipping {} (committed <5s ago)",
                            doc_path.display()
                        );
                        continue;
                    }
                    match agent_doc_commit_io::commit(&doc_path) {
                        Ok(true) => {
                            eprintln!("[preflight] sweep: committed {}", doc_path.display())
                        }
                        Ok(false) => {
                            eprintln!("[preflight] sweep: clean {}", doc_path.display())
                        }
                        Err(e) => eprintln!(
                            "[preflight] sweep: warning for {}: {}",
                            doc_path.display(),
                            e
                        ),
                    }
                }
            }
        }
    }
    if !options.probe {
        let canonical = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
        if let Some(root) = agent_doc_project_root_io::project_root_containing(&canonical) {
            match agent_doc_controller_io::project_controller::close_stale_dead_pane_actors_with_tmux_for_caller(
                &root,
                false,
                "preflight",
                "stale_dead_pane_actor",
            ) {
                Ok((closed, kept)) if closed > 0 => {
                    eprintln!(
                        "[preflight] actors: {} stale dead-pane closed, {} still active",
                        closed, kept
                    );
                }
                Ok(_) => {}
                Err(e) => eprintln!("[preflight] dead-pane actor gc warning: {}", e),
            }
        }
    }

    // Step 3: Read and truncate the claims log.
    eprintln!("[preflight] step 3: claims");
    let claims = if options.probe {
        read_claims(file)
    } else {
        read_and_truncate_claims(file)
    };

    // Step 3b: Wait for file to settle (mtime + typing indicator debounce).
    // Check both file mtime (disk-level) and cross-process typing indicator
    // (buffer-level) to avoid picking up mid-typing edits.
    // Default: 2000ms (configurable via `agent_doc_debounce` frontmatter field).
    if !options.probe {
        agent_doc_preflight_io::debounce::wait_for_lazily_current_observation(file);
    }

    // Step 3c: Check related documents for changes.
    eprintln!("[preflight] step 3c: related docs");
    let linked_changes = if options.probe {
        Vec::new()
    } else {
        check_linked_docs(file)
    };
    for change in &linked_changes {
        eprintln!(
            "[preflight] related doc change: {} — {}",
            change.path, change.summary
        );
    }

    // Step 4: Compute diff between snapshot and current document.
    eprintln!("[preflight] step 4: diff");
    // Lazily reactive state-diff feed (#preflight-lazily-diff-feed): after the
    // diff settles on the disk buffer for prompt-completeness, source the
    // coherent `current` from the reactive CRDT model when the operator's live
    // edits have not yet reached disk. `durable_buffer_state` returns `Some`
    // only when a live editor buffer has diverged from disk; otherwise the diff
    // keeps its disk-sourced content. This is the seam that lets the realtime
    // document model — not a disk read race — own concurrent typing.
    let live_current_source = ReactiveLiveCurrentSource;
    let diff_result_with_current = agent_doc_diff_io::compute_with_current(
        &agent_doc_snapshot_io::DiffBaselineStore::new(agent_doc_ops_log_io::log_op),
        file,
        Some(&live_current_source),
    )?;
    // Do not advance the durable merge baseline here. `current` can contain the
    // operator prompt that opened this cycle; checkpointing it before closeout
    // makes an interrupted cycle forget that prompt. Cycle state records the
    // exact diff/current cut, while the durable baseline advances only after a
    // canonical write or a narrowly scoped maintenance convergence below.
    let raw_diff = diff_result_with_current.diff;
    let harness_diff = agent_doc_harness::prompt_source::synthetic_diff_for_file(
        file,
        agent_doc_codex_hook_io::load_prompt_for_current_session,
    )?;
    let initial_diff = raw_diff.clone().or(harness_diff.clone());

    // Step 4a: Scan diff for inline `/model <x>` command and strip the matching
    // line(s) before downstream classification. The strip prevents `/model` from
    // double-emitting in `builtin_commands`.
    let global_config = agent_doc_config::load().unwrap_or_default();
    let harness = rc.harness();
    let model_scan = initial_diff
        .as_ref()
        .map(|d| agent_doc_model_tier::scan_model_switch(d, &harness, &global_config.model));
    let mut diff_result: Option<String> = if let Some(scan) = model_scan.as_ref() {
        // Use the stripped diff for downstream consumers.
        Some(scan.stripped_diff.clone())
    } else {
        initial_diff.clone()
    };

    // Step 4b: Classify the diff for skill routing.
    let mut classification = diff_result.as_ref().map(|d| diff::classify_diff(d));
    let boundary_artifact_only = classification
        .as_ref()
        .is_some_and(|c| c.diff_type == diff::DiffType::BoundaryArtifact);
    if boundary_artifact_only {
        if raw_diff.is_some() {
            diff_result = harness_diff.clone();
            classification = diff_result.as_ref().map(|d| diff::classify_diff(d));
        } else {
            diff_result = None;
            classification = None;
        }
    }

    // Step 4b2: Queue component analysis — resolve activation, consume start
    // fences, and emit queue prompts for the skill. If the document/harness diff
    // is otherwise empty, an active queue head item becomes the prompt diff for
    // this cycle. This preserves bare no-op invocations while letting persisted
    // `queue_active: true` advance without requiring a fresh document edit.
    let queue_state = if options.probe {
        inspect_queue_state(file, diff_result.as_deref()).unwrap_or_else(|e| {
            eprintln!("[preflight] queue probe warning: {}", e);
            QueueState::default()
        })
    } else {
        match run_queue_maintenance(file, diff_result.as_deref()) {
            Ok(state) => state,
            Err(e) => {
                // `#queuepersistloud`: the mirror/sync progress lines above are
                // printed while the new queue is being *computed*, before it is
                // persisted. When persistence then fails the computed queue is
                // discarded and the previous queue text survives — but the run
                // still reads as success ("mirrored 31 ... / synced backlog →
                // queue (Prepend, 49 active id(s))"), so a freshly filed backlog
                // item silently never appears in the queue and operators re-add
                // `do [#id]` by hand. Say plainly that nothing was applied, and
                // surface it as a structured warning so agents and skills see it
                // in the preflight contract rather than only on stderr.
                eprintln!(
                    "[preflight] queue maintenance FAILED — the recomputed queue was NOT applied; \
                     the previous queue text is still in effect (any backlog→queue mirror, \
                     auto-strike, or reorder reported above did not land): {e}"
                );
                warnings.push(PreflightWarning {
                    code: "queue_maintenance_not_persisted".to_string(),
                    message: format!(
                        "queue maintenance was computed but NOT persisted, so the previous queue \
                         text is still in effect and newly added backlog ids may be absent from \
                         the queue: {e}"
                    ),
                    document_agent: None,
                    active_harness: None,
                });
                // `#queuepersiststall`: report the queue AS IT ACTUALLY STANDS.
                //
                // This used to fall back to `QueueState::default()`, i.e. "no
                // active queue, nothing drainable". That is indistinguishable
                // from a genuinely drained queue, and preflight is the loop gate
                // — so a *persistence* failure silently presented as *queue
                // empty* and stalled the drain. Observed live: session-check
                // reported `queue_continuation_required=true` with a real next
                // head while the very next preflight reported
                // `queue_drainable_head_count: 0`, and the loop stopped after one
                // iteration on a queue that still had work.
                //
                // Only the recomputed queue was lost; the previous queue text is
                // still in effect and its drainability is knowable. Probe it
                // read-only so the contract stays truthful. If even the probe
                // fails, the zeroed default is the honest answer.
                inspect_queue_state(file, diff_result.as_deref()).unwrap_or_else(|probe_err| {
                    eprintln!(
                        "[preflight] queue: post-failure drainability probe also failed, \
                         reporting an inactive queue: {probe_err}"
                    );
                    QueueState::default()
                })
            }
        }
    };
    warnings.extend(queue_state.warnings.clone());
    // #qconvbaseline: the queue maintenance above may converge the live editor
    // buffer to a corrected queue shape (auto-pins, backlog→queue mirrors, do-prompt
    // sort, queue-control frontmatter) via IPC WITHOUT a disk write when a plugin
    // listener is active — the converged shape lands in the editor buffer + the
    // snapshot, but disk (and the baseline saved just above, from pre-maintenance
    // disk) stay behind. At finalize the converged editor buffer then reads as
    // `live_prompt_drift_after_preflight` on EVERY cycle: its queue body differs
    // outside `exchange` from both the baseline and `content_ours`, so the drift
    // guard blocks the response write and forces the `content_ours` carry-forward +
    // a recovery commit (the race the operator hit repeatedly editing the doc).
    // Re-align the baseline with the post-maintenance snapshot (the queue shape now
    // in the editor buffer) so `content_ours` matches the buffer outside `exchange`
    // and ONLY genuine concurrent user edits trip the guard. No-listener sessions
    // are unaffected — their disk write already matches the snapshot, and when
    // nothing converged the snapshot equals the pre-maintenance content so this is
    // a no-op.
    if !options.probe
        && let Ok(Some(converged_snapshot)) = agent_doc_snapshot_io::load_document_baseline(file)
        && let Some(realigned) = realign_baseline_to_converged_queue(
            &diff_result_with_current.previous,
            &converged_snapshot,
        )
    {
        let _ = checkpoint_baseline_content(file, &realigned);
        eprintln!("[preflight] baseline re-aligned to converged queue shape (#qconvbaseline)");
    }
    if diff_result.is_some()
        && queue_state.queue_active == Some(true)
        && let Some(selected_head) = queue_state
            .selected_queue_prompts
            .first()
            .or_else(|| queue_state.queue_prompts.first())
        && queue_body_diff_is_non_selected_future_state(
            &diff_result_with_current.previous,
            &diff_result_with_current.current,
            selected_head,
        )
    {
        diff_result = None;
        classification = None;
        eprintln!("[preflight] queue: absorbed non-selected queue edit into future queue state");
    }
    let diff_has_prompt_bearing_changes = diff_result.as_deref().is_some_and(|d| {
        diff::classify_prompt_bearing_changes(d)
            .iter()
            .any(|change| {
                matches!(
                    change.kind,
                    agent_doc_diff::PromptBearingChangeKind::PromptTarget
                        | agent_doc_diff::PromptBearingChangeKind::ContentEdit
                )
            })
    });
    if !diff_has_prompt_bearing_changes {
        let unresolved_exchange_prompt = match pre_mutation_unresolved_exchange_prompt.clone() {
            Some(prompt) => Some(prompt),
            None => match agent_doc_session_check_io::unresolved_exchange_prompt(file) {
                Ok(prompt) => prompt,
                Err(err) => {
                    warnings.push(PreflightWarning {
                        code: "unresolved_exchange_prompt_unavailable".to_string(),
                        message: format!(
                            "could not inspect unresolved exchange prompt before queue selection: {err}"
                        ),
                        document_agent: None,
                        active_harness: None,
                    });
                    None
                }
            },
        };
        if let Some(unresolved) = unresolved_exchange_prompt
            && !unresolved.trim().is_empty()
        {
            let unresolved = unresolved.trim();
            let prompt_source = if unresolved.starts_with('❯') {
                unresolved.to_string()
            } else {
                format!("❯ {unresolved}")
            };
            diff_result = Some(diff::synthetic_added_lines_diff(&prompt_source, "exchange"));
            classification = diff_result.as_ref().map(|d| diff::classify_diff(d));
            eprintln!(
                "[preflight] queue: deferred active queue for unresolved exchange prompt \
                 (#prompt-preempts-auto-queue)"
            );
        }
    }
    // `#agent-doc-bug` auto-queue stall: when there is no real user/document diff
    // this cycle, an active queue head is synthesized as the cycle's prompt diff.
    // That synthetic head is queue *continuation*, not user intent — so it must
    // NOT populate `user_intent_prompt_changes`, or the skill's auto-loop
    // precondition (`user_intent_prompt_changes` empty) never holds and the
    // `auto` queue stalls after every item. A real user prompt typed mid-queue
    // keeps `diff_result` non-None here, so this flag stays false and the
    // prompt is surfaced normally.
    // `#rt83`: only synthesize the queue head as a prompt diff when SOME drainer
    // will actually act on it — the queue is not controller-paused AND the head is
    // supervisor-scope drainable (`[operator-verify]`/noise heads excluded). An
    // operator-verify-only or paused queue previously synthesized a phantom
    // `+:pushpin: do [#id]` add every preflight, keeping `no_changes:false` and
    // sustaining the qchurn flood even though no head was actionable. The supervisor
    // idle-watch dispatches `[focused-cycle]`/`[clean-session]` heads via its own
    // `live_drainable_continuation_head` check, so suppressing the synthetic diff for
    // non-drainable heads does not strand legitimate supervisor-driven continuation.
    let mut diff_from_queue_head_only = false;
    if diff_result.is_none()
        && !queue_state.queue_paused
        && queue_state.queue_supervisor_drainable
        && let Some(head_prompt) = queue_state
            .selected_queue_prompts
            .first()
            .or_else(|| queue_state.queue_prompts.first())
    {
        let slash_command = agent_doc_queue::queue_command::slash_command_text(head_prompt);
        let prompt_source = slash_command.as_deref().unwrap_or(head_prompt);
        diff_result = Some(diff::synthetic_added_lines_diff(prompt_source, "queue"));
        classification = diff_result.as_ref().map(|d| diff::classify_diff(d));
        diff_from_queue_head_only = true;
    }

    let slash_command_only_diff_commands = diff_result
        .as_deref()
        .and_then(diff::parse_slash_command_only_added_diff);
    let no_changes = diff_result.is_none();

    // Step 4c: Annotate the diff with content-source markers.
    let annotated_diff = diff_result.as_ref().and_then(|d| diff::annotate_diff(d));

    // Step 4c2: Classify user-authored prompt-bearing changes across prompts, edits,
    // and response/boundary artifacts.
    let queue_active_for_prompt_extraction =
        queue_state.queue_active == Some(true) || !queue_state.queue_prompts.is_empty();
    let command_diff_result = diff_result.as_ref().map(|d| {
        if queue_active_for_prompt_extraction {
            d.clone()
        } else {
            diff::suppress_inactive_queue_additions(d, &diff_result_with_current.current)
        }
    });
    let prompt_diff_result = if slash_command_only_diff_commands.is_some() {
        None
    } else {
        command_diff_result.clone()
    };
    let mut prompt_bearing_changes = diff_result
        .as_ref()
        .map(|_| {
            prompt_diff_result
                .as_deref()
                .map(diff::classify_prompt_bearing_changes)
                .unwrap_or_default()
        })
        .unwrap_or_default();
    if raw_diff.is_some()
        && let Some(harness_only_diff) = harness_diff.as_ref()
    {
        push_unique_prompt_bearing_changes(
            &mut prompt_bearing_changes,
            diff::classify_prompt_bearing_changes(harness_only_diff),
        );
    }
    let prompt_targets =
        agent_doc_workflow::session_cycle::prompt_targets_from_changes(&prompt_bearing_changes);
    let directive_target_ids =
        agent_doc_queue::queue_directive::do_directive_target_ids(&prompt_targets);
    let checkpoint_queue_task_id = directive_target_ids.first().map(String::as_str);
    let mut added_diff_lines = prompt_diff_result
        .as_ref()
        .map(|d| agent_doc_prompt_contract::collect_added_diff_lines(d))
        .unwrap_or_default();
    if raw_diff.is_some()
        && let Some(harness_only_diff) = harness_diff.as_ref()
    {
        push_unique_strings(
            &mut added_diff_lines,
            agent_doc_prompt_contract::collect_added_diff_lines(harness_only_diff),
        );
    }

    // Surface inline annotations separately from prompt-intent routing.
    let inline_annotations = annotated_diff
        .as_ref()
        .map(|a| diff::extract_inline_annotations(a))
        .unwrap_or_default();
    let semantic_diff = semantic_diff_summary(
        &diff_result_with_current.previous,
        &diff_result_with_current.current,
        &prompt_bearing_changes,
    );

    // #op-scoped-drift-1: persist this cycle's node ops to the durable op log,
    // tagged with actor + causal (Lamport / session-origin) clock. Best effort:
    // the durable substrate must never block or fail a preflight cycle.
    // `#pcc1`: `compute_with_current` ran BEFORE `run_queue_maintenance`, so any
    // structural normalization maintenance applied is invisible to that cut. The
    // `#qfoldedhead` re-segmentation is the case that matters: a pasted block
    // that arrived folded is ONE node in the pre-maintenance content, so ops
    // recorded from it cover the whole block and every id after the first never
    // reaches the op log. That is the zero-op result observed for a sixteen-item
    // paste on 2026-08-03, and it starves every later per-component phase of its
    // substrate. Record ops from the document as maintenance left it.
    //
    // Deliberately scoped to the op log: `semantic_diff` still feeds the
    // preflight contract from the original cut, so this changes what is
    // DURABLY RECORDED without moving contract semantics.
    if !options.probe
        && let Some(project_root) = rc.project_root()
    {
        let post_maintenance_current =
            agent_doc_document_realtime_io::try_resolve_current_document_content(
                file,
                "preflight_op_log_post_maintenance",
            )
            .ok();
        let op_summary = match post_maintenance_current.as_deref() {
            Some(current) if current != diff_result_with_current.current => semantic_diff_summary(
                &diff_result_with_current.previous,
                current,
                &prompt_bearing_changes,
            ),
            _ => semantic_diff.clone(),
        };
        if let Some(summary) = op_summary.as_ref() {
            let document_path = file.to_string_lossy().to_string();
            if let Err(err) = agent_doc_sqlite::op_log::append_semantic_diff_ops(
                &project_root,
                &document_path,
                initial_frontmatter.session.as_deref(),
                summary,
            ) {
                eprintln!("[preflight] op-log persist skipped: {err}");
            }
        }
    }

    // #op-scoped-drift-2: emit the TurnScope manifest (read/write set + driver)
    // for the prompts this turn is answering.
    let turn_scope = derive_turn_scope(&diff_result_with_current.current, &prompt_targets);

    // Capture the previously persisted owner-turn scope before this preflight
    // considers writing a new scope. A same-owner recursive preflight caused by
    // a sibling queue edit must compare against, and preserve, the active
    // owner's original turn scope (#cwsp).
    let persisted_turn_scope = agent_doc_turn_scope_io::load(file);

    // #op-scoped-drift-3: classify this cycle's node ops against the TurnScope so
    // independent / provenance-spoofed edits integrate without affecting the turn.
    let op_affectedness = match (semantic_diff.as_ref(), turn_scope.as_ref()) {
        (Some(summary), Some(scope)) => {
            let document_path = file.to_string_lossy().to_string();
            let ops = agent_doc_turn::op_log::build_ops_from_semantic_diff(
                &document_path,
                initial_frontmatter.session.as_deref(),
                "",
                summary,
            );
            Some(agent_doc_turn::turn_scope::classify_cycle(&ops, scope))
        }
        _ => None,
    };
    let active_scope_cycle_is_open = entry_cycle_was_open;
    let active_turn_affectedness = match (
        active_scope_cycle_is_open,
        semantic_diff.as_ref(),
        persisted_turn_scope.as_ref(),
    ) {
        (true, Some(summary), Some(scope)) => {
            let document_path = file.to_string_lossy().to_string();
            let ops = agent_doc_turn::op_log::build_ops_from_semantic_diff(
                &document_path,
                initial_frontmatter.session.as_deref(),
                "",
                summary,
            );
            Some(agent_doc_turn::turn_scope::classify_cycle(&ops, scope))
        }
        _ => None,
    };
    let prompt_edit_independent_of_active_turn =
        active_turn_affectedness
            .as_ref()
            .is_some_and(|affectedness| {
                !affectedness.turn_affected && !affectedness.classified.is_empty()
            });

    // #nm1x: persist the scope so the later finalize-path drift gate (a separate
    // process invocation) can intersect incoming document ops against the same
    // scope. Best effort — a write failure must never block a preflight cycle, and
    // a stale scope is cleared so the gate falls back to its coarse behavior.
    //
    // #cwsp: when the diff is independent of an already-open active owner turn,
    // do not replace that owner's persisted scope with the sibling queue edit's
    // derived scope. The edit must stay as document state until the current
    // closeout merges it.
    if !options.probe && !prompt_edit_independent_of_active_turn {
        match turn_scope.as_ref() {
            Some(scope) => {
                if let Err(err) = agent_doc_turn_scope_io::save(file, scope) {
                    eprintln!("[preflight] turn-scope persist skipped: {err}");
                }
            }
            None => {
                if let Err(err) = agent_doc_turn_scope_io::delete(file) {
                    eprintln!("[preflight] turn-scope clear skipped: {err}");
                }
            }
        }
    }

    // Step 4d: Extract slash commands from user-added diff lines (classified into skill vs built-in).
    let mut parsed_commands = command_diff_result
        .as_ref()
        .map(|d| diff::parse_slash_commands_classified(d))
        .unwrap_or_else(|| diff::ParsedSlashCommands {
            skill_commands: vec![],
            builtin_commands: vec![],
        });
    if raw_diff.is_some()
        && let Some(harness_only_diff) = harness_diff.as_ref()
    {
        let harness_commands = diff::parse_slash_commands_classified(harness_only_diff);
        push_unique_strings(
            &mut parsed_commands.skill_commands,
            harness_commands.skill_commands,
        );
        push_unique_strings(
            &mut parsed_commands.builtin_commands,
            harness_commands.builtin_commands,
        );
    }
    let slash_commands = parsed_commands.skill_commands;
    let builtin_commands = parsed_commands.builtin_commands;
    let orchestration_request = prompt_diff_result
        .as_ref()
        .and_then(|d| diff::detect_orchestration_request(d))
        .or_else(|| {
            raw_diff
                .as_ref()
                .and(harness_diff.as_ref())
                .and_then(|d| diff::detect_orchestration_request(d))
        });

    // Step 4e: Resolve model tier sources and compose effective_tier.
    // Sources (highest precedence first): inline /model command, <!-- agent:model --> component,
    // agent_doc_model_tier frontmatter, diff heuristic.
    let (
        source_frontmatter,
        frontmatter_tier,
        component_tier_value,
        frontmatter_env,
        frontmatter_model,
        frontmatter_prompt_presets,
        model_source_content,
    ) = {
        let content = resolve_current_preflight_document(file, "model_tier_sources")?;
        let (source_fm, fm_tier, env_map, fm_model, prompt_presets) = frontmatter::parse(&content)
            .ok()
            .map(|(fm, _)| {
                let resolved = fm.resolve_harness_model(&harness).map(|s| s.to_string());
                let fm_tier = fm.model_tier;
                let env_map = fm.env.clone();
                let prompt_presets = fm.prompt_presets.clone();
                (fm, fm_tier, env_map, resolved, prompt_presets)
            })
            .unwrap_or_default();
        let comp_value = agent_doc_model_tier::extract_model_component(&content);
        (
            source_fm,
            fm_tier,
            comp_value,
            env_map,
            fm_model,
            prompt_presets,
            content,
        )
    };
    let component_tier = component_tier_value.as_deref().and_then(|v| {
        agent_doc_model_tier::component_value_to_tier(v, &harness, &global_config.model)
    });

    let prompt_preset_resolution = agent_doc_prompt_contract::resolve_prompt_preset_requests(
        prompt_diff_result.as_deref(),
        raw_diff
            .as_ref()
            .and(harness_diff.as_ref())
            .map(String::as_str),
        &prompt_targets,
        &added_diff_lines,
        &frontmatter_prompt_presets,
    );
    if !prompt_preset_resolution.missing.is_empty() {
        anyhow::bail!(
            "document references missing prompt preset(s): {}",
            prompt_preset_resolution.missing.join(", ")
        );
    }
    let prompt_presets_requested = prompt_preset_resolution.requested;
    for warning in agent_doc_preflight_io::warnings::content_and_staleness_warnings(
        file,
        &model_source_content,
        &frontmatter_prompt_presets,
    ) {
        eprintln!("[preflight] warning: {}", warning.message);
        warnings.push(warning);
    }
    let backlog_capture_required = agent_doc_prompt_contract::prompt_requests_backlog_work(
        &prompt_targets,
        &added_diff_lines,
        &frontmatter_prompt_presets,
    );
    let explicit_backlog_targets = agent_doc_prompt_contract::explicit_backlog_targets(
        file,
        &prompt_targets,
        &added_diff_lines,
        &frontmatter_prompt_presets,
    )?;
    let explicit_backlog_target_paths = explicit_backlog_targets
        .iter()
        .map(|path| {
            std::fs::canonicalize(path)
                .unwrap_or_else(|_| path.to_path_buf())
                .display()
                .to_string()
        })
        .collect::<Vec<_>>();
    let explicit_backlog_requirements =
        explicit_backlog_target_requirements(file, &source_frontmatter, &explicit_backlog_targets)?;
    let required_explicit_backlog_item_count = if explicit_backlog_requirements.is_empty() {
        0
    } else {
        agent_doc_prompt_contract::required_explicit_backlog_item_count(
            &prompt_targets,
            &added_diff_lines,
            &frontmatter_prompt_presets,
            &prompt_bearing_changes,
        )
    };
    let required_plan_reference_count = agent_doc_prompt_contract::required_plan_reference_count(
        &prompt_targets,
        &added_diff_lines,
        &frontmatter_prompt_presets,
        &prompt_bearing_changes,
    );
    // `#do-id-closeout-open-backlog`: tracked-work ids named by an explicit
    // `do [#id]` directive that are still open in the live backlog must reach a
    // lifecycle outcome before closeout. Record them so `session-check` can fail
    // closed when a directive clears the queue but leaves its target `[ ]`.
    let expect_done_or_gate_ids = {
        if directive_target_ids.is_empty() {
            Vec::new()
        } else {
            let open_backlog: std::collections::HashSet<String> =
                agent_doc_element_backlog::backlog::open_backlog_ids_in_content(
                    &model_source_content,
                )
                .into_iter()
                .collect();
            let synced_queue_ids = queue_state
                .synced_queue_ids
                .iter()
                .cloned()
                .collect::<std::collections::HashSet<String>>();
            agent_doc_queue::queue_directive::filter_expect_done_or_gate_ids(
                &directive_target_ids,
                &open_backlog,
                &synced_queue_ids,
            )
        }
    };
    // Diff heuristic — counts user-added lines (excluding +++ headers).
    let lines_added = diff_result
        .as_ref()
        .map(|d| {
            d.lines()
                .filter(|l| l.starts_with('+') && !l.starts_with("+++"))
                .count()
        })
        .unwrap_or(0);
    let diff_type_str: Option<String> = classification.as_ref().and_then(|c| {
        serde_json::to_value(&c.diff_type)
            .ok()
            .and_then(|v| v.as_str().map(|s| s.to_string()))
    });
    let suggested =
        agent_doc_model_tier::suggested_tier(diff_type_str.as_deref(), lines_added, file);

    let model_switch_name = model_scan.as_ref().and_then(|s| s.model_switch.clone());
    let model_switch_tier = model_scan.as_ref().and_then(|s| s.model_switch_tier);
    let required_tier_value = component_tier.or(frontmatter_tier);
    let effective_tier_value = agent_doc_model_tier::compose_effective_tier(
        model_switch_tier,
        component_tier,
        frontmatter_tier,
        suggested,
    );

    // Step 5: Scan for pending callback requests from other processes.
    let pending_callbacks = agent_doc_callback_io::scan_pending_callbacks(None).unwrap_or_default();
    if !pending_callbacks.is_empty() {
        eprintln!(
            "[preflight] found {} pending callback(s)",
            pending_callbacks.len()
        );
    }
    warnings.extend(agent_doc_preflight_io::warnings::semantic_completion_warnings(file));

    let agent_model = agent_doc_model_tier::resolve_agent_model(
        frontmatter_model.as_deref(),
        &harness,
        &global_config.model,
    );
    let session_accretion = agent_doc_session_accretion_io::inspect(file)
        .ok()
        .filter(|report| !report.is_healthy());

    // Publish one document-scoped read cut before opening a cycle. The hashes
    // identify the exact document/baseline/config observations; every
    // agent-facing field below is read back from this Computed projection.
    let config_cut = format!(
        "harness={harness};effective={effective_tier_value};required={};suggested={suggested};agent_model={}",
        required_tier_value
            .map(|tier| tier.to_string())
            .unwrap_or_default(),
        agent_model.as_deref().unwrap_or_default(),
    );
    let mut preflight_read_facts = PreflightReadFacts {
        document_hash: agent_doc_hash::content_hash(&diff_result_with_current.current),
        baseline_hash: agent_doc_hash::content_hash(&diff_result_with_current.previous),
        config_hash: agent_doc_hash::content_hash(&config_cut),
        diff: diff_result.clone(),
        queue: PreflightQueueProjection {
            prompts: queue_state.queue_prompts.clone(),
            selected_prompts: queue_state.selected_queue_prompts.clone(),
            active: queue_state.queue_active,
            deferred: queue_state.queue_deferred,
            start_at: queue_state.queue_start_at.clone(),
            trigger: queue_state
                .queue_trigger
                .as_ref()
                .and_then(|trigger| serde_json::to_value(trigger).ok()),
            halted: queue_state.queue_halted.clone(),
            paused: queue_state.queue_paused,
            pause_reason: queue_state.queue_pause_reason.clone(),
            drainable_head_count: queue_state.queue_drainable_head_count,
            continuation_required: queue_state.queue_continuation_required,
            continuation_guidance: None,
        },
        tiers: PreflightTierProjection {
            effective: Some(effective_tier_value.to_string()),
            required: required_tier_value.map(|tier| tier.to_string()),
            suggested: Some(suggested.to_string()),
            agent_model: agent_model.clone(),
        },
        claims: claims.clone(),
        related_documents: linked_changes
            .iter()
            .map(|change| PreflightRelatedDocumentProjection {
                path: change.path.clone(),
                summary: change.summary.clone(),
                exists: change.exists,
            })
            .collect(),
        session_accretion: session_accretion
            .as_ref()
            .and_then(|report| serde_json::to_value(report).ok()),
    };
    preflight_reads.observe(preflight_read_facts.clone());
    let controller_project_root = agent_doc_project_root_io::project_root_containing(file)
        .ok_or_else(|| {
            anyhow::anyhow!(
                "preflight reactive projection requires a project root for {}",
                file.display()
            )
        })?;
    // Unit tests run many temporary projects concurrently and intentionally do
    // not launch a separately-installed controller binary. Production normal
    // preflight always takes the controller branch; probes remain actorless.
    let controller_projection_enabled = !options.probe && !cfg!(test);
    let cycle_read_projection = if !controller_projection_enabled {
        preflight_reads.projection()
    } else {
        agent_doc_controller_io::project_controller::ensure_controller_running_for_file(file)?;
        agent_doc_controller_io::project_controller::preflight_read_projection(
            &controller_project_root,
            file,
            &preflight_read_facts,
        )?
    };
    if !cycle_read_projection.current {
        anyhow::bail!(
            "preflight derived read projection is not current for {}",
            file.display()
        );
    }
    preflight_effects.observe_derived_reads_current(true);
    preflight_effects
        .settle(PreflightEffect::BaselineCheckpoint)
        .context("preflight baseline checkpoint effect gate")?;

    // Cycle-open remains exactly-once durable state. It is enabled only after
    // the complete derived read cut and baseline checkpoint signal exist.
    let cycle_open_required =
        !options.probe && !no_changes && slash_command_only_diff_commands.is_none();
    preflight_effects.observe_cycle_open_required(cycle_open_required);
    if !no_changes {
        if let Some(commands) = slash_command_only_diff_commands.as_ref() {
            if !options.probe {
                agent_doc_ops_log_io::log_op(
                    file,
                    &format!(
                        "preflight_slash_command_only_handoff file={} commands={:?}",
                        file.display(),
                        commands
                    ),
                );
            }
            eprintln!(
                "[preflight] slash command diff {:?} is command-only; skipping preflight_started so the harness/supervisor can submit it without an agent-doc response cycle",
                commands
            );
        } else if options.probe {
            // `#preflight-probe-side-effect-free`: a pure inspection probe must
            // not open a `preflight_started` cycle.
            eprintln!("[preflight] probe: skipping preflight_started cycle (inspection only)");
        } else {
            preflight_effects
                .require(PreflightEffect::CycleOpen)
                .context("preflight cycle-open effect gate")?;
            let snap = agent_doc_snapshot_io::load_document_baseline(file).unwrap_or(None);
            let file_content = resolve_current_preflight_document(file, "start_preflight")?;
            let snap_len = snap.as_ref().map(|s| s.len()).unwrap_or(0);
            let file_len = file_content.len();
            agent_doc_controller_io::project_controller::ensure_controller_running_for_file(file)?;
            agent_doc_cycle_state_io::start_preflight(file, snap.as_deref(), Some(&file_content))?;
            preflight_effects
                .settle(PreflightEffect::CycleOpen)
                .context("preflight cycle-open effect settlement")?;
            agent_doc_ops_log_io::log_op(
                file,
                &format!(
                    "{} file={} snap_len={} file_len={}",
                    OpsLogEvent::PreflightDiffStart,
                    file.display(),
                    snap_len,
                    file_len
                ),
            );
        }
    }
    preflight_effects
        .require_complete()
        .context("preflight effect graph did not reach its terminal projection")?;

    if !options.probe {
        agent_doc_cycle_state_io::record_turn_checkpoint(
            file,
            &prompt_targets,
            checkpoint_queue_task_id,
            checkpoint_queue_task_id,
        )?;
    }
    if !options.probe && !no_changes {
        agent_doc_cycle_state_io::record_backlog_capture_requirement(
            file,
            backlog_capture_required,
        )?;
        agent_doc_cycle_state_io::record_backlog_target_requirements(
            file,
            &explicit_backlog_requirements,
        )?;
        agent_doc_cycle_state_io::record_expect_done_or_gate_ids(file, &expect_done_or_gate_ids)?;
        agent_doc_cycle_state_io::record_required_explicit_backlog_item_count(
            file,
            required_explicit_backlog_item_count,
        )?;
        agent_doc_cycle_state_io::record_required_plan_reference_count(
            file,
            required_plan_reference_count,
        )?;
    }

    // `#queue-no-stop-unrelated-edit`: compute before owner-pane detection so
    // same-pane recursion signals use only prompt changes that affect this turn.
    let mut user_intent_prompt_changes = compute_user_intent_prompt_changes(
        &prompt_bearing_changes,
        diff_from_queue_head_only,
        op_affectedness.as_ref(),
    );
    if prompt_edit_independent_of_active_turn {
        user_intent_prompt_changes.clear();
    }
    // #exchange-active-prompt-marker: once preflight has selected the exact
    // prompt targets for this turn, publish that working state into the live
    // exchange tail. The pure exchange transformation preserves Markdown
    // structure: prose is `❯`-prefixed, headings keep their `#` syntax and
    // carry `❯ 🚧` inside the heading text, and list/fenced regions are never
    // decorated. Probe mode remains side-effect-free.
    if !options.probe {
        let active_exchange_prompt_targets = user_intent_prompt_changes
            .iter()
            .filter(|change| change.kind == agent_doc_diff::PromptBearingChangeKind::PromptTarget)
            .map(|change| change.text.clone())
            .collect::<Vec<_>>();
        if !active_exchange_prompt_targets.is_empty() {
            let projection = match resolve_current_preflight_document(
                file,
                "active_exchange_prompt_marker",
            ) {
                Ok(authoritative) => {
                    let marked =
                        agent_doc_element_exchange::mark_active_exchange_prompts_in_progress(
                            &authoritative,
                            &active_exchange_prompt_targets,
                        );
                    project_active_prompt_marker_with(
                        &authoritative,
                        &marked,
                        |expected_current, content| {
                            agent_doc_document_realtime_io::apply_cp_write_through_relay_authority(
                                file,
                                expected_current,
                                content,
                                "active_exchange_prompt_marker",
                            )
                            .map(|write| write.map(|write| write.delivery_converged))
                        },
                    )
                }
                Err(err) => ActivePromptMarkerProjection::SkippedError(err.to_string()),
            };
            let status = match &projection {
                ActivePromptMarkerProjection::Unchanged => "unchanged",
                ActivePromptMarkerProjection::Applied {
                    delivery_converged: true,
                } => "applied_acknowledged",
                ActivePromptMarkerProjection::Applied {
                    delivery_converged: false,
                } => "applied_ack_pending",
                ActivePromptMarkerProjection::SkippedDetached => "skipped_detached",
                ActivePromptMarkerProjection::SkippedError(_) => "skipped_error",
            };
            let error = match &projection {
                ActivePromptMarkerProjection::SkippedError(error) => {
                    format!(" error={error:?}")
                }
                _ => String::new(),
            };
            agent_doc_ops_log_io::log_op(
                file,
                &format!(
                    "active_exchange_prompt_marker_projection file={} targets={} marker=🚧 prefix=❯ status={status} durable_intent=false{error}",
                    file.display(),
                    active_exchange_prompt_targets.len()
                ),
            );
            if matches!(
                projection,
                ActivePromptMarkerProjection::SkippedDetached
                    | ActivePromptMarkerProjection::SkippedError(_)
            ) {
                eprintln!(
                    "[preflight] warning: active exchange prompt marker projection {status}; continuing because the marker is transient"
                );
            }
        }
    }
    // `#qgoalstall`: only a prompt authored OUTSIDE the active queue preempts the
    // drain. An operator adding a directive to `agent:queue` under go mode is
    // queueing work, not competing with the queue, and must not stall it.
    let exchange_prompt_preempts_queue =
        agent_doc_workflow::session_cycle::prompt_changes_preempt_queue(
            &user_intent_prompt_changes,
            queue_state.queue_active == Some(true),
            &queue_state.queue_prompts,
        );

    // #codex-owned-pane-prompt-miss-followups: surface a structured owner-pane
    // self-invocation contract so Codex guidance can drive an in-pane response
    // cycle. Non-null only under a Codex owner-pane self-invocation with
    // unresolved exchange work (an unanswered prompt or a ready auto-queue head).
    let owned_pane_self_invocation = {
        // Derive the unresolved prompt from this cycle's diff (prompt-target
        // change) rather than the boundary-keyed exchange detector: preflight's
        // commit has already inserted a trailing boundary, which would hide a
        // freshly-committed prompt from `unresolved_exchange_prompt`.
        let unresolved_prompt = user_intent_prompt_changes
            .iter()
            .find(|change| {
                matches!(
                    change.kind,
                    agent_doc_diff::PromptBearingChangeKind::PromptTarget
                )
            })
            .map(|change| change.text.clone());
        let suppress_active_queue_head = exchange_prompt_preempts_queue
            || (!diff_from_queue_head_only
                && !prompt_bearing_changes.is_empty()
                && user_intent_prompt_changes.is_empty()
                && (prompt_edit_independent_of_active_turn
                    || op_affectedness.as_ref().is_some_and(|affectedness| {
                        !affectedness.turn_affected && !affectedness.classified.is_empty()
                    })));
        match agent_doc_frontmatter_io::session::parse_for_file_with_context(
            &model_source_content,
            file,
            &rc.ssh_context(),
        ) {
            Ok((owner_fm, _)) => match owner_fm.session.as_deref() {
                Some(session_id) => {
                    let agent_name = owner_fm.agent.as_deref().unwrap_or("claude");
                    agent_doc_run_io::detect_owned_pane_self_invocation_with_options(
                        file,
                        session_id,
                        agent_name,
                        unresolved_prompt,
                        agent_doc_workflow::owner_pane_self_invocation::OwnedPaneSelfInvocationOptions {
                            suppress_active_queue_head,
                        },
                    )
                    .unwrap_or(None)
                }
                None => None,
            },
            Err(_) => None,
        }
    };

    let pipeline = resolve_pipeline_state(file)?;

    // `#wd40` / `#staleloop-recycle-restart`: a stale route-owned supervisor that
    // can never reach its own recycle boundary during a continuously self-draining
    // session asks the in-session loop to yield. While that request is live, drop
    // `queue_continuation_required` so the loop ends its turn (the idle boundary
    // lets the `execve` recycle fire), and surface the recycle-yield guidance so
    // the agent understands the yield is intentional and resumes after recycle.
    let recycle_yield_pending =
        agent_doc_controller_io::project_controller::supervisor_recycle_yield_pending_for_file(
            file,
        );
    // `#queueblockquoteintent`: do NOT pre-fold the preemption into
    // `raw_required` — a stop with heads remaining must name its reason instead
    // of looking identical to a drained queue.
    let effective_continuation =
        agent_doc_queue::queue_continuation::effective_continuation_output_with_preemption(
            queue_state.queue_continuation_required,
            exchange_prompt_preempts_queue,
            recycle_yield_pending,
            queue_state.queue_pause_reason.as_deref(),
        );
    let queue_continuation_required = effective_continuation.required;
    let queue_continuation_guidance = effective_continuation.guidance;
    preflight_read_facts.queue.continuation_required = queue_continuation_required;
    preflight_read_facts.queue.continuation_guidance = queue_continuation_guidance.clone();
    preflight_reads.observe(preflight_read_facts.clone());
    let preflight_read_projection = if !controller_projection_enabled {
        preflight_reads.projection()
    } else {
        agent_doc_controller_io::project_controller::preflight_read_projection(
            &controller_project_root,
            file,
            &preflight_read_facts,
        )?
    };

    // `#qstallguard` Layer B: reconcile the continuation-pending projection recorded by
    // the prior clean closeout (session-check). If drainable work remained, the
    // loop did not continue (no fresh drain-owner lease), and no valid stop reason
    // applies, the previous turn STALLED the queue — emit a hard diagnostic instead
    // of letting the silent stall pass. The projection is one-shot: every branch
    // clears it so the diagnostic fires once per stall, not every preflight.
    {
        let file_str = file.to_string_lossy().to_string();
        if agent_doc_controller_io::project_controller::queue_drain_stall_continuation_pending_for_file(file)
            .ok()
            .flatten()
            .is_some()
        {
            let now = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .map(|d| d.as_secs())
                .unwrap_or(0);
            let facts = StallFacts {
                continuation_pending_projection: true,
                continuation_required_now: queue_continuation_required,
                drainable_head_count: queue_state.queue_drainable_head_count,
                user_prompt_preempts: !user_intent_prompt_changes.is_empty(),
                queue_stopped: queue_state.queue_active != Some(true)
                    || queue_state.queue_halted.is_some(),
                // `#queuestallloopfalsepositive`: the STALL grace, not the
                // ownership TTL. A healthy self-paced cycle outlives the 90s
                // ownership window, so asking `fresh_loop_drain_owner_lease` here
                // reported every normal drain as a stall.
                loop_is_continuing:
                    agent_doc_queue_io::drain_owner::loop_drain_owner_lease_within_stall_grace(
                        &file_str, now,
                    )
                    .is_some(),
            };
            if let StallVerdict::Stalled(message) = classify_stall(&facts) {
                if !options.probe {
                    agent_doc_ops_log_io::log_op(file, &message);
                }
                warnings.push(PreflightWarning {
                    code: "queue_stall_detected".to_string(),
                    message,
                    document_agent: None,
                    active_harness: None,
                });
            }
            // One-shot: clear the projection on every reconciliation outcome.
            if !options.probe {
                let _ = agent_doc_controller_io::project_controller::clear_queue_drain_stall_continuation_pending_for_file(
                    file,
                    "preflight_reconciled",
                );
            }
        }
    }

    let projected_linked_changes = preflight_read_projection
        .related_documents
        .iter()
        .map(|change| agent_doc_preflight_io::RelatedDocChange {
            path: change.path.clone(),
            summary: change.summary.clone(),
            exists: change.exists,
        })
        .collect();
    let projected_queue_trigger = preflight_read_projection
        .queue
        .trigger
        .clone()
        .map(serde_json::from_value)
        .transpose()
        .context("failed to decode derived preflight queue trigger")?;
    let projected_session_accretion = preflight_read_projection
        .session_accretion
        .clone()
        .map(serde_json::from_value)
        .transpose()
        .context("failed to decode derived preflight session accretion")?;
    let output = PreflightOutput {
        warnings,
        layout_issues,
        recovered,
        committed,
        response_contract,
        claims: preflight_read_projection.claims.clone(),
        diff: preflight_read_projection.diff.clone(),
        no_changes,
        linked_changes: projected_linked_changes,
        diff_type: diff_type_str.clone(),
        diff_type_reason: classification.map(|c| c.diff_type_reason),
        annotated_diff,
        semantic_diff,
        turn_scope,
        op_affectedness,
        user_intent_prompt_changes,
        inline_annotations,
        slash_commands,
        builtin_commands,
        orchestration_request,
        prompt_presets_requested,
        explicit_backlog_targets: explicit_backlog_target_paths,
        effective_tier: preflight_read_projection.tiers.effective.clone(),
        required_tier: preflight_read_projection.tiers.required.clone(),
        suggested_tier: preflight_read_projection.tiers.suggested.clone(),
        model_switch: model_switch_name,
        model_switch_tier: model_switch_tier.map(|t| t.to_string()),
        pending_callbacks,
        owned_pane_self_invocation,
        env: frontmatter_env,
        backlog_reordered,
        backlog_gated_count,
        review_count: pending_report.review_count,
        review_gated_count: pending_report.review_gated_count,
        gate_verify: gate_verify_results,
        agent_model: preflight_read_projection.tiers.agent_model.clone(),
        queue_prompts: preflight_read_projection.queue.prompts.clone(),
        selected_queue_prompts: if exchange_prompt_preempts_queue {
            Vec::new()
        } else {
            preflight_read_projection.queue.selected_prompts.clone()
        },
        queue_active: preflight_read_projection.queue.active,
        queue_deferred: preflight_read_projection.queue.deferred,
        queue_start_at: preflight_read_projection.queue.start_at.clone(),
        queue_trigger: projected_queue_trigger,
        queue_halted: preflight_read_projection.queue.halted.clone(),
        queue_paused: preflight_read_projection.queue.paused,
        queue_pause_reason: preflight_read_projection.queue.pause_reason.clone(),
        queue_drainable_head_count: preflight_read_projection.queue.drainable_head_count,
        queue_continuation_required: preflight_read_projection.queue.continuation_required,
        queue_continuation_guidance: preflight_read_projection
            .queue
            .continuation_guidance
            .clone(),
        session_accretion: projected_session_accretion,
        pipeline,
    };

    let json =
        serde_json::to_string_pretty(&output).context("failed to serialize preflight output")?;
    writeln!(writer, "{}", json)?;

    Ok(())
}

fn commit_previous_cycle_for_preflight(file: &Path) -> Result<bool> {
    match agent_doc_commit_io::commit(file) {
        Ok(did_commit) => Ok(did_commit),
        Err(err)
            if preflight_commit_error_is_live_editor_pending(&err)
                && !agent_doc_git_io::status::focused_tracked_file_modified(file)? =>
        {
            eprintln!(
                "[preflight] commit: live editor convergence pending but focused file is clean; closing no-op cycle from disk authority"
            );
            agent_doc_commit_io::commit_for_authority(file, true)
        }
        Err(err) => Err(err),
    }
}

fn preflight_commit_error_is_live_editor_pending(err: &anyhow::Error) -> bool {
    let message = format!("{err:#}");
    message.contains("CRDT relay convergence is still pending")
        || message.contains("editor_sync_pending")
        || message.contains("editor_attached_model_missing")
}

fn retain_preflight_controller_projection(
    file: &Path,
    target: &str,
    expected_current: &str,
    source: &str,
) -> Result<bool> {
    match agent_doc_document_realtime_io::atomic_write_if_current_through_authority(
        file,
        target,
        expected_current,
        source,
    ) {
        Ok(()) => Ok(false),
        Err(err)
            if err
                .downcast_ref::<agent_doc_document_realtime_io::AwaitEditorReplicaNoDiskWrite>()
                .is_some() =>
        {
            agent_doc_ops_log_io::log_op(
                file,
                &format!(
                    "{source}_retained file={} target_hash={} driver=lazy_projection",
                    file.display(),
                    agent_doc_hash::content_hash(target),
                ),
            );
            Ok(true)
        }
        Err(err) => Err(err.context(format!(
            "{source}: failed to retain controller projection for {}",
            file.display(),
        ))),
    }
}

fn converge_exact_document_replay_before_preflight(file: &Path) -> Result<(String, Option<usize>)> {
    let current = resolve_current_preflight_document(file, "exact_document_replay_check")?;
    let Some(replay) =
        agent_doc_document_realtime::write_policy::coalesce_exact_document_replay(&current)
    else {
        return Ok((current, None));
    };
    let copies = replay.copies;
    let canonical = replay.canonical.to_string();
    eprintln!(
        "[preflight] coalescing {copies} whole-document replay projections through CRDT while retaining monotonic live additions"
    );
    agent_doc_ops_log_io::log_op(
        file,
        &format!(
            "preflight_exact_document_replay action=coalesce transport=crdt copies={} replay_len={} canonical_len={} replay_hash={} canonical_hash={}",
            copies,
            current.len(),
            canonical.len(),
            agent_doc_hash::content_hash(&current),
            agent_doc_hash::content_hash(&canonical),
        ),
    );
    let retained = retain_preflight_controller_projection(
        file,
        &canonical,
        &current,
        "preflight_exact_document_replay",
    )?;
    let converged = resolve_current_preflight_document(file, "exact_document_replay_converged")?;
    anyhow::ensure!(
        agent_doc_document_realtime::write_policy::coalesce_exact_document_replay(&converged)
            .is_none(),
        "exact whole-document replay remained after CRDT convergence"
    );
    agent_doc_ops_log_io::log_op(
        file,
        &format!(
            "preflight_exact_document_replay action={} transport=crdt driver=lazy_projection copies={} canonical_len={} canonical_hash={}",
            if retained { "retained" } else { "converged" },
            copies,
            converged.len(),
            agent_doc_hash::content_hash(&converged),
        ),
    );
    Ok((converged, Some(copies)))
}

#[cfg(test)]
mod tests {
    #![allow(unused_imports)]
    use super::*;
    use agent_doc_document_realtime::write_policy::ipc_snapshot_would_absorb_live_prompt_drift_after_preflight;
    use std::io::Write;
    use std::process::Command;
    use tempfile::TempDir;

    #[test]
    fn active_prompt_marker_ack_delay_is_non_fatal_and_non_durable() {
        let authoritative =
            "<!-- agent:exchange patch=append -->\nPrompt\n<!-- /agent:exchange -->\n";
        let marked =
            "<!-- agent:exchange patch=append -->\n❯ 🚧 Prompt\n<!-- /agent:exchange -->\n";
        let mut projections = 0usize;
        let outcome = project_active_prompt_marker_with(authoritative, marked, |expected, next| {
            projections += 1;
            assert_eq!(expected, authoritative);
            assert_eq!(next, marked);
            Ok(Some(false))
        });

        assert_eq!(
            projections, 1,
            "the cosmetic path must make one projection only"
        );
        assert_eq!(
            outcome,
            ActivePromptMarkerProjection::Applied {
                delivery_converged: false
            }
        );
    }

    #[test]
    fn active_prompt_marker_projection_error_is_a_skip() {
        let outcome = project_active_prompt_marker_with("Prompt", "❯ 🚧 Prompt", |_, _| {
            anyhow::bail!("editor unavailable")
        });
        assert_eq!(
            outcome,
            ActivePromptMarkerProjection::SkippedError("editor unavailable".to_string())
        );
    }

    #[test]
    fn closeout_cycle_is_open_uses_terminal_projection() {
        let dir = TempDir::new().unwrap();
        std::fs::create_dir_all(dir.path().join(".agent-doc")).unwrap();
        let doc = dir.path().join("session.md");
        let content = "---\nsession: test-session\n---\n\ncontent";
        std::fs::write(&doc, content).unwrap();

        let opened =
            agent_doc_cycle_state_io::start_preflight(&doc, Some(content), Some(content)).unwrap();
        agent_doc_cycle_state_io::mark_committed(
            &doc,
            "commit_success",
            Some(content),
            Some(content),
        )
        .unwrap();
        assert_eq!(
            agent_doc_cycle_state_io::load(&doc).unwrap().unwrap().phase,
            agent_doc_turn::CyclePhase::Committed
        );
        assert_eq!(
            agent_doc_cycle_state_io::load_with_closeout_projection(&doc)
                .unwrap()
                .unwrap()
                .cycle_id,
            opened.cycle_id
        );
        assert!(
            !closeout_cycle_is_open(&doc).unwrap(),
            "preflight should trust the terminal closeout projection"
        );
    }

    #[test]
    fn preflight_produces_valid_json() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        std::fs::write(&doc, "---\nsession: test\n---\n\n## User\n\nHello\n").unwrap();

        // Snapshot matches document → no_changes = true.
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            &std::fs::read_to_string(&doc).unwrap(),
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();
        // If run() returns Ok(()), the JSON was printed to stdout without error.
        // The test verifies no panic and no error return.
    }

    #[test]
    fn preflight_boundary_coalesces_exact_document_replay_through_crdt() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let canonical = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "operator text\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:done -->\n",
            "<!-- /agent:done -->\n",
        );
        let replayed = canonical.repeat(2);
        std::fs::write(&doc, &replayed).unwrap();
        agent_doc_test_support::publish_editor_text_via_crdt_relay(
            &doc,
            "preflight-replay-test",
            &replayed,
        );

        let (settled, copies) = converge_exact_document_replay_before_preflight(&doc).unwrap();

        assert_eq!(copies, Some(2));
        assert_eq!(settled, canonical);
        let disk = std::fs::read_to_string(&doc).unwrap();
        assert!(
            disk == replayed || disk == canonical,
            "disk may trail the retained projection or contain the editor's exact native save",
        );
        let ops = std::fs::read_to_string(dir.path().join(".agent-doc/logs/ops.log")).unwrap();
        assert!(ops.contains("preflight_exact_document_replay action="));
        assert!(ops.contains("transport=crdt driver=lazy_projection"));
        if disk == replayed {
            assert!(
                agent_doc_document_realtime_io::pending_document_write(&doc).is_some(),
                "a trailing disk projection must retain the exact controller target",
            );
        }

        std::fs::remove_file(&doc).unwrap();
    }

    #[test]
    fn preflight_persists_durable_turn_checkpoint() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let base = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- agent:boundary:abc -->\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#durablerecycle] Durable recycle\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, base).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            base,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(dir.path())
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(dir.path())
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let live = base.replace(
            "<!-- agent:boundary:abc -->\n",
            "do [#durablerecycle]\n<!-- agent:boundary:abc -->\n",
        );
        std::fs::write(&doc, &live).unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(state.phase, agent_doc_turn::CyclePhase::PreflightStarted);
        assert_eq!(state.queue_task_id.as_deref(), Some("#durablerecycle"));
        assert_eq!(state.turn_id.as_deref(), Some("#durablerecycle"));
        assert!(
            state
                .prompt_targets
                .iter()
                .any(|target| target.contains("do [#durablerecycle]")),
            "prompt targets should persist the active prompt: {:?}",
            state.prompt_targets
        );
        assert_eq!(
            agent_doc_snapshot_io::load_document_baseline(&doc)
                .unwrap()
                .as_deref(),
            Some(base),
            "preflight must not advance the merge baseline across an unclosed prompt"
        );
        assert_eq!(
            state.file_hash.as_deref(),
            Some(agent_doc_hash::content_hash(&live).as_str()),
            "cycle state should checkpoint the exact current projection"
        );
    }

    #[test]
    fn preflight_fails_closed_when_required_ssh_doc_mapping_resolves_no_targets() {
        let dir = setup_project();
        std::fs::create_dir_all(dir.path().join("tasks")).unwrap();
        std::fs::write(
            dir.path().join(".agent-doc/config.toml"),
            "[ssh.docs.\"tasks/sampleorders.md\"]\nprofile = \"missing\"\n",
        )
        .unwrap();
        let doc = dir.path().join("tasks/sampleorders.md");
        std::fs::write(&doc, "---\nagent: codex\n---\n\n## User\n\nHello\n").unwrap();

        let err = run(&doc).unwrap_err();
        assert!(err.to_string().contains("requires SSH profile `missing`"));
    }
    #[test]
    fn preflight_fails_closed_on_uncommitted_closeout_drift_even_without_diff() {
        let dir = setup_project();
        let root = dir.path();
        std::fs::create_dir_all(root.join("news/2026-05-01")).unwrap();

        let doc = root.join("session.md");
        let news_index = root.join("news/README.md");
        let news_day = root.join("news/2026-05-01/README.md");
        let old_doc = "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n## Exchange\n\n<!-- agent:exchange patch=append -->\nold body\n<!-- /agent:exchange -->\n";
        std::fs::write(&doc, old_doc).unwrap();
        std::fs::write(&news_index, "old news index\n").unwrap();
        std::fs::write(&news_day, "old news day\n").unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            old_doc,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args([
                "add",
                "session.md",
                "news/README.md",
                "news/2026-05-01/README.md",
            ])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "initial", "--no-verify"])
            .output()
            .unwrap();

        let new_doc = "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n## Exchange\n\n<!-- agent:exchange patch=append -->\nold body\n### Re: create today's news — codex\nresponse\n<!-- /agent:exchange -->\n";
        std::fs::write(&doc, new_doc).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            new_doc,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        std::fs::write(&news_index, "new news index\n").unwrap();
        std::fs::write(&news_day, "new news day\n").unwrap();

        let err =
            run(&doc).expect_err("preflight should fail before diffing hidden closeout drift");
        let message = err.to_string();
        assert!(message.contains("snapshot differs from HEAD"));
        assert!(message.contains("tracked side-effect edits"));
        assert!(message.contains("news/README.md"));
        assert!(message.contains("news/2026-05-01/README.md"));
        assert!(message.contains("agent-doc write --commit"));
    }
    #[test]
    fn preflight_fails_closed_on_uncommitted_exchange_drift_without_response_heading() {
        let dir = setup_project();
        let root = dir.path();

        let doc = root.join("sampleorders.md");
        let committed = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "❯ deploy v0.4.9\n",
            "### Re: shopcozi mobile CSS fix — glm-5.1\n\n",
            "Patched the mobile CSS and deployed v0.4.9.\n",
            "<!-- /agent:exchange -->\n",
        );
        std::fs::write(&doc, committed).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            committed,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "sampleorders.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "initial", "--no-verify"])
            .output()
            .unwrap();

        let dirty = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "❯ deploy v0.4.9\n",
            "### Re: shopcozi mobile CSS fix — glm-5.1\n\n",
            "Patched the mobile CSS and deployed v0.4.9.\n\n",
            "Verification:\n",
            "- npm test\n",
            "- docker compose run post-deploy\n",
            "<!-- /agent:exchange -->\n",
        );
        std::fs::write(&doc, dirty).unwrap();

        let err = run(&doc).expect_err("preflight should block uncommitted exchange drift");
        let message = err.to_string();
        assert!(message.contains("uncommitted exchange changes"));
        assert!(message.contains("agent-doc write --commit"));
        assert!(
            !message.contains("snapshot differs from HEAD"),
            "body-only exchange drift should be diagnosed before generic snapshot drift: {message}"
        );
    }
    #[test]
    fn preflight_file_not_found() {
        let err = run(Path::new("/nonexistent/missing.md")).unwrap_err();
        assert!(err.to_string().contains("file not found"));
    }
    #[test]
    fn preflight_closes_stale_starting_actors_even_when_daily_gc_state_is_fresh() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = "---\nagent_doc_session: test\n---\n\n## User\n\nHello\n";
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        agent_doc_preflight_io::gc::record_auto_gc_run(
            dir.path(),
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_secs(),
        )
        .unwrap();

        let stale_doc = dir.path().join("tasks/stale-starting.md");
        std::fs::create_dir_all(stale_doc.parent().unwrap()).unwrap();
        std::fs::write(&stale_doc, "body").unwrap();
        let stale_record = agent_doc_controller::actor::ActorRecord {
            document_id: stale_doc.to_string_lossy().to_string(),
            session_id: "session-stale-starting".to_string(),
            generation: 1,
            pane_id: "%71".to_string(),
            window_id: "@7".to_string(),
            harness: "codex".to_string(),
            state: agent_doc_controller::actor::ActorState::Starting,
            last_transition: agent_doc_controller::actor::ActorLastTransition {
                caller: "start".to_string(),
                reason: "session_start".to_string(),
                timestamp: 1,
                prior_generation: 0,
                new_generation: 1,
            },
        };
        agent_doc_controller_io::project_controller::store_actor_record(
            dir.path(),
            Some(0),
            &stale_record,
        )
        .unwrap();

        run(&doc).unwrap();

        let updated = agent_doc_controller_io::project_controller::load_actor_record(
            dir.path(),
            &stale_record.document_id,
        )
        .unwrap()
        .unwrap();
        assert_eq!(
            updated.state,
            agent_doc_controller::actor::ActorState::Closed
        );
        assert_eq!(updated.last_transition.caller, "preflight");
        assert_eq!(updated.last_transition.reason, "stale_starting_actor");
    }
    #[test]
    fn preflight_opens_cycle_from_harness_prompt_when_document_has_no_diff() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "prompt_presets:\n",
            "  '#code-review': Please review the codebase. '#follow-up-backlog'\n",
            "  '#follow-up-backlog': Any follow-up items to place in the backlog?\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- /agent:exchange -->\n\n",
            "## Backlog\n\n",
            "<!-- agent:backlog -->\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        let _prompt = EnvGuard::set(
            "AGENT_DOC_HARNESS_PROMPT",
            &format!("agent-doc {} #code-review", doc.display()),
        );

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(state.phase, agent_doc_turn::CyclePhase::PreflightStarted);
        assert!(
            state.requires_backlog_capture,
            "harness prompt preset expansion should record backlog capture requirement"
        );
    }
    #[test]
    fn preflight_opens_cycle_from_active_queue_when_document_has_no_diff() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue_active: true\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue auto go -->\n",
            "- do [#oobpmt]\n",
            "<!-- /agent:queue -->\n\n",
            "## Backlog\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#oobpmt] Fix OOB prompt absorption.\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(
            state.phase,
            agent_doc_turn::CyclePhase::PreflightStarted,
            "active queue prompt should open a cycle even when the file matches the snapshot"
        );
    }

    #[test]
    fn preflight_non_active_queue_edit_updates_future_state_without_retargeting_turn() {
        // A settled edit to a non-selected queue head must update the future queue
        // state, but it must not become this turn's prompt while the selected
        // head remains unchanged.
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let snapshot_content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue_active: true\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue go -->\n",
            "- 🚧 do [#active]\n",
            "- do [#future-old]\n",
            "<!-- /agent:queue -->\n\n",
            "## Backlog\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#active] active work\n",
            "- [ ] [#future-new] future work\n",
            "<!-- /agent:backlog -->\n"
        );
        let current_content = snapshot_content.replace("do [#future-old]", "do [#future-new]");
        std::fs::write(&doc, &current_content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot_content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(state.queue_task_id.as_deref(), Some("#active"));
        assert!(
            state
                .prompt_targets
                .iter()
                .all(|target| !target.contains("#future-new")),
            "future queue edit must not retarget the active turn: {:?}",
            state.prompt_targets
        );
        let snap = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert!(
            snap.contains("do [#future-new]"),
            "snapshot must adopt the future queue edit for later turns:\n{snap}"
        );
    }

    #[test]
    fn preflight_auto_dag_queue_edit_that_changes_selected_head_affects_turn() {
        // The exception to non-active isolation: if a future queue edit changes
        // the auto-DAG's selected head, the current turn must follow the new
        // selected head.
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let snapshot_content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue_active: true\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue priority go -->\n",
            "- 🚧 do [#active] after=#blocker\n",
            "- do [#old]\n",
            "<!-- /agent:queue -->\n\n",
            "## Backlog\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#active] active work after=#blocker\n",
            "- [ ] [#blocker] blocker work\n",
            "<!-- /agent:backlog -->\n"
        );
        let current_content = snapshot_content.replace("do [#old]", "do [#blocker]");
        std::fs::write(&doc, &current_content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot_content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();

        let updated = std::fs::read_to_string(&doc).unwrap();
        assert!(
            updated.contains("- 🚧 do [#blocker]\n- do [#active] after=#blocker"),
            // `#pinoperatoronly`: auto-DAG expresses order by POSITION; it no
            // longer injects an agent pin marker onto the promoted head.
            "auto-DAG must reselect the blocker as the active head:\n{updated}"
        );
        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(state.queue_task_id.as_deref(), Some("#blocker"));
    }

    #[test]
    fn preflight_exchange_edit_stays_active_turn_affecting_with_future_queue_edit() {
        // Exchange edits are direct turn input. Even when the same edit also
        // updates a non-selected future queue head, the exchange delta must keep
        // the turn scoped to the operator's latest exchange text instead of being
        // absorbed as future queue state.
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let snapshot_content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue_active: true\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue go -->\n",
            "- 🚧 do [#active]\n",
            "- do [#future-old]\n",
            "<!-- /agent:queue -->\n\n",
            "## Backlog\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#active] active work\n",
            "- [ ] [#future-new] future work\n",
            "<!-- /agent:backlog -->\n"
        );
        let current_content = snapshot_content
            .replace("do [#future-old]", "do [#future-new]")
            .replace(
                "Done.\n<!-- /agent:exchange -->",
                "Done.\n\nOperator follow-up.\n<!-- /agent:exchange -->",
            );
        std::fs::write(&doc, &current_content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot_content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_ne!(
            state.queue_task_id.as_deref(),
            Some("#active"),
            "exchange edit must not be collapsed into queue-head-only continuation"
        );
        assert!(
            state
                .prompt_targets
                .iter()
                .any(|target| target.contains("Operator follow-up")),
            "exchange edit must remain prompt-bearing: {:?}",
            state.prompt_targets
        );
    }
    #[test]
    fn preflight_unresolved_exchange_tail_preempts_queue_even_when_snapshot_matches() {
        // #prompt-preempts-auto-queue: a live unresolved exchange prompt is
        // authoritative turn input even when it was already mirrored into the
        // snapshot, leaving ordinary diff with no user-prompt delta. Preflight
        // must synthesize the exchange prompt instead of selecting the active
        // queue head.
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue: start\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior - gpt-5\n\n",
            "Done.\n",
            "<!-- agent:boundary:test -->\n",
            "Please answer this exchange prompt before the queue.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue priority preset=\"#spec-test-build-install-commit-push\" -->\n",
            "- do [#active]\n",
            "<!-- /agent:queue -->\n\n",
            "## Backlog\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#active] active work\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_ne!(
            state.queue_task_id.as_deref(),
            Some("#active"),
            "baselined unresolved exchange prompt must preempt active queue head"
        );
        assert!(
            state
                .prompt_targets
                .iter()
                .any(|target| target
                    .contains("Please answer this exchange prompt before the queue.")),
            "preflight should synthesize the unresolved exchange prompt: {:?}",
            state.prompt_targets
        );
    }
    #[test]
    fn preflight_does_not_open_cycle_from_active_queue_slash_command() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue_active: true\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue auto go -->\n",
            "-   /clear  \n",
            "<!-- /agent:queue -->\n"
        );
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();

        if let Some(state) = agent_doc_cycle_state_io::load(&doc).unwrap() {
            assert!(
                !state.is_open(),
                "slash-only active queue heads must be supervisor handoffs, not response cycles: {:?}",
                state
            );
        }
        assert_eq!(
            agent_doc_queue_io::queue_continuation::detect(&doc)
                .unwrap()
                .map(|continuation| continuation.head_prompt),
            Some("  /clear  ".to_string()),
            "the literal queue head must stay live for the supervisor"
        );
    }
    #[test]
    fn preflight_probe_is_read_only_even_with_dispatchable_queue_work() {
        // #preflight-probe-side-effect-free: the SAME active-queue input that
        // opens a `preflight_started` cycle in the dispatch path (see
        // `preflight_opens_cycle_from_active_queue_when_document_has_no_diff`)
        // must leave no document, snapshot, baseline, claims, or cycle mutation
        // when run as a pure inspection probe, so a diagnostic preflight never
        // wedges a later `session-check`.
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue: go\n",
            "queue_active: true\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue -->\n",
            "<!-- /agent:queue -->\n\n",
            "## Backlog\n\n",
            "<!-- agent:backlog priority queue -->\n",
            "- [ ] [#oobpmt] Fix OOB prompt absorption.\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        let snapshot_before = agent_doc_snapshot_io::load_document_baseline(&doc).unwrap();
        let claims_log = dir.path().join(".agent-doc/claims.log");
        std::fs::write(&claims_log, "claim-one\n").unwrap();

        run_with_options(&doc, PreflightOptions { probe: true }).unwrap();

        assert_eq!(std::fs::read_to_string(&doc).unwrap(), content);
        assert_eq!(
            agent_doc_snapshot_io::load_document_baseline(&doc).unwrap(),
            snapshot_before
        );
        assert_eq!(std::fs::read_to_string(&claims_log).unwrap(), "claim-one\n");
        assert!(
            agent_doc_cycle_state_io::load(&doc).unwrap().is_none(),
            "probe must not create or advance cycle state"
        );
    }
    #[test]
    fn queue_convergence_realigns_baseline_so_finalize_sees_no_false_drift() {
        // #qconvbaseline regression: when queue maintenance converges the live
        // buffer to a corrected queue shape, the baseline saved BEFORE maintenance
        // (pre-convergence) makes every finalize read the converged queue as
        // `live_prompt_drift_after_preflight`. Re-aligning the baseline with the
        // post-maintenance snapshot must clear that false drift while a genuine
        // user edit would still trip the guard.
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let pre = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue: go\n",
            "queue_active: true\n",
            "---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\nDone.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue go -->\n",
            "<!-- /agent:queue -->\n\n",
            "<!-- agent:backlog priority queue -->\n",
            "- [ ] [#alpha] run the alpha task\n",
            "<!-- /agent:backlog -->\n",
        );
        std::fs::write(&doc, pre).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            pre,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        // Simulate run.rs:570 — baseline captured from pre-maintenance content.
        checkpoint_baseline_content(&doc, pre);

        // Queue maintenance mirrors the backlog `queue`-attr id into the queue and
        // updates the snapshot to that converged shape.
        let _ = run_queue_maintenance(&doc, None).unwrap();
        let converged = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert_ne!(
            converged, pre,
            "precondition: queue maintenance must have converged the queue shape"
        );

        // Helper: splice an agent response into the exchange (no user edit).
        let with_response = |d: &str| {
            d.replace(
                "<!-- /agent:exchange -->",
                "### Re: do [#alpha] — gpt-5\n\nAnswered.\n<!-- /agent:exchange -->",
            )
        };
        let candidate = with_response(&converged); // editor buffer + response
        let content_ours_pre = with_response(pre); // OLD baseline + response

        // The bug: with the pre-maintenance baseline, the converged queue reads as drift.
        assert!(
            ipc_snapshot_would_absorb_live_prompt_drift_after_preflight(
                pre,
                &candidate,
                &content_ours_pre,
            ),
            "pre-maintenance baseline must (incorrectly) flag the convergence as drift"
        );

        // The fix: re-align the baseline by splicing the converged queue into the
        // pre-maintenance content (preserving exchange/boundary).
        let realigned = realign_baseline_to_converged_queue(pre, &converged)
            .expect("queue convergence delta must produce a re-aligned baseline");
        checkpoint_baseline_content(&doc, &realigned);
        let baseline_after = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .expect("re-aligned state.db baseline");
        assert!(
            baseline_after.contains("[#alpha]"),
            "re-aligned baseline must carry the converged queue shape"
        );
        let content_ours_aligned = with_response(&realigned);
        assert!(
            !ipc_snapshot_would_absorb_live_prompt_drift_after_preflight(
                &realigned,
                &candidate,
                &content_ours_aligned,
            ),
            "re-aligned baseline must NOT flag the queue convergence as drift"
        );

        // Guard preserved: a GENUINE concurrent user edit still trips the drift guard.
        let user_edited = candidate.replace(
            "<!-- /agent:exchange -->",
            "❯ a brand new user prompt typed mid-turn\n<!-- /agent:exchange -->",
        );
        assert!(
            ipc_snapshot_would_absorb_live_prompt_drift_after_preflight(
                &realigned,
                &user_edited,
                &content_ours_aligned,
            ),
            "a real concurrent user prompt edit must STILL be detected as drift"
        );
    }

    #[test]
    fn run_queue_maintenance_does_not_sync_icebox_into_empty_queue() {
        // Parked icebox work must not become the next active prompt just because the
        // queue and backlog are drained. Move the item to backlog or mark the item
        // with an explicit enqueue token when it should run.
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue: go\n",
            "queue_active: true\n",
            "---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\nDone.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue -->\n",
            "<!-- /agent:queue -->\n\n",
            "<!-- agent:backlog queue=append -->\n",
            "<!-- /agent:backlog -->\n\n",
            "<!-- agent:icebox queue=append -->\n",
            "- [ ] [#parked] parked follow-up\n",
            "<!-- /agent:icebox -->\n",
        );
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        let state = run_queue_maintenance(&doc, None).unwrap();
        let updated = std::fs::read_to_string(&doc).unwrap();

        assert!(
            !updated.contains("- do [#parked]"),
            "icebox queue attr must not auto-populate a drained queue:\n{updated}"
        );
        assert!(
            state.synced_queue_ids.is_empty(),
            "icebox ids must not be reported as synced queue ids: {:?}",
            state.synced_queue_ids
        );
    }
    #[test]
    fn run_queue_maintenance_normalizes_boolean_true_queue_attrs() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\nDone.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue priority=true preset=\"#spec-test-build-install-commit-push\"=true go=true -->\n",
            "- do [#alpha]\n",
            "<!-- /agent:queue -->\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#alpha] run the alpha task\n",
            "<!-- /agent:backlog -->\n",
        );
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        let state = run_queue_maintenance(&doc, None).unwrap();
        let updated = std::fs::read_to_string(&doc).unwrap();

        assert_eq!(state.queue_active, Some(true));
        assert!(
            updated.contains(
                "<!-- agent:queue priority preset=\"#spec-test-build-install-commit-push\" go -->"
            ),
            "queue tag should be canonical:\n{updated}"
        );
        assert!(
            !updated.contains("=true"),
            "malformed attrs repaired:\n{updated}"
        );

        let snap = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert!(
            snap.contains(
                "<!-- agent:queue priority preset=\"#spec-test-build-install-commit-push\" go -->"
            ),
            "snapshot queue tag should be canonical:\n{snap}"
        );
        assert!(
            !snap.contains("=true"),
            "snapshot malformed attrs repaired:\n{snap}"
        );
    }
    #[test]
    fn preflight_flags_inactive_queue_when_changed_this_cycle() {
        // Counterpart guard (Scenario B): when the operator adds content to an
        // inactive queue this cycle (snapshot empty queue, file has a new live
        // item), the residue warning must still fire so the user knows the
        // `do [#id]` they added will not run while the queue is inactive.
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let snapshot_content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue_active: false\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\nDone.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue -->\n",
            "<!-- /agent:queue -->\n"
        );
        let current_content = snapshot_content.replace(
            "<!-- agent:queue -->\n<!-- /agent:queue -->",
            "<!-- agent:queue -->\n- do [#freshly-added]\n<!-- /agent:queue -->",
        );
        std::fs::write(&doc, &current_content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot_content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        let state = run_queue_maintenance(&doc, None).unwrap();

        assert!(
            state
                .warnings
                .iter()
                .any(|w| w.code == "inactive_queue_residue"),
            "inactive queue changed this cycle must warn residue: {:?}",
            state.warnings
        );
    }
    #[test]
    fn preflight_clears_completed_auto_queue_when_no_prompts_remain() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue_active: false\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue auto -->\n",
            "preset #spec-test-build-install-commit-push\n",
            "- ~do [#crossdocpend]~\n",
            "- ~do [#spfxnorm]~\n",
            "<!-- /agent:queue -->\n"
        );
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();

        let updated = std::fs::read_to_string(&doc).unwrap();
        assert!(updated.contains("<!-- agent:queue -->\n<!-- /agent:queue -->"));
        assert!(!updated.contains("agent:queue auto"));
        assert!(!updated.contains("preset #spec-test-build-install-commit-push"));
        assert!(!updated.contains("[#crossdocpend]"));
        assert!(!updated.contains("[#spfxnorm]"));
        assert!(updated.contains("queue: stop"));

        let snap = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert!(snap.contains("<!-- agent:queue -->\n<!-- /agent:queue -->"));
        assert!(!snap.contains("agent:queue auto"));
        assert!(!snap.contains("[#crossdocpend]"));
        assert!(!snap.contains("[#spfxnorm]"));
    }
    #[test]
    fn preflight_clears_completed_non_auto_queue_without_snapshot_proof() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue_active: false\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue -->\n",
            "- ~do [#item-a]~\n",
            "- ~do [#item-b]~\n",
            "<!-- /agent:queue -->\n"
        );
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();

        let updated = std::fs::read_to_string(&doc).unwrap();
        assert!(
            updated.contains("<!-- agent:queue -->\n<!-- /agent:queue -->"),
            "completed non-auto queue should be cleared without snapshot proof:\n{updated}"
        );
        assert!(!updated.contains("[#item-a]"));
        assert!(!updated.contains("[#item-b]"));
        assert!(updated.contains("queue: stop"));
    }
    #[test]
    fn preflight_does_not_clear_live_inactive_queue_without_snapshot_proof() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue_active: false\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue -->\n",
            "- ~do [#done-item]~\n",
            "- do [#still-live]\n",
            "<!-- /agent:queue -->\n"
        );
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();

        let updated = std::fs::read_to_string(&doc).unwrap();
        assert!(
            updated.contains("- do [#still-live]"),
            "queue with live prompts must not be cleared:\n{updated}"
        );
    }
    #[test]
    fn preflight_clears_completed_non_auto_queue_when_snapshot_was_active() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let snapshot_content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "queue_active: true\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue -->\n",
            "dispatch #spec-test-build-install-commit-push\n",
            "- ~do [#cspe]~\n",
            "<!-- /agent:queue -->\n"
        );
        let current_content = snapshot_content.replace("queue_active: true", "queue_active: false");
        std::fs::write(&doc, &current_content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot_content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();

        let updated = std::fs::read_to_string(&doc).unwrap();
        assert!(
            updated.contains("<!-- agent:queue -->\n<!-- /agent:queue -->"),
            "proven drained non-auto queue should be cleared:\n{updated}"
        );
        assert!(!updated.contains("dispatch #spec-test-build-install-commit-push"));
        assert!(!updated.contains("[#cspe]"));
        assert!(updated.contains("queue: stop"));

        let snap = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert!(snap.contains("<!-- agent:queue -->\n<!-- /agent:queue -->"));
        assert!(!snap.contains("dispatch #spec-test-build-install-commit-push"));
        assert!(!snap.contains("[#cspe]"));
        assert!(snap.contains("queue: stop"));
    }
    #[test]
    fn preflight_does_not_swallow_user_prose_that_mentions_head() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let baseline = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- agent:boundary:abc123 -->\n",
            "<!-- /agent:exchange -->\n"
        );
        let current = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "agent_doc_write: crdt\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- agent:boundary:abc123 -->\n",
            "`❯ ` prompt prefix is being stripped away by the uncommitted user affordance that adds the ` (HEAD)` suffix. spec-test-build-install-commit-push\n",
            "<!-- /agent:exchange -->\n"
        );
        std::fs::write(&doc, current).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            baseline,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(state.phase, agent_doc_turn::CyclePhase::PreflightStarted);
    }
    #[test]
    fn preflight_auto_commits_open_write_applied_cycle() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = "---\nsession: test\n---\n\n## User\n\nHello\n\n## Assistant\n\nAnswer\n";
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        agent_doc_cycle_state_io::mark_write_applied(
            &doc,
            "write_template",
            Some(content),
            Some(content),
        )
        .unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(state.phase, agent_doc_turn::CyclePhase::Committed);
        assert_eq!(state.last_event, "commit_success");
    }
    /// Phase 3 (#jbccc3): the jb_cache_conflict_cancel pattern leaves a cycle
    /// marked `Committed` while the snapshot still has the visible response
    /// and `HEAD` does not — the commit boundary never actually landed (e.g.
    /// the user canceled the JB File Cache Conflict dialog mid-IPC, or a
    /// sibling compact-exchange closed the cycle while a separate `finalize`
    /// race lost its write). Without recovery, `preflight` bails on the next
    /// invocation. With Phase 3, the recoverable pattern triggers an
    /// automatic `git::commit` and the cycle lands cleanly.
    #[test]
    fn preflight_auto_recovers_jb_cache_conflict_cancel_committed_with_snapshot_drift() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");

        let original = "---\nsession: test\n---\n\n## User\n\nHello\n";
        std::fs::write(&doc, original).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            original,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        // Simulate the post-cancel state: snapshot and working tree both
        // contain the response, HEAD does not, cycle is marked Committed.
        let patched = "---\nsession: test\n---\n\n## User\n\nHello\n\n## Assistant\n\nReply\n";
        std::fs::write(&doc, patched).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            patched,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        agent_doc_cycle_state_io::mark_write_applied(
            &doc,
            "write_template",
            Some(patched),
            Some(patched),
        )
        .unwrap();
        agent_doc_cycle_state_io::pipeline_frontmatter::mark_committed(
            &agent_doc_document_realtime_io::RUNTIME_PIPELINE_FRONTMATTER_EFFECTS,
            &doc,
            "commit_success",
            Some(patched),
            Some(patched),
        )
        .unwrap();
        let pre_state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(pre_state.phase, agent_doc_turn::CyclePhase::Committed);
        assert!(matches!(
            agent_doc_snapshot_io::verify_snapshot_committed(&doc).unwrap(),
            agent_doc_snapshot_io::SnapshotCommitStatus::SnapshotDiffersFromHead { .. }
        ));
        assert!(
            agent_doc_session_check_io::detect_jb_cache_conflict_cancel_recoverable(&doc).unwrap(),
            "preconditions: cancel pattern should be detected before recovery"
        );

        run(&doc).unwrap();

        assert!(matches!(
            agent_doc_snapshot_io::verify_snapshot_committed(&doc).unwrap(),
            agent_doc_snapshot_io::SnapshotCommitStatus::Committed
        ));
        let show = Command::new("git")
            .current_dir(root)
            .args(["show", "HEAD:session.md"])
            .output()
            .unwrap();
        assert!(
            String::from_utf8_lossy(&show.stdout).contains("Reply"),
            "HEAD should now contain the response after auto-recovery"
        );
    }
    /// Phase 3 (#jbccc3): the direct Cancel shape can also leave the cycle at
    /// `write_applied` rather than `committed`: the response is visible and
    /// saved in the snapshot, but the post-write commit never landed in HEAD.
    /// The next preflight must treat that as the same recoverable
    /// jb_cache_conflict_cancel pattern and close the missing commit boundary.
    #[test]
    fn preflight_auto_recovers_jb_cache_conflict_cancel_write_applied_with_snapshot_drift() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");

        let original = "---\nsession: test\n---\n\n## User\n\nHello\n";
        std::fs::write(&doc, original).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            original,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let patched = "---\nsession: test\n---\n\n## User\n\nHello\n\n## Assistant\n\nReply\n";
        std::fs::write(&doc, patched).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            patched,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        agent_doc_cycle_state_io::mark_write_applied(
            &doc,
            "write_template",
            Some(patched),
            Some(patched),
        )
        .unwrap();

        let pre_state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(pre_state.phase, agent_doc_turn::CyclePhase::WriteApplied);
        assert!(matches!(
            agent_doc_snapshot_io::verify_snapshot_committed(&doc).unwrap(),
            agent_doc_snapshot_io::SnapshotCommitStatus::SnapshotDiffersFromHead { .. }
        ));
        assert!(
            agent_doc_session_check_io::detect_jb_cache_conflict_cancel_recoverable(&doc).unwrap(),
            "preconditions: write_applied cancel pattern should be detected before recovery"
        );

        run(&doc).unwrap();

        assert!(matches!(
            agent_doc_snapshot_io::verify_snapshot_committed(&doc).unwrap(),
            agent_doc_snapshot_io::SnapshotCommitStatus::Committed
        ));
        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(state.phase, agent_doc_turn::CyclePhase::Committed);
        let show = Command::new("git")
            .current_dir(root)
            .args(["show", "HEAD:session.md"])
            .output()
            .unwrap();
        assert!(
            String::from_utf8_lossy(&show.stdout).contains("Reply"),
            "HEAD should now contain the response after write_applied auto-recovery"
        );
    }
    /// `#hookcontractlost`: document-only closeout drift used to be a
    /// self-sustaining wedge. Preflight owns recovery *before* diffing, so
    /// `enforce_no_uncommitted_closeout_drift` bailing aborted the
    /// `UserPromptSubmit` hook before it could emit a cycle contract — and the
    /// remedy the error named (`agent-doc commit`) can only be run by the
    /// operator, because the agent is correctly instructed never to shell
    /// preflight itself. Nothing else clears the drift, so every following turn
    /// hit the same bail (observed across three consecutive turns on
    /// 2026-08-09).
    ///
    /// Unlike the jb_cache_conflict_cancel shapes above, there is no response
    /// body in play: the drift is document-only, so the recovery classifier
    /// reports `Clean` and the binary-owned commit boundary closes it
    /// losslessly.
    #[test]
    fn preflight_auto_commits_document_only_closeout_drift_instead_of_wedging() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");

        let original = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "❯ hello\n\n",
            "### Re: hello — opus-4-6\n\n",
            "Answered.\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:status -->\n",
            "idle\n",
            "<!-- /agent:status -->\n"
        );
        std::fs::write(&doc, original).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            original,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        commit_all(root, "add doc", None);

        // Document-only drift: a binary-owned `status` line moved in the
        // snapshot only. The answered exchange is byte-identical and no
        // response body is pending. The working file stays at HEAD, so this is
        // NOT the jb_cache_conflict_cancel shape (which requires
        // document == snapshot) and falls through to the bail this test covers.
        let drifted = original.replace("idle\n", "draining\n");
        assert_ne!(drifted, original, "precondition: drift must be real");
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            &drifted,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        agent_doc_cycle_state_io::pipeline_frontmatter::mark_committed(
            &agent_doc_document_realtime_io::RUNTIME_PIPELINE_FRONTMATTER_EFFECTS,
            &doc,
            "commit_success",
            Some(&drifted),
            Some(&drifted),
        )
        .unwrap();

        assert!(
            matches!(
                agent_doc_snapshot_io::verify_snapshot_committed(&doc).unwrap(),
                agent_doc_snapshot_io::SnapshotCommitStatus::SnapshotDiffersFromHead { .. }
            ),
            "precondition: snapshot must differ from HEAD"
        );
        assert!(
            !agent_doc_session_check_io::detect_jb_cache_conflict_cancel_recoverable(&doc).unwrap(),
            "precondition: this must NOT be the jb_cache_conflict_cancel shape, \
             otherwise the pre-existing recovery would handle it and prove nothing"
        );
        assert!(
            agent_doc_closeout_runtime_io::document_only_drift_is_commit_recoverable(&doc),
            "precondition: drift must classify as document-only (recovery state Clean)"
        );

        // Before the fix this bailed, aborting contract emission.
        run(&doc).expect("preflight must recover document-only drift in place");

        assert!(
            matches!(
                agent_doc_snapshot_io::verify_snapshot_committed(&doc).unwrap(),
                agent_doc_snapshot_io::SnapshotCommitStatus::Committed
            ),
            "document-only drift should be committed, clearing the wedge"
        );
    }
    #[test]
    fn preflight_recovers_jb_cache_conflict_cancel_orphaned_capture_once() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");

        let original = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "❯ do #0ep7\n",
            "<!-- agent:boundary:test -->\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue -->\n",
            "<!-- /agent:queue -->\n"
        );
        std::fs::write(&doc, original).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            original,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let response = concat!(
            "<!-- patch:exchange -->\n",
            "### Re: #0ep7 — gpt-5\n\n",
            "Recovered once.\n",
            "<!-- /patch:exchange -->\n"
        );
        agent_doc_repair_io::pending::save_pending(&doc, response).unwrap();
        let capture = agent_doc_capture_io::load_active(&doc).unwrap().unwrap();
        assert!(
            agent_doc_repair_io::load_active_pending_response(&doc)
                .unwrap()
                .is_some(),
            "precondition: retained response intent"
        );

        let materialized = original.replace(
            "<!-- agent:boundary:test -->",
            concat!(
                "### Re: #0ep7 — gpt-5\n\n",
                "Recovered once.\n",
                "<!-- agent:boundary:test -->"
            ),
        );
        std::fs::write(&doc, &materialized).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            &materialized,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        agent_doc_cycle_state_io::pipeline_frontmatter::mark_committed(
            &agent_doc_document_realtime_io::RUNTIME_PIPELINE_FRONTMATTER_EFFECTS,
            &doc,
            "commit_success",
            Some(&materialized),
            Some(&materialized),
        )
        .unwrap();

        assert!(
            agent_doc_session_check_io::detect_jb_cache_conflict_cancel_recoverable(&doc).unwrap(),
            "preconditions: committed cancel pattern should be recoverable before preflight"
        );

        run(&doc).unwrap();

        let count = Command::new("git")
            .current_dir(root)
            .args(["rev-list", "--count", "HEAD"])
            .output()
            .unwrap();
        assert_eq!(String::from_utf8_lossy(&count.stdout).trim(), "2");
        assert!(
            agent_doc_repair_io::load_active_pending_response(&doc)
                .unwrap()
                .is_none(),
            "orphaned response intent should be retired"
        );

        let content = std::fs::read_to_string(&doc).unwrap();
        assert_eq!(
            content.matches("### Re: #0ep7 — gpt-5").count(),
            1,
            "visible response must not be replayed a second time:\n{content}"
        );
        assert_eq!(
            content.matches("<!-- agent:queue -->").count(),
            1,
            "template queue scaffold should stay balanced:\n{content}"
        );
        assert!(matches!(
            agent_doc_session_check_io::inspect(
                &doc,
                &agent_doc_closeout_runtime_io::session_check_effects()
            )
            .unwrap(),
            agent_doc_session_check_io::SessionCheckStatus::Ok(_)
        ));

        let refreshed = agent_doc_capture_io::load_by_id(&doc, &capture.capture_id)
            .unwrap()
            .unwrap();
        let snapshot_content = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert_eq!(
            refreshed.state,
            agent_doc_workflow::capture::CaptureState::Committed
        );
        assert_eq!(
            refreshed.file_hash.as_deref(),
            Some(agent_doc_hash::content_hash(&content).as_str()),
            "capture file hash should refresh to the recovered visible file:\n{content}"
        );
        assert_eq!(
            refreshed.snapshot_hash.as_deref(),
            Some(agent_doc_hash::content_hash(&snapshot_content).as_str()),
            "capture snapshot hash should refresh to the recovered snapshot"
        );
    }
    #[test]
    fn preflight_repairs_jb_cache_conflict_accept_duplicate_replay() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");

        let committed = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: #gsqlwrite — gpt-5\n\n",
            "Committed response.\n",
            "<!-- agent:boundary:committed -->\n",
            "<!-- /agent:exchange -->\n",
        );
        std::fs::write(&doc, committed).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            committed,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "committed response", "--no-verify"])
            .output()
            .unwrap();
        agent_doc_cycle_state_io::start_preflight(&doc, Some(committed), Some(committed)).unwrap();
        agent_doc_cycle_state_io::pipeline_frontmatter::mark_committed(
            &agent_doc_document_realtime_io::RUNTIME_PIPELINE_FRONTMATTER_EFFECTS,
            &doc,
            "commit_success",
            Some(committed),
            Some(committed),
        )
        .unwrap();

        let replayed = committed.replace(
            "<!-- agent:boundary:committed -->\n<!-- /agent:exchange -->",
            "### Re: #gsqlwrite — gpt-5 (HEAD)\n\nCommitted response.\n<!-- agent:boundary:replayed -->\n<!-- /agent:exchange -->",
        );
        std::fs::write(&doc, replayed).unwrap();
        assert!(
            agent_doc_session_check_io::detect_jb_cache_conflict_accept_duplicate_replay(&doc)
                .unwrap()
                .is_some(),
            "preconditions: accepted-conflict duplicate replay should be detected"
        );

        run(&doc).unwrap();

        assert_eq!(std::fs::read_to_string(&doc).unwrap(), committed);
        assert_eq!(
            agent_doc_snapshot_io::load_document_baseline(&doc)
                .unwrap()
                .unwrap(),
            committed
        );
        let diff = Command::new("git")
            .current_dir(root)
            .args(["diff", "--", "session.md"])
            .output()
            .unwrap();
        assert!(
            diff.stdout.is_empty(),
            "preflight repair should restore the working tree to committed HEAD"
        );
    }
    #[test]
    fn preflight_repairs_late_ipc_response_overapplication() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");

        // HEAD has two distinct committed responses, A then B.
        let committed = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: first answer — opus-4-8\n\n",
            "Answer A.\n",
            "### Re: second answer — opus-4-8\n\n",
            "Answer B.\n",
            "<!-- agent:boundary:committed -->\n",
            "<!-- /agent:exchange -->\n",
        );
        std::fs::write(&doc, committed).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            committed,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "committed responses", "--no-verify"])
            .output()
            .unwrap();
        agent_doc_cycle_state_io::start_preflight(&doc, Some(committed), Some(committed)).unwrap();
        agent_doc_cycle_state_io::pipeline_frontmatter::mark_committed(
            &agent_doc_document_realtime_io::RUNTIME_PIPELINE_FRONTMATTER_EFFECTS,
            &doc,
            "commit_success",
            Some(committed),
            Some(committed),
        )
        .unwrap();

        // Late-IPC replay re-inserts an EARLIER committed response (A) at the
        // tail, separated from its original by response B. This is NOT a
        // consecutive duplicate, so the JB-cache-conflict replay detector misses
        // it, but it is still a committed-response over-application.
        let overapplied = committed.replace(
            "<!-- agent:boundary:committed -->\n<!-- /agent:exchange -->",
            "### Re: first answer — opus-4-8\n\nAnswer A.\n<!-- agent:boundary:replayed -->\n<!-- /agent:exchange -->",
        );
        std::fs::write(&doc, overapplied).unwrap();

        assert!(
            agent_doc_session_check_io::detect_jb_cache_conflict_accept_duplicate_replay(&doc)
                .unwrap()
                .is_none(),
            "preconditions: non-adjacent duplicate is missed by the consecutive replay detector"
        );
        assert!(
            agent_doc_session_check_io::detect_late_ipc_response_overapplication(&doc)
                .unwrap()
                .is_some(),
            "preconditions: late-IPC over-application should be detected"
        );

        run(&doc).unwrap();

        assert_eq!(std::fs::read_to_string(&doc).unwrap(), committed);
        assert_eq!(
            agent_doc_snapshot_io::load_document_baseline(&doc)
                .unwrap()
                .unwrap(),
            committed
        );
        let diff = Command::new("git")
            .current_dir(root)
            .args(["diff", "--", "session.md"])
            .output()
            .unwrap();
        assert!(
            diff.stdout.is_empty(),
            "preflight repair should restore the working tree to committed HEAD"
        );
    }

    #[test]
    fn preflight_repairs_whole_document_late_ipc_overapplication() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let committed = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: answer — opus-4-8\n\n",
            "Committed answer.\n",
            "<!-- agent:boundary:committed -->\n",
            "<!-- /agent:exchange -->\n",
        );
        std::fs::write(&doc, committed).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            committed,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "committed response", "--no-verify"])
            .output()
            .unwrap();
        agent_doc_cycle_state_io::start_preflight(&doc, Some(committed), Some(committed)).unwrap();
        agent_doc_cycle_state_io::pipeline_frontmatter::mark_committed(
            &agent_doc_document_realtime_io::RUNTIME_PIPELINE_FRONTMATTER_EFFECTS,
            &doc,
            "commit_success",
            Some(committed),
            Some(committed),
        )
        .unwrap();

        let first = committed.replace("boundary:committed", "boundary:first");
        let second = committed
            .replace("boundary:committed", "boundary:second")
            .replace("\n\n", "\n");
        std::fs::write(&doc, format!("{first}{second}")).unwrap();

        assert!(
            agent_doc_session_check_io::detect_late_ipc_response_overapplication(&doc)
                .unwrap()
                .is_some(),
            "whole-document replay should be classified before singleton integrity checks"
        );

        run(&doc).unwrap();

        assert_eq!(std::fs::read_to_string(&doc).unwrap(), committed);
        assert_eq!(
            agent_doc_snapshot_io::load_document_baseline(&doc)
                .unwrap()
                .unwrap(),
            committed,
        );
        let diff = Command::new("git")
            .current_dir(root)
            .args(["diff", "--", "session.md"])
            .output()
            .unwrap();
        assert!(
            diff.stdout.is_empty(),
            "preflight repair should restore committed HEAD before structural validation"
        );
    }

    #[test]
    fn preflight_repairs_stale_jb_cache_conflict_accept_replay() {
        // #jb-cache-conflict-stale-accept-replay: a JB File Cache Conflict
        // accepted hours later replayed a STALE queued IPC reposition patch — an
        // earlier draft of a response whose final version is already committed.
        // Disk becomes HEAD plus a surplus block with the same `### Re:` topic
        // (and a `(HEAD)` marker) but a DRIFTED body. The strict over-application
        // detector misses it (bodies differ); the topic-tolerant fallback must
        // still auto-repair to committed HEAD instead of accusing a patchback.
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");

        let committed = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: fix thing — opus-4-8\n\n",
            "Final answer.\n",
            "<!-- agent:boundary:committed -->\n",
            "<!-- /agent:exchange -->\n",
        );
        std::fs::write(&doc, committed).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            committed,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "committed response", "--no-verify"])
            .output()
            .unwrap();
        agent_doc_cycle_state_io::start_preflight(&doc, Some(committed), Some(committed)).unwrap();
        agent_doc_cycle_state_io::pipeline_frontmatter::mark_committed(
            &agent_doc_document_realtime_io::RUNTIME_PIPELINE_FRONTMATTER_EFFECTS,
            &doc,
            "commit_success",
            Some(committed),
            Some(committed),
        )
        .unwrap();

        // Surplus STALE replay of the same topic, body drifted, `(HEAD)` marked.
        let replayed = committed.replace(
            "<!-- agent:boundary:committed -->\n<!-- /agent:exchange -->",
            "### Re: fix thing — opus-4-8 (HEAD)\n\nFinal answer.\nNote: stale draft paragraph the committed copy dropped.\n<!-- agent:boundary:stale -->\n<!-- /agent:exchange -->",
        );
        std::fs::write(&doc, &replayed).unwrap();

        assert!(
            !agent_doc_turn::response_replay::is_committed_response_overapplication(
                &replayed, committed
            ),
            "preconditions: strict over-application must NOT match a drifted-body replay"
        );
        assert!(
            agent_doc_session_check_io::detect_late_ipc_response_overapplication(&doc)
                .unwrap()
                .is_some(),
            "the stale-replay fallback should detect the over-application"
        );

        run(&doc).unwrap();

        assert_eq!(std::fs::read_to_string(&doc).unwrap(), committed);
        assert_eq!(
            agent_doc_snapshot_io::load_document_baseline(&doc)
                .unwrap()
                .unwrap(),
            committed
        );
        let diff = Command::new("git")
            .current_dir(root)
            .args(["diff", "--", "session.md"])
            .output()
            .unwrap();
        assert!(
            diff.stdout.is_empty(),
            "preflight repair should restore the working tree to committed HEAD"
        );
    }
    #[test]
    fn preflight_refreshes_capture_after_user_committed_baseline_drift() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");

        let original = concat!(
            "---\n",
            "session: test\n",
            "agent_doc_format: template\n",
            "---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "❯ do #bdauc\n",
            "<!-- agent:boundary:test -->\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#bdauc] Baseline drift task\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, original).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            original,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let response = concat!(
            "<!-- patch:exchange -->\n",
            "### Re: #bdauc — gpt-5\n\n",
            "Implemented and verified.\n",
            "❯ Submodule pointer updated.\n",
            "<!-- /patch:exchange -->\n"
        );
        let capture = agent_doc_capture_io::capture_response(&doc, response).unwrap();

        let current = original
            .replace(
                "<!-- agent:boundary:test -->",
                concat!(
                    "### Re: #bdauc — gpt-5\n\n",
                    "Implemented and verified.\n",
                    "Submodule pointer updated.\n",
                    "<!-- agent:boundary:test -->"
                ),
            )
            .replace(
                "- [ ] [#bdauc] Baseline drift task\n",
                concat!(
                    "- [ ] [#bdauc] Baseline drift task\n",
                    "- [ ] [#manual] User committed unrelated follow-up\n"
                ),
            );
        std::fs::write(&doc, &current).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            &current,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "manual baseline drift", "--no-verify"])
            .output()
            .unwrap();

        run(&doc).unwrap();

        let refreshed = agent_doc_capture_io::load_by_id(&doc, &capture.capture_id)
            .unwrap()
            .unwrap();
        assert_eq!(
            refreshed.file_hash.as_deref(),
            Some(agent_doc_hash::content_hash(&current).as_str()),
            "preflight should refresh the capture file hash before replay"
        );
        assert_eq!(
            refreshed.snapshot_hash.as_deref(),
            Some(agent_doc_hash::content_hash(&current).as_str()),
            "preflight should refresh the capture snapshot hash before replay"
        );
        let log = std::fs::read_to_string(root.join(".agent-doc/logs/ops.log")).unwrap();
        assert!(
            log.contains("capture_baseline_refreshed_for_benign_drift"),
            "preflight must drive validate_replay's baseline refresh path:\n{log}"
        );
    }

    #[test]
    fn stuck_capture_detection_uses_committed_ledger_projection() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");

        let original = concat!(
            "---\n",
            "session: test\n",
            "agent_doc_format: template\n",
            "---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "❯ do #stuckproj\n",
            "<!-- agent:boundary:test -->\n",
            "<!-- /agent:exchange -->\n"
        );
        std::fs::write(&doc, original).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            original,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let opened =
            agent_doc_cycle_state_io::start_preflight(&doc, Some(original), Some(original))
                .unwrap();
        let response = concat!(
            "<!-- patch:exchange -->\n",
            "### Re: #stuckproj — gpt-5\n\n",
            "This response never reached HEAD.\n",
            "<!-- /patch:exchange -->\n"
        );
        let capture = agent_doc_capture_io::capture_response(&doc, response).unwrap();
        agent_doc_cycle_state_io::mark_write_applied(
            &doc,
            "write_applied",
            Some(original),
            Some(original),
        )
        .unwrap();
        agent_doc_cycle_state_io::pipeline_frontmatter::mark_committed(
            &agent_doc_document_realtime_io::RUNTIME_PIPELINE_FRONTMATTER_EFFECTS,
            &doc,
            "commit_success",
            Some(original),
            Some(original),
        )
        .unwrap();

        assert_eq!(
            agent_doc_cycle_state_io::load(&doc).unwrap().unwrap().phase,
            agent_doc_turn::CyclePhase::Committed
        );

        let stuck = agent_doc_flow_io::closeout::stuck_captured_cycle(&doc)
            .expect("committed lazily projection should drive stuck-capture detection");
        assert_eq!(stuck.cycle_id, opened.cycle_id);
        assert_eq!(stuck.capture_id, capture.capture_id);
    }

    #[test]
    fn preflight_fails_closed_when_open_backlog_item_exists_only_in_shadow_copy() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "## Pending / Not Built\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#keep1] Keep me live\n",
            "<!-- /agent:backlog -->\n\n",
            "<!-- parked digest\n",
            "- [ ] [#lost1] Drifted out of backlog\n",
            "-->\n"
        );
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        let err = run(&doc).unwrap_err();
        assert!(
            err.to_string()
                .contains("open backlog item(s) exist only outside")
        );
        assert!(err.to_string().contains("#lost1"));
    }
    #[test]
    fn preflight_allows_shadow_copy_when_live_backlog_entry_still_exists() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "## Pending / Not Built\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#keep1] Keep me live\n",
            "<!-- /agent:backlog -->\n\n",
            "<!-- parked digest\n",
            "- [ ] [#keep1] Duplicate parked copy\n",
            "-->\n"
        );
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        run(&doc).unwrap();
    }
    #[test]
    fn preflight_reruns_cleanly_after_open_preflight_started_cycle() {
        let dir = setup_project();
        let root = dir.path();
        let doc = dir.path().join("session.md");
        let content = "---\nsession: test\n---\n\n## User\n\nHello\n";
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        agent_doc_commit_io::commit(&doc).unwrap();
        let prior =
            agent_doc_cycle_state_io::start_preflight(&doc, Some(content), Some(content)).unwrap();
        std::fs::write(&doc, "---\nsession: test\n---\n\n## User\n\nHello again\n").unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(state.phase, agent_doc_turn::CyclePhase::PreflightStarted);
        assert_ne!(
            state.cycle_id, prior.cycle_id,
            "rerun should close the old preflight and open a fresh one"
        );
        let log = std::fs::read_to_string(root.join(".agent-doc/logs/ops.log")).unwrap();
        assert!(
            log.contains("commit_already_current file="),
            "rerun should close the previous preflight via the no-op commit path:\n{log}"
        );
    }
    #[test]
    fn preflight_abandons_stale_empty_preflight_started_prompt_drift_without_capture() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = concat!(
            "---\nagent_doc_format: template\nagent_doc_session: test\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: older — gpt-5\n",
            "old body\n",
            "<!-- agent:boundary:abc123 -->\n",
            "<!-- /agent:exchange -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();
        let prior = agent_doc_cycle_state_io::start_preflight(&doc, Some(snapshot), Some(snapshot))
            .unwrap();

        let live = snapshot.replace(
            "<!-- agent:boundary:abc123 -->\n",
            "do [#root-empty-preflight]. spec-test-build-install-commit-push\n<!-- agent:boundary:abc123 -->\n",
        );
        std::fs::write(&doc, &live).unwrap();
        age_cycle_state(
            &doc,
            agent_doc_turn::repair::STALE_EMPTY_PREFLIGHT_TTL_SECS + 1,
        );

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(state.phase, agent_doc_turn::CyclePhase::PreflightStarted);
        assert_ne!(
            state.cycle_id, prior.cycle_id,
            "preflight should abandon the stale empty cycle and open a fresh cycle for the prompt"
        );
        assert_eq!(state.last_event, "preflight_started");

        let log = std::fs::read_to_string(root.join(".agent-doc/logs/ops.log")).unwrap();
        assert!(
            log.contains("repair_preflight_stale_prompt_cycle_abandoned file="),
            "preflight should log the abandoned empty cycle:\n{log}"
        );
    }
    #[test]
    fn preflight_abandoned_stale_next_steps_prompt_stays_actionable() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = concat!(
            "---\n",
            "agent_doc_format: template\n",
            "agent_doc_session: test\n",
            "prompt_presets:\n",
            "  '#next-steps': Any follow-up items to place in the backlog?\n",
            "---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Session Summary\n",
            "Compacted.\n",
            "<!-- agent:boundary:abc123 -->\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:backlog -->\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();
        let prior = agent_doc_cycle_state_io::start_preflight(&doc, Some(snapshot), Some(snapshot))
            .unwrap();

        let prompt = "Left/Right buttons still do not work with agent-doc opencode. #next-steps";
        let live = snapshot.replace(
            "<!-- agent:boundary:abc123 -->\n",
            &format!("{prompt}\n<!-- agent:boundary:abc123 -->\n"),
        );
        std::fs::write(&doc, &live).unwrap();
        age_cycle_state(
            &doc,
            agent_doc_turn::repair::STALE_EMPTY_PREFLIGHT_TTL_SECS + 1,
        );

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(state.phase, agent_doc_turn::CyclePhase::PreflightStarted);
        assert_ne!(
            state.cycle_id, prior.cycle_id,
            "preflight should abandon the stale empty cycle and open a fresh cycle for the prompt"
        );
        assert!(
            state.requires_backlog_capture,
            "the inline #next-steps prompt should still require backlog capture"
        );
        let diff = agent_doc_diff_io::compute(
            &agent_doc_snapshot_io::DiffBaselineStore::new(agent_doc_ops_log_io::log_op),
            &doc,
        )
        .unwrap()
        .unwrap();
        let prompt_targets = agent_doc_diff::classify_prompt_bearing_changes(&diff)
            .into_iter()
            .filter(|change| change.kind == agent_doc_diff::PromptBearingChangeKind::PromptTarget)
            .map(|change| change.text)
            .collect::<Vec<_>>();
        assert!(
            prompt_targets.iter().any(|target| target.contains(prompt)),
            "fresh preflight should surface the abandoned #next-steps prompt as actionable, got {prompt_targets:?}"
        );

        let log = std::fs::read_to_string(root.join(".agent-doc/logs/ops.log")).unwrap();
        assert!(
            log.contains("repair_preflight_stale_prompt_cycle_abandoned file="),
            "preflight should log the abandoned empty cycle:\n{log}"
        );
        assert!(
            log.contains("post_commit_user_follow_up file="),
            "step-2 commit should classify the prompt-bearing drift as a follow-up, not absorb it:\n{log}"
        );
    }
    #[test]
    fn preflight_compact_follow_up_next_steps_is_not_swallowed_by_commit_recovery() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = concat!(
            "---\n",
            "agent_doc_format: template\n",
            "agent_doc_session: test\n",
            "prompt_presets:\n",
            "  '#next-steps': Any follow-up items to place in the backlog?\n",
            "---\n\n",
            "## Status\n\n",
            "<!-- agent:status patch=replace -->\n",
            "Compacted.\n",
            "<!-- /agent:status -->\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Session Summary\n",
            "Compacted content archived.\n",
            "<!-- agent:boundary:deadbeef -->\n",
            "<!-- /agent:exchange -->\n\n",
            "## Pending\n\n",
            "<!-- agent:backlog -->\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "compact exchange", "--no-verify"])
            .output()
            .unwrap();

        let live = snapshot.replace(
            "<!-- agent:boundary:deadbeef -->\n",
            "#next-steps\n<!-- agent:boundary:deadbeef -->\n",
        );
        std::fs::write(&doc, live).unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(
            state.phase,
            agent_doc_turn::CyclePhase::PreflightStarted,
            "compact follow-up should open a response cycle instead of becoming no_changes"
        );
        assert!(
            state.requires_backlog_capture,
            "compact follow-up #next-steps should carry the backlog-capture contract"
        );
        let snapshot_after = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert!(
            snapshot_after.matches("#next-steps").count() == 1,
            "preflight must not absorb the compact follow-up prompt into the snapshot"
        );
        let head = Command::new("git")
            .current_dir(root)
            .args(["show", "HEAD:session.md"])
            .output()
            .unwrap();
        assert!(head.status.success(), "git show HEAD:session.md failed");
        assert!(
            String::from_utf8_lossy(&head.stdout)
                .matches("#next-steps")
                .count()
                == 1,
            "step-2 commit must not silently commit the compact follow-up prompt"
        );
    }
    #[test]
    fn preflight_commits_route_queue_snapshot_before_live_prompt_edit() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let original_prompt =
            "Run Agent Doc queued this prompt. #spec-test-build-install-commit-push";
        let edited_prompt = "Run Agent Doc queued this prompt. Same with this file. #spec-test-build-install-commit-push";
        let head = concat!(
            "---\n",
            "agent_doc_format: template\n",
            "agent_doc_session: test\n",
            "queue_active: false\n",
            "---\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "<!-- agent:boundary:abc123 -->\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:queue -->\n",
            "<!-- /agent:queue -->\n\n",
            "<!-- agent:backlog -->\n",
            "<!-- /agent:backlog -->\n"
        );
        let queued = head
            .replace("queue_active: false", "queue_active: true")
            .replace(
                "<!-- agent:boundary:abc123 -->\n",
                &format!("{original_prompt}\n<!-- agent:boundary:abc123 -->\n"),
            )
            .replace(
                "<!-- agent:queue -->\n<!-- /agent:queue -->",
                &format!("<!-- agent:queue auto -->\n- {original_prompt}\n<!-- /agent:queue -->"),
            );
        let live = queued.replacen(original_prompt, edited_prompt, 1);

        std::fs::write(&doc, head).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            head,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            &queued,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        std::fs::write(&doc, &live).unwrap();
        agent_doc_cycle_state_io::start_preflight(&doc, Some(head), Some(head)).unwrap();
        agent_doc_cycle_state_io::pipeline_frontmatter::mark_committed(
            &agent_doc_document_realtime_io::RUNTIME_PIPELINE_FRONTMATTER_EFFECTS,
            &doc,
            "commit_success",
            Some(&queued),
            Some(&queued),
        )
        .unwrap();

        run(&doc).unwrap();

        let committed = Command::new("git")
            .current_dir(root)
            .args(["show", "HEAD:session.md"])
            .output()
            .unwrap();
        assert!(
            committed.status.success(),
            "git show HEAD:session.md failed"
        );
        let committed = String::from_utf8_lossy(&committed.stdout);
        assert!(
            committed.contains(original_prompt),
            "route queued prompt should be committed from the saved snapshot:\n{committed}"
        );
        assert!(
            !committed.contains("Same with this file"),
            "live prompt edit must not be swallowed into the queue snapshot commit:\n{committed}"
        );
        let working = std::fs::read_to_string(&doc).unwrap();
        assert!(
            working.contains(edited_prompt),
            "later live prompt edit should remain visible for the fresh preflight cycle:\n{working}"
        );
        let snapshot_after = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert!(
            snapshot_after.contains(original_prompt),
            "snapshot should stay on the route queued prompt:\n{snapshot_after}"
        );
        assert!(
            !snapshot_after.contains("Same with this file"),
            "preflight must not absorb the live edit into the committed snapshot:\n{snapshot_after}"
        );
        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(
            state.phase,
            agent_doc_turn::CyclePhase::PreflightStarted,
            "after committing the queued snapshot, preflight should open a fresh cycle for the live edit"
        );
        let log = std::fs::read_to_string(root.join(".agent-doc/logs/ops.log")).unwrap();
        assert!(
            log.contains("route_queue_snapshot_auto_recovery_succeeded file="),
            "route queue commit-boundary recovery should be logged:\n{log}"
        );
    }
    #[test]
    fn preflight_started_cycle_does_not_revert_stale_snapshot_head() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = "---\nsession: test\n---\n\n<!-- agent:exchange patch=append -->\n### Re: older\nold body\n<!-- /agent:exchange -->\n";
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let live = "---\nsession: test\n---\n\n<!-- agent:exchange patch=append -->\n### Re: older\nold body\n### Re: newer\nnew body\n❯ follow-up question\n<!-- /agent:exchange -->\n";
        std::fs::write(&doc, live).unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "manual patchback", "--no-verify"])
            .output()
            .unwrap();

        agent_doc_cycle_state_io::start_preflight(&doc, Some(snapshot), Some(live)).unwrap();

        run(&doc).unwrap();

        let show = Command::new("git")
            .current_dir(root)
            .args(["show", "HEAD:session.md"])
            .output()
            .unwrap();
        assert!(show.status.success(), "git show HEAD:session.md failed");
        let committed = String::from_utf8_lossy(&show.stdout);
        assert!(
            committed.contains("### Re: newer"),
            "HEAD should stay at the newer manual content instead of reverting:\n{committed}"
        );
        assert!(
            committed.contains("❯ follow-up question"),
            "HEAD should keep the live follow-up question instead of reverting:\n{committed}"
        );

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(state.phase, agent_doc_turn::CyclePhase::Committed);
    }
    #[test]
    fn preflight_fails_closed_on_ambiguous_preflight_started_patchback_without_artifact() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let snapshot = concat!(
            "---\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "❯ Please reply\n",
            "<!-- agent:boundary:abc123 -->\n",
            "<!-- /agent:exchange -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        agent_doc_cycle_state_io::start_preflight(&doc, Some(snapshot), Some(snapshot)).unwrap();

        let live = concat!(
            "---\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "❯ Please reply\n",
            "### Re: topic — gpt-5\n",
            "Recovered body.\n",
            "<!-- agent:boundary:abc123 -->\n",
            "<!-- /agent:exchange -->\n"
        );
        std::fs::write(&doc, live).unwrap();

        let err = run(&doc).unwrap_err();
        let message = err.to_string();
        assert!(
            message.contains(agent_doc_turn::repair::AMBIGUOUS_PREFLIGHT_STARTED_PATCHBACK_ERROR),
            "expected fail-closed ambiguous patchback error, got: {message}"
        );

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(
            state.phase,
            agent_doc_turn::CyclePhase::PreflightStarted,
            "ambiguous patchback must not be auto-committed"
        );
    }
    #[test]
    fn preflight_started_repair_fails_when_matching_cycle_file_has_uncommitted_patchback() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = concat!(
            "---\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "❯ Please reply\n",
            "<!-- agent:boundary:abc123 -->\n",
            "<!-- /agent:exchange -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let live = concat!(
            "---\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "❯ Please reply\n",
            "### Re: topic — gpt-5\n",
            "Recovered body.\n",
            "<!-- agent:boundary:abc123 -->\n",
            "<!-- /agent:exchange -->\n"
        );
        std::fs::write(&doc, live).unwrap();
        agent_doc_cycle_state_io::start_preflight(&doc, Some(snapshot), Some(live)).unwrap();

        let err = run(&doc).unwrap_err();
        let message = err.to_string();
        assert!(
            message.contains(agent_doc_turn::repair::RESPONSE_PATCHBACK_UNCOMMITTED_ERROR),
            "expected uncommitted response patchback error, got: {message}"
        );

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(
            state.phase,
            agent_doc_turn::CyclePhase::PreflightStarted,
            "recovery must not mark the stale cycle committed while HEAD lacks the visible response"
        );
    }
    #[test]
    fn preflight_completed_backlog_reap_does_not_swallow_live_prompt() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: do #scopeid — gpt-5\n",
            "Implemented.\n",
            "<!-- agent:boundary:head -->\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:backlog -->\n",
            "- [x] [#scopeid] completed item\n",
            "<!-- /agent:backlog -->\n\n",
            "<!-- agent:done -->\n",
            "<!-- /agent:done -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let live = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: do #scopeid — gpt-5\n",
            "Implemented.\n",
            "do #statusws. spec-test-build-install-commit-push\n",
            "<!-- agent:boundary:head -->\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:backlog -->\n",
            "- [x] [#scopeid] completed item\n",
            "<!-- /agent:backlog -->\n\n",
            "<!-- agent:done -->\n",
            "<!-- /agent:done -->\n"
        );
        std::fs::write(&doc, live).unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(
            state.phase,
            agent_doc_turn::CyclePhase::PreflightStarted,
            "preflight should still open a response cycle for the live prompt"
        );

        let file_after = std::fs::read_to_string(&doc).unwrap();
        assert!(file_after.contains("do #statusws. spec-test-build-install-commit-push"));
        assert!(!file_after.contains("- [x] [#scopeid] completed item"));

        let snapshot_after = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert!(
            !snapshot_after.contains("do #statusws. spec-test-build-install-commit-push"),
            "snapshot must not absorb the live prompt during backlog reap"
        );
        assert!(!snapshot_after.contains("- [x] [#scopeid] completed item"));

        let head = Command::new("git")
            .current_dir(root)
            .args(["show", "HEAD:session.md"])
            .output()
            .unwrap();
        assert!(head.status.success(), "git show HEAD:session.md failed");
        let head_text = String::from_utf8_lossy(&head.stdout);
        assert!(
            !head_text.contains("do #statusws. spec-test-build-install-commit-push"),
            "repair/commit must not silently commit the live prompt:\n{head_text}"
        );
    }
    #[test]
    fn preflight_relocates_out_of_exchange_prompt_without_swallowing_live_diff() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n",
            "Done.\n",
            "<!-- agent:boundary:head -->\n",
            "<!-- /agent:exchange -->\n\n",
            "###\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#keepme] keep me\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let live = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n",
            "Done.\n",
            "<!-- agent:boundary:head -->\n",
            "<!-- /agent:exchange -->\n\n",
            "do [#oobprompt]. spec-test-build-install-commit-push\n",
            "###\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#keepme] keep me\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, live).unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(
            state.phase,
            agent_doc_turn::CyclePhase::PreflightStarted,
            "preflight should still open a response cycle for the relocated prompt"
        );

        let file_after = std::fs::read_to_string(&doc).unwrap();
        let exchange_close = file_after.find("<!-- /agent:exchange -->").unwrap();
        let prompt = file_after
            .find("❯ do [#oobprompt]. spec-test-build-install-commit-push")
            .unwrap();
        let gap_marker = file_after.find("\n###\n\n").unwrap();
        assert!(
            prompt < exchange_close,
            "preflight should move the prompt back inside exchange:\n{file_after}"
        );
        assert!(
            gap_marker > exchange_close,
            "preflight should leave the gap marker outside exchange:\n{file_after}"
        );
        assert!(
            !file_after.contains(
                "\n<!-- /agent:exchange -->\n\ndo [#oobprompt]. spec-test-build-install-commit-push"
            ),
            "out-of-exchange prompt should not remain in the gap:\n{file_after}"
        );

        let snapshot_after = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert!(
            !snapshot_after.contains("oobprompt"),
            "snapshot must not absorb the live prompt during preflight relocation:\n{snapshot_after}"
        );
    }
    #[test]
    fn preflight_does_not_relocate_prompt_text_inside_post_exchange_html_comment() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n",
            "Done.\n",
            "<!-- agent:boundary:head -->\n",
            "<!-- /agent:exchange -->\n\n",
            "###\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#keepme] keep me\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let live = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n",
            "Done.\n",
            "<!-- agent:boundary:head -->\n",
            "<!-- /agent:exchange -->\n\n",
            "###\n\n",
            "<!--\n",
            "Content that I added into the html comment below agent:exchange in this doc was deleted by agent-doc.\n",
            "spec-test-build-install-commit-push\n",
            "---\n",
            "older scratch note\n",
            "-->\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#keepme] keep me\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, live).unwrap();

        run(&doc).unwrap();

        let file_after = std::fs::read_to_string(&doc).unwrap();
        let exchange_close = file_after.find("<!-- /agent:exchange -->").unwrap();
        let hidden_prompt = file_after
            .find("Content that I added into the html comment below agent:exchange")
            .unwrap();
        let comment_open = file_after.find("\n<!--\n").unwrap();
        let comment_close = file_after.find("\n-->\n\n<!-- agent:backlog -->").unwrap();
        assert!(
            hidden_prompt > exchange_close,
            "scratch-comment prompt text must stay outside exchange:\n{file_after}"
        );
        assert!(
            hidden_prompt > comment_open && hidden_prompt < comment_close,
            "scratch-comment prompt text must remain inside the ordinary HTML comment:\n{file_after}"
        );
        assert!(
            !file_after.contains(
                "\nContent that I added into the html comment below agent:exchange in this doc was deleted by agent-doc.\nspec-test-build-install-commit-push\n<!-- /agent:exchange -->"
            ),
            "preflight must not move scratch-comment text into exchange:\n{file_after}"
        );
    }
    #[test]
    fn post_exchange_comment_with_horizontal_rule_and_prose_is_user_note() {
        let content = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "prompt_presets:\n",
            "  '#spec-test-build-install-commit-push': update spec + tests\n",
            "  '#next-steps': Any follow-up items?\n",
            "---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n\n",
            "Done.\n",
            "<!-- /agent:exchange -->\n\n",
            "###\n\n",
            "<!--\n",
            "The last run had a code fence stripped away by agent-doc.\n",
            "#spec-test-build-install-commit-push\n",
            "---\n",
            "What are #next-steps to fix bugs?\n",
            "-->\n\n",
            "<!-- agent:backlog -->\n",
            "<!-- /agent:backlog -->\n",
        );
        let (fm, _) = agent_doc_frontmatter::frontmatter::parse(content).unwrap();
        let warning =
            agent_doc_workflow::preflight_policy::post_exchange_comment_prompt_preset_warning(
                "session.md",
                content,
                &fm.prompt_presets,
            );
        assert!(
            warning.is_none(),
            "post-exchange comment with horizontal rule and prose is a user note, not a directive: {:?}",
            warning
        );
    }
    #[test]
    fn preflight_preserves_post_exchange_duplicate_prompt_comment_before_diff() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior — gpt-5\n",
            "Done.\n",
            "<!-- agent:boundary:head -->\n",
            "<!-- /agent:exchange -->\n\n",
            "###\n\n",
            "<!--\n",
            "Keep this unrelated scratch note hidden.\n",
            "-->\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#keepme] keep me\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let prompt = "The duplicate content corrupting document and duplicate prompt issues happened yet again. Very tired of playing whack-a-mole. Reproduce bugs with tests first that fail and fix the implementation. #spec-test-build-install-commit-push";
        let live = format!(
            concat!(
                "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
                "<!-- agent:exchange patch=append -->\n",
                "### Re: prior — gpt-5\n",
                "Done.\n",
                "<!-- agent:boundary:head -->\n",
                "{prompt}\n",
                "<!-- /agent:exchange -->\n\n",
                "###\n\n",
                "<!--\n",
                "{prompt}\n",
                "-->\n\n",
                "<!--\n",
                "Keep this unrelated scratch note hidden.\n",
                "-->\n\n",
                "<!-- agent:backlog -->\n",
                "- [ ] [#keepme] keep me\n",
                "<!-- /agent:backlog -->\n"
            ),
            prompt = prompt
        );
        std::fs::write(&doc, live).unwrap();

        run(&doc).unwrap();

        let file_after = std::fs::read_to_string(&doc).unwrap();
        let duplicate_comment = format!("\n<!--\n{prompt}\n-->\n");
        assert!(
            file_after.contains(&duplicate_comment),
            "preflight must preserve visible post-exchange scratch comments even when they duplicate prompt text:\n{file_after}"
        );
        assert!(
            file_after.contains("Keep this unrelated scratch note hidden."),
            "unrelated scratch comments must remain outside exchange:\n{file_after}"
        );
        let snapshot_after = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert!(
            !snapshot_after.contains(prompt),
            "snapshot must not absorb the live prompt during preflight:\n{snapshot_after}"
        );
    }
    #[test]
    fn preflight_preserves_unrelated_lines_in_mixed_post_exchange_duplicate_prompt_comment() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: prior - gpt-5\n",
            "Done.\n",
            "<!-- agent:boundary:head -->\n",
            "<!-- /agent:exchange -->\n\n",
            "###\n",
            "<!--\n",
            "-->\n\n",
            "<!-- agent:backlog -->\n",
            "- [ ] [#keepme] keep me\n",
            "<!-- /agent:backlog -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let exchange_prompt = "The content of the html comment below this agent:exchange element was deleted after the last agent-doc turn. The duplicate corrupt document bug & the duplicated prompt happened yet again as I was typing in this prompt. Should we diff line by line? Do we still have race conditions?";
        let duplicate_prompt_line = "The duplicate corrupt document bug & the duplicated prompt happened yet again as I was typing in this prompt. Should we diff line by line? Do we still have race conditions?";
        let live = format!(
            concat!(
                "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
                "<!-- agent:exchange patch=append -->\n",
                "### Re: prior - gpt-5\n",
                "Done.\n",
                "<!-- agent:boundary:head -->\n",
                "{exchange_prompt}\n",
                "#spec-test-build-install-commit-push\n",
                "<!-- /agent:exchange -->\n\n",
                "###\n",
                "<!--\n",
                "{duplicate_prompt_line}\n",
                "#spec-test-build-install-commit-push\n",
                "---\n",
                "Look through the Claude + Codex + agent-doc session logs for #next-steps to fix bugs.\n",
                "-->\n\n",
                "<!-- agent:backlog -->\n",
                "- [ ] [#keepme] keep me\n",
                "<!-- /agent:backlog -->\n"
            ),
            exchange_prompt = exchange_prompt,
            duplicate_prompt_line = duplicate_prompt_line,
        );
        std::fs::write(&doc, live).unwrap();

        run(&doc).unwrap();

        let file_after = std::fs::read_to_string(&doc).unwrap();
        assert!(
            file_after.contains(&format!("<!--\n{duplicate_prompt_line}")),
            "preflight must preserve visible duplicate-looking lines in post-exchange scratch comments:\n{file_after}"
        );
        assert!(
            file_after.contains("Look through the Claude + Codex + agent-doc session logs"),
            "preflight must preserve unrelated scratch lines in the same ordinary comment:\n{file_after}"
        );
        assert!(
        file_after.contains(&format!(
            "<!--\n{duplicate_prompt_line}\n#spec-test-build-install-commit-push\n---\nLook through"
        )),
        "preflight must keep the full mixed ordinary comment body:\n{file_after}"
    );
        let snapshot_after = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert!(
            !snapshot_after.contains(exchange_prompt),
            "snapshot must not absorb the live prompt during preflight:\n{snapshot_after}"
        );
    }
    #[test]
    fn preflight_scrubs_duplicate_answered_prompt_tail_before_diff() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let prompt = "The content of the html comment below this agent:exchange element was deleted after the last agent-doc turn. Should we diff line by line?";
        let snapshot = format!(
            concat!(
                "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
                "<!-- agent:exchange patch=append -->\n",
                "❯ {prompt}\n",
                "❯ #spec-test-build-install-commit-push\n",
                "### Re: mixed scratch comment deletion - gpt-5\n\n",
                "Answered already.\n",
                "<!-- agent:boundary:head -->\n",
                "<!-- /agent:exchange -->\n\n",
                "###\n",
                "<!--\n",
                "Keep this scratch note.\n",
                "-->\n\n",
                "<!-- agent:backlog -->\n",
                "- [ ] [#keepme] keep me\n",
                "<!-- /agent:backlog -->\n"
            ),
            prompt = prompt
        );
        std::fs::write(&doc, &snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            &snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        // Genuine replay residue carries the `❯ ` answered-form marker — that is
        // the ownership proof that lets the scrub remove it without eating a live
        // re-typed prompt (#ipcfullprompt-recur).
        let live = snapshot.replace(
            "<!-- agent:boundary:head -->\n<!-- /agent:exchange -->",
            &format!(
                "<!-- agent:boundary:head -->\n❯ {prompt}\n❯ #spec-test-build-install-commit-push\n<!-- /agent:exchange -->"
            ),
        );
        std::fs::write(&doc, live).unwrap();

        run(&doc).unwrap();

        let file_after = std::fs::read_to_string(&doc).unwrap();
        assert!(
            !file_after.contains(&format!(
                "head -->\n❯ {prompt}\n❯ #spec-test-build-install-commit-push"
            )),
            "preflight should scrub duplicate answered-form prompt tails before diffing:\n{file_after}"
        );
        assert!(
            file_after.contains("Keep this scratch note."),
            "preflight cleanup must preserve unrelated scratch comments:\n{file_after}"
        );
        let snapshot_after = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert!(
            !snapshot_after.contains(&format!(
                "head -->\n❯ {prompt}\n❯ #spec-test-build-install-commit-push"
            )),
            "snapshot must not absorb the duplicate tail cleanup prompt"
        );
    }
    #[test]
    fn preflight_session_accretion_does_not_auto_compact_exchange() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\nagent_doc_write: crdt\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Session Summary\n\nExisting summary.\n\n",
            "### Re: first topic — gpt-5\n\nFirst response.\n\n",
            "### Re: second topic — gpt-5\n\nSecond response.\n",
            "<!-- agent:boundary:head -->\n",
            "<!-- /agent:exchange -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let relative = doc
            .strip_prefix(root)
            .unwrap()
            .to_string_lossy()
            .to_string();
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs();
        write_cycles_log(
            &doc,
            &[
                agent_doc_ops_log_io::CycleEntry {
                    timestamp: now.saturating_sub(10).to_string(),
                    file: relative.clone(),
                    op: "commit_noop".to_string(),
                    commit_hash: None,
                    snapshot_hash: None,
                    file_hash: None,
                },
                agent_doc_ops_log_io::CycleEntry {
                    timestamp: now.saturating_sub(5).to_string(),
                    file: relative,
                    op: "commit_noop".to_string(),
                    commit_hash: None,
                    snapshot_hash: None,
                    file_hash: None,
                },
            ],
        );

        let live = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\nagent_doc_write: crdt\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Session Summary\n\nExisting summary.\n\n",
            "### Re: first topic — gpt-5\n\nFirst response.\n\n",
            "### Re: second topic — gpt-5\n\nSecond response.\n",
            "<!-- agent:boundary:head -->\n",
            "do #autocmp. spec-test-build-install-commit-push\n",
            "<!-- /agent:exchange -->\n"
        );
        std::fs::write(&doc, live).unwrap();

        run(&doc).unwrap();

        let file_after = std::fs::read_to_string(&doc).unwrap();
        assert!(!file_after.contains("1 earlier topic(s) archived"));
        assert!(file_after.contains("### Re: second topic — gpt-5"));
        assert!(file_after.contains("### Re: first topic — gpt-5"));
        assert!(file_after.contains("do #autocmp. spec-test-build-install-commit-push"));

        let snapshot_after = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert_eq!(snapshot_after, snapshot);
    }
    #[test]
    fn preflight_reaps_flush_left_spill_with_completed_backlog_item() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: do #scopeid — gpt-5\n",
            "Implemented.\n",
            "<!-- agent:boundary:head -->\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:backlog -->\n",
            "- [x] [#scopeid] completed item\n",
            "Commands:\n",
            "  cargo test -p agent-doc backlog::\n",
            "Diff:\n",
            "@@ -1 +1 @@\n",
            "<!-- /agent:backlog -->\n\n",
            "<!-- agent:done -->\n",
            "<!-- /agent:done -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let live = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Re: do #scopeid — gpt-5\n",
            "Implemented.\n",
            "do #statusws. spec-test-build-install-commit-push\n",
            "<!-- agent:boundary:head -->\n",
            "<!-- /agent:exchange -->\n\n",
            "<!-- agent:backlog -->\n",
            "- [x] [#scopeid] completed item\n",
            "Commands:\n",
            "  cargo test -p agent-doc backlog::\n",
            "Diff:\n",
            "@@ -1 +1 @@\n",
            "<!-- /agent:backlog -->\n\n",
            "<!-- agent:done -->\n",
            "<!-- /agent:done -->\n"
        );
        std::fs::write(&doc, live).unwrap();

        run(&doc).unwrap();

        let file_after = std::fs::read_to_string(&doc).unwrap();
        let backlog_after = agent_doc_element::element::parse(&file_after).unwrap();
        let backlog_after = backlog_after
            .iter()
            .find(|component| agent_doc_element::element::is_backlog_component(&component.name))
            .map(|component| component.content(&file_after))
            .unwrap();
        assert!(file_after.contains("do #statusws. spec-test-build-install-commit-push"));
        assert!(!backlog_after.contains("- [x] [#scopeid] completed item"));
        assert!(!backlog_after.contains("Commands:"));
        assert!(!backlog_after.contains("@@ -1 +1 @@"));

        let snapshot_after = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        let snapshot_backlog = agent_doc_element::element::parse(&snapshot_after).unwrap();
        let snapshot_backlog = snapshot_backlog
            .iter()
            .find(|component| agent_doc_element::element::is_backlog_component(&component.name))
            .map(|component| component.content(&snapshot_after))
            .unwrap();
        assert!(!snapshot_backlog.contains("- [x] [#scopeid] completed item"));
        assert!(!snapshot_backlog.contains("Commands:"));
        assert!(!snapshot_backlog.contains("@@ -1 +1 @@"));
        assert!(
            !snapshot_after.contains("do #statusws. spec-test-build-install-commit-push"),
            "snapshot must not absorb the live prompt during backlog reap"
        );
    }
    #[test]
    fn preflight_status_prompt_preset_addition_does_not_swallow_diff() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let snapshot = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "prompt_presets:\n",
            "  '#next-steps': Print the top backlog item.\n",
            "---\n\n",
            "## Status\n\n",
            "<!-- agent:status patch=replace -->\n",
            "Compacted.\n",
            "<!-- /agent:status -->\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Session Summary\n",
            "Compacted.\n",
            "<!-- /agent:exchange -->\n"
        );
        std::fs::write(&doc, snapshot).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            snapshot,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let live = concat!(
            "---\n",
            "agent_doc_session: test\n",
            "agent_doc_format: template\n",
            "prompt_presets:\n",
            "  '#next-steps': Print the top backlog item.\n",
            "---\n\n",
            "## Status\n\n",
            "<!-- agent:status patch=replace -->\n",
            "Compacted.\n",
            "#next-steps for calibrating session benchmarks with expected scores\n",
            "<!-- /agent:status -->\n\n",
            "## Exchange\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "### Session Summary\n",
            "Compacted.\n",
            "<!-- /agent:exchange -->\n"
        );
        std::fs::write(&doc, live).unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(
            state.phase,
            agent_doc_turn::CyclePhase::PreflightStarted,
            "preflight should still open a response cycle for the prompt-preset status edit"
        );

        let snapshot_after = agent_doc_snapshot_io::load_document_baseline(&doc)
            .unwrap()
            .unwrap();
        assert_eq!(
            snapshot_after, snapshot,
            "snapshot must not absorb prompt-bearing status drift"
        );

        let head = Command::new("git")
            .current_dir(root)
            .args(["show", "HEAD:session.md"])
            .output()
            .unwrap();
        assert!(head.status.success(), "git show HEAD:session.md failed");
        let head_text = String::from_utf8_lossy(&head.stdout);
        assert_eq!(
            head_text.as_ref(),
            snapshot,
            "step 2 commit must not silently commit the prompt-preset status edit:\n{head_text}"
        );
    }
    #[test]
    fn preflight_boundary_artifact_only_diff_does_not_start_cycle() {
        let dir = setup_project();
        let root = dir.path();
        let doc = root.join("session.md");
        let tracked = "---\nsession: test\n---\n\n\
            <!-- agent:exchange patch=append -->\n\
            ### Re: older\n\
            old body\n\
            ### Re: newer\n\
            new body\n\
            <!-- agent:boundary:head -->\n\
            <!-- /agent:exchange -->\n";
        std::fs::write(&doc, tracked).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            tracked,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "session.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add doc", "--no-verify"])
            .output()
            .unwrap();

        let visible = "---\nsession: test\n---\n\n\
            <!-- agent:exchange patch=append -->\n\
            ### Re: older\n\
            old body\n\
            ### Re: newer (HEAD)\n\
            new body\n\
            <!-- agent:boundary:live -->\n\
            <!-- /agent:exchange -->\n";
        std::fs::write(&doc, visible).unwrap();

        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap();
        assert!(
            state.as_ref().is_none_or(|state| !state.is_open()),
            "boundary-artifact-only preflight must not leave an open cycle"
        );

        let log = std::fs::read_to_string(root.join(".agent-doc/logs/ops.log")).unwrap_or_default();
        assert!(
            !log.contains("preflight_diff_start file="),
            "boundary-artifact-only diff must not log preflight_diff_start:\n{log}"
        );
        match agent_doc_session_check_io::inspect(
            &doc,
            &agent_doc_closeout_runtime_io::session_check_effects(),
        )
        .unwrap()
        {
            agent_doc_session_check_io::SessionCheckStatus::Ok(_) => {}
            status => {
                panic!(
                    "expected clean closeout after boundary-artifact-only preflight, got {status:?}"
                )
            }
        }
    }
    #[test]
    fn preflight_recovers_response_captured_cycle_from_ledger() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let content = "---\nsession: test\nagent_doc_format: append\nagent_doc_write: merge\n---\n\n## User\n\nHello\n";
        std::fs::write(&doc, content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        agent_doc_repair_io::pending::save_pending(&doc, "Recovered answer.").unwrap();
        run(&doc).unwrap();

        let state = agent_doc_cycle_state_io::load(&doc).unwrap().unwrap();
        assert_eq!(state.phase, agent_doc_turn::CyclePhase::Committed);
        let result = std::fs::read_to_string(&doc).unwrap();
        assert!(result.contains("Recovered answer."));
    }
    #[test]
    fn preflight_sweep_commits_other_tracked_docs() {
        use std::fs;
        let dir = setup_project();
        let root = dir.path();

        // Create initial commit so HEAD exists
        let readme = root.join("README.md");
        fs::write(&readme, "# project\n").unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "README.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "initial", "--no-verify"])
            .output()
            .unwrap();

        // Primary doc (the one preflight runs on)
        let primary = root.join("primary.md");
        let primary_content = "---\nagent_doc_session: primary\n---\n\n## User\n\nHello\n\n## Assistant\n\nReply\n\n## User\n\n";
        fs::write(&primary, primary_content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &primary,
            primary_content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "primary.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add primary", "--no-verify"])
            .output()
            .unwrap();

        // Secondary doc (tracked in the durable registry, snapshot newer than file — needs sweep)
        let secondary = root.join("secondary.md");
        let secondary_content = "---\nagent_doc_session: secondary\n---\n\n## User\n\nHi\n\n## Assistant\n\nResponse\n\n## User\n\n";
        fs::write(&secondary, secondary_content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &secondary,
            secondary_content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "secondary.md"])
            .output()
            .unwrap();
        // Backdate the commit so the <5s freshness gate in sweep doesn't skip it.
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add secondary", "--no-verify"])
            .env("GIT_COMMITTER_DATE", "2026-01-01T00:00:00Z")
            .env("GIT_AUTHOR_DATE", "2026-01-01T00:00:00Z")
            .output()
            .unwrap();

        // Touch snapshot to make it newer than the file (simulates agent write without commit)
        let new_snap = format!("{}\n<!-- agent updated -->", secondary_content);
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &secondary,
            &new_snap,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        write_session_registry(
            root,
            &[("secondary-session", "%1", &secondary, "@1", "2026-01-01")],
        );

        // Run preflight on primary — sweep should commit secondary
        run(&primary).unwrap();

        // Verify secondary was committed by the sweep
        let log = Command::new("git")
            .current_dir(root)
            .args(["log", "--oneline", "-4"])
            .output()
            .unwrap();
        let log_str = String::from_utf8_lossy(&log.stdout);
        assert!(
            log_str.contains("agent-doc(secondary):"),
            "preflight sweep should have committed secondary.md, got:\n{log_str}"
        );
    }
    #[test]
    fn preflight_sweep_skips_doc_with_unresponded_user_content() {
        use std::fs;
        let dir = setup_project();
        let root = dir.path();

        // Create initial commit so HEAD exists
        let readme = root.join("README.md");
        fs::write(&readme, "# project\n").unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "README.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "initial", "--no-verify"])
            .output()
            .unwrap();

        // Primary doc (the one preflight runs on)
        let primary = root.join("primary.md");
        let primary_content = "---\nagent_doc_session: primary\n---\n\n## User\n\nHello\n\n## Assistant\n\nReply\n\n## User\n\n";
        fs::write(&primary, primary_content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &primary,
            primary_content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "primary.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add primary", "--no-verify"])
            .output()
            .unwrap();

        // Secondary doc with agent response in snapshot but user added new content in document
        let secondary = root.join("secondary.md");
        let snap_content = "---\nagent_doc_session: secondary\n---\n\n## User\n\nHi\n\n## Assistant\n\nResponse\n\n## User\n\n";
        // Document has user additions not in the snapshot
        let doc_content = "---\nagent_doc_session: secondary\n---\n\n## User\n\nHi\n\n## Assistant\n\nResponse\n\n## User\n\nNew question from user\n";
        fs::write(&secondary, doc_content).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &secondary,
            snap_content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["add", "secondary.md"])
            .output()
            .unwrap();
        Command::new("git")
            .current_dir(root)
            .args(["commit", "-m", "add secondary", "--no-verify"])
            .env("GIT_COMMITTER_DATE", "2026-01-01T00:00:00Z")
            .env("GIT_AUTHOR_DATE", "2026-01-01T00:00:00Z")
            .output()
            .unwrap();

        // Touch snapshot to make it newer than the file
        std::thread::sleep(std::time::Duration::from_millis(50));
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &secondary,
            snap_content,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        write_session_registry(
            root,
            &[("secondary-session", "%1", &secondary, "@1", "2026-01-01")],
        );

        // Count commits before sweep
        let log_before = Command::new("git")
            .current_dir(root)
            .args(["log", "--oneline"])
            .output()
            .unwrap();
        let count_before = String::from_utf8_lossy(&log_before.stdout).lines().count();

        // Run preflight on primary — sweep should SKIP secondary due to user additions
        run(&primary).unwrap();

        // Verify secondary was NOT committed
        let log_after = Command::new("git")
            .current_dir(root)
            .args(["log", "--oneline"])
            .output()
            .unwrap();
        let log_str = String::from_utf8_lossy(&log_after.stdout);
        assert!(
            !log_str.contains("agent-doc(secondary):"),
            "preflight sweep should NOT have committed secondary.md (has unresponded user content), got:\n{log_str}"
        );
        // Only primary should have been committed (by step 2, not sweep)
        let count_after = log_str.lines().count();
        assert!(
            count_after <= count_before + 1,
            "expected at most one new commit (primary), got {} new commits",
            count_after - count_before
        );
    }
    #[test]
    fn preflight_sweep_skips_foreign_owned_doc() {
        use std::fs;
        let dir = setup_project();
        let root = dir.path();
        initialize_git_head(root);

        let primary_content = "---\nagent_doc_session: primary\n---\n\n## User\n\nHello\n\n## Assistant\n\nReply\n\n## User\n\n";
        let primary = write_committed_doc(root, "primary.md", primary_content, "add primary", None);

        let secondary_content = "---\nagent_doc_session: secondary\n---\n\n## User\n\nHi\n\n## Assistant\n\nResponse\n\n## User\n\n";
        let secondary = write_committed_doc(
            root,
            "secondary.md",
            secondary_content,
            "add secondary",
            Some("2026-01-01T00:00:00Z"),
        );

        std::thread::sleep(std::time::Duration::from_millis(50));
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &secondary,
            &format!("{}\n<!-- agent updated -->", secondary_content),
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        write_session_registry(
            root,
            &[
                ("primary-session", "%70", &primary, "@1", "2026-01-01"),
                ("secondary-session", "%73", &secondary, "@2", "2026-01-01"),
            ],
        );

        run(&primary).unwrap();

        let log = Command::new("git")
            .current_dir(root)
            .args(["log", "--oneline", "-4"])
            .output()
            .unwrap();
        let log_str = String::from_utf8_lossy(&log.stdout);
        assert!(
            !log_str.contains("agent-doc(secondary):"),
            "foreign-owned secondary.md must not be sweep-committed, got:\n{log_str}"
        );

        let head_secondary = Command::new("git")
            .current_dir(root)
            .args(["show", "HEAD:secondary.md"])
            .output()
            .unwrap();
        let head_secondary = String::from_utf8_lossy(&head_secondary.stdout);
        assert!(
            !head_secondary.contains("agent updated"),
            "foreign-owned snapshot drift must stay out of HEAD:\n{head_secondary}"
        );

        let ops_log = fs::read_to_string(root.join(".agent-doc/logs/ops.log")).unwrap();
        assert!(
            ops_log.contains("foreign_owned_sweep_skip")
                && ops_log.contains("owner_pane=%73")
                && ops_log.contains("current_pane=%70"),
            "foreign-owned skip should be logged for audit:\n{ops_log}"
        );
    }
    #[test]
    fn preflight_sweep_commits_same_owner_doc() {
        use std::fs;
        let dir = setup_project();
        let root = dir.path();
        initialize_git_head(root);

        let primary_content = "---\nagent_doc_session: primary\n---\n\n## User\n\nHello\n\n## Assistant\n\nReply\n\n## User\n\n";
        let primary = write_committed_doc(root, "primary.md", primary_content, "add primary", None);

        let secondary_content = "---\nagent_doc_session: secondary\n---\n\n## User\n\nHi\n\n## Assistant\n\nResponse\n\n## User\n\n";
        let secondary = write_committed_doc(
            root,
            "secondary.md",
            secondary_content,
            "add secondary",
            Some("2026-01-01T00:00:00Z"),
        );

        std::thread::sleep(std::time::Duration::from_millis(50));
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &secondary,
            &format!("{}\n<!-- agent updated -->", secondary_content),
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();

        write_session_registry(
            root,
            &[
                ("primary-session", "%70", &primary, "@1", "2026-01-01"),
                ("secondary-session", "%70", &secondary, "@1", "2026-01-01"),
            ],
        );
        agent_doc_session_actor_io::project_binding_in(
            root,
            &primary.to_string_lossy(),
            "primary-session",
            "%70",
            "@1",
            "test",
            "primary_owner",
        )
        .unwrap();
        // Leave the sibling owner in the durable registry so this exercises the sweep
        // fallback projection without seeding an invalid two-document actor
        // alias for pane %70.

        run(&primary).unwrap();

        let log = Command::new("git")
            .current_dir(root)
            .args(["log", "--oneline", "-4"])
            .output()
            .unwrap();
        let log_str = String::from_utf8_lossy(&log.stdout);
        assert!(
            log_str.contains("agent-doc(secondary):"),
            "same-owner secondary.md should still be sweep-committed, got:\n{log_str}"
        );

        let head_secondary = Command::new("git")
            .current_dir(root)
            .args(["show", "HEAD:secondary.md"])
            .output()
            .unwrap();
        let head_secondary = String::from_utf8_lossy(&head_secondary.stdout);
        assert!(
            head_secondary.contains("agent updated"),
            "same-owner snapshot drift should land in HEAD:\n{head_secondary}"
        );
    }

    #[test]
    fn preflight_integrity_gate_fails_before_recovery_or_maintenance() {
        use std::fs;

        let dir = tempfile::tempdir().unwrap();
        let doc = dir.path().join("session.md");
        let malformed = concat!(
            "---\nagent_doc_session: test\nagent_doc_lint_dialect: off\n---\n\n",
            "<!-- agent:exchange -->\n",
            "<!-- agent:notes -->\ncorrupted\n",
            "<!-- /agent:exchange -->\n",
            "<!-- /agent:notes -->\n",
        );
        fs::write(&doc, malformed).unwrap();

        let err = run_with_options(&doc, PreflightOptions { probe: true })
            .expect_err("preflight must reject malformed component authority");
        assert!(err.to_string().contains("[integrity-gate] INTERRUPTED"));
        assert_eq!(fs::read_to_string(&doc).unwrap(), malformed);
        assert!(!dir.path().join(".agent-doc").exists());
    }

    #[test]
    fn preflight_normalizes_duplicate_response_replay_boundary_before_integrity_gate() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let clean = concat!(
            "---\nagent_doc_session: test\nagent_doc_format: template\nagent_doc_write: crdt\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "❯ operator prompt\n",
            "<!-- agent:boundary:clean -->\n",
            "<!-- /agent:exchange -->\n",
        );
        let duplicated = clean.replace(
            "<!-- agent:boundary:clean -->",
            concat!(
                "<!-- agent:boundary:stale -->\n",
                "### Re: retained — gpt-5\n\nRetained response.\n",
                "<!-- agent:boundary:latest -->",
            ),
        );
        std::fs::write(&doc, clean).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            clean,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        agent_doc_test_support::publish_editor_text_via_crdt_relay(
            &doc,
            "preflight-boundary-dedup",
            &duplicated,
        );

        run_with_options(&doc, PreflightOptions { probe: true }).unwrap();

        let current = resolve_current_preflight_document(&doc, "test_boundary_dedup").unwrap();
        assert_eq!(current.matches("agent:boundary:").count(), 1);
        assert!(current.contains("agent:boundary:latest"));
        assert!(current.contains("❯ operator prompt"));
        assert!(current.contains("Retained response."));
    }

    #[test]
    fn preflight_retires_redundant_intent_before_retaining_exact_projection() {
        let dir = setup_project();
        let doc = dir.path().join("session.md");
        let clean = concat!(
            "---\nagent_doc_session: exact-doubled-preflight\nagent_doc_format: template\nagent_doc_write: crdt\n---\n\n",
            "<!-- agent:exchange patch=append -->\n",
            "❯ operator prompt\n",
            "<!-- agent:boundary:clean -->\n",
            "<!-- /agent:exchange -->\n",
        );
        let doubled = format!("{clean}{clean}");
        std::fs::write(&doc, clean).unwrap();
        agent_doc_snapshot_io::checkpoint_document_baseline(
            &doc,
            clean,
            agent_doc_ops_log_io::log_op,
        )
        .unwrap();
        agent_doc_document_realtime_io::retain_deferred_document_write_target(
            &doc,
            clean,
            clean,
            "legacy_delivery_failed_to_all",
            agent_doc_state_backbone::DocumentWriteDeferredReason::EditorOwnerWithoutRegisteredReplica,
        )
        .unwrap();
        agent_doc_test_support::publish_editor_text_via_crdt_relay(
            &doc,
            "preflight-exact-doubled-retirement",
            &doubled,
        );

        run_with_options(&doc, PreflightOptions { probe: true }).unwrap();

        let current =
            resolve_current_preflight_document(&doc, "test_exact_doubled_retirement").unwrap();
        assert_eq!(current, clean);
        assert_eq!(std::fs::read_to_string(&doc).unwrap(), clean);
        let journal = agent_doc_document_realtime_io::pending_document_write_journal(&doc);
        assert!(
            journal
                .iter()
                .all(|intent| intent.source.token() != "legacy_delivery_failed_to_all"),
            "the redundant historical lineage must be retired through state events: {journal:?}",
        );
        assert!(
            journal.iter().all(|intent| intent.target_content == clean),
            "any remaining intent must be only the exact lazy controller projection: {journal:?}",
        );
    }
}
