//! `mimir-librarian` binary — CLI entry point.
//!
//! Six subcommands:
//!
//! - `submit` — stage one scope-aware draft into `pending/`.
//! - `sweep` — stage explicit memory files/directories into `pending/`.
//! - `run` — one-shot: process all drafts in `pending/`, exit.
//! - `watch` — long-running: watch `pending/` for new drafts and
//!   process them as they arrive.
//! - `quorum` — record file-backed quorum episodes and participant
//!   outputs for later synthesis.
//! - `copilot` — read Copilot CLI's local session store as untrusted
//!   recall and optional draft input.
//!
//! # Status
//!
//! `run` currently performs lifecycle-safe one-shot processing with
//! bounded LLM validation retry and durable canonical commit. `watch`
//! repeats that same run path on a configurable polling cadence.

use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, ExitCode, Stdio};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

use mimir_core::ClockTime;
use mimir_librarian::{
    run_once, ClaudeCliInvoker, ConsensusLevel, DecisionStatus, DedupPolicy,
    DeferredDraftProcessor, Draft, DraftMetadata, DraftRunSummary, DraftSourceSurface, DraftStore,
    LibrarianConfig, LibrarianError, ParticipantVote, QuorumAdapterRequest, QuorumEpisode,
    QuorumEpisodeState, QuorumParticipant, QuorumParticipantOutput, QuorumResult, QuorumRound,
    QuorumStore, RawArchiveDraftProcessor, RetryingDraftProcessor, SupersessionConflictPolicy,
    VoteChoice, QUORUM_SCHEMA_VERSION,
};
use wait_timeout::ChildExt as _;

mod copilot_session_store;

const DEFAULT_WATCH_POLL_SECS: u64 = 30;
const DEFAULT_QUORUM_ADAPTER_TIMEOUT_SECS: u64 = 300;
#[cfg(unix)]
const TEXT_FILE_BUSY_OS_ERROR: i32 = 26;

const USAGE: &str = "\
mimir-librarian — prose drafts → canonical log.

Usage:
    mimir-librarian submit   --text TEXT [--drafts-dir PATH]
                              [--source-surface cli|claude-memory|codex-memory|mcp|directory|repo-handoff|agent-export|consensus-quorum|copilot-session-store]
                              [--agent NAME] [--project NAME] [--operator NAME]
                              [--provenance URI] [--tag TAG]...
    mimir-librarian sweep    --path PATH [--drafts-dir PATH]
                              --source-surface claude-memory|codex-memory|directory|repo-handoff|agent-export
                              [--agent NAME] [--project NAME] [--operator NAME]
                              [--tag TAG]...
    mimir-librarian run      [--drafts-dir PATH] [--workspace PATH]
                              [--stale-processing-secs N]
                              [--max-retries N] [--llm-timeout-secs N]
                              [--dedup-valid-at-window-secs N]
                              [--review-conflicts] [--archive-raw] [--defer]
    mimir-librarian watch    [--drafts-dir PATH] [--workspace PATH]
                              [--poll-secs N] [--iterations N]
                              [run flags...]
    mimir-librarian quorum   create --quorum-dir PATH --id ID
                              --requester NAME --question TEXT
                              --participant ID:ADAPTER:PERSONA[:MODEL]...
                              [--requested-at-unix-ms N]
                              [--target-project NAME] [--target-scope SCOPE]
                              [--evidence-policy TEXT] [--provenance URI]
    mimir-librarian quorum   pilot-plan --quorum-dir PATH --episode-id ID
                              --through-round independent|critique|revision
                              --out-dir PATH --drafts-dir PATH
                              --synthesizer claude|codex
                              [--adapter-binary claude=PATH] [--adapter-binary codex=PATH]
                              [--synthesizer-binary PATH] [--timeout-secs N]
                              [--require-proposed-drafts N]
                              [--project NAME] [--operator NAME] [--tag TAG]...
    mimir-librarian quorum   pilot-status --manifest-file PATH
    mimir-librarian quorum   pilot-run --manifest-file PATH
    mimir-librarian quorum   pilot-review --manifest-file PATH
                              --reviewer ID --decision pass|needs-work|fail
                              --summary TEXT [--finding info|warning|blocker:TEXT]...
                              [--next-action TEXT]...
    mimir-librarian quorum   pilot-summary --manifest-file PATH
    mimir-librarian quorum   append-output --quorum-dir PATH
                              --episode-id ID --output-id ID --participant-id ID
                              --round independent|critique|revision
                              (--prompt TEXT | --prompt-file PATH)
                              (--response TEXT | --response-file PATH)
                              [--submitted-at-unix-ms N]
                              [--visible-prior-output-id ID]... [--evidence URI]...
    mimir-librarian quorum   append-status-output --status-file PATH
                              [--submitted-at-unix-ms N]
    mimir-librarian quorum   outputs --quorum-dir PATH --episode-id ID
                              --round independent|critique|revision
    mimir-librarian quorum   visible --quorum-dir PATH --episode-id ID
                              --round independent|critique|revision
    mimir-librarian quorum   adapter-request --quorum-dir PATH --episode-id ID
                              --participant-id ID --round independent|critique|revision
    mimir-librarian quorum   adapter-plan --quorum-dir PATH --episode-id ID
                              --participant-id ID --round independent|critique|revision
                              --adapter claude|codex --out-dir PATH
                              [--binary NAME] [--output-id ID]
    mimir-librarian quorum   adapter-run --quorum-dir PATH --episode-id ID
                              --participant-id ID --round independent|critique|revision
                              --adapter claude|codex --out-dir PATH
                              [--binary NAME] [--output-id ID] [--timeout-secs N]
    mimir-librarian quorum   adapter-run-round --quorum-dir PATH --episode-id ID
                              --round independent|critique|revision --out-dir PATH
                              [--adapter-binary claude=PATH] [--adapter-binary codex=PATH]
                              [--timeout-secs N]
    mimir-librarian quorum   adapter-run-rounds --quorum-dir PATH --episode-id ID
                              --through-round independent|critique|revision --out-dir PATH
                              [--adapter-binary claude=PATH] [--adapter-binary codex=PATH]
                              [--timeout-secs N] [--submitted-at-unix-ms N]
    mimir-librarian quorum   synthesize-plan --quorum-dir PATH --episode-id ID
                              --adapter claude|codex --out-dir PATH
                              [--binary NAME]
    mimir-librarian quorum   synthesize-run --quorum-dir PATH --episode-id ID
                              --adapter claude|codex --out-dir PATH
                              [--binary NAME] [--timeout-secs N]
    mimir-librarian quorum   accept-synthesis --quorum-dir PATH
                              (--result-file PATH | --status-file PATH)
                              [--episode-id ID]
    mimir-librarian quorum   synthesize --quorum-dir PATH --episode-id ID
                              --recommendation TEXT
                              --decision-status recommend|split|needs-evidence|reject|unsafe
                              --consensus-level unanimous|strong-majority|weak-majority|contested|abstained
                              --confidence N
                              [--supporting-point TEXT]... [--dissenting-point TEXT]...
                              [--unresolved-question TEXT]... [--evidence URI]...
                              [--participant-vote PARTICIPANT:agree|disagree|abstain:CONFIDENCE:RATIONALE]...
                              [--proposed-memory-draft TEXT]...
    mimir-librarian quorum   submit-drafts --quorum-dir PATH --drafts-dir PATH
                              --episode-id ID
                              [--project NAME] [--operator NAME] [--tag TAG]...
    mimir-librarian copilot  schema-check [--db PATH]
    mimir-librarian copilot  recent|files|checkpoints [--db PATH]
                              [--repo OWNER/REPO|all] [--repo-root PATH] [--limit N]
    mimir-librarian copilot  search --query TEXT [--db PATH]
                              [--repo OWNER/REPO|all] [--repo-root PATH] [--limit N]
    mimir-librarian copilot  submit-drafts --drafts-dir PATH [--db PATH]
                              [--repo OWNER/REPO|all] [--repo-root PATH] [--limit N]
                              [--project NAME] [--operator NAME] [--tag TAG]...
    mimir-librarian --help

`submit` is wired and writes a v2 draft JSON envelope to `pending/`.
`sweep` imports explicit file/directory paths as untrusted pending drafts.
`run` is lifecycle-safe and validates LLM output with bounded retry.
`--dedup-valid-at-window-secs` controls same-fact valid_at duplicate skipping.
`--review-conflicts` writes supersession conflicts to `drafts/conflicts/` and quarantines them.
`--archive-raw` deterministically commits raw drafts as pending-verification evidence without LLM work.
`--defer` claims and returns drafts without invoking the LLM.
`watch` repeats `run` on a polling cadence; `--iterations` bounds it for scripts/tests.
`quorum` records file-backed quorum artifacts; `pilot-plan` writes a replayable
manifest, and adapter execution still requires explicit status/result gates.
`copilot` opens Copilot CLI's local SQLite session store read-only, validates schema
before every query, and stages only untrusted drafts through the librarian path.
";

fn main() -> ExitCode {
    init_tracing();
    let args: Vec<String> = std::env::args().skip(1).collect();
    match args.first().map(String::as_str) {
        Some("submit") => cmd_submit(&args[1..], SystemTime::now()),
        Some("sweep") => cmd_sweep(&args[1..], SystemTime::now()),
        Some("run") => cmd_run(&args[1..], SystemTime::now()),
        Some("watch") => cmd_watch(&args[1..]),
        Some("quorum") => cmd_quorum(&args[1..]),
        Some("copilot") => cmd_copilot(&args[1..], SystemTime::now()),
        Some("--help" | "-h") => {
            println!("{USAGE}");
            ExitCode::SUCCESS
        }
        Some(other) => {
            eprintln!("mimir-librarian: unknown subcommand '{other}'\n");
            eprintln!("{USAGE}");
            ExitCode::from(64) // EX_USAGE
        }
        None => {
            eprintln!("{USAGE}");
            ExitCode::from(64)
        }
    }
}

fn init_tracing() {
    // Stderr subscriber with `info` default; `RUST_LOG` overrides.
    // Install failures are swallowed — another subscriber may be
    // installed by an embedder; we never want tracing setup to
    // take the binary down.
    use tracing_subscriber::{fmt, EnvFilter};
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
    let _ = fmt()
        .with_env_filter(filter)
        .with_writer(std::io::stderr)
        .try_init();
}

fn cmd_run(args: &[String], now: SystemTime) -> ExitCode {
    match run_from_args(args, now) {
        Ok(outcome) => match serde_json::to_string(&outcome) {
            Ok(json) => {
                println!("{json}");
                if outcome.summary.deferred > 0 {
                    ExitCode::from(70)
                } else {
                    ExitCode::SUCCESS
                }
            }
            Err(err) => {
                eprintln!("mimir-librarian run failed to render JSON: {err}");
                ExitCode::from(70)
            }
        },
        Err(CliError::Usage(message)) => {
            eprintln!("mimir-librarian run: {message}\n");
            eprintln!("{USAGE}");
            ExitCode::from(64)
        }
        Err(CliError::Librarian(err)) => {
            eprintln!("mimir-librarian run failed: {err}");
            ExitCode::from(70)
        }
        Err(CliError::Copilot(err)) => {
            eprintln!("mimir-librarian run failed: {err}");
            ExitCode::from(70)
        }
    }
}

fn cmd_watch(args: &[String]) -> ExitCode {
    match watch_from_args(args, |outcome| match serde_json::to_string(outcome) {
        Ok(json) => println!("{json}"),
        Err(err) => eprintln!("mimir-librarian watch failed to render JSON: {err}"),
    }) {
        Ok(_) => ExitCode::SUCCESS,
        Err(CliError::Usage(message)) => {
            eprintln!("mimir-librarian watch: {message}\n");
            eprintln!("{USAGE}");
            ExitCode::from(64)
        }
        Err(CliError::Librarian(err)) => {
            eprintln!("mimir-librarian watch failed: {err}");
            ExitCode::from(70)
        }
        Err(CliError::Copilot(err)) => {
            eprintln!("mimir-librarian watch failed: {err}");
            ExitCode::from(70)
        }
    }
}

fn cmd_submit(args: &[String], submitted_at: SystemTime) -> ExitCode {
    match submit_from_args(args, submitted_at) {
        Ok(outcome) => {
            println!(
                "{}",
                serde_json::json!({
                    "id": outcome.id,
                    "path": outcome.path,
                })
            );
            ExitCode::SUCCESS
        }
        Err(CliError::Usage(message)) => {
            eprintln!("mimir-librarian submit: {message}\n");
            eprintln!("{USAGE}");
            ExitCode::from(64)
        }
        Err(CliError::Librarian(err)) => {
            eprintln!("mimir-librarian submit failed: {err}");
            ExitCode::from(70)
        }
        Err(CliError::Copilot(err)) => {
            eprintln!("mimir-librarian submit failed: {err}");
            ExitCode::from(70)
        }
    }
}

fn cmd_sweep(args: &[String], submitted_at: SystemTime) -> ExitCode {
    match sweep_from_args(args, submitted_at) {
        Ok(outcome) => {
            println!(
                "{}",
                serde_json::json!({
                    "submitted": outcome.submitted,
                    "skipped_empty": outcome.skipped_empty,
                    "drafts": outcome.drafts,
                })
            );
            ExitCode::SUCCESS
        }
        Err(CliError::Usage(message)) => {
            eprintln!("mimir-librarian sweep: {message}\n");
            eprintln!("{USAGE}");
            ExitCode::from(64)
        }
        Err(CliError::Librarian(err)) => {
            eprintln!("mimir-librarian sweep failed: {err}");
            ExitCode::from(70)
        }
        Err(CliError::Copilot(err)) => {
            eprintln!("mimir-librarian sweep failed: {err}");
            ExitCode::from(70)
        }
    }
}

fn cmd_quorum(args: &[String]) -> ExitCode {
    match quorum_from_args(args, SystemTime::now()) {
        Ok(outcome) => match serde_json::to_string(&outcome) {
            Ok(json) => {
                let exit_code = quorum_outcome_exit_code(&outcome);
                println!("{json}");
                exit_code
            }
            Err(err) => {
                eprintln!("mimir-librarian quorum failed to render JSON: {err}");
                ExitCode::from(70)
            }
        },
        Err(CliError::Usage(message)) => {
            eprintln!("mimir-librarian quorum: {message}\n");
            eprintln!("{USAGE}");
            ExitCode::from(64)
        }
        Err(CliError::Librarian(err)) => {
            eprintln!("mimir-librarian quorum failed: {err}");
            ExitCode::from(70)
        }
        Err(CliError::Copilot(err)) => {
            eprintln!("mimir-librarian quorum failed: {err}");
            ExitCode::from(70)
        }
    }
}

fn cmd_copilot(args: &[String], submitted_at: SystemTime) -> ExitCode {
    match copilot_session_store::copilot_session_store_from_args(args, submitted_at) {
        Ok(outcome) => match serde_json::to_string(&outcome) {
            Ok(json) => {
                println!("{json}");
                if outcome.is_failure() {
                    ExitCode::from(70)
                } else {
                    ExitCode::SUCCESS
                }
            }
            Err(err) => {
                eprintln!("mimir-librarian copilot failed to render JSON: {err}");
                ExitCode::from(70)
            }
        },
        Err(CliError::Usage(message)) => {
            eprintln!("mimir-librarian copilot: {message}\n");
            eprintln!("{USAGE}");
            ExitCode::from(64)
        }
        Err(CliError::Librarian(err)) => {
            eprintln!("mimir-librarian copilot failed: {err}");
            ExitCode::from(70)
        }
        Err(CliError::Copilot(err)) => {
            eprintln!("mimir-librarian copilot failed: {err}");
            ExitCode::from(70)
        }
    }
}

fn quorum_outcome_exit_code(outcome: &QuorumCliOutcome) -> ExitCode {
    match outcome {
        QuorumCliOutcome::PilotRun { run } if !run.success => ExitCode::from(70),
        _ => ExitCode::SUCCESS,
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct SubmitOutcome {
    id: String,
    path: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct SweepOutcome {
    submitted: usize,
    skipped_empty: usize,
    drafts: Vec<SweepDraftOutcome>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct SweepDraftOutcome {
    id: String,
    path: String,
    provenance_uri: String,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct RunOutcome {
    processor: &'static str,
    summary: DraftRunSummary,
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct WatchOutcome {
    iterations: u64,
    last: Option<RunOutcome>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum QuorumCliOutcome {
    EpisodeCreated {
        id: String,
        path: String,
    },
    OutputAppended {
        episode_id: String,
        output_id: String,
        path: String,
    },
    StatusOutputsAppended {
        status_path: String,
        appended: usize,
        outputs: Vec<QuorumStatusOutputAppend>,
    },
    OutputsLoaded {
        episode_id: String,
        round: String,
        outputs: Vec<QuorumParticipantOutput>,
    },
    VisibleOutputs {
        episode_id: String,
        round: String,
        outputs: Vec<QuorumParticipantOutput>,
    },
    AdapterRequest {
        request: Box<QuorumAdapterRequest>,
    },
    AdapterPlan {
        plan: Box<QuorumAdapterPlan>,
    },
    AdapterRun {
        run: Box<QuorumAdapterRun>,
    },
    AdapterRoundRun {
        round_run: Box<QuorumAdapterRoundRun>,
    },
    AdapterRoundsRun {
        rounds_run: Box<QuorumAdapterRoundsRun>,
    },
    PilotPlan {
        plan: Box<QuorumPilotPlan>,
    },
    PilotStatus {
        status: Box<QuorumPilotStatus>,
    },
    PilotRun {
        run: Box<QuorumPilotRun>,
    },
    PilotReview {
        review: Box<QuorumPilotReview>,
    },
    PilotSummary {
        summary: Box<QuorumPilotSummary>,
    },
    SynthesisPlan {
        plan: Box<QuorumSynthesisPlan>,
    },
    SynthesisRun {
        run: Box<QuorumSynthesisRun>,
    },
    ResultSaved {
        episode_id: String,
        path: String,
    },
    DraftsSubmitted {
        episode_id: String,
        submitted: usize,
        drafts: Vec<SweepDraftOutcome>,
    },
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct QuorumStatusOutputAppend {
    episode_id: String,
    output_id: String,
    participant_id: String,
    round: String,
    path: String,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct QuorumPilotPlan {
    schema_version: u32,
    episode_id: String,
    quorum_dir: String,
    out_dir: String,
    drafts_dir: String,
    through_round: String,
    synthesizer_adapter: String,
    timeout_secs: u64,
    #[serde(default)]
    required_proposed_drafts: usize,
    participants: Vec<QuorumParticipant>,
    manifest_path: String,
    artifacts: QuorumPilotArtifacts,
    steps: Vec<QuorumPilotStep>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct QuorumPilotArtifacts {
    round_statuses: Vec<QuorumPilotRoundArtifact>,
    synthesis_status_path: String,
    synthesis_result_path: String,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct QuorumPilotRoundArtifact {
    round: String,
    status_path: String,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct QuorumPilotStep {
    name: String,
    description: String,
    argv: Vec<String>,
    writes: Vec<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "snake_case")]
enum QuorumPilotGateStatus {
    Pending,
    Complete,
    Failed,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct QuorumPilotStatus {
    schema_version: u32,
    episode_id: String,
    manifest_path: String,
    complete: bool,
    overall_status: QuorumPilotGateStatus,
    gates: Vec<QuorumPilotGate>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct QuorumPilotGate {
    name: String,
    status: QuorumPilotGateStatus,
    detail: String,
    artifacts: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct QuorumPilotRun {
    schema_version: u32,
    episode_id: String,
    manifest_path: String,
    success: bool,
    executed_steps: Vec<String>,
    skipped_steps: Vec<String>,
    failed_step: Option<String>,
    error: Option<String>,
    final_status: QuorumPilotStatus,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
enum QuorumPilotReviewDecision {
    Pass,
    NeedsWork,
    Fail,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct QuorumPilotReviewFinding {
    severity: String,
    text: String,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct QuorumPilotReview {
    schema_version: u32,
    episode_id: String,
    manifest_path: String,
    review_path: String,
    reviewed_at_unix_ms: u64,
    reviewer: String,
    decision: QuorumPilotReviewDecision,
    summary: String,
    findings: Vec<QuorumPilotReviewFinding>,
    next_actions: Vec<String>,
    status_at_review: QuorumPilotStatus,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct QuorumPilotSummary {
    schema_version: u32,
    episode_id: String,
    manifest_path: String,
    complete: bool,
    overall_status: QuorumPilotGateStatus,
    required_proposed_drafts: usize,
    proposed_memory_drafts: usize,
    submitted_drafts: usize,
    result_status: String,
    result_path: Option<String>,
    review_status: String,
    review_path: String,
    review_decision: Option<QuorumPilotReviewDecision>,
    next_action: String,
    gates: Vec<QuorumPilotGate>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct QuorumAdapterPlan {
    schema_version: u32,
    adapter: String,
    episode_id: String,
    participant_id: String,
    round: String,
    request_path: String,
    prompt_path: String,
    response_path: String,
    status_path: String,
    stdin_path: String,
    stdout_path: Option<String>,
    stdout_capture_path: String,
    stderr_capture_path: String,
    argv: Vec<String>,
    append_output_argv: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct QuorumAdapterRun {
    schema_version: u32,
    status: QuorumAdapterRunStatus,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct QuorumAdapterRunStatus {
    schema_version: u32,
    adapter: String,
    episode_id: String,
    participant_id: String,
    round: String,
    status_path: String,
    request_path: String,
    prompt_path: String,
    response_path: String,
    stdout_capture_path: String,
    stderr_capture_path: String,
    success: bool,
    timed_out: bool,
    exit_code: Option<i32>,
    duration_ms: u64,
    response_bytes: u64,
    stdout_bytes: u64,
    stderr_bytes: u64,
    append_output_argv: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct QuorumAdapterRoundRun {
    schema_version: u32,
    episode_id: String,
    round: String,
    status_path: String,
    success: bool,
    completed: usize,
    failed: usize,
    timed_out: usize,
    statuses: Vec<QuorumAdapterRunStatus>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct QuorumAdapterRoundsRun {
    schema_version: u32,
    episode_id: String,
    through_round: String,
    success: bool,
    rounds: Vec<QuorumAdapterRoundRun>,
    appended: Vec<QuorumStatusOutputAppend>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct QuorumSynthesisPlan {
    schema_version: u32,
    adapter: String,
    quorum_dir: String,
    episode_id: String,
    transcript_path: String,
    prompt_path: String,
    result_path: String,
    status_path: String,
    stdin_path: String,
    stdout_path: Option<String>,
    stdout_capture_path: String,
    stderr_capture_path: String,
    argv: Vec<String>,
    accept_synthesis_argv: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
struct QuorumSynthesisRun {
    schema_version: u32,
    status: QuorumSynthesisRunStatus,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
enum QuorumProcessStatus {
    Succeeded,
    Failed,
    TimedOut,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct QuorumSynthesisRunStatus {
    schema_version: u32,
    adapter: String,
    episode_id: String,
    status_path: String,
    transcript_path: String,
    prompt_path: String,
    result_path: String,
    stdout_capture_path: String,
    stderr_capture_path: String,
    success: bool,
    #[serde(default)]
    process_status: Option<QuorumProcessStatus>,
    timed_out: bool,
    exit_code: Option<i32>,
    duration_ms: u64,
    #[serde(default)]
    result_valid: bool,
    #[serde(default)]
    validation_error: Option<String>,
    result_bytes: u64,
    stdout_bytes: u64,
    stderr_bytes: u64,
    accept_synthesis_argv: Vec<String>,
}

#[derive(Debug)]
enum CliError {
    Usage(String),
    Librarian(LibrarianError),
    Copilot(copilot_session_store::CopilotSessionStoreError),
}

impl std::fmt::Display for CliError {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Usage(message) => write!(formatter, "{message}"),
            Self::Librarian(err) => write!(formatter, "{err}"),
            Self::Copilot(err) => write!(formatter, "{err}"),
        }
    }
}

impl std::error::Error for CliError {}

impl From<LibrarianError> for CliError {
    fn from(value: LibrarianError) -> Self {
        Self::Librarian(value)
    }
}

fn run_from_args(args: &[String], now: SystemTime) -> Result<RunOutcome, CliError> {
    let mut cfg = LibrarianConfig::default();
    let mut defer = false;
    let mut archive_raw = false;

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--drafts-dir" => {
                cfg.drafts_dir = take_value(args, &mut i, "--drafts-dir")?.into();
            }
            "--workspace" => {
                cfg.workspace_log = take_value(args, &mut i, "--workspace")?.into();
            }
            "--stale-processing-secs" => {
                let value = take_value(args, &mut i, "--stale-processing-secs")?;
                let secs = value.parse::<u64>().map_err(|_| {
                    CliError::Usage(format!(
                        "--stale-processing-secs must be an integer: {value}"
                    ))
                })?;
                cfg.processing_stale_after = Duration::from_secs(secs);
            }
            "--max-retries" => {
                let value = take_value(args, &mut i, "--max-retries")?;
                cfg.max_retries_per_record = value.parse::<u32>().map_err(|_| {
                    CliError::Usage(format!("--max-retries must be an integer: {value}"))
                })?;
            }
            "--llm-timeout-secs" => {
                let value = take_value(args, &mut i, "--llm-timeout-secs")?;
                let secs = value.parse::<u64>().map_err(|_| {
                    CliError::Usage(format!("--llm-timeout-secs must be an integer: {value}"))
                })?;
                cfg.llm_timeout = Duration::from_secs(secs);
            }
            "--dedup-valid-at-window-secs" => {
                let value = take_value(args, &mut i, "--dedup-valid-at-window-secs")?;
                let secs = value.parse::<u64>().map_err(|_| {
                    CliError::Usage(format!(
                        "--dedup-valid-at-window-secs must be an integer: {value}"
                    ))
                })?;
                cfg.dedup_valid_at_window = Duration::from_secs(secs);
            }
            "--defer" => {
                defer = true;
            }
            "--archive-raw" => {
                archive_raw = true;
            }
            "--review-conflicts" => {
                cfg.review_conflicts = true;
            }
            other => {
                return Err(CliError::Usage(format!("unknown option '{other}'")));
            }
        }
        i += 1;
    }

    if defer && archive_raw {
        return Err(CliError::Usage(
            "--archive-raw cannot be combined with --defer".to_string(),
        ));
    }

    let store = DraftStore::new(&cfg.drafts_dir);
    if defer {
        let mut processor = DeferredDraftProcessor;
        let summary = run_once(&store, &mut processor, now, cfg.processing_stale_after)?;
        return Ok(RunOutcome {
            processor: "deferred",
            summary,
        });
    }
    if archive_raw {
        let clock = clock_time_from_system_time(now)?;
        let mut processor = RawArchiveDraftProcessor::new_at(clock, &cfg.workspace_log)?;
        let summary = run_once(&store, &mut processor, now, cfg.processing_stale_after)?;
        return Ok(RunOutcome {
            processor: "archive_raw",
            summary,
        });
    }

    let invoker = ClaudeCliInvoker::default().with_timeout(cfg.llm_timeout);
    let mut processor =
        RetryingDraftProcessor::new(invoker, cfg.max_retries_per_record, &cfg.workspace_log)?
            .with_dedup_policy(DedupPolicy {
                valid_at_window: cfg.dedup_valid_at_window,
            });
    if cfg.review_conflicts {
        processor = processor.with_conflict_policy(SupersessionConflictPolicy::Review {
            dir: cfg.drafts_dir.join("conflicts"),
        });
    }
    let summary = run_once(&store, &mut processor, now, cfg.processing_stale_after)?;
    Ok(RunOutcome {
        processor: "retrying_llm",
        summary,
    })
}

fn watch_from_args<F>(args: &[String], mut on_outcome: F) -> Result<WatchOutcome, CliError>
where
    F: FnMut(&RunOutcome),
{
    let mut run_args = Vec::new();
    let mut poll_interval = Duration::from_secs(DEFAULT_WATCH_POLL_SECS);
    let mut max_iterations: Option<u64> = None;

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--poll-secs" => {
                let value = take_value(args, &mut i, "--poll-secs")?;
                let secs = parse_positive_u64("--poll-secs", &value)?;
                poll_interval = Duration::from_secs(secs);
            }
            "--iterations" => {
                let value = take_value(args, &mut i, "--iterations")?;
                max_iterations = Some(parse_positive_u64("--iterations", &value)?);
            }
            other => {
                run_args.push(other.to_string());
            }
        }
        i += 1;
    }

    let limit = max_iterations.unwrap_or(u64::MAX);
    let mut iterations = 0;
    let mut last = None;
    while iterations < limit {
        let outcome = run_from_args(&run_args, SystemTime::now())?;
        on_outcome(&outcome);
        last = Some(outcome);
        iterations += 1;
        if iterations < limit {
            std::thread::sleep(poll_interval);
        }
    }

    Ok(WatchOutcome { iterations, last })
}

fn submit_from_args(args: &[String], submitted_at: SystemTime) -> Result<SubmitOutcome, CliError> {
    let default_cfg = LibrarianConfig::default();
    let mut drafts_dir = default_cfg.drafts_dir;
    let mut raw_text: Option<String> = None;
    let mut metadata = DraftMetadata::new(DraftSourceSurface::Cli, submitted_at);
    metadata.provenance_uri = Some("cli://mimir-librarian/submit".to_string());

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--text" => {
                raw_text = Some(take_value(args, &mut i, "--text")?);
            }
            "--drafts-dir" => {
                drafts_dir = take_value(args, &mut i, "--drafts-dir")?.into();
            }
            "--source-surface" => {
                let value = take_value(args, &mut i, "--source-surface")?;
                metadata.source_surface = DraftSourceSurface::parse(&value)
                    .ok_or_else(|| CliError::Usage(format!("unknown source surface '{value}'")))?;
            }
            "--agent" => {
                metadata.source_agent = Some(take_value(args, &mut i, "--agent")?);
            }
            "--project" => {
                metadata.source_project = Some(take_value(args, &mut i, "--project")?);
            }
            "--operator" => {
                metadata.operator = Some(take_value(args, &mut i, "--operator")?);
            }
            "--provenance" => {
                metadata.provenance_uri = Some(take_value(args, &mut i, "--provenance")?);
            }
            "--tag" => {
                metadata
                    .context_tags
                    .push(take_value(args, &mut i, "--tag")?);
            }
            other => {
                return Err(CliError::Usage(format!("unknown option '{other}'")));
            }
        }
        i += 1;
    }

    let raw_text = raw_text.ok_or_else(|| CliError::Usage("--text is required".to_string()))?;
    if raw_text.trim().is_empty() {
        return Err(CliError::Usage("--text must not be empty".to_string()));
    }

    let draft = Draft::with_metadata(raw_text, metadata);
    let path = DraftStore::new(drafts_dir).submit(&draft)?;
    Ok(SubmitOutcome {
        id: draft.id().to_hex(),
        path: path.display().to_string(),
    })
}

fn sweep_from_args(args: &[String], submitted_at: SystemTime) -> Result<SweepOutcome, CliError> {
    let default_cfg = LibrarianConfig::default();
    let mut drafts_dir = default_cfg.drafts_dir;
    let mut source_path: Option<PathBuf> = None;
    let mut metadata = DraftMetadata::new(DraftSourceSurface::Directory, submitted_at);
    let mut source_surface_seen = false;

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--path" => {
                source_path = Some(take_value(args, &mut i, "--path")?.into());
            }
            "--drafts-dir" => {
                drafts_dir = take_value(args, &mut i, "--drafts-dir")?.into();
            }
            "--source-surface" => {
                let value = take_value(args, &mut i, "--source-surface")?;
                let source_surface = DraftSourceSurface::parse(&value)
                    .ok_or_else(|| CliError::Usage(format!("unknown source surface '{value}'")))?;
                if !is_sweep_source_surface(source_surface) {
                    return Err(CliError::Usage(format!(
                        "source surface '{value}' is not valid for sweep"
                    )));
                }
                metadata.source_surface = source_surface;
                source_surface_seen = true;
            }
            "--agent" => {
                metadata.source_agent = Some(take_value(args, &mut i, "--agent")?);
            }
            "--project" => {
                metadata.source_project = Some(take_value(args, &mut i, "--project")?);
            }
            "--operator" => {
                metadata.operator = Some(take_value(args, &mut i, "--operator")?);
            }
            "--tag" => {
                metadata
                    .context_tags
                    .push(take_value(args, &mut i, "--tag")?);
            }
            "--provenance" => {
                return Err(CliError::Usage(
                    "--provenance is per swept file; use --path for sweep provenance".to_string(),
                ));
            }
            other => {
                return Err(CliError::Usage(format!("unknown option '{other}'")));
            }
        }
        i += 1;
    }

    let source_path =
        source_path.ok_or_else(|| CliError::Usage("--path is required".to_string()))?;
    if !source_surface_seen {
        return Err(CliError::Usage(
            "--source-surface is required for sweep".to_string(),
        ));
    }
    fill_default_agent(&mut metadata);

    let files = collect_sweep_files(&source_path)?;
    let store = DraftStore::new(drafts_dir);
    let mut outcome = SweepOutcome {
        submitted: 0,
        skipped_empty: 0,
        drafts: Vec::new(),
    };

    for file in files {
        let raw_text = fs::read_to_string(&file).map_err(LibrarianError::from)?;
        if raw_text.trim().is_empty() {
            outcome.skipped_empty += 1;
            continue;
        }

        let mut file_metadata = metadata.clone();
        let provenance_uri = file_uri(&file);
        file_metadata.provenance_uri = Some(provenance_uri.clone());
        let draft = Draft::with_metadata(raw_text, file_metadata);
        let path = store.submit(&draft)?;
        outcome.submitted += 1;
        outcome.drafts.push(SweepDraftOutcome {
            id: draft.id().to_hex(),
            path: path.display().to_string(),
            provenance_uri,
        });
    }

    Ok(outcome)
}

fn quorum_from_args(args: &[String], now: SystemTime) -> Result<QuorumCliOutcome, CliError> {
    let Some(command) = args.first() else {
        return Err(CliError::Usage("quorum subcommand is required".to_string()));
    };
    match command.as_str() {
        "create" => quorum_create_from_args(&args[1..], now),
        "pilot-plan" => quorum_pilot_plan_from_args(&args[1..]),
        "pilot-status" => quorum_pilot_status_from_args(&args[1..]),
        "pilot-run" => quorum_pilot_run_from_args(&args[1..], now),
        "pilot-review" => quorum_pilot_review_from_args(&args[1..], now),
        "pilot-summary" => quorum_pilot_summary_from_args(&args[1..]),
        "append-output" => quorum_append_output_from_args(&args[1..], now),
        "append-status-output" => quorum_append_status_output_from_args(&args[1..], now),
        "outputs" => quorum_outputs_from_args(&args[1..]),
        "visible" => quorum_visible_from_args(&args[1..]),
        "adapter-request" => quorum_adapter_request_from_args(&args[1..]),
        "adapter-plan" => quorum_adapter_plan_from_args(&args[1..]),
        "adapter-run" => quorum_adapter_run_from_args(&args[1..]),
        "adapter-run-round" => quorum_adapter_run_round_from_args(&args[1..]),
        "adapter-run-rounds" => quorum_adapter_run_rounds_from_args(&args[1..], now),
        "synthesize-plan" => quorum_synthesize_plan_from_args(&args[1..]),
        "synthesize-run" => quorum_synthesize_run_from_args(&args[1..]),
        "accept-synthesis" => quorum_accept_synthesis_from_args(&args[1..]),
        "synthesize" => quorum_synthesize_from_args(&args[1..]),
        "submit-drafts" => quorum_submit_drafts_from_args(&args[1..], now),
        other => Err(CliError::Usage(format!(
            "unknown quorum subcommand '{other}'"
        ))),
    }
}

fn quorum_create_from_args(args: &[String], now: SystemTime) -> Result<QuorumCliOutcome, CliError> {
    let mut quorum_dir: Option<PathBuf> = None;
    let mut id: Option<String> = None;
    let mut requested_at_unix_ms: Option<u64> = None;
    let mut requester: Option<String> = None;
    let mut question: Option<String> = None;
    let mut target_project: Option<String> = None;
    let mut target_scope: Option<String> = None;
    let mut evidence_policy = "operator_supplied".to_string();
    let mut provenance_uri: Option<String> = None;
    let mut participants = Vec::new();

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--quorum-dir" => {
                quorum_dir = Some(take_value(args, &mut i, "--quorum-dir")?.into());
            }
            "--id" => {
                id = Some(take_value(args, &mut i, "--id")?);
            }
            "--requested-at-unix-ms" => {
                let value = take_value(args, &mut i, "--requested-at-unix-ms")?;
                requested_at_unix_ms = Some(parse_u64("--requested-at-unix-ms", &value)?);
            }
            "--requester" => {
                requester = Some(take_value(args, &mut i, "--requester")?);
            }
            "--question" => {
                question = Some(take_value(args, &mut i, "--question")?);
            }
            "--target-project" => {
                target_project = Some(take_value(args, &mut i, "--target-project")?);
            }
            "--target-scope" => {
                target_scope = Some(take_value(args, &mut i, "--target-scope")?);
            }
            "--evidence-policy" => {
                evidence_policy = take_value(args, &mut i, "--evidence-policy")?;
            }
            "--provenance" => {
                provenance_uri = Some(take_value(args, &mut i, "--provenance")?);
            }
            "--participant" => {
                let value = take_value(args, &mut i, "--participant")?;
                participants.push(parse_quorum_participant(&value)?);
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum create option '{other}'"
                )));
            }
        }
        i += 1;
    }

    let quorum_dir = require_path(quorum_dir, "--quorum-dir")?;
    let id = require_non_empty(id, "--id")?;
    let requester = require_non_empty(requester, "--requester")?;
    let question = require_non_empty(question, "--question")?;
    if participants.is_empty() {
        return Err(CliError::Usage(
            "--participant is required at least once".to_string(),
        ));
    }

    let episode = QuorumEpisode {
        schema_version: QUORUM_SCHEMA_VERSION,
        requested_at_unix_ms: requested_at_unix_ms.unwrap_or_else(|| system_time_to_unix_ms(now)),
        provenance_uri: provenance_uri.unwrap_or_else(|| format!("quorum://episode/{id}")),
        id: id.clone(),
        requester,
        question,
        target_project,
        target_scope,
        evidence_policy,
        state: QuorumEpisodeState::Requested,
        participants,
    };
    let path = QuorumStore::new(quorum_dir).create_episode(&episode)?;
    Ok(QuorumCliOutcome::EpisodeCreated {
        id,
        path: path.display().to_string(),
    })
}

struct QuorumPilotPlanArgs {
    quorum_dir: PathBuf,
    episode_id: String,
    through_round: QuorumRound,
    out_dir: PathBuf,
    drafts_dir: PathBuf,
    synthesizer_adapter: String,
    adapter_binaries: BTreeMap<String, String>,
    synthesizer_binary: Option<String>,
    timeout_secs: u64,
    required_proposed_drafts: usize,
    project: Option<String>,
    operator: Option<String>,
    tags: Vec<String>,
}

struct QuorumPilotReviewArgs {
    manifest_file: PathBuf,
    reviewer: String,
    decision: QuorumPilotReviewDecision,
    summary: String,
    findings: Vec<QuorumPilotReviewFinding>,
    next_actions: Vec<String>,
}

fn quorum_pilot_plan_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let parsed = parse_quorum_pilot_plan_args(args)?;
    Ok(QuorumCliOutcome::PilotPlan {
        plan: Box::new(build_quorum_pilot_plan(&parsed)?),
    })
}

fn parse_quorum_pilot_plan_args(args: &[String]) -> Result<QuorumPilotPlanArgs, CliError> {
    let mut quorum_dir: Option<PathBuf> = None;
    let mut episode_id: Option<String> = None;
    let mut through_round: Option<QuorumRound> = None;
    let mut out_dir: Option<PathBuf> = None;
    let mut drafts_dir: Option<PathBuf> = None;
    let mut synthesizer_adapter: Option<String> = None;
    let mut adapter_binaries = BTreeMap::new();
    let mut synthesizer_binary: Option<String> = None;
    let mut timeout_secs: Option<u64> = None;
    let mut required_proposed_drafts: Option<usize> = None;
    let mut project: Option<String> = None;
    let mut operator: Option<String> = None;
    let mut tags = Vec::new();

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--quorum-dir" => {
                quorum_dir = Some(take_value(args, &mut i, "--quorum-dir")?.into());
            }
            "--episode-id" => {
                episode_id = Some(take_value(args, &mut i, "--episode-id")?);
            }
            "--through-round" => {
                let value = take_value(args, &mut i, "--through-round")?;
                through_round = Some(parse_quorum_round(&value)?);
            }
            "--out-dir" => {
                out_dir = Some(take_value(args, &mut i, "--out-dir")?.into());
            }
            "--drafts-dir" => {
                drafts_dir = Some(take_value(args, &mut i, "--drafts-dir")?.into());
            }
            "--synthesizer" => {
                let value = take_value(args, &mut i, "--synthesizer")?;
                synthesizer_adapter = Some(parse_quorum_adapter(&value)?);
            }
            "--adapter-binary" => {
                let value = take_value(args, &mut i, "--adapter-binary")?;
                let (adapter, binary) = parse_adapter_binary(&value)?;
                if adapter_binaries.insert(adapter.clone(), binary).is_some() {
                    return Err(CliError::Usage(format!(
                        "--adapter-binary supplied more than once for adapter '{adapter}'"
                    )));
                }
            }
            "--synthesizer-binary" => {
                synthesizer_binary = Some(take_value(args, &mut i, "--synthesizer-binary")?);
            }
            "--timeout-secs" => {
                let value = take_value(args, &mut i, "--timeout-secs")?;
                timeout_secs = Some(parse_positive_u64("--timeout-secs", &value)?);
            }
            "--require-proposed-drafts" => {
                let value = take_value(args, &mut i, "--require-proposed-drafts")?;
                required_proposed_drafts = Some(parse_usize("--require-proposed-drafts", &value)?);
            }
            "--project" => {
                project = Some(take_value(args, &mut i, "--project")?);
            }
            "--operator" => {
                operator = Some(take_value(args, &mut i, "--operator")?);
            }
            "--tag" => {
                tags.push(take_value(args, &mut i, "--tag")?);
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum pilot-plan option '{other}'"
                )));
            }
        }
        i += 1;
    }

    Ok(QuorumPilotPlanArgs {
        quorum_dir: require_path(quorum_dir, "--quorum-dir")?,
        episode_id: require_non_empty(episode_id, "--episode-id")?,
        through_round: through_round
            .ok_or_else(|| CliError::Usage("--through-round is required".to_string()))?,
        out_dir: require_path(out_dir, "--out-dir")?,
        drafts_dir: require_path(drafts_dir, "--drafts-dir")?,
        synthesizer_adapter: require_non_empty(synthesizer_adapter, "--synthesizer")?,
        adapter_binaries,
        synthesizer_binary,
        timeout_secs: timeout_secs.unwrap_or(DEFAULT_QUORUM_ADAPTER_TIMEOUT_SECS),
        required_proposed_drafts: required_proposed_drafts.unwrap_or(0),
        project,
        operator,
        tags,
    })
}

fn build_quorum_pilot_plan(parsed: &QuorumPilotPlanArgs) -> Result<QuorumPilotPlan, CliError> {
    let store = QuorumStore::new(&parsed.quorum_dir);
    let episode = store.load_episode(&parsed.episode_id)?;
    let artifacts = build_quorum_pilot_artifacts(parsed);
    let steps = build_quorum_pilot_steps(parsed, &artifacts);
    let manifest_path = artifacts_manifest_path(&parsed.out_dir, &parsed.episode_id);
    fs::create_dir_all(&parsed.out_dir).map_err(LibrarianError::from)?;

    let plan = QuorumPilotPlan {
        schema_version: QUORUM_SCHEMA_VERSION,
        episode_id: parsed.episode_id.clone(),
        quorum_dir: parsed.quorum_dir.display().to_string(),
        out_dir: parsed.out_dir.display().to_string(),
        drafts_dir: parsed.drafts_dir.display().to_string(),
        through_round: quorum_round_name(parsed.through_round).to_string(),
        synthesizer_adapter: parsed.synthesizer_adapter.clone(),
        timeout_secs: parsed.timeout_secs,
        required_proposed_drafts: parsed.required_proposed_drafts,
        participants: episode.participants,
        manifest_path: manifest_path.display().to_string(),
        artifacts,
        steps,
    };
    write_quorum_pilot_manifest(&plan)?;
    Ok(plan)
}

fn build_quorum_pilot_artifacts(parsed: &QuorumPilotPlanArgs) -> QuorumPilotArtifacts {
    let synthesis_paths = synthesis_plan_paths(&parsed.out_dir, &parsed.episode_id);
    QuorumPilotArtifacts {
        round_statuses: quorum_rounds_through(parsed.through_round)
            .into_iter()
            .map(|round| QuorumPilotRoundArtifact {
                round: quorum_round_name(round).to_string(),
                status_path: adapter_round_status_path(&parsed.out_dir, &parsed.episode_id, round)
                    .display()
                    .to_string(),
            })
            .collect(),
        synthesis_status_path: synthesis_paths.status.display().to_string(),
        synthesis_result_path: synthesis_paths.result.display().to_string(),
    }
}

fn build_quorum_pilot_steps(
    parsed: &QuorumPilotPlanArgs,
    artifacts: &QuorumPilotArtifacts,
) -> Vec<QuorumPilotStep> {
    vec![
        QuorumPilotStep {
            name: "run_rounds".to_string(),
            description: "Execute participant rounds and append only successful status artifacts."
                .to_string(),
            argv: pilot_run_rounds_argv(parsed),
            writes: artifacts
                .round_statuses
                .iter()
                .map(|artifact| artifact.status_path.clone())
                .collect(),
        },
        QuorumPilotStep {
            name: "run_synthesis".to_string(),
            description: "Run the synthesizer into a proposed result artifact.".to_string(),
            argv: pilot_run_synthesis_argv(parsed),
            writes: vec![
                artifacts.synthesis_status_path.clone(),
                artifacts.synthesis_result_path.clone(),
            ],
        },
        QuorumPilotStep {
            name: "accept_synthesis".to_string(),
            description: "Validate the successful synthesis status before saving result.json."
                .to_string(),
            argv: pilot_accept_synthesis_argv(parsed, artifacts),
            writes: vec![QuorumStore::new(&parsed.quorum_dir)
                .result_artifact_path(&parsed.episode_id)
                .display()
                .to_string()],
        },
        QuorumPilotStep {
            name: "submit_drafts".to_string(),
            description: "Submit accepted proposed memories as consensus_quorum drafts."
                .to_string(),
            argv: pilot_submit_drafts_argv(parsed),
            writes: vec![parsed.drafts_dir.display().to_string()],
        },
    ]
}

fn pilot_run_rounds_argv(parsed: &QuorumPilotPlanArgs) -> Vec<String> {
    let mut argv = vec![
        "mimir-librarian".to_string(),
        "quorum".to_string(),
        "adapter-run-rounds".to_string(),
        "--quorum-dir".to_string(),
        parsed.quorum_dir.display().to_string(),
        "--episode-id".to_string(),
        parsed.episode_id.clone(),
        "--through-round".to_string(),
        quorum_round_name(parsed.through_round).to_string(),
        "--out-dir".to_string(),
        parsed.out_dir.display().to_string(),
    ];
    for (adapter, binary) in &parsed.adapter_binaries {
        argv.push("--adapter-binary".to_string());
        argv.push(format!("{adapter}={binary}"));
    }
    argv.push("--timeout-secs".to_string());
    argv.push(parsed.timeout_secs.to_string());
    argv
}

fn pilot_run_synthesis_argv(parsed: &QuorumPilotPlanArgs) -> Vec<String> {
    let mut argv = vec![
        "mimir-librarian".to_string(),
        "quorum".to_string(),
        "synthesize-run".to_string(),
        "--quorum-dir".to_string(),
        parsed.quorum_dir.display().to_string(),
        "--episode-id".to_string(),
        parsed.episode_id.clone(),
        "--adapter".to_string(),
        parsed.synthesizer_adapter.clone(),
        "--out-dir".to_string(),
        parsed.out_dir.display().to_string(),
    ];
    if let Some(binary) = parsed
        .synthesizer_binary
        .as_ref()
        .or_else(|| parsed.adapter_binaries.get(&parsed.synthesizer_adapter))
    {
        argv.push("--binary".to_string());
        argv.push(binary.clone());
    }
    argv.push("--timeout-secs".to_string());
    argv.push(parsed.timeout_secs.to_string());
    argv
}

fn pilot_accept_synthesis_argv(
    parsed: &QuorumPilotPlanArgs,
    artifacts: &QuorumPilotArtifacts,
) -> Vec<String> {
    vec![
        "mimir-librarian".to_string(),
        "quorum".to_string(),
        "accept-synthesis".to_string(),
        "--quorum-dir".to_string(),
        parsed.quorum_dir.display().to_string(),
        "--episode-id".to_string(),
        parsed.episode_id.clone(),
        "--status-file".to_string(),
        artifacts.synthesis_status_path.clone(),
    ]
}

fn pilot_submit_drafts_argv(parsed: &QuorumPilotPlanArgs) -> Vec<String> {
    let mut argv = vec![
        "mimir-librarian".to_string(),
        "quorum".to_string(),
        "submit-drafts".to_string(),
        "--quorum-dir".to_string(),
        parsed.quorum_dir.display().to_string(),
        "--drafts-dir".to_string(),
        parsed.drafts_dir.display().to_string(),
        "--episode-id".to_string(),
        parsed.episode_id.clone(),
    ];
    if let Some(project) = &parsed.project {
        argv.push("--project".to_string());
        argv.push(project.clone());
    }
    if let Some(operator) = &parsed.operator {
        argv.push("--operator".to_string());
        argv.push(operator.clone());
    }
    for tag in &parsed.tags {
        argv.push("--tag".to_string());
        argv.push(tag.clone());
    }
    argv
}

fn artifacts_manifest_path(out_dir: &Path, episode_id: &str) -> PathBuf {
    out_dir.join(format!("{}-pilot-plan.json", safe_file_stem(episode_id)))
}

fn write_quorum_pilot_manifest(plan: &QuorumPilotPlan) -> Result<(), CliError> {
    let bytes = serde_json::to_vec_pretty(plan).map_err(LibrarianError::from)?;
    fs::write(&plan.manifest_path, bytes).map_err(LibrarianError::from)?;
    Ok(())
}

fn quorum_pilot_status_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let manifest_file = parse_quorum_pilot_status_args(args)?;
    let plan = load_quorum_pilot_manifest(&manifest_file)?;
    Ok(QuorumCliOutcome::PilotStatus {
        status: Box::new(build_quorum_pilot_status(&manifest_file, &plan)),
    })
}

fn quorum_pilot_run_from_args(
    args: &[String],
    now: SystemTime,
) -> Result<QuorumCliOutcome, CliError> {
    let manifest_file = parse_quorum_pilot_status_args(args)?;
    let plan = load_quorum_pilot_manifest(&manifest_file)?;
    Ok(QuorumCliOutcome::PilotRun {
        run: Box::new(execute_quorum_pilot_manifest(&manifest_file, &plan, now)?),
    })
}

fn quorum_pilot_review_from_args(
    args: &[String],
    now: SystemTime,
) -> Result<QuorumCliOutcome, CliError> {
    let parsed = parse_quorum_pilot_review_args(args)?;
    let plan = load_quorum_pilot_manifest(&parsed.manifest_file)?;
    Ok(QuorumCliOutcome::PilotReview {
        review: Box::new(write_quorum_pilot_review(&parsed, &plan, now)?),
    })
}

fn quorum_pilot_summary_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let manifest_file = parse_quorum_pilot_manifest_args(args, "pilot-summary")?;
    let plan = load_quorum_pilot_manifest(&manifest_file)?;
    Ok(QuorumCliOutcome::PilotSummary {
        summary: Box::new(build_quorum_pilot_summary(&manifest_file, &plan)?),
    })
}

fn parse_quorum_pilot_status_args(args: &[String]) -> Result<PathBuf, CliError> {
    parse_quorum_pilot_manifest_args(args, "pilot-status")
}

fn parse_quorum_pilot_manifest_args(args: &[String], command: &str) -> Result<PathBuf, CliError> {
    let mut manifest_file: Option<PathBuf> = None;
    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--manifest-file" => {
                manifest_file = Some(take_value(args, &mut i, "--manifest-file")?.into());
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum {command} option '{other}'"
                )));
            }
        }
        i += 1;
    }
    require_path(manifest_file, "--manifest-file")
}

fn parse_quorum_pilot_review_args(args: &[String]) -> Result<QuorumPilotReviewArgs, CliError> {
    let mut manifest_file: Option<PathBuf> = None;
    let mut reviewer: Option<String> = None;
    let mut decision: Option<QuorumPilotReviewDecision> = None;
    let mut summary: Option<String> = None;
    let mut findings = Vec::new();
    let mut next_actions = Vec::new();

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--manifest-file" => {
                manifest_file = Some(take_value(args, &mut i, "--manifest-file")?.into());
            }
            "--reviewer" => {
                reviewer = Some(take_value(args, &mut i, "--reviewer")?);
            }
            "--decision" => {
                let value = take_value(args, &mut i, "--decision")?;
                decision = Some(parse_quorum_pilot_review_decision(&value)?);
            }
            "--summary" => {
                summary = Some(take_value(args, &mut i, "--summary")?);
            }
            "--finding" => {
                let value = take_value(args, &mut i, "--finding")?;
                findings.push(parse_quorum_pilot_review_finding(&value)?);
            }
            "--next-action" => {
                next_actions.push(take_value(args, &mut i, "--next-action")?);
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum pilot-review option '{other}'"
                )));
            }
        }
        i += 1;
    }

    Ok(QuorumPilotReviewArgs {
        manifest_file: require_path(manifest_file, "--manifest-file")?,
        reviewer: require_non_empty(reviewer, "--reviewer")?,
        decision: decision.ok_or_else(|| CliError::Usage("--decision is required".to_string()))?,
        summary: require_non_empty(summary, "--summary")?,
        findings,
        next_actions,
    })
}

fn load_quorum_pilot_manifest(path: &Path) -> Result<QuorumPilotPlan, CliError> {
    let bytes = fs::read(path).map_err(LibrarianError::from)?;
    let plan: QuorumPilotPlan = serde_json::from_slice(&bytes).map_err(LibrarianError::from)?;
    if plan.schema_version != QUORUM_SCHEMA_VERSION {
        return Err(CliError::Usage(format!(
            "unsupported pilot manifest schema version {}; expected {QUORUM_SCHEMA_VERSION}",
            plan.schema_version
        )));
    }
    Ok(plan)
}

fn write_quorum_pilot_review(
    parsed: &QuorumPilotReviewArgs,
    plan: &QuorumPilotPlan,
    now: SystemTime,
) -> Result<QuorumPilotReview, CliError> {
    let status = build_quorum_pilot_status(&parsed.manifest_file, plan);
    if parsed.decision == QuorumPilotReviewDecision::Pass && !status.complete {
        return Err(CliError::Usage(
            "--decision pass requires a complete pilot-status".to_string(),
        ));
    }
    let review_path = pilot_review_path(plan);
    if let Some(parent) = review_path.parent() {
        fs::create_dir_all(parent).map_err(LibrarianError::from)?;
    }
    let review = QuorumPilotReview {
        schema_version: QUORUM_SCHEMA_VERSION,
        episode_id: plan.episode_id.clone(),
        manifest_path: parsed.manifest_file.display().to_string(),
        review_path: review_path.display().to_string(),
        reviewed_at_unix_ms: system_time_to_unix_ms(now),
        reviewer: parsed.reviewer.clone(),
        decision: parsed.decision,
        summary: parsed.summary.clone(),
        findings: parsed.findings.clone(),
        next_actions: parsed.next_actions.clone(),
        status_at_review: status,
    };
    let bytes = serde_json::to_vec_pretty(&review).map_err(LibrarianError::from)?;
    let mut file = fs::OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(&review_path)
        .map_err(LibrarianError::from)?;
    file.write_all(&bytes).map_err(LibrarianError::from)?;
    Ok(review)
}

fn build_quorum_pilot_summary(
    manifest_file: &Path,
    plan: &QuorumPilotPlan,
) -> Result<QuorumPilotSummary, CliError> {
    let status = build_quorum_pilot_status(manifest_file, plan);
    let store = QuorumStore::new(&plan.quorum_dir);
    let result_path = store.result_artifact_path(&plan.episode_id);
    let result = store.load_result(&plan.episode_id).ok();
    let review_path = pilot_review_path(plan);
    let review_decision = load_quorum_pilot_review_decision(&review_path)?;
    let submitted_drafts = count_quorum_drafts(Path::new(&plan.drafts_dir), &plan.episode_id)?;
    let next_action = pilot_summary_next_action(&status, review_decision, &review_path);

    Ok(QuorumPilotSummary {
        schema_version: QUORUM_SCHEMA_VERSION,
        episode_id: plan.episode_id.clone(),
        manifest_path: manifest_file.display().to_string(),
        complete: status.complete,
        overall_status: status.overall_status,
        required_proposed_drafts: plan.required_proposed_drafts,
        proposed_memory_drafts: result
            .as_ref()
            .map_or(0, |result| result.proposed_memory_drafts.len()),
        submitted_drafts,
        result_status: if result_path.is_file() {
            "present".to_string()
        } else {
            "missing".to_string()
        },
        result_path: result_path
            .is_file()
            .then(|| result_path.display().to_string()),
        review_status: if review_path.is_file() {
            "present".to_string()
        } else {
            "missing".to_string()
        },
        review_path: review_path.display().to_string(),
        review_decision,
        next_action,
        gates: status.gates,
    })
}

fn load_quorum_pilot_review_decision(
    review_path: &Path,
) -> Result<Option<QuorumPilotReviewDecision>, CliError> {
    if !review_path.exists() {
        return Ok(None);
    }
    let bytes = fs::read(review_path).map_err(LibrarianError::from)?;
    let value: serde_json::Value = serde_json::from_slice(&bytes).map_err(LibrarianError::from)?;
    let Some(decision) = value.get("decision").and_then(serde_json::Value::as_str) else {
        return Err(CliError::Usage(format!(
            "pilot review artifact '{}' is missing a decision",
            review_path.display()
        )));
    };
    parse_quorum_pilot_review_decision(decision).map(Some)
}

fn pilot_summary_next_action(
    status: &QuorumPilotStatus,
    review_decision: Option<QuorumPilotReviewDecision>,
    review_path: &Path,
) -> String {
    if !status.complete {
        let gate = pilot_first_incomplete_gate_name(status);
        return format!(
            "complete pilot gate `{gate}` with `mimir-librarian quorum pilot-run --manifest-file {}`",
            status.manifest_path
        );
    }
    if review_decision.is_none() {
        return format!(
            "certify pilot with `mimir-librarian quorum pilot-review --manifest-file {} --reviewer ID --decision pass --summary TEXT`",
            status.manifest_path
        );
    }
    if review_decision == Some(QuorumPilotReviewDecision::Pass) {
        "none".to_string()
    } else {
        format!("address pilot review findings in {}", review_path.display())
    }
}

fn pilot_review_path(plan: &QuorumPilotPlan) -> PathBuf {
    PathBuf::from(&plan.out_dir).join(format!(
        "{}-pilot-review.json",
        safe_file_stem(&plan.episode_id)
    ))
}

fn build_quorum_pilot_status(manifest_file: &Path, plan: &QuorumPilotPlan) -> QuorumPilotStatus {
    let store = QuorumStore::new(&plan.quorum_dir);
    let gates = vec![
        pilot_rounds_gate(plan, &store),
        pilot_synthesis_gate(plan),
        pilot_acceptance_gate(plan, &store),
        pilot_submit_drafts_gate(plan, &store),
    ];
    let overall_status = pilot_overall_status(&gates);
    QuorumPilotStatus {
        schema_version: QUORUM_SCHEMA_VERSION,
        episode_id: plan.episode_id.clone(),
        manifest_path: manifest_file.display().to_string(),
        complete: overall_status == QuorumPilotGateStatus::Complete,
        overall_status,
        gates,
    }
}

fn execute_quorum_pilot_manifest(
    manifest_file: &Path,
    plan: &QuorumPilotPlan,
    now: SystemTime,
) -> Result<QuorumPilotRun, CliError> {
    validate_quorum_pilot_manifest_steps(plan)?;
    let mut executed_steps = Vec::new();
    let mut skipped_steps = Vec::new();
    for step in &plan.steps {
        let current_status = build_quorum_pilot_status(manifest_file, plan);
        if pilot_step_is_complete(&current_status, &step.name) {
            skipped_steps.push(step.name.clone());
            continue;
        }
        if current_status.overall_status == QuorumPilotGateStatus::Failed {
            let failed_step = pilot_first_incomplete_gate_name(&current_status);
            return Ok(QuorumPilotRun {
                schema_version: QUORUM_SCHEMA_VERSION,
                episode_id: plan.episode_id.clone(),
                manifest_path: manifest_file.display().to_string(),
                success: false,
                executed_steps,
                skipped_steps,
                failed_step: Some(failed_step.clone()),
                error: Some(format!(
                    "pilot manifest cannot continue because gate '{failed_step}' failed"
                )),
                final_status: current_status,
            });
        }
        if let Err(err) = execute_quorum_pilot_step(step, now) {
            let final_status = build_quorum_pilot_status(manifest_file, plan);
            return Ok(QuorumPilotRun {
                schema_version: QUORUM_SCHEMA_VERSION,
                episode_id: plan.episode_id.clone(),
                manifest_path: manifest_file.display().to_string(),
                success: false,
                executed_steps,
                skipped_steps,
                failed_step: Some(step.name.clone()),
                error: Some(format!("pilot step '{}' failed: {err}", step.name)),
                final_status,
            });
        }
        executed_steps.push(step.name.clone());
    }
    let final_status = build_quorum_pilot_status(manifest_file, plan);
    let success = final_status.complete;
    let failed_step = (!success).then(|| pilot_first_incomplete_gate_name(&final_status));
    let error = failed_step.as_ref().map(|step| {
        format!(
            "pilot manifest finished but final status is {:?}; first incomplete gate: {step}",
            final_status.overall_status
        )
    });
    Ok(QuorumPilotRun {
        schema_version: QUORUM_SCHEMA_VERSION,
        episode_id: plan.episode_id.clone(),
        manifest_path: manifest_file.display().to_string(),
        success,
        executed_steps,
        skipped_steps,
        failed_step,
        error,
        final_status,
    })
}

fn execute_quorum_pilot_step(step: &QuorumPilotStep, now: SystemTime) -> Result<(), CliError> {
    let outcome = quorum_from_args(&step.argv[2..], now)?;
    match (step.name.as_str(), outcome) {
        ("run_rounds", QuorumCliOutcome::AdapterRoundsRun { rounds_run }) if rounds_run.success => {
            Ok(())
        }
        ("run_synthesis", QuorumCliOutcome::SynthesisRun { run }) if run.status.success => Ok(()),
        ("accept_synthesis", QuorumCliOutcome::ResultSaved { .. })
        | ("submit_drafts", QuorumCliOutcome::DraftsSubmitted { .. }) => Ok(()),
        (name, outcome) => Err(CliError::Usage(format!(
            "pilot step '{name}' did not complete successfully: {outcome:?}"
        ))),
    }
}

fn validate_quorum_pilot_manifest_steps(plan: &QuorumPilotPlan) -> Result<(), CliError> {
    for step in &plan.steps {
        validate_quorum_pilot_step_command(step)?;
    }
    Ok(())
}

fn validate_quorum_pilot_step_command(step: &QuorumPilotStep) -> Result<(), CliError> {
    if step.argv.len() < 3 || step.argv[0] != "mimir-librarian" || step.argv[1] != "quorum" {
        return Err(CliError::Usage(format!(
            "pilot step '{}' does not carry a valid mimir-librarian quorum argv",
            step.name
        )));
    }
    let expected = match step.name.as_str() {
        "run_rounds" => "adapter-run-rounds",
        "run_synthesis" => "synthesize-run",
        "accept_synthesis" => "accept-synthesis",
        "submit_drafts" => "submit-drafts",
        other => {
            return Err(CliError::Usage(format!(
                "unknown pilot manifest step '{other}'"
            )));
        }
    };
    if step.argv[2] != expected {
        return Err(CliError::Usage(format!(
            "pilot step '{}' expected command '{expected}', got '{}'",
            step.name, step.argv[2]
        )));
    }
    Ok(())
}

fn pilot_step_is_complete(status: &QuorumPilotStatus, step_name: &str) -> bool {
    status
        .gates
        .iter()
        .any(|gate| gate.name == step_name && gate.status == QuorumPilotGateStatus::Complete)
}

fn pilot_first_incomplete_gate_name(status: &QuorumPilotStatus) -> String {
    status
        .gates
        .iter()
        .find(|gate| gate.status != QuorumPilotGateStatus::Complete)
        .map_or_else(|| "<unknown>".to_string(), |gate| gate.name.clone())
}

fn pilot_overall_status(gates: &[QuorumPilotGate]) -> QuorumPilotGateStatus {
    if gates
        .iter()
        .any(|gate| gate.status == QuorumPilotGateStatus::Failed)
    {
        return QuorumPilotGateStatus::Failed;
    }
    if gates
        .iter()
        .all(|gate| gate.status == QuorumPilotGateStatus::Complete)
    {
        return QuorumPilotGateStatus::Complete;
    }
    QuorumPilotGateStatus::Pending
}

fn pilot_rounds_gate(plan: &QuorumPilotPlan, store: &QuorumStore) -> QuorumPilotGate {
    let artifacts = plan
        .artifacts
        .round_statuses
        .iter()
        .map(|artifact| artifact.status_path.clone())
        .collect::<Vec<_>>();
    for artifact in &plan.artifacts.round_statuses {
        let path = Path::new(&artifact.status_path);
        if !path.exists() {
            return pilot_gate(
                "run_rounds",
                QuorumPilotGateStatus::Pending,
                "round status artifact is missing",
                artifacts,
            );
        }
        if let Err(err) = pilot_validate_round_artifact(plan, store, artifact) {
            return pilot_gate("run_rounds", err.0, &err.1, artifacts);
        }
    }
    pilot_gate(
        "run_rounds",
        QuorumPilotGateStatus::Complete,
        "all round statuses are successful and participant outputs are recorded",
        artifacts,
    )
}

fn pilot_validate_round_artifact(
    plan: &QuorumPilotPlan,
    store: &QuorumStore,
    artifact: &QuorumPilotRoundArtifact,
) -> Result<(), (QuorumPilotGateStatus, String)> {
    let statuses = load_adapter_status_artifact(Path::new(&artifact.status_path))
        .map_err(|err| (QuorumPilotGateStatus::Failed, err.to_string()))?;
    for status in &statuses {
        validate_adapter_status_for_append(status)
            .map_err(|err| (QuorumPilotGateStatus::Failed, err.to_string()))?;
    }
    let round = parse_quorum_round(&artifact.round)
        .map_err(|err| (QuorumPilotGateStatus::Failed, err.to_string()))?;
    let outputs = store
        .load_round_outputs(&plan.episode_id, round)
        .map_err(|err| (QuorumPilotGateStatus::Failed, err.to_string()))?;
    if outputs.len() < plan.participants.len() {
        return Err((
            QuorumPilotGateStatus::Pending,
            "round status exists but participant outputs have not all been appended".to_string(),
        ));
    }
    Ok(())
}

fn pilot_synthesis_gate(plan: &QuorumPilotPlan) -> QuorumPilotGate {
    let artifacts = vec![
        plan.artifacts.synthesis_status_path.clone(),
        plan.artifacts.synthesis_result_path.clone(),
    ];
    let status_file = Path::new(&plan.artifacts.synthesis_status_path);
    if !status_file.exists() {
        return pilot_gate(
            "run_synthesis",
            QuorumPilotGateStatus::Pending,
            "synthesis status artifact is missing",
            artifacts,
        );
    }
    match pilot_validate_synthesis_status(plan, status_file) {
        Ok(()) => pilot_gate(
            "run_synthesis",
            QuorumPilotGateStatus::Complete,
            "synthesis status and proposed result are valid",
            artifacts,
        ),
        Err(message) => pilot_gate(
            "run_synthesis",
            QuorumPilotGateStatus::Failed,
            &message,
            artifacts,
        ),
    }
}

fn pilot_validate_synthesis_status(
    plan: &QuorumPilotPlan,
    status_file: &Path,
) -> Result<(), String> {
    let status = load_synthesis_status_artifact(status_file).map_err(|err| err.to_string())?;
    let parsed = QuorumAcceptSynthesisArgs {
        quorum_dir: PathBuf::from(&plan.quorum_dir),
        episode_id: Some(plan.episode_id.clone()),
        result_file: None,
        status_file: Some(status_file.to_path_buf()),
    };
    validate_synthesis_status_for_accept(&parsed, status_file, &status)
        .map_err(|err| err.to_string())
}

fn pilot_acceptance_gate(plan: &QuorumPilotPlan, store: &QuorumStore) -> QuorumPilotGate {
    let result_path = store.result_artifact_path(&plan.episode_id);
    let artifacts = vec![result_path.display().to_string()];
    if !result_path.exists() {
        return pilot_gate(
            "accept_synthesis",
            QuorumPilotGateStatus::Pending,
            "accepted quorum result is missing",
            artifacts,
        );
    }
    match store.load_result(&plan.episode_id) {
        Ok(result)
            if result.schema_version == QUORUM_SCHEMA_VERSION
                && result.episode_id == plan.episode_id =>
        {
            if result.proposed_memory_drafts.len() < plan.required_proposed_drafts {
                return pilot_gate(
                    "accept_synthesis",
                    QuorumPilotGateStatus::Failed,
                    &format!(
                        "accepted quorum result has {} proposed memory drafts; pilot requires at least {}",
                        result.proposed_memory_drafts.len(),
                        plan.required_proposed_drafts
                    ),
                    artifacts,
                );
            }
            pilot_gate(
                "accept_synthesis",
                QuorumPilotGateStatus::Complete,
                "accepted quorum result exists",
                artifacts,
            )
        }
        Ok(_) => pilot_gate(
            "accept_synthesis",
            QuorumPilotGateStatus::Failed,
            "accepted quorum result does not match the pilot episode",
            artifacts,
        ),
        Err(err) => pilot_gate(
            "accept_synthesis",
            QuorumPilotGateStatus::Failed,
            &err.to_string(),
            artifacts,
        ),
    }
}

fn pilot_submit_drafts_gate(plan: &QuorumPilotPlan, store: &QuorumStore) -> QuorumPilotGate {
    let artifacts = vec![plan.drafts_dir.clone()];
    let Ok(result) = store.load_result(&plan.episode_id) else {
        return pilot_gate(
            "submit_drafts",
            QuorumPilotGateStatus::Pending,
            "accepted quorum result is missing",
            artifacts,
        );
    };
    let expected = result.proposed_memory_drafts.len();
    if expected < plan.required_proposed_drafts {
        return pilot_gate(
            "submit_drafts",
            QuorumPilotGateStatus::Failed,
            &format!(
                "accepted quorum result has {expected} proposed memory drafts; pilot requires at least {}",
                plan.required_proposed_drafts
            ),
            artifacts,
        );
    }
    if expected == 0 {
        return pilot_gate(
            "submit_drafts",
            QuorumPilotGateStatus::Complete,
            "accepted result has no proposed memory drafts",
            artifacts,
        );
    }
    match count_quorum_drafts(Path::new(&plan.drafts_dir), &plan.episode_id) {
        Ok(count) if count >= expected => pilot_gate(
            "submit_drafts",
            QuorumPilotGateStatus::Complete,
            "proposed memory drafts have been submitted",
            artifacts,
        ),
        Ok(_) => pilot_gate(
            "submit_drafts",
            QuorumPilotGateStatus::Pending,
            "proposed memory drafts have not all been submitted",
            artifacts,
        ),
        Err(err) => pilot_gate(
            "submit_drafts",
            QuorumPilotGateStatus::Failed,
            &err.to_string(),
            artifacts,
        ),
    }
}

fn count_quorum_drafts(drafts_dir: &Path, episode_id: &str) -> Result<usize, CliError> {
    let provenance = format!("quorum://episode/{episode_id}");
    let mut count = 0;
    for state_dir in [
        "pending",
        "processing",
        "accepted",
        "skipped",
        "failed",
        "quarantined",
    ] {
        count += count_matching_drafts_in_dir(&drafts_dir.join(state_dir), &provenance)?;
    }
    Ok(count)
}

fn count_matching_drafts_in_dir(dir: &Path, provenance: &str) -> Result<usize, CliError> {
    let entries = match fs::read_dir(dir) {
        Ok(entries) => entries,
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(0),
        Err(err) => return Err(LibrarianError::from(err).into()),
    };
    let mut count = 0;
    for entry in entries {
        let path = entry.map_err(LibrarianError::from)?.path();
        if path.extension().and_then(|value| value.to_str()) != Some("json") {
            continue;
        }
        let bytes = fs::read(path).map_err(LibrarianError::from)?;
        let value: serde_json::Value =
            serde_json::from_slice(&bytes).map_err(LibrarianError::from)?;
        if value
            .get("source_surface")
            .and_then(serde_json::Value::as_str)
            == Some("consensus_quorum")
            && value
                .get("provenance_uri")
                .and_then(serde_json::Value::as_str)
                == Some(provenance)
        {
            count += 1;
        }
    }
    Ok(count)
}

fn pilot_gate(
    name: &str,
    status: QuorumPilotGateStatus,
    detail: &str,
    artifacts: Vec<String>,
) -> QuorumPilotGate {
    QuorumPilotGate {
        name: name.to_string(),
        status,
        detail: detail.to_string(),
        artifacts,
    }
}

struct QuorumAppendOutputArgs {
    quorum_dir: PathBuf,
    episode_id: String,
    output_id: String,
    participant_id: String,
    round: QuorumRound,
    submitted_at_unix_ms: Option<u64>,
    prompt: String,
    response: String,
    visible_prior_output_ids: Vec<String>,
    evidence_used: Vec<String>,
}

struct QuorumAppendStatusOutputArgs {
    status_file: PathBuf,
    submitted_at_unix_ms: Option<u64>,
}

fn quorum_append_output_from_args(
    args: &[String],
    now: SystemTime,
) -> Result<QuorumCliOutcome, CliError> {
    let parsed = parse_quorum_append_output_args(args)?;
    let output = QuorumParticipantOutput {
        schema_version: QUORUM_SCHEMA_VERSION,
        episode_id: parsed.episode_id.clone(),
        output_id: parsed.output_id.clone(),
        participant_id: parsed.participant_id,
        round: parsed.round,
        submitted_at_unix_ms: parsed
            .submitted_at_unix_ms
            .unwrap_or_else(|| system_time_to_unix_ms(now)),
        prompt: parsed.prompt,
        response: parsed.response,
        visible_prior_output_ids: parsed.visible_prior_output_ids,
        evidence_used: parsed.evidence_used,
    };
    let path = QuorumStore::new(parsed.quorum_dir).append_participant_output(&output)?;
    Ok(QuorumCliOutcome::OutputAppended {
        episode_id: parsed.episode_id,
        output_id: parsed.output_id,
        path: path.display().to_string(),
    })
}

fn quorum_append_status_output_from_args(
    args: &[String],
    now: SystemTime,
) -> Result<QuorumCliOutcome, CliError> {
    let parsed = parse_quorum_append_status_output_args(args)?;
    let statuses = load_adapter_status_artifact(&parsed.status_file)?;
    let append_args = statuses
        .iter()
        .map(validated_append_output_args_from_status)
        .collect::<Result<Vec<_>, _>>()?;
    let mut outputs = Vec::with_capacity(append_args.len());

    for mut args in append_args {
        if let Some(submitted_at_unix_ms) = parsed.submitted_at_unix_ms {
            if flag_value(&args, "--submitted-at-unix-ms").is_some() {
                return Err(CliError::Usage(
                    "status append command already includes --submitted-at-unix-ms".to_string(),
                ));
            }
            args.push("--submitted-at-unix-ms".to_string());
            args.push(submitted_at_unix_ms.to_string());
        }
        let status = matching_status_for_append_args(&statuses, &args)?;
        let outcome = quorum_from_args(&args, now)?;
        match outcome {
            QuorumCliOutcome::OutputAppended {
                episode_id,
                output_id,
                path,
            } => outputs.push(QuorumStatusOutputAppend {
                episode_id,
                output_id,
                participant_id: status.participant_id.clone(),
                round: status.round.clone(),
                path,
            }),
            other => {
                return Err(CliError::Usage(format!(
                    "append-status-output expected append-output result, got {other:?}"
                )));
            }
        }
    }

    Ok(QuorumCliOutcome::StatusOutputsAppended {
        status_path: parsed.status_file.display().to_string(),
        appended: outputs.len(),
        outputs,
    })
}

fn parse_quorum_append_status_output_args(
    args: &[String],
) -> Result<QuorumAppendStatusOutputArgs, CliError> {
    let mut status_file: Option<PathBuf> = None;
    let mut submitted_at_unix_ms: Option<u64> = None;

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--status-file" => {
                status_file = Some(take_value(args, &mut i, "--status-file")?.into());
            }
            "--submitted-at-unix-ms" => {
                let value = take_value(args, &mut i, "--submitted-at-unix-ms")?;
                submitted_at_unix_ms = Some(parse_u64("--submitted-at-unix-ms", &value)?);
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum append-status-output option '{other}'"
                )));
            }
        }
        i += 1;
    }

    Ok(QuorumAppendStatusOutputArgs {
        status_file: require_path(status_file, "--status-file")?,
        submitted_at_unix_ms,
    })
}

fn parse_quorum_append_output_args(args: &[String]) -> Result<QuorumAppendOutputArgs, CliError> {
    let mut quorum_dir: Option<PathBuf> = None;
    let mut episode_id: Option<String> = None;
    let mut output_id: Option<String> = None;
    let mut participant_id: Option<String> = None;
    let mut round: Option<QuorumRound> = None;
    let mut submitted_at_unix_ms: Option<u64> = None;
    let mut prompt: Option<String> = None;
    let mut prompt_file: Option<PathBuf> = None;
    let mut response: Option<String> = None;
    let mut response_file: Option<PathBuf> = None;
    let mut visible_prior_output_ids = Vec::new();
    let mut evidence_used = Vec::new();

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--quorum-dir" => {
                quorum_dir = Some(take_value(args, &mut i, "--quorum-dir")?.into());
            }
            "--episode-id" => {
                episode_id = Some(take_value(args, &mut i, "--episode-id")?);
            }
            "--output-id" => {
                output_id = Some(take_value(args, &mut i, "--output-id")?);
            }
            "--participant-id" => {
                participant_id = Some(take_value(args, &mut i, "--participant-id")?);
            }
            "--round" => {
                let value = take_value(args, &mut i, "--round")?;
                round = Some(parse_quorum_round(&value)?);
            }
            "--submitted-at-unix-ms" => {
                let value = take_value(args, &mut i, "--submitted-at-unix-ms")?;
                submitted_at_unix_ms = Some(parse_u64("--submitted-at-unix-ms", &value)?);
            }
            "--prompt" => {
                if prompt_file.is_some() {
                    return Err(CliError::Usage(
                        "--prompt and --prompt-file are mutually exclusive".to_string(),
                    ));
                }
                prompt = Some(take_value(args, &mut i, "--prompt")?);
            }
            "--prompt-file" => {
                if prompt.is_some() {
                    return Err(CliError::Usage(
                        "--prompt and --prompt-file are mutually exclusive".to_string(),
                    ));
                }
                prompt_file = Some(take_value(args, &mut i, "--prompt-file")?.into());
            }
            "--response" => {
                if response_file.is_some() {
                    return Err(CliError::Usage(
                        "--response and --response-file are mutually exclusive".to_string(),
                    ));
                }
                response = Some(take_value(args, &mut i, "--response")?);
            }
            "--response-file" => {
                if response.is_some() {
                    return Err(CliError::Usage(
                        "--response and --response-file are mutually exclusive".to_string(),
                    ));
                }
                response_file = Some(take_value(args, &mut i, "--response-file")?.into());
            }
            "--visible-prior-output-id" => {
                visible_prior_output_ids.push(take_value(
                    args,
                    &mut i,
                    "--visible-prior-output-id",
                )?);
            }
            "--evidence" => {
                evidence_used.push(take_value(args, &mut i, "--evidence")?);
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum append-output option '{other}'"
                )));
            }
        }
        i += 1;
    }

    Ok(QuorumAppendOutputArgs {
        quorum_dir: require_path(quorum_dir, "--quorum-dir")?,
        episode_id: require_non_empty(episode_id, "--episode-id")?,
        output_id: require_non_empty(output_id, "--output-id")?,
        participant_id: require_non_empty(participant_id, "--participant-id")?,
        round: round.ok_or_else(|| CliError::Usage("--round is required".to_string()))?,
        submitted_at_unix_ms,
        prompt: require_text_or_file(prompt, prompt_file, "--prompt", "--prompt-file")?,
        response: require_text_or_file(response, response_file, "--response", "--response-file")?,
        visible_prior_output_ids,
        evidence_used,
    })
}

fn quorum_outputs_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let (quorum_dir, episode_id, round) = parse_quorum_read_args(args, "outputs")?;
    let outputs = QuorumStore::new(quorum_dir).load_round_outputs(&episode_id, round)?;
    Ok(QuorumCliOutcome::OutputsLoaded {
        episode_id,
        round: quorum_round_name(round).to_string(),
        outputs,
    })
}

fn quorum_visible_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let (quorum_dir, episode_id, round) = parse_quorum_read_args(args, "visible")?;
    let outputs = QuorumStore::new(quorum_dir).visible_outputs_for_round(&episode_id, round)?;
    Ok(QuorumCliOutcome::VisibleOutputs {
        episode_id,
        round: quorum_round_name(round).to_string(),
        outputs,
    })
}

fn quorum_adapter_request_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let mut quorum_dir: Option<PathBuf> = None;
    let mut episode_id: Option<String> = None;
    let mut participant_id: Option<String> = None;
    let mut round: Option<QuorumRound> = None;

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--quorum-dir" => {
                quorum_dir = Some(take_value(args, &mut i, "--quorum-dir")?.into());
            }
            "--episode-id" => {
                episode_id = Some(take_value(args, &mut i, "--episode-id")?);
            }
            "--participant-id" => {
                participant_id = Some(take_value(args, &mut i, "--participant-id")?);
            }
            "--round" => {
                let value = take_value(args, &mut i, "--round")?;
                round = Some(parse_quorum_round(&value)?);
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum adapter-request option '{other}'"
                )));
            }
        }
        i += 1;
    }

    let request = QuorumStore::new(require_path(quorum_dir, "--quorum-dir")?)
        .build_adapter_request(
            &require_non_empty(episode_id, "--episode-id")?,
            &require_non_empty(participant_id, "--participant-id")?,
            round.ok_or_else(|| CliError::Usage("--round is required".to_string()))?,
        )?;
    Ok(QuorumCliOutcome::AdapterRequest {
        request: Box::new(request),
    })
}

#[derive(Debug, Clone)]
struct QuorumAdapterPlanArgs {
    quorum_dir: PathBuf,
    episode_id: String,
    participant_id: String,
    round: QuorumRound,
    adapter: String,
    out_dir: PathBuf,
    binary: Option<String>,
    output_id: Option<String>,
}

struct QuorumAdapterPlanPaths {
    request: PathBuf,
    prompt: PathBuf,
    response: PathBuf,
    status: PathBuf,
    stdout: PathBuf,
    stderr: PathBuf,
}

struct QuorumAdapterRunArgs {
    plan: QuorumAdapterPlanArgs,
    timeout: Duration,
}

struct QuorumAdapterRunRoundArgs {
    quorum_dir: PathBuf,
    episode_id: String,
    round: QuorumRound,
    out_dir: PathBuf,
    adapter_binaries: BTreeMap<String, String>,
    timeout: Duration,
}

struct QuorumAdapterRunRoundsArgs {
    quorum_dir: PathBuf,
    episode_id: String,
    through_round: QuorumRound,
    out_dir: PathBuf,
    adapter_binaries: BTreeMap<String, String>,
    timeout: Duration,
    submitted_at_unix_ms: Option<u64>,
}

fn quorum_adapter_plan_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let parsed = parse_quorum_adapter_plan_args(args)?;
    Ok(QuorumCliOutcome::AdapterPlan {
        plan: Box::new(build_quorum_adapter_plan(&parsed)?),
    })
}

fn quorum_adapter_run_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let parsed = parse_quorum_adapter_run_args(args)?;
    let plan = build_quorum_adapter_plan(&parsed.plan)?;
    let status = execute_quorum_adapter_plan(&plan, parsed.timeout)?;
    Ok(QuorumCliOutcome::AdapterRun {
        run: Box::new(QuorumAdapterRun {
            schema_version: QUORUM_SCHEMA_VERSION,
            status,
        }),
    })
}

fn quorum_adapter_run_round_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let parsed = parse_quorum_adapter_run_round_args(args)?;
    let round_run = execute_quorum_adapter_round(&parsed)?;
    Ok(QuorumCliOutcome::AdapterRoundRun {
        round_run: Box::new(round_run),
    })
}

fn quorum_adapter_run_rounds_from_args(
    args: &[String],
    now: SystemTime,
) -> Result<QuorumCliOutcome, CliError> {
    let parsed = parse_quorum_adapter_run_rounds_args(args)?;
    let rounds_run = execute_quorum_adapter_rounds(&parsed, now)?;
    Ok(QuorumCliOutcome::AdapterRoundsRun {
        rounds_run: Box::new(rounds_run),
    })
}

fn build_quorum_adapter_plan(
    parsed: &QuorumAdapterPlanArgs,
) -> Result<QuorumAdapterPlan, CliError> {
    let store = QuorumStore::new(&parsed.quorum_dir);
    let request =
        store.build_adapter_request(&parsed.episode_id, &parsed.participant_id, parsed.round)?;
    validate_adapter_plan_request(&request, &parsed.adapter)?;
    let paths = adapter_plan_paths(
        &parsed.out_dir,
        &parsed.episode_id,
        &parsed.participant_id,
        parsed.round,
    );
    fs::create_dir_all(&parsed.out_dir).map_err(LibrarianError::from)?;
    write_adapter_plan_files(&paths, &request)?;

    let response_path = paths.response.display().to_string();
    let status_path = paths.status.display().to_string();
    let prompt_path = paths.prompt.display().to_string();
    let command_argv =
        adapter_plan_argv(&parsed.adapter, parsed.binary.as_deref(), &response_path)?;
    let stdout_path = adapter_plan_stdout_path(&parsed.adapter, &response_path)?;
    let stdout_capture_path = stdout_path
        .clone()
        .unwrap_or_else(|| paths.stdout.display().to_string());
    let stderr_capture_path = paths.stderr.display().to_string();
    let output_id = parsed.output_id.clone().unwrap_or_else(|| {
        format!(
            "out-{}-{}",
            quorum_round_name(parsed.round),
            parsed.participant_id
        )
    });
    let append_output_argv =
        append_output_plan_argv(parsed, &request, &output_id, &prompt_path, &response_path);

    Ok(QuorumAdapterPlan {
        schema_version: QUORUM_SCHEMA_VERSION,
        adapter: parsed.adapter.clone(),
        episode_id: parsed.episode_id.clone(),
        participant_id: parsed.participant_id.clone(),
        round: quorum_round_name(parsed.round).to_string(),
        request_path: paths.request.display().to_string(),
        prompt_path: prompt_path.clone(),
        response_path: response_path.clone(),
        status_path,
        stdin_path: prompt_path,
        stdout_path,
        stdout_capture_path,
        stderr_capture_path,
        argv: command_argv,
        append_output_argv,
    })
}

fn parse_quorum_adapter_plan_args(args: &[String]) -> Result<QuorumAdapterPlanArgs, CliError> {
    parse_quorum_adapter_args(args, "adapter-plan", false).map(|parsed| parsed.plan)
}

fn parse_quorum_adapter_run_args(args: &[String]) -> Result<QuorumAdapterRunArgs, CliError> {
    let mut parsed = parse_quorum_adapter_args(args, "adapter-run", true)?;
    let timeout = parsed
        .timeout
        .take()
        .unwrap_or_else(|| Duration::from_secs(DEFAULT_QUORUM_ADAPTER_TIMEOUT_SECS));
    Ok(QuorumAdapterRunArgs {
        plan: parsed.plan,
        timeout,
    })
}

fn parse_quorum_adapter_run_round_args(
    args: &[String],
) -> Result<QuorumAdapterRunRoundArgs, CliError> {
    let mut quorum_dir: Option<PathBuf> = None;
    let mut episode_id: Option<String> = None;
    let mut round: Option<QuorumRound> = None;
    let mut out_dir: Option<PathBuf> = None;
    let mut adapter_binaries = BTreeMap::new();
    let mut timeout: Option<Duration> = None;

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--quorum-dir" => {
                quorum_dir = Some(take_value(args, &mut i, "--quorum-dir")?.into());
            }
            "--episode-id" => {
                episode_id = Some(take_value(args, &mut i, "--episode-id")?);
            }
            "--round" => {
                let value = take_value(args, &mut i, "--round")?;
                round = Some(parse_quorum_round(&value)?);
            }
            "--out-dir" => {
                out_dir = Some(take_value(args, &mut i, "--out-dir")?.into());
            }
            "--adapter-binary" => {
                let value = take_value(args, &mut i, "--adapter-binary")?;
                let (adapter, binary) = parse_adapter_binary(&value)?;
                if adapter_binaries.insert(adapter.clone(), binary).is_some() {
                    return Err(CliError::Usage(format!(
                        "--adapter-binary supplied more than once for adapter '{adapter}'"
                    )));
                }
            }
            "--timeout-secs" => {
                let value = take_value(args, &mut i, "--timeout-secs")?;
                timeout = Some(Duration::from_secs(parse_positive_u64(
                    "--timeout-secs",
                    &value,
                )?));
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum adapter-run-round option '{other}'"
                )));
            }
        }
        i += 1;
    }

    Ok(QuorumAdapterRunRoundArgs {
        quorum_dir: require_path(quorum_dir, "--quorum-dir")?,
        episode_id: require_non_empty(episode_id, "--episode-id")?,
        round: round.ok_or_else(|| CliError::Usage("--round is required".to_string()))?,
        out_dir: require_path(out_dir, "--out-dir")?,
        adapter_binaries,
        timeout: timeout
            .unwrap_or_else(|| Duration::from_secs(DEFAULT_QUORUM_ADAPTER_TIMEOUT_SECS)),
    })
}

fn parse_quorum_adapter_run_rounds_args(
    args: &[String],
) -> Result<QuorumAdapterRunRoundsArgs, CliError> {
    let mut quorum_dir: Option<PathBuf> = None;
    let mut episode_id: Option<String> = None;
    let mut through_round: Option<QuorumRound> = None;
    let mut out_dir: Option<PathBuf> = None;
    let mut adapter_binaries = BTreeMap::new();
    let mut timeout: Option<Duration> = None;
    let mut submitted_at_unix_ms: Option<u64> = None;

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--quorum-dir" => {
                quorum_dir = Some(take_value(args, &mut i, "--quorum-dir")?.into());
            }
            "--episode-id" => {
                episode_id = Some(take_value(args, &mut i, "--episode-id")?);
            }
            "--through-round" => {
                let value = take_value(args, &mut i, "--through-round")?;
                through_round = Some(parse_quorum_round(&value)?);
            }
            "--out-dir" => {
                out_dir = Some(take_value(args, &mut i, "--out-dir")?.into());
            }
            "--adapter-binary" => {
                let value = take_value(args, &mut i, "--adapter-binary")?;
                let (adapter, binary) = parse_adapter_binary(&value)?;
                if adapter_binaries.insert(adapter.clone(), binary).is_some() {
                    return Err(CliError::Usage(format!(
                        "--adapter-binary supplied more than once for adapter '{adapter}'"
                    )));
                }
            }
            "--timeout-secs" => {
                let value = take_value(args, &mut i, "--timeout-secs")?;
                timeout = Some(Duration::from_secs(parse_positive_u64(
                    "--timeout-secs",
                    &value,
                )?));
            }
            "--submitted-at-unix-ms" => {
                let value = take_value(args, &mut i, "--submitted-at-unix-ms")?;
                submitted_at_unix_ms = Some(parse_u64("--submitted-at-unix-ms", &value)?);
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum adapter-run-rounds option '{other}'"
                )));
            }
        }
        i += 1;
    }

    Ok(QuorumAdapterRunRoundsArgs {
        quorum_dir: require_path(quorum_dir, "--quorum-dir")?,
        episode_id: require_non_empty(episode_id, "--episode-id")?,
        through_round: through_round
            .ok_or_else(|| CliError::Usage("--through-round is required".to_string()))?,
        out_dir: require_path(out_dir, "--out-dir")?,
        adapter_binaries,
        timeout: timeout
            .unwrap_or_else(|| Duration::from_secs(DEFAULT_QUORUM_ADAPTER_TIMEOUT_SECS)),
        submitted_at_unix_ms,
    })
}

fn parse_adapter_binary(value: &str) -> Result<(String, String), CliError> {
    let Some((adapter, binary)) = value.split_once('=') else {
        return Err(CliError::Usage(
            "--adapter-binary must be ADAPTER=PATH".to_string(),
        ));
    };
    let adapter = parse_quorum_adapter(adapter)?;
    let binary = require_non_empty(Some(binary.to_string()), "--adapter-binary PATH")?;
    Ok((adapter, binary))
}

struct ParsedQuorumAdapterArgs {
    plan: QuorumAdapterPlanArgs,
    timeout: Option<Duration>,
}

fn parse_quorum_adapter_args(
    args: &[String],
    command: &str,
    allow_timeout: bool,
) -> Result<ParsedQuorumAdapterArgs, CliError> {
    let mut quorum_dir: Option<PathBuf> = None;
    let mut episode_id: Option<String> = None;
    let mut participant_id: Option<String> = None;
    let mut round: Option<QuorumRound> = None;
    let mut adapter: Option<String> = None;
    let mut out_dir: Option<PathBuf> = None;
    let mut binary: Option<String> = None;
    let mut output_id: Option<String> = None;
    let mut timeout: Option<Duration> = None;

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--quorum-dir" => {
                quorum_dir = Some(take_value(args, &mut i, "--quorum-dir")?.into());
            }
            "--episode-id" => {
                episode_id = Some(take_value(args, &mut i, "--episode-id")?);
            }
            "--participant-id" => {
                participant_id = Some(take_value(args, &mut i, "--participant-id")?);
            }
            "--round" => {
                let value = take_value(args, &mut i, "--round")?;
                round = Some(parse_quorum_round(&value)?);
            }
            "--adapter" => {
                adapter = Some(parse_quorum_adapter(&take_value(
                    args,
                    &mut i,
                    "--adapter",
                )?)?);
            }
            "--out-dir" => {
                out_dir = Some(take_value(args, &mut i, "--out-dir")?.into());
            }
            "--binary" => {
                binary = Some(take_value(args, &mut i, "--binary")?);
            }
            "--output-id" => {
                output_id = Some(take_value(args, &mut i, "--output-id")?);
            }
            "--timeout-secs" if allow_timeout => {
                let value = take_value(args, &mut i, "--timeout-secs")?;
                timeout = Some(Duration::from_secs(parse_positive_u64(
                    "--timeout-secs",
                    &value,
                )?));
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum {command} option '{other}'"
                )));
            }
        }
        i += 1;
    }

    Ok(ParsedQuorumAdapterArgs {
        plan: QuorumAdapterPlanArgs {
            quorum_dir: require_path(quorum_dir, "--quorum-dir")?,
            episode_id: require_non_empty(episode_id, "--episode-id")?,
            participant_id: require_non_empty(participant_id, "--participant-id")?,
            round: round.ok_or_else(|| CliError::Usage("--round is required".to_string()))?,
            adapter: require_non_empty(adapter, "--adapter")?,
            out_dir: require_path(out_dir, "--out-dir")?,
            binary,
            output_id,
        },
        timeout,
    })
}

fn validate_adapter_plan_request(
    request: &QuorumAdapterRequest,
    adapter: &str,
) -> Result<(), CliError> {
    if request.participant.adapter == adapter {
        return Ok(());
    }
    Err(CliError::Usage(format!(
        "--adapter {adapter} does not match participant '{}' adapter '{}'",
        request.participant.id, request.participant.adapter
    )))
}

fn adapter_plan_paths(
    out_dir: &Path,
    episode_id: &str,
    participant_id: &str,
    round: QuorumRound,
) -> QuorumAdapterPlanPaths {
    let stem = format!(
        "{}-{}-{}",
        safe_file_stem(episode_id),
        safe_file_stem(participant_id),
        quorum_round_name(round)
    );
    QuorumAdapterPlanPaths {
        request: out_dir.join(format!("{stem}-request.json")),
        prompt: out_dir.join(format!("{stem}-prompt.md")),
        response: out_dir.join(format!("{stem}-response.md")),
        status: out_dir.join(format!("{stem}-status.json")),
        stdout: out_dir.join(format!("{stem}-stdout.log")),
        stderr: out_dir.join(format!("{stem}-stderr.log")),
    }
}

fn write_adapter_plan_files(
    paths: &QuorumAdapterPlanPaths,
    request: &QuorumAdapterRequest,
) -> Result<(), CliError> {
    let request_json = serde_json::to_vec_pretty(request).map_err(LibrarianError::from)?;
    let prompt = build_adapter_prompt(request);
    fs::write(&paths.request, request_json).map_err(LibrarianError::from)?;
    fs::write(&paths.prompt, prompt).map_err(LibrarianError::from)?;
    Ok(())
}

fn spawn_with_transient_exec_retry(command: &mut Command) -> io::Result<Child> {
    let mut attempts = 0_u32;
    loop {
        match command.spawn() {
            Err(error) if is_transient_text_file_busy(&error) && attempts < 3 => {
                attempts += 1;
                std::thread::sleep(Duration::from_millis(10 * u64::from(attempts)));
            }
            result => return result,
        }
    }
}

#[cfg(unix)]
fn is_transient_text_file_busy(error: &io::Error) -> bool {
    error.raw_os_error() == Some(TEXT_FILE_BUSY_OS_ERROR)
}

#[cfg(not(unix))]
fn is_transient_text_file_busy(_: &io::Error) -> bool {
    false
}

fn execute_quorum_adapter_plan(
    plan: &QuorumAdapterPlan,
    timeout: Duration,
) -> Result<QuorumAdapterRunStatus, CliError> {
    let program = plan
        .argv
        .first()
        .ok_or_else(|| CliError::Usage("adapter plan argv must not be empty".to_string()))?;
    let started = Instant::now();
    let mut command = Command::new(program);
    command.args(&plan.argv[1..]);
    command.stdin(Stdio::from(
        fs::File::open(&plan.stdin_path).map_err(LibrarianError::from)?,
    ));
    command.stdout(Stdio::from(
        fs::File::create(&plan.stdout_capture_path).map_err(LibrarianError::from)?,
    ));
    command.stderr(Stdio::from(
        fs::File::create(&plan.stderr_capture_path).map_err(LibrarianError::from)?,
    ));

    let mut child = spawn_with_transient_exec_retry(&mut command).map_err(LibrarianError::from)?;
    let wait_result = child.wait_timeout(timeout);
    let (timed_out, exit_code, process_success) = match wait_result {
        Ok(Some(status)) => (false, status.code(), status.success()),
        Ok(None) => {
            let _ = child.kill();
            let _ = child.wait();
            (true, None, false)
        }
        Err(err) => return Err(LibrarianError::from(err).into()),
    };
    let duration_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX);
    let status = QuorumAdapterRunStatus {
        schema_version: QUORUM_SCHEMA_VERSION,
        adapter: plan.adapter.clone(),
        episode_id: plan.episode_id.clone(),
        participant_id: plan.participant_id.clone(),
        round: plan.round.clone(),
        status_path: plan.status_path.clone(),
        request_path: plan.request_path.clone(),
        prompt_path: plan.prompt_path.clone(),
        response_path: plan.response_path.clone(),
        stdout_capture_path: plan.stdout_capture_path.clone(),
        stderr_capture_path: plan.stderr_capture_path.clone(),
        success: process_success && !timed_out,
        timed_out,
        exit_code,
        duration_ms,
        response_bytes: file_len(&plan.response_path)?,
        stdout_bytes: file_len(&plan.stdout_capture_path)?,
        stderr_bytes: file_len(&plan.stderr_capture_path)?,
        append_output_argv: plan.append_output_argv.clone(),
    };
    write_adapter_run_status(&status)?;
    Ok(status)
}

fn execute_quorum_adapter_round(
    parsed: &QuorumAdapterRunRoundArgs,
) -> Result<QuorumAdapterRoundRun, CliError> {
    let store = QuorumStore::new(&parsed.quorum_dir);
    let episode = store.load_episode(&parsed.episode_id)?;
    let mut statuses = Vec::with_capacity(episode.participants.len());
    for participant in &episode.participants {
        let plan_args = QuorumAdapterPlanArgs {
            quorum_dir: parsed.quorum_dir.clone(),
            episode_id: parsed.episode_id.clone(),
            participant_id: participant.id.clone(),
            round: parsed.round,
            adapter: participant.adapter.clone(),
            out_dir: parsed.out_dir.clone(),
            binary: parsed.adapter_binaries.get(&participant.adapter).cloned(),
            output_id: None,
        };
        let plan = build_quorum_adapter_plan(&plan_args)?;
        statuses.push(execute_quorum_adapter_plan(&plan, parsed.timeout)?);
    }
    let status_path = adapter_round_status_path(&parsed.out_dir, &parsed.episode_id, parsed.round);
    let round_run = QuorumAdapterRoundRun {
        schema_version: QUORUM_SCHEMA_VERSION,
        episode_id: parsed.episode_id.clone(),
        round: quorum_round_name(parsed.round).to_string(),
        status_path: status_path.display().to_string(),
        success: statuses.iter().all(|status| status.success),
        completed: statuses.len(),
        failed: statuses.iter().filter(|status| !status.success).count(),
        timed_out: statuses.iter().filter(|status| status.timed_out).count(),
        statuses,
    };
    write_adapter_round_status(&round_run)?;
    Ok(round_run)
}

fn execute_quorum_adapter_rounds(
    parsed: &QuorumAdapterRunRoundsArgs,
    now: SystemTime,
) -> Result<QuorumAdapterRoundsRun, CliError> {
    let mut rounds = Vec::new();
    let mut appended = Vec::new();
    for round in quorum_rounds_through(parsed.through_round) {
        let round_args = QuorumAdapterRunRoundArgs {
            quorum_dir: parsed.quorum_dir.clone(),
            episode_id: parsed.episode_id.clone(),
            round,
            out_dir: parsed.out_dir.clone(),
            adapter_binaries: parsed.adapter_binaries.clone(),
            timeout: parsed.timeout,
        };
        let round_run = execute_quorum_adapter_round(&round_args)?;
        let mut append_args = vec!["--status-file".to_string(), round_run.status_path.clone()];
        if let Some(submitted_at_unix_ms) = parsed.submitted_at_unix_ms {
            append_args.push("--submitted-at-unix-ms".to_string());
            append_args.push(submitted_at_unix_ms.to_string());
        }
        match quorum_append_status_output_from_args(&append_args, now)? {
            QuorumCliOutcome::StatusOutputsAppended {
                outputs: round_appended,
                ..
            } => appended.extend(round_appended),
            other => {
                return Err(CliError::Usage(format!(
                    "adapter-run-rounds expected status append result, got {other:?}"
                )));
            }
        }
        rounds.push(round_run);
    }
    Ok(QuorumAdapterRoundsRun {
        schema_version: QUORUM_SCHEMA_VERSION,
        episode_id: parsed.episode_id.clone(),
        through_round: quorum_round_name(parsed.through_round).to_string(),
        success: rounds.iter().all(|round| round.success),
        rounds,
        appended,
    })
}

fn quorum_rounds_through(through_round: QuorumRound) -> Vec<QuorumRound> {
    match through_round {
        QuorumRound::Independent => vec![QuorumRound::Independent],
        QuorumRound::Critique => vec![QuorumRound::Independent, QuorumRound::Critique],
        QuorumRound::Revision => vec![
            QuorumRound::Independent,
            QuorumRound::Critique,
            QuorumRound::Revision,
        ],
    }
}

fn write_adapter_run_status(status: &QuorumAdapterRunStatus) -> Result<(), CliError> {
    let bytes = serde_json::to_vec_pretty(status).map_err(LibrarianError::from)?;
    fs::write(&status.status_path, bytes).map_err(LibrarianError::from)?;
    Ok(())
}

fn write_adapter_round_status(status: &QuorumAdapterRoundRun) -> Result<(), CliError> {
    let bytes = serde_json::to_vec_pretty(status).map_err(LibrarianError::from)?;
    fs::write(&status.status_path, bytes).map_err(LibrarianError::from)?;
    Ok(())
}

fn adapter_round_status_path(out_dir: &Path, episode_id: &str, round: QuorumRound) -> PathBuf {
    out_dir.join(format!(
        "{}-{}-round-status.json",
        safe_file_stem(episode_id),
        quorum_round_name(round)
    ))
}

fn load_adapter_status_artifact(path: &Path) -> Result<Vec<QuorumAdapterRunStatus>, CliError> {
    let bytes = fs::read(path).map_err(LibrarianError::from)?;
    let value: serde_json::Value = serde_json::from_slice(&bytes).map_err(LibrarianError::from)?;
    if value.get("statuses").is_some() {
        let round_run: QuorumAdapterRoundRun =
            serde_json::from_value(value).map_err(LibrarianError::from)?;
        validate_adapter_round_status_for_append(&round_run)?;
        return Ok(round_run.statuses);
    }
    let status: QuorumAdapterRunStatus =
        serde_json::from_value(value).map_err(LibrarianError::from)?;
    Ok(vec![status])
}

fn validate_adapter_round_status_for_append(
    round_run: &QuorumAdapterRoundRun,
) -> Result<(), CliError> {
    if round_run.schema_version != QUORUM_SCHEMA_VERSION {
        return Err(CliError::Usage(format!(
            "unsupported adapter round status schema version {}; expected {QUORUM_SCHEMA_VERSION}",
            round_run.schema_version
        )));
    }
    if !round_run.success {
        return Err(CliError::Usage(format!(
            "adapter round status for episode '{}' round '{}' is not successful",
            round_run.episode_id, round_run.round
        )));
    }
    if round_run.statuses.is_empty() {
        return Err(CliError::Usage(
            "adapter round status contains no participant statuses".to_string(),
        ));
    }
    let failed = round_run
        .statuses
        .iter()
        .filter(|status| !status.success)
        .count();
    let timed_out = round_run
        .statuses
        .iter()
        .filter(|status| status.timed_out)
        .count();
    if round_run.failed != failed || round_run.timed_out != timed_out {
        return Err(CliError::Usage(
            "adapter round status counters do not match participant statuses".to_string(),
        ));
    }
    for status in &round_run.statuses {
        if status.episode_id != round_run.episode_id || status.round != round_run.round {
            return Err(CliError::Usage(format!(
                "adapter round status contains mismatched participant status for episode '{}' round '{}'",
                status.episode_id, status.round
            )));
        }
    }
    Ok(())
}

fn validated_append_output_args_from_status(
    status: &QuorumAdapterRunStatus,
) -> Result<Vec<String>, CliError> {
    validate_adapter_status_for_append(status)?;
    let args = append_output_args_from_status(status)?;
    require_append_arg_eq(&args, "--episode-id", &status.episode_id)?;
    require_append_arg_eq(&args, "--participant-id", &status.participant_id)?;
    require_append_arg_eq(&args, "--round", &status.round)?;
    require_append_arg_eq(&args, "--prompt-file", &status.prompt_path)?;
    require_append_arg_eq(&args, "--response-file", &status.response_path)?;
    Ok(args)
}

fn validate_adapter_status_for_append(status: &QuorumAdapterRunStatus) -> Result<(), CliError> {
    if status.schema_version != QUORUM_SCHEMA_VERSION {
        return Err(CliError::Usage(format!(
            "unsupported adapter status schema version {}; expected {QUORUM_SCHEMA_VERSION}",
            status.schema_version
        )));
    }
    if !status.success {
        return Err(CliError::Usage(format!(
            "adapter status for participant '{}' round '{}' is not successful",
            status.participant_id, status.round
        )));
    }
    if status.timed_out {
        return Err(CliError::Usage(format!(
            "adapter status for participant '{}' round '{}' timed out",
            status.participant_id, status.round
        )));
    }
    if file_len(&status.prompt_path)? == 0 {
        return Err(CliError::Usage(format!(
            "adapter status prompt artifact is missing or empty: {}",
            status.prompt_path
        )));
    }
    if status.response_bytes == 0 {
        return Err(CliError::Usage(format!(
            "adapter status response artifact is empty: {}",
            status.response_path
        )));
    }
    let actual_response_bytes = file_len(&status.response_path)?;
    if actual_response_bytes != status.response_bytes {
        return Err(CliError::Usage(format!(
            "adapter status response byte count mismatch for {}; status says {}, file has {}",
            status.response_path, status.response_bytes, actual_response_bytes
        )));
    }
    Ok(())
}

fn append_output_args_from_status(
    status: &QuorumAdapterRunStatus,
) -> Result<Vec<String>, CliError> {
    if status.append_output_argv.len() < 3
        || status.append_output_argv[0] != "mimir-librarian"
        || status.append_output_argv[1] != "quorum"
        || status.append_output_argv[2] != "append-output"
    {
        return Err(CliError::Usage(format!(
            "adapter status for participant '{}' does not carry a valid append-output command",
            status.participant_id
        )));
    }
    Ok(status.append_output_argv.iter().skip(2).cloned().collect())
}

fn matching_status_for_append_args<'a>(
    statuses: &'a [QuorumAdapterRunStatus],
    args: &[String],
) -> Result<&'a QuorumAdapterRunStatus, CliError> {
    let episode_id = required_flag_value(args, "--episode-id")?;
    let participant_id = required_flag_value(args, "--participant-id")?;
    let round = required_flag_value(args, "--round")?;
    statuses
        .iter()
        .find(|status| {
            status.episode_id == episode_id
                && status.participant_id == participant_id
                && status.round == round
        })
        .ok_or_else(|| {
            CliError::Usage(format!(
                "append-output command does not match any loaded adapter status for participant '{participant_id}'"
            ))
        })
}

fn require_append_arg_eq(args: &[String], flag: &str, expected: &str) -> Result<(), CliError> {
    require_cli_arg_eq(args, "adapter status append command", flag, expected)
}

fn require_cli_arg_eq(
    args: &[String],
    command: &str,
    flag: &str,
    expected: &str,
) -> Result<(), CliError> {
    let actual = required_flag_value(args, flag)?;
    if actual == expected {
        return Ok(());
    }
    Err(CliError::Usage(format!(
        "{command} {flag} mismatch; expected '{expected}', got '{actual}'"
    )))
}

fn file_len(path: &str) -> Result<u64, CliError> {
    match fs::metadata(path) {
        Ok(metadata) => Ok(metadata.len()),
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(0),
        Err(err) => Err(LibrarianError::from(err).into()),
    }
}

fn build_adapter_prompt(request: &QuorumAdapterRequest) -> String {
    let prior_outputs = format_visible_prior_outputs(&request.visible_prior_outputs);
    format!(
        "# Mimir quorum participant request\n\n\
You are responding as a Mimir quorum participant.\n\
episode_id: {}\n\
participant_id: {}\n\
adapter: {}\n\
persona: {}\n\
model: {}\n\
round: {}\n\n\
Question:\n{}\n\n\
Evidence policy:\n{}\n\n\
Target project: {}\n\
Target scope: {}\n\n\
Visible prior outputs:\n{}\n\n\
Write only your participant response prose. Do not write canonical Mimir memory, \
do not edit project files, and do not call Mimir write commands. Mimir will record \
your response through `mimir-librarian quorum append-output`.\n",
        request.episode_id,
        request.participant.id,
        request.participant.adapter,
        request.participant.persona,
        request
            .participant
            .model
            .as_deref()
            .unwrap_or("<unspecified>"),
        quorum_round_name(request.round),
        request.question,
        request.evidence_policy,
        request.target_project.as_deref().unwrap_or("<none>"),
        request.target_scope.as_deref().unwrap_or("<none>"),
        prior_outputs,
    )
}

fn format_visible_prior_outputs(outputs: &[QuorumParticipantOutput]) -> String {
    if outputs.is_empty() {
        return "- <none>".to_string();
    }
    outputs
        .iter()
        .map(|output| {
            format!(
                "- output_id: {}\n  participant_id: {}\n  round: {}\n  response: {}",
                output.output_id,
                output.participant_id,
                quorum_round_name(output.round),
                output.response
            )
        })
        .collect::<Vec<_>>()
        .join("\n")
}

fn adapter_plan_argv(
    adapter: &str,
    binary: Option<&str>,
    response_path: &str,
) -> Result<Vec<String>, CliError> {
    match adapter {
        "claude" => Ok(vec![
            binary.unwrap_or("claude").to_string(),
            "-p".to_string(),
        ]),
        "codex" => Ok(vec![
            binary.unwrap_or("codex").to_string(),
            "exec".to_string(),
            "--output-last-message".to_string(),
            response_path.to_string(),
            "-".to_string(),
        ]),
        other => Err(CliError::Usage(format!("unknown quorum adapter '{other}'"))),
    }
}

fn adapter_plan_stdout_path(
    adapter: &str,
    response_path: &str,
) -> Result<Option<String>, CliError> {
    match adapter {
        "claude" => Ok(Some(response_path.to_string())),
        "codex" => Ok(None),
        other => Err(CliError::Usage(format!("unknown quorum adapter '{other}'"))),
    }
}

fn append_output_plan_argv(
    parsed: &QuorumAdapterPlanArgs,
    request: &QuorumAdapterRequest,
    output_id: &str,
    prompt_path: &str,
    response_path: &str,
) -> Vec<String> {
    let mut argv = vec![
        "mimir-librarian".to_string(),
        "quorum".to_string(),
        "append-output".to_string(),
        "--quorum-dir".to_string(),
        parsed.quorum_dir.display().to_string(),
        "--episode-id".to_string(),
        parsed.episode_id.clone(),
        "--output-id".to_string(),
        output_id.to_string(),
        "--participant-id".to_string(),
        parsed.participant_id.clone(),
        "--round".to_string(),
        quorum_round_name(parsed.round).to_string(),
        "--prompt-file".to_string(),
        prompt_path.to_string(),
        "--response-file".to_string(),
        response_path.to_string(),
    ];
    for visible_id in &request.visible_prior_output_ids {
        argv.push("--visible-prior-output-id".to_string());
        argv.push(visible_id.clone());
    }
    argv
}

#[derive(Debug, Clone)]
struct QuorumSynthesisPlanArgs {
    quorum_dir: PathBuf,
    episode_id: String,
    adapter: String,
    out_dir: PathBuf,
    binary: Option<String>,
}

struct QuorumSynthesisPlanPaths {
    transcript: PathBuf,
    prompt: PathBuf,
    result: PathBuf,
    status: PathBuf,
    stdout: PathBuf,
    stderr: PathBuf,
}

struct QuorumSynthesisRunArgs {
    plan: QuorumSynthesisPlanArgs,
    timeout: Duration,
}

struct ParsedQuorumSynthesisArgs {
    plan: QuorumSynthesisPlanArgs,
    timeout: Option<Duration>,
}

struct QuorumAcceptSynthesisArgs {
    quorum_dir: PathBuf,
    episode_id: Option<String>,
    result_file: Option<PathBuf>,
    status_file: Option<PathBuf>,
}

#[derive(Debug, Clone, serde::Deserialize)]
struct QuorumProposedSynthesis {
    schema_version: u32,
    episode_id: String,
    recommendation: String,
    decision_status: DecisionStatus,
    consensus_level: ConsensusLevel,
    confidence: f32,
    #[serde(default)]
    supporting_points: Vec<String>,
    #[serde(default)]
    dissenting_points: Vec<String>,
    #[serde(default)]
    unresolved_questions: Vec<String>,
    #[serde(default)]
    evidence_used: Vec<String>,
    #[serde(default)]
    participant_votes: Vec<ParticipantVote>,
    #[serde(default)]
    proposed_memory_drafts: Vec<String>,
}

fn quorum_synthesize_plan_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let parsed = parse_quorum_synthesis_plan_args(args)?;
    Ok(QuorumCliOutcome::SynthesisPlan {
        plan: Box::new(build_quorum_synthesis_plan(&parsed)?),
    })
}

fn quorum_synthesize_run_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let parsed = parse_quorum_synthesis_run_args(args)?;
    let plan = build_quorum_synthesis_plan(&parsed.plan)?;
    let status = execute_quorum_synthesis_plan(&plan, parsed.timeout)?;
    Ok(QuorumCliOutcome::SynthesisRun {
        run: Box::new(QuorumSynthesisRun {
            schema_version: QUORUM_SCHEMA_VERSION,
            status,
        }),
    })
}

fn quorum_accept_synthesis_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let parsed = parse_quorum_accept_synthesis_args(args)?;
    let result_file = synthesis_result_file_for_accept(&parsed)?;
    let proposed = load_proposed_synthesis(&result_file)?;
    if let Some(expected_episode_id) = parsed.episode_id.as_deref() {
        if proposed.episode_id != expected_episode_id {
            return Err(CliError::Usage(format!(
                "synthesis result episode_id '{}' does not match --episode-id '{expected_episode_id}'",
                proposed.episode_id
            )));
        }
    }
    let store = QuorumStore::new(&parsed.quorum_dir);
    let episode = store.load_episode(&proposed.episode_id)?;
    validate_proposed_synthesis(&proposed, &episode)?;
    let synthesize_args = synthesize_args_from_proposed(&parsed.quorum_dir, proposed);
    quorum_synthesize_from_args(&synthesize_args)
}

fn synthesis_result_file_for_accept(
    parsed: &QuorumAcceptSynthesisArgs,
) -> Result<PathBuf, CliError> {
    match (&parsed.result_file, &parsed.status_file) {
        (Some(_), Some(_)) => Err(CliError::Usage(
            "--result-file and --status-file are mutually exclusive".to_string(),
        )),
        (Some(result_file), None) => Ok(result_file.clone()),
        (None, Some(status_file)) => {
            let status = load_synthesis_status_artifact(status_file)?;
            validate_synthesis_status_for_accept(parsed, status_file, &status)?;
            Ok(PathBuf::from(status.result_path))
        }
        (None, None) => Err(CliError::Usage(
            "--result-file or --status-file is required".to_string(),
        )),
    }
}

fn build_quorum_synthesis_plan(
    parsed: &QuorumSynthesisPlanArgs,
) -> Result<QuorumSynthesisPlan, CliError> {
    let store = QuorumStore::new(&parsed.quorum_dir);
    let episode = store.load_episode(&parsed.episode_id)?;
    let outputs = load_all_quorum_outputs(&store, &parsed.episode_id)?;
    let paths = synthesis_plan_paths(&parsed.out_dir, &parsed.episode_id);
    fs::create_dir_all(&parsed.out_dir).map_err(LibrarianError::from)?;
    write_synthesis_plan_files(&paths, &episode, &outputs)?;

    let result_path = paths.result.display().to_string();
    let status_path = paths.status.display().to_string();
    let prompt_path = paths.prompt.display().to_string();
    let command_argv = adapter_plan_argv(&parsed.adapter, parsed.binary.as_deref(), &result_path)?;
    let stdout_path = adapter_plan_stdout_path(&parsed.adapter, &result_path)?;
    let stdout_capture_path = stdout_path
        .clone()
        .unwrap_or_else(|| paths.stdout.display().to_string());
    let stderr_capture_path = paths.stderr.display().to_string();
    let accept_synthesis_argv = accept_synthesis_plan_argv(parsed, &result_path);

    Ok(QuorumSynthesisPlan {
        schema_version: QUORUM_SCHEMA_VERSION,
        adapter: parsed.adapter.clone(),
        quorum_dir: parsed.quorum_dir.display().to_string(),
        episode_id: parsed.episode_id.clone(),
        transcript_path: paths.transcript.display().to_string(),
        prompt_path: prompt_path.clone(),
        result_path: result_path.clone(),
        status_path,
        stdin_path: prompt_path,
        stdout_path,
        stdout_capture_path,
        stderr_capture_path,
        argv: command_argv,
        accept_synthesis_argv,
    })
}

fn execute_quorum_synthesis_plan(
    plan: &QuorumSynthesisPlan,
    timeout: Duration,
) -> Result<QuorumSynthesisRunStatus, CliError> {
    let program = plan
        .argv
        .first()
        .ok_or_else(|| CliError::Usage("synthesis plan argv must not be empty".to_string()))?;
    let started = Instant::now();
    let mut command = Command::new(program);
    command.args(&plan.argv[1..]);
    command.stdin(Stdio::from(
        fs::File::open(&plan.stdin_path).map_err(LibrarianError::from)?,
    ));
    command.stdout(Stdio::from(
        fs::File::create(&plan.stdout_capture_path).map_err(LibrarianError::from)?,
    ));
    command.stderr(Stdio::from(
        fs::File::create(&plan.stderr_capture_path).map_err(LibrarianError::from)?,
    ));

    let mut child = spawn_with_transient_exec_retry(&mut command).map_err(LibrarianError::from)?;
    let wait_result = child.wait_timeout(timeout);
    let (timed_out, exit_code, process_success) = match wait_result {
        Ok(Some(status)) => (false, status.code(), status.success()),
        Ok(None) => {
            let _ = child.kill();
            let _ = child.wait();
            (true, None, false)
        }
        Err(err) => return Err(LibrarianError::from(err).into()),
    };
    let process_status = if timed_out {
        QuorumProcessStatus::TimedOut
    } else if process_success {
        QuorumProcessStatus::Succeeded
    } else {
        QuorumProcessStatus::Failed
    };
    let duration_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX);
    let (result_valid, validation_error) = if process_success && !timed_out {
        match validate_synthesis_result_file(plan) {
            Ok(()) => (true, None),
            Err(err) => (false, Some(err.to_string())),
        }
    } else {
        (false, None)
    };
    let status = QuorumSynthesisRunStatus {
        schema_version: QUORUM_SCHEMA_VERSION,
        adapter: plan.adapter.clone(),
        episode_id: plan.episode_id.clone(),
        status_path: plan.status_path.clone(),
        transcript_path: plan.transcript_path.clone(),
        prompt_path: plan.prompt_path.clone(),
        result_path: plan.result_path.clone(),
        stdout_capture_path: plan.stdout_capture_path.clone(),
        stderr_capture_path: plan.stderr_capture_path.clone(),
        success: process_success && !timed_out && result_valid,
        process_status: Some(process_status),
        timed_out,
        exit_code,
        duration_ms,
        result_valid,
        validation_error,
        result_bytes: file_len(&plan.result_path)?,
        stdout_bytes: file_len(&plan.stdout_capture_path)?,
        stderr_bytes: file_len(&plan.stderr_capture_path)?,
        accept_synthesis_argv: plan.accept_synthesis_argv.clone(),
    };
    write_synthesis_run_status(&status)?;
    Ok(status)
}

fn validate_synthesis_result_file(plan: &QuorumSynthesisPlan) -> Result<(), CliError> {
    let proposed = load_proposed_synthesis(Path::new(&plan.result_path))?;
    let store = QuorumStore::new(Path::new(&plan.quorum_dir));
    let episode = store.load_episode(&plan.episode_id)?;
    validate_proposed_synthesis(&proposed, &episode)
}

fn write_synthesis_run_status(status: &QuorumSynthesisRunStatus) -> Result<(), CliError> {
    let bytes = serde_json::to_vec_pretty(status).map_err(LibrarianError::from)?;
    fs::write(&status.status_path, bytes).map_err(LibrarianError::from)?;
    Ok(())
}

fn load_synthesis_status_artifact(path: &Path) -> Result<QuorumSynthesisRunStatus, CliError> {
    let bytes = fs::read(path).map_err(LibrarianError::from)?;
    serde_json::from_slice(&bytes)
        .map_err(LibrarianError::from)
        .map_err(CliError::from)
}

fn validate_synthesis_status_for_accept(
    parsed: &QuorumAcceptSynthesisArgs,
    status_file: &Path,
    status: &QuorumSynthesisRunStatus,
) -> Result<(), CliError> {
    if status.schema_version != QUORUM_SCHEMA_VERSION {
        return Err(CliError::Usage(format!(
            "unsupported synthesis status schema version {}; expected {QUORUM_SCHEMA_VERSION}",
            status.schema_version
        )));
    }
    if let Some(expected_episode_id) = parsed.episode_id.as_deref() {
        if status.episode_id != expected_episode_id {
            return Err(CliError::Usage(format!(
                "synthesis status episode_id '{}' does not match --episode-id '{expected_episode_id}'",
                status.episode_id
            )));
        }
    }
    if !status.success {
        return Err(CliError::Usage(format!(
            "synthesis status for episode '{}' is not successful",
            status.episode_id
        )));
    }
    if status.timed_out || status.process_status != Some(QuorumProcessStatus::Succeeded) {
        return Err(CliError::Usage(format!(
            "synthesis status for episode '{}' did not finish successfully",
            status.episode_id
        )));
    }
    if !status.result_valid {
        return Err(CliError::Usage(format!(
            "synthesis status for episode '{}' has invalid proposed result",
            status.episode_id
        )));
    }
    if status.validation_error.is_some() {
        return Err(CliError::Usage(format!(
            "synthesis status for episode '{}' carries a validation error",
            status.episode_id
        )));
    }
    if status.result_bytes == 0 {
        return Err(CliError::Usage(format!(
            "synthesis status result artifact is empty: {}",
            status.result_path
        )));
    }
    let actual_result_bytes = file_len(&status.result_path)?;
    if actual_result_bytes != status.result_bytes {
        return Err(CliError::Usage(format!(
            "synthesis status result byte count mismatch for {}; status says {}, file has {}",
            status.result_path, status.result_bytes, actual_result_bytes
        )));
    }
    if status.status_path != status_file.display().to_string() {
        return Err(CliError::Usage(format!(
            "synthesis status path mismatch; expected '{}', got '{}'",
            status_file.display(),
            status.status_path
        )));
    }
    let args = accept_synthesis_args_from_status(status)?;
    require_cli_arg_eq(
        &args,
        "synthesis status accept command",
        "--quorum-dir",
        &parsed.quorum_dir.display().to_string(),
    )?;
    require_cli_arg_eq(
        &args,
        "synthesis status accept command",
        "--episode-id",
        &status.episode_id,
    )?;
    require_cli_arg_eq(
        &args,
        "synthesis status accept command",
        "--result-file",
        &status.result_path,
    )?;
    Ok(())
}

fn accept_synthesis_args_from_status(
    status: &QuorumSynthesisRunStatus,
) -> Result<Vec<String>, CliError> {
    if status.accept_synthesis_argv.len() < 3
        || status.accept_synthesis_argv[0] != "mimir-librarian"
        || status.accept_synthesis_argv[1] != "quorum"
        || status.accept_synthesis_argv[2] != "accept-synthesis"
    {
        return Err(CliError::Usage(format!(
            "synthesis status for episode '{}' does not carry a valid accept-synthesis command",
            status.episode_id
        )));
    }
    Ok(status
        .accept_synthesis_argv
        .iter()
        .skip(2)
        .cloned()
        .collect())
}

fn parse_quorum_synthesis_plan_args(args: &[String]) -> Result<QuorumSynthesisPlanArgs, CliError> {
    parse_quorum_synthesis_args(args, "synthesize-plan", false).map(|parsed| parsed.plan)
}

fn parse_quorum_synthesis_run_args(args: &[String]) -> Result<QuorumSynthesisRunArgs, CliError> {
    let mut parsed = parse_quorum_synthesis_args(args, "synthesize-run", true)?;
    let timeout = parsed
        .timeout
        .take()
        .unwrap_or_else(|| Duration::from_secs(DEFAULT_QUORUM_ADAPTER_TIMEOUT_SECS));
    Ok(QuorumSynthesisRunArgs {
        plan: parsed.plan,
        timeout,
    })
}

fn parse_quorum_synthesis_args(
    args: &[String],
    command: &str,
    allow_timeout: bool,
) -> Result<ParsedQuorumSynthesisArgs, CliError> {
    let mut quorum_dir: Option<PathBuf> = None;
    let mut episode_id: Option<String> = None;
    let mut adapter: Option<String> = None;
    let mut out_dir: Option<PathBuf> = None;
    let mut binary: Option<String> = None;
    let mut timeout: Option<Duration> = None;

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--quorum-dir" => {
                quorum_dir = Some(take_value(args, &mut i, "--quorum-dir")?.into());
            }
            "--episode-id" => {
                episode_id = Some(take_value(args, &mut i, "--episode-id")?);
            }
            "--adapter" => {
                adapter = Some(parse_quorum_adapter(&take_value(
                    args,
                    &mut i,
                    "--adapter",
                )?)?);
            }
            "--out-dir" => {
                out_dir = Some(take_value(args, &mut i, "--out-dir")?.into());
            }
            "--binary" => {
                binary = Some(take_value(args, &mut i, "--binary")?);
            }
            "--timeout-secs" if allow_timeout => {
                let value = take_value(args, &mut i, "--timeout-secs")?;
                timeout = Some(Duration::from_secs(parse_positive_u64(
                    "--timeout-secs",
                    &value,
                )?));
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum {command} option '{other}'"
                )));
            }
        }
        i += 1;
    }

    Ok(ParsedQuorumSynthesisArgs {
        plan: QuorumSynthesisPlanArgs {
            quorum_dir: require_path(quorum_dir, "--quorum-dir")?,
            episode_id: require_non_empty(episode_id, "--episode-id")?,
            adapter: require_non_empty(adapter, "--adapter")?,
            out_dir: require_path(out_dir, "--out-dir")?,
            binary,
        },
        timeout,
    })
}

fn parse_quorum_accept_synthesis_args(
    args: &[String],
) -> Result<QuorumAcceptSynthesisArgs, CliError> {
    let mut quorum_dir: Option<PathBuf> = None;
    let mut episode_id: Option<String> = None;
    let mut result_file: Option<PathBuf> = None;
    let mut status_file: Option<PathBuf> = None;

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--quorum-dir" => {
                quorum_dir = Some(take_value(args, &mut i, "--quorum-dir")?.into());
            }
            "--episode-id" => {
                episode_id = Some(take_value(args, &mut i, "--episode-id")?);
            }
            "--result-file" => {
                result_file = Some(take_value(args, &mut i, "--result-file")?.into());
            }
            "--status-file" => {
                status_file = Some(take_value(args, &mut i, "--status-file")?.into());
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum accept-synthesis option '{other}'"
                )));
            }
        }
        i += 1;
    }

    Ok(QuorumAcceptSynthesisArgs {
        quorum_dir: require_path(quorum_dir, "--quorum-dir")?,
        episode_id,
        result_file,
        status_file,
    })
}

fn synthesis_plan_paths(out_dir: &Path, episode_id: &str) -> QuorumSynthesisPlanPaths {
    let stem = format!("{}-synthesis", safe_file_stem(episode_id));
    QuorumSynthesisPlanPaths {
        transcript: out_dir.join(format!("{stem}-transcript.json")),
        prompt: out_dir.join(format!("{stem}-prompt.md")),
        result: out_dir.join(format!("{stem}-result.json")),
        status: out_dir.join(format!("{stem}-status.json")),
        stdout: out_dir.join(format!("{stem}-stdout.log")),
        stderr: out_dir.join(format!("{stem}-stderr.log")),
    }
}

fn write_synthesis_plan_files(
    paths: &QuorumSynthesisPlanPaths,
    episode: &QuorumEpisode,
    outputs: &[QuorumParticipantOutput],
) -> Result<(), CliError> {
    let transcript = serde_json::json!({
        "schema_version": QUORUM_SCHEMA_VERSION,
        "episode": episode,
        "outputs": outputs,
    });
    let transcript_json = serde_json::to_vec_pretty(&transcript).map_err(LibrarianError::from)?;
    fs::write(&paths.transcript, transcript_json).map_err(LibrarianError::from)?;
    fs::write(&paths.prompt, build_synthesis_prompt(episode, outputs))
        .map_err(LibrarianError::from)?;
    Ok(())
}

fn build_synthesis_prompt(episode: &QuorumEpisode, outputs: &[QuorumParticipantOutput]) -> String {
    let participants = episode
        .participants
        .iter()
        .map(|participant| {
            format!(
                "- participant_id: {}\n  adapter: {}\n  persona: {}\n  model: {}",
                participant.id,
                participant.adapter,
                participant.persona,
                participant.model.as_deref().unwrap_or("<unspecified>")
            )
        })
        .collect::<Vec<_>>()
        .join("\n");
    let outputs = format_synthesis_outputs(outputs);
    format!(
        "# Mimir quorum synthesis request\n\n\
You are synthesizing a governed Mimir quorum result.\n\
episode_id: {}\n\
requester: {}\n\
target_project: {}\n\
target_scope: {}\n\n\
Question:\n{}\n\n\
Evidence policy:\n{}\n\n\
Participants:\n{}\n\n\
Stored participant outputs:\n{}\n\n\
Return only a JSON object with these fields:\n\
{{\n\
  \"schema_version\": {},\n\
  \"episode_id\": \"{}\",\n\
  \"recommendation\": \"short recommendation\",\n\
  \"decision_status\": \"recommend|split|needs_evidence|reject|unsafe\",\n\
  \"consensus_level\": \"unanimous|strong_majority|weak_majority|contested|abstained\",\n\
  \"confidence\": 0.0,\n\
  \"supporting_points\": [\"point\"],\n\
  \"dissenting_points\": [\"dissent or limitation\"],\n\
  \"unresolved_questions\": [\"question\"],\n\
  \"evidence_used\": [\"source or artifact uri\"],\n\
  \"participant_votes\": [{{\"participant_id\":\"id\",\"vote\":\"agree|disagree|abstain\",\"confidence\":0.0,\"rationale\":\"short rationale\"}}],\n\
  \"proposed_memory_drafts\": [\"raw draft for librarian review\"]\n\
}}\n\n\
Preserve dissent and uncertainty. Do not write canonical Mimir memory, do not edit \
project files, and do not call Mimir write commands. This JSON is a proposed \
result artifact; it is recorded only after `mimir-librarian quorum accept-synthesis`.\n",
        episode.id,
        episode.requester,
        episode.target_project.as_deref().unwrap_or("<none>"),
        episode.target_scope.as_deref().unwrap_or("<none>"),
        episode.question,
        episode.evidence_policy,
        participants,
        outputs,
        QUORUM_SCHEMA_VERSION,
        episode.id,
    )
}

fn format_synthesis_outputs(outputs: &[QuorumParticipantOutput]) -> String {
    if outputs.is_empty() {
        return "- <none>".to_string();
    }
    outputs
        .iter()
        .map(|output| {
            format!(
                "- output_id: {}\n  participant_id: {}\n  round: {}\n  visible_prior_output_ids: {:?}\n  response: {}",
                output.output_id,
                output.participant_id,
                quorum_round_name(output.round),
                output.visible_prior_output_ids,
                output.response
            )
        })
        .collect::<Vec<_>>()
        .join("\n")
}

fn accept_synthesis_plan_argv(parsed: &QuorumSynthesisPlanArgs, result_path: &str) -> Vec<String> {
    vec![
        "mimir-librarian".to_string(),
        "quorum".to_string(),
        "accept-synthesis".to_string(),
        "--quorum-dir".to_string(),
        parsed.quorum_dir.display().to_string(),
        "--episode-id".to_string(),
        parsed.episode_id.clone(),
        "--result-file".to_string(),
        result_path.to_string(),
    ]
}

fn load_all_quorum_outputs(
    store: &QuorumStore,
    episode_id: &str,
) -> Result<Vec<QuorumParticipantOutput>, CliError> {
    let mut outputs = Vec::new();
    for round in [
        QuorumRound::Independent,
        QuorumRound::Critique,
        QuorumRound::Revision,
    ] {
        outputs.extend(store.load_round_outputs(episode_id, round)?);
    }
    Ok(outputs)
}

fn load_proposed_synthesis(path: &Path) -> Result<QuorumProposedSynthesis, CliError> {
    let raw = fs::read_to_string(path).map_err(LibrarianError::from)?;
    match serde_json::from_str(&raw) {
        Ok(proposed) => Ok(proposed),
        Err(err) => {
            if let Some(candidate) = json_object_slice(&raw) {
                return serde_json::from_str(candidate).map_err(|json_err| {
                    CliError::Usage(format!(
                        "synthesis result JSON is invalid: {json_err}; initial parse error: {err}"
                    ))
                });
            }
            Err(LibrarianError::from(err).into())
        }
    }
}

fn json_object_slice(raw: &str) -> Option<&str> {
    let start = raw.find('{')?;
    let end = raw.rfind('}')?;
    (start <= end).then_some(&raw[start..=end])
}

fn validate_proposed_synthesis(
    proposed: &QuorumProposedSynthesis,
    episode: &QuorumEpisode,
) -> Result<(), CliError> {
    if proposed.schema_version != QUORUM_SCHEMA_VERSION {
        return Err(CliError::Usage(format!(
            "unsupported synthesis result schema version {}; expected {QUORUM_SCHEMA_VERSION}",
            proposed.schema_version
        )));
    }
    require_non_empty(Some(proposed.episode_id.clone()), "episode_id")?;
    if proposed.episode_id != episode.id {
        return Err(CliError::Usage(format!(
            "synthesis result episode_id '{}' does not match episode '{}'",
            proposed.episode_id, episode.id
        )));
    }
    require_non_empty(Some(proposed.recommendation.clone()), "recommendation")?;
    validate_unit_f32_value("confidence", proposed.confidence)?;
    validate_text_items("supporting_points", &proposed.supporting_points)?;
    validate_text_items("dissenting_points", &proposed.dissenting_points)?;
    validate_text_items("unresolved_questions", &proposed.unresolved_questions)?;
    validate_text_items("evidence_used", &proposed.evidence_used)?;
    validate_text_items("proposed_memory_drafts", &proposed.proposed_memory_drafts)?;
    validate_participant_votes(episode, &proposed.participant_votes)?;
    Ok(())
}

fn validate_unit_f32_value(flag: &str, value: f32) -> Result<(), CliError> {
    if !value.is_finite() || !(0.0..=1.0).contains(&value) {
        return Err(CliError::Usage(format!(
            "{flag} must be between 0.0 and 1.0"
        )));
    }
    Ok(())
}

fn validate_text_items(field: &str, values: &[String]) -> Result<(), CliError> {
    for (index, value) in values.iter().enumerate() {
        if value.trim().is_empty() {
            return Err(CliError::Usage(format!(
                "{field}[{index}] must not be empty"
            )));
        }
    }
    Ok(())
}

fn synthesize_args_from_proposed(
    quorum_dir: &Path,
    proposed: QuorumProposedSynthesis,
) -> Vec<String> {
    let mut args = vec![
        "--quorum-dir".to_string(),
        quorum_dir.display().to_string(),
        "--episode-id".to_string(),
        proposed.episode_id,
        "--recommendation".to_string(),
        proposed.recommendation,
        "--decision-status".to_string(),
        decision_status_name(proposed.decision_status).to_string(),
        "--consensus-level".to_string(),
        consensus_level_name(proposed.consensus_level).to_string(),
        "--confidence".to_string(),
        proposed.confidence.to_string(),
    ];
    for point in proposed.supporting_points {
        args.push("--supporting-point".to_string());
        args.push(point);
    }
    for point in proposed.dissenting_points {
        args.push("--dissenting-point".to_string());
        args.push(point);
    }
    for question in proposed.unresolved_questions {
        args.push("--unresolved-question".to_string());
        args.push(question);
    }
    for evidence in proposed.evidence_used {
        args.push("--evidence".to_string());
        args.push(evidence);
    }
    for vote in proposed.participant_votes {
        args.push("--participant-vote".to_string());
        args.push(format!(
            "{}:{}:{}:{}",
            vote.participant_id,
            vote_choice_name(vote.vote),
            vote.confidence,
            vote.rationale
        ));
    }
    for draft in proposed.proposed_memory_drafts {
        args.push("--proposed-memory-draft".to_string());
        args.push(draft);
    }
    args
}

struct QuorumSynthesizeArgs {
    quorum_dir: PathBuf,
    episode_id: String,
    recommendation: String,
    decision_status: DecisionStatus,
    consensus_level: ConsensusLevel,
    confidence: f32,
    supporting_points: Vec<String>,
    dissenting_points: Vec<String>,
    unresolved_questions: Vec<String>,
    evidence_used: Vec<String>,
    participant_votes: Vec<ParticipantVote>,
    proposed_memory_drafts: Vec<String>,
}

fn quorum_synthesize_from_args(args: &[String]) -> Result<QuorumCliOutcome, CliError> {
    let mut parsed = parse_quorum_synthesize_args(args)?;
    let store = QuorumStore::new(&parsed.quorum_dir);
    let episode = store.load_episode(&parsed.episode_id)?;
    validate_synthesize_args(&episode, &parsed)?;
    parsed
        .evidence_used
        .extend(stored_output_evidence(&store, &parsed.episode_id)?);
    let result = QuorumResult {
        schema_version: QUORUM_SCHEMA_VERSION,
        episode_id: parsed.episode_id.clone(),
        question: episode.question,
        recommendation: parsed.recommendation,
        decision_status: parsed.decision_status,
        consensus_level: parsed.consensus_level,
        confidence: parsed.confidence,
        supporting_points: parsed.supporting_points,
        dissenting_points: parsed.dissenting_points,
        unresolved_questions: parsed.unresolved_questions,
        evidence_used: parsed.evidence_used,
        participant_votes: parsed.participant_votes,
        proposed_memory_drafts: parsed.proposed_memory_drafts,
    };
    let path = store.save_result(&result)?;
    Ok(QuorumCliOutcome::ResultSaved {
        episode_id: parsed.episode_id,
        path: path.display().to_string(),
    })
}

struct QuorumSubmitDraftsArgs {
    quorum_dir: PathBuf,
    drafts_dir: PathBuf,
    episode_id: String,
    project: Option<String>,
    operator: Option<String>,
    tags: Vec<String>,
}

fn quorum_submit_drafts_from_args(
    args: &[String],
    submitted_at: SystemTime,
) -> Result<QuorumCliOutcome, CliError> {
    let parsed = parse_quorum_submit_drafts_args(args)?;
    let store = QuorumStore::new(&parsed.quorum_dir);
    let episode = store.load_episode(&parsed.episode_id)?;
    let result = store.load_result(&parsed.episode_id)?;
    let draft_store = DraftStore::new(parsed.drafts_dir);
    let provenance_uri = format!("quorum://episode/{}", parsed.episode_id);
    let mut drafts = Vec::new();

    for raw_text in result.proposed_memory_drafts {
        if raw_text.trim().is_empty() {
            return Err(CliError::Usage(
                "quorum result contains an empty proposed memory draft".to_string(),
            ));
        }
        let mut metadata = DraftMetadata::new(DraftSourceSurface::ConsensusQuorum, submitted_at);
        metadata.source_agent = Some("quorum".to_string());
        metadata.source_project = parsed
            .project
            .clone()
            .or_else(|| episode.target_project.clone());
        metadata.operator.clone_from(&parsed.operator);
        metadata.provenance_uri = Some(provenance_uri.clone());
        metadata.context_tags =
            quorum_draft_context_tags(result.decision_status, result.consensus_level, &parsed.tags);
        let draft = Draft::with_metadata(raw_text, metadata);
        let path = draft_store.submit(&draft)?;
        drafts.push(SweepDraftOutcome {
            id: draft.id().to_hex(),
            path: path.display().to_string(),
            provenance_uri: provenance_uri.clone(),
        });
    }

    Ok(QuorumCliOutcome::DraftsSubmitted {
        episode_id: parsed.episode_id,
        submitted: drafts.len(),
        drafts,
    })
}

fn parse_quorum_submit_drafts_args(args: &[String]) -> Result<QuorumSubmitDraftsArgs, CliError> {
    let mut quorum_dir: Option<PathBuf> = None;
    let mut drafts_dir: Option<PathBuf> = None;
    let mut episode_id: Option<String> = None;
    let mut project: Option<String> = None;
    let mut operator: Option<String> = None;
    let mut tags = Vec::new();

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--quorum-dir" => {
                quorum_dir = Some(take_value(args, &mut i, "--quorum-dir")?.into());
            }
            "--drafts-dir" => {
                drafts_dir = Some(take_value(args, &mut i, "--drafts-dir")?.into());
            }
            "--episode-id" => {
                episode_id = Some(take_value(args, &mut i, "--episode-id")?);
            }
            "--project" => {
                project = Some(take_value(args, &mut i, "--project")?);
            }
            "--operator" => {
                operator = Some(take_value(args, &mut i, "--operator")?);
            }
            "--tag" => {
                tags.push(take_value(args, &mut i, "--tag")?);
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum submit-drafts option '{other}'"
                )));
            }
        }
        i += 1;
    }

    Ok(QuorumSubmitDraftsArgs {
        quorum_dir: require_path(quorum_dir, "--quorum-dir")?,
        drafts_dir: require_path(drafts_dir, "--drafts-dir")?,
        episode_id: require_non_empty(episode_id, "--episode-id")?,
        project,
        operator,
        tags,
    })
}

fn quorum_draft_context_tags(
    decision_status: DecisionStatus,
    consensus_level: ConsensusLevel,
    extra_tags: &[String],
) -> Vec<String> {
    let mut tags = vec![
        "quorum".to_string(),
        decision_status_name(decision_status).to_string(),
        consensus_level_name(consensus_level).to_string(),
    ];
    tags.extend(extra_tags.iter().cloned());
    tags
}

fn parse_quorum_synthesize_args(args: &[String]) -> Result<QuorumSynthesizeArgs, CliError> {
    let mut quorum_dir: Option<PathBuf> = None;
    let mut episode_id: Option<String> = None;
    let mut recommendation: Option<String> = None;
    let mut decision_status: Option<DecisionStatus> = None;
    let mut consensus_level: Option<ConsensusLevel> = None;
    let mut confidence: Option<f32> = None;
    let mut supporting_points = Vec::new();
    let mut dissenting_points = Vec::new();
    let mut unresolved_questions = Vec::new();
    let mut evidence_used = Vec::new();
    let mut participant_votes = Vec::new();
    let mut proposed_memory_drafts = Vec::new();

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--quorum-dir" => {
                quorum_dir = Some(take_value(args, &mut i, "--quorum-dir")?.into());
            }
            "--episode-id" => {
                episode_id = Some(take_value(args, &mut i, "--episode-id")?);
            }
            "--recommendation" => {
                recommendation = Some(take_value(args, &mut i, "--recommendation")?);
            }
            "--decision-status" => {
                let value = take_value(args, &mut i, "--decision-status")?;
                decision_status = Some(parse_decision_status(&value)?);
            }
            "--consensus-level" => {
                let value = take_value(args, &mut i, "--consensus-level")?;
                consensus_level = Some(parse_consensus_level(&value)?);
            }
            "--confidence" => {
                let value = take_value(args, &mut i, "--confidence")?;
                confidence = Some(parse_unit_f32("--confidence", &value)?);
            }
            "--supporting-point" => {
                supporting_points.push(take_value(args, &mut i, "--supporting-point")?);
            }
            "--dissenting-point" => {
                dissenting_points.push(take_value(args, &mut i, "--dissenting-point")?);
            }
            "--unresolved-question" => {
                unresolved_questions.push(take_value(args, &mut i, "--unresolved-question")?);
            }
            "--evidence" => {
                evidence_used.push(take_value(args, &mut i, "--evidence")?);
            }
            "--participant-vote" => {
                let value = take_value(args, &mut i, "--participant-vote")?;
                participant_votes.push(parse_participant_vote(&value)?);
            }
            "--proposed-memory-draft" => {
                proposed_memory_drafts.push(take_value(args, &mut i, "--proposed-memory-draft")?);
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum synthesize option '{other}'"
                )));
            }
        }
        i += 1;
    }

    Ok(QuorumSynthesizeArgs {
        quorum_dir: require_path(quorum_dir, "--quorum-dir")?,
        episode_id: require_non_empty(episode_id, "--episode-id")?,
        recommendation: require_non_empty(recommendation, "--recommendation")?,
        decision_status: decision_status
            .ok_or_else(|| CliError::Usage("--decision-status is required".to_string()))?,
        consensus_level: consensus_level
            .ok_or_else(|| CliError::Usage("--consensus-level is required".to_string()))?,
        confidence: confidence
            .ok_or_else(|| CliError::Usage("--confidence is required".to_string()))?,
        supporting_points,
        dissenting_points,
        unresolved_questions,
        evidence_used,
        participant_votes,
        proposed_memory_drafts,
    })
}

fn validate_participant_votes(
    episode: &QuorumEpisode,
    participant_votes: &[ParticipantVote],
) -> Result<(), CliError> {
    let expected: BTreeSet<String> = episode
        .participants
        .iter()
        .map(|participant| participant.id.clone())
        .collect();
    let mut seen = BTreeSet::new();
    for vote in participant_votes {
        require_non_empty(
            Some(vote.participant_id.clone()),
            "participant vote participant_id",
        )?;
        require_non_empty(Some(vote.rationale.clone()), "participant vote rationale")?;
        validate_unit_f32_value("participant vote confidence", vote.confidence)?;
        if !seen.insert(vote.participant_id.clone()) {
            return Err(CliError::Usage(format!(
                "duplicate participant vote for '{}'",
                vote.participant_id
            )));
        }
        if !episode
            .participants
            .iter()
            .any(|participant| participant.id == vote.participant_id)
        {
            return Err(CliError::Usage(format!(
                "participant vote references unknown participant '{}'",
                vote.participant_id
            )));
        }
    }
    if seen != expected {
        let missing = expected
            .difference(&seen)
            .cloned()
            .collect::<Vec<_>>()
            .join(", ");
        let unexpected = seen
            .difference(&expected)
            .cloned()
            .collect::<Vec<_>>()
            .join(", ");
        let detail = match (missing.is_empty(), unexpected.is_empty()) {
            (false, false) => format!("missing: {missing}; unexpected: {unexpected}"),
            (false, true) => format!("missing: {missing}"),
            (true, false) => format!("unexpected: {unexpected}"),
            (true, true) => "vote set mismatch".to_string(),
        };
        return Err(CliError::Usage(format!(
            "synthesis result must include exactly one participant vote per episode participant ({detail})"
        )));
    }
    Ok(())
}

fn validate_synthesize_args(
    episode: &QuorumEpisode,
    parsed: &QuorumSynthesizeArgs,
) -> Result<(), CliError> {
    validate_unit_f32_value("confidence", parsed.confidence)?;
    validate_text_items("supporting_points", &parsed.supporting_points)?;
    validate_text_items("dissenting_points", &parsed.dissenting_points)?;
    validate_text_items("unresolved_questions", &parsed.unresolved_questions)?;
    validate_text_items("evidence_used", &parsed.evidence_used)?;
    validate_text_items("proposed_memory_drafts", &parsed.proposed_memory_drafts)?;
    validate_participant_votes(episode, &parsed.participant_votes)
}

fn stored_output_evidence(store: &QuorumStore, episode_id: &str) -> Result<Vec<String>, CliError> {
    let mut evidence = Vec::new();
    for round in [
        QuorumRound::Independent,
        QuorumRound::Critique,
        QuorumRound::Revision,
    ] {
        for output in store.load_round_outputs(episode_id, round)? {
            evidence.push(format!(
                "quorum-output://{episode_id}/{}/{}",
                quorum_round_name(round),
                output.output_id
            ));
        }
    }
    Ok(evidence)
}

fn parse_quorum_read_args(
    args: &[String],
    command: &str,
) -> Result<(PathBuf, String, QuorumRound), CliError> {
    let mut quorum_dir: Option<PathBuf> = None;
    let mut episode_id: Option<String> = None;
    let mut round: Option<QuorumRound> = None;

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--quorum-dir" => {
                quorum_dir = Some(take_value(args, &mut i, "--quorum-dir")?.into());
            }
            "--episode-id" => {
                episode_id = Some(take_value(args, &mut i, "--episode-id")?);
            }
            "--round" => {
                let value = take_value(args, &mut i, "--round")?;
                round = Some(parse_quorum_round(&value)?);
            }
            other => {
                return Err(CliError::Usage(format!(
                    "unknown quorum {command} option '{other}'"
                )));
            }
        }
        i += 1;
    }

    Ok((
        require_path(quorum_dir, "--quorum-dir")?,
        require_non_empty(episode_id, "--episode-id")?,
        round.ok_or_else(|| CliError::Usage("--round is required".to_string()))?,
    ))
}

fn fill_default_agent(metadata: &mut DraftMetadata) {
    if metadata.source_agent.is_some() {
        return;
    }
    metadata.source_agent = match metadata.source_surface {
        DraftSourceSurface::ClaudeMemory => Some("claude".to_string()),
        DraftSourceSurface::CodexMemory => Some("codex".to_string()),
        DraftSourceSurface::CopilotSessionStore => Some("copilot".to_string()),
        _ => None,
    };
}

fn collect_sweep_files(path: &Path) -> Result<Vec<PathBuf>, CliError> {
    if path.is_file() {
        return Ok(vec![path.to_path_buf()]);
    }
    if !path.is_dir() {
        return Err(CliError::Usage(format!(
            "--path must be a file or directory: {}",
            path.display()
        )));
    }

    let mut files = Vec::new();
    collect_memory_files(path, &mut files)?;
    files.sort();
    Ok(files)
}

fn collect_memory_files(dir: &Path, files: &mut Vec<PathBuf>) -> Result<(), LibrarianError> {
    for entry in fs::read_dir(dir)? {
        let path = entry?.path();
        if path.is_dir() {
            collect_memory_files(&path, files)?;
        } else if path.is_file() && is_supported_sweep_file(&path) {
            files.push(path);
        }
    }
    Ok(())
}

fn is_supported_sweep_file(path: &Path) -> bool {
    matches!(
        path.extension().and_then(|extension| extension.to_str()),
        Some("md" | "markdown" | "txt")
    )
}

fn is_sweep_source_surface(source_surface: DraftSourceSurface) -> bool {
    matches!(
        source_surface,
        DraftSourceSurface::ClaudeMemory
            | DraftSourceSurface::CodexMemory
            | DraftSourceSurface::Directory
            | DraftSourceSurface::RepoHandoff
            | DraftSourceSurface::AgentExport
    )
}

fn file_uri(path: &Path) -> String {
    format!("file://{}", path.display())
}

fn safe_file_stem(value: &str) -> String {
    let stem: String = value
        .chars()
        .map(|character| {
            if character.is_ascii_alphanumeric() || matches!(character, '-' | '_') {
                character
            } else {
                '-'
            }
        })
        .take(80)
        .collect();
    if stem.trim_matches('-').is_empty() {
        "quorum".to_string()
    } else {
        stem
    }
}

fn take_value(args: &[String], index: &mut usize, flag: &str) -> Result<String, CliError> {
    *index += 1;
    args.get(*index)
        .cloned()
        .ok_or_else(|| CliError::Usage(format!("{flag} requires a value")))
}

fn flag_value<'a>(args: &'a [String], flag: &str) -> Option<&'a str> {
    args.iter()
        .position(|arg| arg == flag)
        .and_then(|index| args.get(index + 1))
        .map(String::as_str)
}

fn required_flag_value<'a>(args: &'a [String], flag: &str) -> Result<&'a str, CliError> {
    flag_value(args, flag).ok_or_else(|| {
        CliError::Usage(format!(
            "adapter status append command is missing required {flag}"
        ))
    })
}

fn require_text_or_file(
    inline: Option<String>,
    file: Option<PathBuf>,
    inline_flag: &str,
    file_flag: &str,
) -> Result<String, CliError> {
    match (inline, file) {
        (Some(_), Some(_)) => Err(CliError::Usage(format!(
            "{inline_flag} and {file_flag} are mutually exclusive"
        ))),
        (Some(value), None) => require_non_empty(Some(value), inline_flag),
        (None, Some(path)) => {
            let value = fs::read_to_string(path).map_err(LibrarianError::from)?;
            require_non_empty(Some(value), file_flag)
        }
        (None, None) => Err(CliError::Usage(format!(
            "{inline_flag} or {file_flag} is required"
        ))),
    }
}

fn require_non_empty(value: Option<String>, flag: &str) -> Result<String, CliError> {
    let value = value.ok_or_else(|| CliError::Usage(format!("{flag} is required")))?;
    if value.trim().is_empty() {
        return Err(CliError::Usage(format!("{flag} must not be empty")));
    }
    Ok(value)
}

fn require_path(value: Option<PathBuf>, flag: &str) -> Result<PathBuf, CliError> {
    value.ok_or_else(|| CliError::Usage(format!("{flag} is required")))
}

fn parse_u64(flag: &str, value: &str) -> Result<u64, CliError> {
    value
        .parse::<u64>()
        .map_err(|_| CliError::Usage(format!("{flag} must be an integer: {value}")))
}

fn parse_usize(flag: &str, value: &str) -> Result<usize, CliError> {
    value
        .parse::<usize>()
        .map_err(|_| CliError::Usage(format!("{flag} must be an integer: {value}")))
}

fn parse_positive_u64(flag: &str, value: &str) -> Result<u64, CliError> {
    let parsed = value
        .parse::<u64>()
        .map_err(|_| CliError::Usage(format!("{flag} must be an integer: {value}")))?;
    if parsed == 0 {
        return Err(CliError::Usage(format!("{flag} must be greater than zero")));
    }
    Ok(parsed)
}

fn parse_quorum_participant(value: &str) -> Result<QuorumParticipant, CliError> {
    let parts: Vec<&str> = value.split(':').collect();
    if !(3..=4).contains(&parts.len()) || parts.iter().take(3).any(|part| part.trim().is_empty()) {
        return Err(CliError::Usage(
            "--participant must be ID:ADAPTER:PERSONA[:MODEL]".to_string(),
        ));
    }
    Ok(QuorumParticipant {
        id: parts[0].to_string(),
        adapter: parts[1].to_string(),
        model: parts
            .get(3)
            .and_then(|model| (!model.trim().is_empty()).then(|| (*model).to_string())),
        persona: parts[2].to_string(),
        prompt_template_version: "v1".to_string(),
        runtime_surface: parts[1].to_string(),
        tool_permissions: Vec::new(),
    })
}

fn parse_quorum_round(value: &str) -> Result<QuorumRound, CliError> {
    match value {
        "independent" | "independent-round" | "independent_round" => Ok(QuorumRound::Independent),
        "critique" | "critique-round" | "critique_round" => Ok(QuorumRound::Critique),
        "revision" | "revision-round" | "revision_round" => Ok(QuorumRound::Revision),
        other => Err(CliError::Usage(format!("unknown quorum round '{other}'"))),
    }
}

fn parse_quorum_pilot_review_decision(value: &str) -> Result<QuorumPilotReviewDecision, CliError> {
    match value {
        "pass" => Ok(QuorumPilotReviewDecision::Pass),
        "needs-work" | "needs_work" => Ok(QuorumPilotReviewDecision::NeedsWork),
        "fail" => Ok(QuorumPilotReviewDecision::Fail),
        other => Err(CliError::Usage(format!(
            "unknown quorum pilot review decision '{other}'"
        ))),
    }
}

fn parse_quorum_pilot_review_finding(value: &str) -> Result<QuorumPilotReviewFinding, CliError> {
    let Some((severity, text)) = value.split_once(':') else {
        return Err(CliError::Usage(
            "--finding must be info|warning|blocker:TEXT".to_string(),
        ));
    };
    match severity {
        "info" | "warning" | "blocker" => {}
        other => {
            return Err(CliError::Usage(format!(
                "unknown quorum pilot review finding severity '{other}'"
            )));
        }
    }
    Ok(QuorumPilotReviewFinding {
        severity: severity.to_string(),
        text: require_non_empty(Some(text.to_string()), "--finding text")?,
    })
}

fn parse_quorum_adapter(value: &str) -> Result<String, CliError> {
    match value {
        "claude" | "codex" => Ok(value.to_string()),
        other => Err(CliError::Usage(format!("unknown quorum adapter '{other}'"))),
    }
}

fn parse_decision_status(value: &str) -> Result<DecisionStatus, CliError> {
    match value {
        "recommend" => Ok(DecisionStatus::Recommend),
        "split" => Ok(DecisionStatus::Split),
        "needs-evidence" | "needs_evidence" => Ok(DecisionStatus::NeedsEvidence),
        "reject" => Ok(DecisionStatus::Reject),
        "unsafe" => Ok(DecisionStatus::Unsafe),
        other => Err(CliError::Usage(format!(
            "unknown decision status '{other}'"
        ))),
    }
}

fn decision_status_name(status: DecisionStatus) -> &'static str {
    match status {
        DecisionStatus::Recommend => "recommend",
        DecisionStatus::Split => "split",
        DecisionStatus::NeedsEvidence => "needs_evidence",
        DecisionStatus::Reject => "reject",
        DecisionStatus::Unsafe => "unsafe",
    }
}

fn parse_consensus_level(value: &str) -> Result<ConsensusLevel, CliError> {
    match value {
        "unanimous" => Ok(ConsensusLevel::Unanimous),
        "strong-majority" | "strong_majority" => Ok(ConsensusLevel::StrongMajority),
        "weak-majority" | "weak_majority" => Ok(ConsensusLevel::WeakMajority),
        "contested" => Ok(ConsensusLevel::Contested),
        "abstained" => Ok(ConsensusLevel::Abstained),
        other => Err(CliError::Usage(format!(
            "unknown consensus level '{other}'"
        ))),
    }
}

fn consensus_level_name(level: ConsensusLevel) -> &'static str {
    match level {
        ConsensusLevel::Unanimous => "unanimous",
        ConsensusLevel::StrongMajority => "strong_majority",
        ConsensusLevel::WeakMajority => "weak_majority",
        ConsensusLevel::Contested => "contested",
        ConsensusLevel::Abstained => "abstained",
    }
}

fn parse_vote_choice(value: &str) -> Result<VoteChoice, CliError> {
    match value {
        "agree" => Ok(VoteChoice::Agree),
        "disagree" => Ok(VoteChoice::Disagree),
        "abstain" => Ok(VoteChoice::Abstain),
        other => Err(CliError::Usage(format!("unknown vote choice '{other}'"))),
    }
}

fn vote_choice_name(choice: VoteChoice) -> &'static str {
    match choice {
        VoteChoice::Agree => "agree",
        VoteChoice::Disagree => "disagree",
        VoteChoice::Abstain => "abstain",
    }
}

fn parse_participant_vote(value: &str) -> Result<ParticipantVote, CliError> {
    let parts: Vec<&str> = value.splitn(4, ':').collect();
    if parts.len() != 4
        || parts[0].trim().is_empty()
        || parts[1].trim().is_empty()
        || parts[2].trim().is_empty()
        || parts[3].trim().is_empty()
    {
        return Err(CliError::Usage(
            "--participant-vote must be PARTICIPANT:agree|disagree|abstain:CONFIDENCE:RATIONALE"
                .to_string(),
        ));
    }
    Ok(ParticipantVote {
        participant_id: parts[0].to_string(),
        vote: parse_vote_choice(parts[1])?,
        confidence: parse_unit_f32("--participant-vote confidence", parts[2])?,
        rationale: parts[3].to_string(),
    })
}

fn parse_unit_f32(flag: &str, value: &str) -> Result<f32, CliError> {
    let parsed = value
        .parse::<f32>()
        .map_err(|_| CliError::Usage(format!("{flag} must be a number: {value}")))?;
    if !parsed.is_finite() || !(0.0..=1.0).contains(&parsed) {
        return Err(CliError::Usage(format!(
            "{flag} must be between 0.0 and 1.0"
        )));
    }
    Ok(parsed)
}

fn quorum_round_name(round: QuorumRound) -> &'static str {
    match round {
        QuorumRound::Independent => "independent",
        QuorumRound::Critique => "critique",
        QuorumRound::Revision => "revision",
    }
}

fn system_time_to_unix_ms(time: SystemTime) -> u64 {
    match time.duration_since(UNIX_EPOCH) {
        Ok(duration) => u64::try_from(duration.as_millis()).unwrap_or(u64::MAX),
        Err(_) => 0,
    }
}

fn clock_time_from_system_time(time: SystemTime) -> Result<ClockTime, LibrarianError> {
    let millis = time
        .duration_since(UNIX_EPOCH)
        .map_err(|err| LibrarianError::ValidationClock {
            message: err.to_string(),
        })?
        .as_millis();
    let millis = u64::try_from(millis).unwrap_or(u64::MAX - 1);
    ClockTime::try_from_millis(millis).map_err(|err| LibrarianError::ValidationClock {
        message: err.to_string(),
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[cfg(unix)]
    fn make_quorum_shim(
        name: &str,
        script_body: &str,
    ) -> Result<(tempfile::TempDir, PathBuf), Box<dyn std::error::Error>> {
        use std::os::unix::fs::PermissionsExt as _;

        let dir = tempfile::tempdir()?;
        let path = dir.path().join(name);
        let tmp_path = dir.path().join(format!(".{name}.tmp"));
        let mut file = std::fs::File::create(&tmp_path)?;
        file.write_all(script_body.as_bytes())?;
        file.sync_all()?;
        drop(file);
        let mut perms = std::fs::metadata(&tmp_path)?.permissions();
        perms.set_mode(0o755);
        std::fs::set_permissions(&tmp_path, perms)?;
        std::fs::rename(&tmp_path, &path)?;
        Ok((dir, path))
    }

    #[cfg(windows)]
    fn make_quorum_shim(
        name: &str,
        script_body: &str,
    ) -> Result<(tempfile::TempDir, PathBuf), Box<dyn std::error::Error>> {
        let dir = tempfile::tempdir()?;
        let script_path = dir.path().join(format!("{name}.sh"));
        let path = dir.path().join(format!("{name}.cmd"));
        let tmp_script_path = dir.path().join(format!(".{name}.sh.tmp"));
        let tmp_path = dir.path().join(format!(".{name}.cmd.tmp"));
        let mut script = std::fs::File::create(&tmp_script_path)?;
        script.write_all(script_body.as_bytes())?;
        script.sync_all()?;
        drop(script);
        let mut command = std::fs::File::create(&tmp_path)?;
        command.write_all(format!("@echo off\r\nsh \"%~dp0{name}.sh\" %*\r\n").as_bytes())?;
        command.sync_all()?;
        drop(command);
        std::fs::rename(&tmp_script_path, &script_path)?;
        std::fs::rename(&tmp_path, &path)?;
        Ok((dir, path))
    }

    #[test]
    fn submit_from_args_writes_pending_draft() -> Result<(), Box<dyn std::error::Error>> {
        let tmp = tempfile::tempdir()?;
        let args = vec![
            "--drafts-dir".to_string(),
            tmp.path().display().to_string(),
            "--text".to_string(),
            "Mimir should import Codex memory as drafts.".to_string(),
            "--source-surface".to_string(),
            "codex-memory".to_string(),
            "--agent".to_string(),
            "codex".to_string(),
            "--project".to_string(),
            "buildepicshit/Mimir".to_string(),
            "--operator".to_string(),
            "AlainDor".to_string(),
            "--provenance".to_string(),
            "file:///home/hasnobeef/.codex/memories/mimir.md".to_string(),
            "--tag".to_string(),
            "scope-model".to_string(),
        ];

        let outcome = match submit_from_args(&args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("submit failed: {err:?}").into()),
        };

        assert!(outcome.id.len() == 16);
        assert!(std::path::Path::new(&outcome.path).exists());
        let saved = std::fs::read_to_string(outcome.path)?;
        assert!(saved.contains("\"source_surface\": \"codex_memory\""));
        assert!(saved.contains("\"source_agent\": \"codex\""));
        assert!(saved.contains("\"operator\": \"AlainDor\""));
        Ok(())
    }

    #[test]
    fn submit_from_args_rejects_missing_text() {
        let args = Vec::new();
        match submit_from_args(&args, SystemTime::UNIX_EPOCH) {
            Err(CliError::Usage(message)) => assert!(message.contains("--text is required")),
            other => assert!(
                matches!(other, Err(CliError::Usage(_))),
                "expected usage error, got {other:?}"
            ),
        }
    }

    #[test]
    fn submit_from_args_writes_consensus_quorum_draft() -> Result<(), Box<dyn std::error::Error>> {
        let tmp = tempfile::tempdir()?;
        let args = vec![
            "--drafts-dir".to_string(),
            tmp.path().display().to_string(),
            "--text".to_string(),
            "Quorum recommends keeping remote sync explicit; dissent preserved.".to_string(),
            "--source-surface".to_string(),
            "consensus-quorum".to_string(),
            "--agent".to_string(),
            "quorum".to_string(),
            "--project".to_string(),
            "buildepicshit/Mimir".to_string(),
            "--operator".to_string(),
            "AlainDor".to_string(),
            "--provenance".to_string(),
            "quorum://episode/2026-04-24T21:00:00Z".to_string(),
            "--tag".to_string(),
            "quorum".to_string(),
            "--tag".to_string(),
            "strong_majority".to_string(),
        ];

        let outcome = match submit_from_args(&args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("submit failed: {err:?}").into()),
        };

        let saved = std::fs::read_to_string(outcome.path)?;
        assert!(saved.contains("\"source_surface\": \"consensus_quorum\""));
        assert!(saved.contains("\"source_agent\": \"quorum\""));
        assert!(saved.contains("\"provenance_uri\": \"quorum://episode/2026-04-24T21:00:00Z\""));
        assert!(saved.contains("\"quorum\""));
        assert!(saved.contains("\"strong_majority\""));
        Ok(())
    }

    #[test]
    fn quorum_create_from_args_writes_episode() -> Result<(), Box<dyn std::error::Error>> {
        let tmp = tempfile::tempdir()?;
        let args = vec![
            "create".to_string(),
            "--quorum-dir".to_string(),
            tmp.path().display().to_string(),
            "--id".to_string(),
            "qr-cli-001".to_string(),
            "--requester".to_string(),
            "operator:AlainDor".to_string(),
            "--question".to_string(),
            "Should quorum artifacts stay governed drafts?".to_string(),
            "--target-project".to_string(),
            "buildepicshit/Mimir".to_string(),
            "--target-scope".to_string(),
            "project".to_string(),
            "--participant".to_string(),
            "claude:claude:architect:claude-sonnet-4.6".to_string(),
            "--participant".to_string(),
            "codex:codex:implementation_engineer:gpt-5.5".to_string(),
        ];

        let outcome = match quorum_from_args(&args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("quorum create failed: {err:?}").into()),
        };

        let path = match outcome {
            QuorumCliOutcome::EpisodeCreated { id, path } => {
                assert_eq!(id, "qr-cli-001");
                path
            }
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };
        assert!(std::path::Path::new(&path).exists());
        let saved = std::fs::read_to_string(path)?;
        assert!(saved.contains("\"id\": \"qr-cli-001\""));
        assert!(saved.contains("\"participant"));
        Ok(())
    }

    #[test]
    fn quorum_append_output_from_args_writes_independent_output(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let tmp = tempfile::tempdir()?;
        let create_args = vec![
            "create".to_string(),
            "--quorum-dir".to_string(),
            tmp.path().display().to_string(),
            "--id".to_string(),
            "qr-cli-002".to_string(),
            "--requester".to_string(),
            "operator:AlainDor".to_string(),
            "--question".to_string(),
            "Should Mimir keep quorum outputs auditable?".to_string(),
            "--participant".to_string(),
            "claude:claude:architect".to_string(),
        ];
        quorum_from_args(&create_args, SystemTime::UNIX_EPOCH)?;

        let append_args = vec![
            "append-output".to_string(),
            "--quorum-dir".to_string(),
            tmp.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-002".to_string(),
            "--output-id".to_string(),
            "out-independent-claude".to_string(),
            "--participant-id".to_string(),
            "claude".to_string(),
            "--round".to_string(),
            "independent".to_string(),
            "--prompt".to_string(),
            "Answer independently.".to_string(),
            "--response".to_string(),
            "Keep the artifact auditable.".to_string(),
            "--evidence".to_string(),
            "docs/concepts/consensus-quorum.md".to_string(),
        ];

        let outcome = match quorum_from_args(&append_args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("quorum append failed: {err:?}").into()),
        };

        match outcome {
            QuorumCliOutcome::OutputAppended {
                episode_id,
                output_id,
                path,
            } => {
                assert_eq!(episode_id, "qr-cli-002");
                assert_eq!(output_id, "out-independent-claude");
                assert!(std::path::Path::new(&path).exists());
            }
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        }
        Ok(())
    }

    #[test]
    fn quorum_visible_from_args_returns_complete_independent_round(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let tmp = tempfile::tempdir()?;
        let create_args = vec![
            "create".to_string(),
            "--quorum-dir".to_string(),
            tmp.path().display().to_string(),
            "--id".to_string(),
            "qr-cli-003".to_string(),
            "--requester".to_string(),
            "operator:AlainDor".to_string(),
            "--question".to_string(),
            "Should critique wait for complete independent outputs?".to_string(),
            "--participant".to_string(),
            "claude:claude:architect".to_string(),
            "--participant".to_string(),
            "codex:codex:implementation_engineer".to_string(),
        ];
        quorum_from_args(&create_args, SystemTime::UNIX_EPOCH)?;

        for participant_id in ["claude", "codex"] {
            let append_args = vec![
                "append-output".to_string(),
                "--quorum-dir".to_string(),
                tmp.path().display().to_string(),
                "--episode-id".to_string(),
                "qr-cli-003".to_string(),
                "--output-id".to_string(),
                format!("out-independent-{participant_id}"),
                "--participant-id".to_string(),
                participant_id.to_string(),
                "--round".to_string(),
                "independent".to_string(),
                "--prompt".to_string(),
                "Answer independently.".to_string(),
                "--response".to_string(),
                format!("Independent answer from {participant_id}."),
            ];
            quorum_from_args(&append_args, SystemTime::UNIX_EPOCH)?;
        }

        let visible_args = vec![
            "visible".to_string(),
            "--quorum-dir".to_string(),
            tmp.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-003".to_string(),
            "--round".to_string(),
            "critique".to_string(),
        ];
        let outcome = match quorum_from_args(&visible_args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("quorum visible failed: {err:?}").into()),
        };

        match outcome {
            QuorumCliOutcome::VisibleOutputs { round, outputs, .. } => {
                assert_eq!(round, "critique");
                let ids: Vec<_> = outputs
                    .iter()
                    .map(|output| output.output_id.as_str())
                    .collect();
                assert_eq!(ids, vec!["out-independent-claude", "out-independent-codex"]);
            }
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        }
        Ok(())
    }

    #[test]
    fn quorum_adapter_request_from_args_returns_contract() -> Result<(), Box<dyn std::error::Error>>
    {
        let tmp = tempfile::tempdir()?;
        let create_args = vec![
            "create".to_string(),
            "--quorum-dir".to_string(),
            tmp.path().display().to_string(),
            "--id".to_string(),
            "qr-cli-004".to_string(),
            "--requester".to_string(),
            "operator:AlainDor".to_string(),
            "--question".to_string(),
            "Should adapters consume a stable JSON request?".to_string(),
            "--participant".to_string(),
            "claude:claude:architect:claude-sonnet-4.6".to_string(),
        ];
        quorum_from_args(&create_args, SystemTime::UNIX_EPOCH)?;

        let request_args = vec![
            "adapter-request".to_string(),
            "--quorum-dir".to_string(),
            tmp.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-004".to_string(),
            "--participant-id".to_string(),
            "claude".to_string(),
            "--round".to_string(),
            "independent".to_string(),
        ];
        let outcome = match quorum_from_args(&request_args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("quorum adapter-request failed: {err:?}").into()),
        };

        match outcome {
            QuorumCliOutcome::AdapterRequest { request } => {
                assert_eq!(request.episode_id, "qr-cli-004");
                assert_eq!(request.participant.id, "claude");
                assert_eq!(request.round, QuorumRound::Independent);
                assert!(request.visible_prior_outputs.is_empty());
            }
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        }
        Ok(())
    }

    #[test]
    fn quorum_synthesize_from_args_saves_structured_result(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let tmp = tempfile::tempdir()?;
        let create_args = vec![
            "create".to_string(),
            "--quorum-dir".to_string(),
            tmp.path().display().to_string(),
            "--id".to_string(),
            "qr-cli-005".to_string(),
            "--requester".to_string(),
            "operator:AlainDor".to_string(),
            "--question".to_string(),
            "Should Mimir preserve dissent in quorum results?".to_string(),
            "--participant".to_string(),
            "claude:claude:architect".to_string(),
            "--participant".to_string(),
            "codex:codex:implementation_engineer".to_string(),
        ];
        quorum_from_args(&create_args, SystemTime::UNIX_EPOCH)?;

        for participant_id in ["claude", "codex"] {
            let append_args = vec![
                "append-output".to_string(),
                "--quorum-dir".to_string(),
                tmp.path().display().to_string(),
                "--episode-id".to_string(),
                "qr-cli-005".to_string(),
                "--output-id".to_string(),
                format!("out-independent-{participant_id}"),
                "--participant-id".to_string(),
                participant_id.to_string(),
                "--round".to_string(),
                "independent".to_string(),
                "--prompt".to_string(),
                "Answer independently.".to_string(),
                "--response".to_string(),
                format!("{participant_id} says dissent must stay visible."),
            ];
            quorum_from_args(&append_args, SystemTime::UNIX_EPOCH)?;
        }

        let synthesize_args = vec![
            "synthesize".to_string(),
            "--quorum-dir".to_string(),
            tmp.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-005".to_string(),
            "--recommendation".to_string(),
            "Preserve dissent as first-class evidence.".to_string(),
            "--decision-status".to_string(),
            "recommend".to_string(),
            "--consensus-level".to_string(),
            "strong-majority".to_string(),
            "--confidence".to_string(),
            "0.82".to_string(),
            "--supporting-point".to_string(),
            "Both independent outputs support dissent preservation.".to_string(),
            "--dissenting-point".to_string(),
            "Codex still wants review burden tracked.".to_string(),
            "--participant-vote".to_string(),
            "claude:agree:0.88:Preserves auditability".to_string(),
            "--participant-vote".to_string(),
            "codex:disagree:0.41:Review burden remains unresolved".to_string(),
            "--proposed-memory-draft".to_string(),
            "Quorum results must preserve dissent as first-class evidence.".to_string(),
        ];
        let outcome = match quorum_from_args(&synthesize_args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("quorum synthesize failed: {err:?}").into()),
        };

        let path = match outcome {
            QuorumCliOutcome::ResultSaved { episode_id, path } => {
                assert_eq!(episode_id, "qr-cli-005");
                path
            }
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };
        assert!(std::path::Path::new(&path).exists());
        let result = QuorumStore::new(tmp.path()).load_result("qr-cli-005")?;
        assert_eq!(result.participant_votes.len(), 2);
        assert_eq!(result.dissenting_points.len(), 1);
        assert!(result
            .evidence_used
            .iter()
            .any(|item| item.contains("out-independent-claude")));
        Ok(())
    }

    #[test]
    fn quorum_submit_drafts_from_result_uses_consensus_surface(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        let create_args = vec![
            "create".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--id".to_string(),
            "qr-cli-006".to_string(),
            "--requester".to_string(),
            "operator:AlainDor".to_string(),
            "--question".to_string(),
            "Should quorum drafts enter the governed draft store?".to_string(),
            "--target-project".to_string(),
            "buildepicshit/Mimir".to_string(),
            "--participant".to_string(),
            "claude:claude:architect".to_string(),
        ];
        quorum_from_args(&create_args, SystemTime::UNIX_EPOCH)?;
        let synthesize_args = vec![
            "synthesize".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-006".to_string(),
            "--recommendation".to_string(),
            "Submit only proposed memory drafts.".to_string(),
            "--decision-status".to_string(),
            "recommend".to_string(),
            "--consensus-level".to_string(),
            "unanimous".to_string(),
            "--confidence".to_string(),
            "0.9".to_string(),
            "--participant-vote".to_string(),
            "claude:agree:0.9:Clear governance path".to_string(),
            "--proposed-memory-draft".to_string(),
            "Quorum-proposed memory drafts must enter through consensus_quorum drafts.".to_string(),
        ];
        quorum_from_args(&synthesize_args, SystemTime::UNIX_EPOCH)?;

        let submit_args = vec![
            "submit-drafts".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-006".to_string(),
            "--operator".to_string(),
            "AlainDor".to_string(),
        ];
        let outcome = match quorum_from_args(&submit_args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("quorum submit-drafts failed: {err:?}").into()),
        };

        match outcome {
            QuorumCliOutcome::DraftsSubmitted {
                episode_id,
                submitted,
                drafts: submitted_drafts,
            } => {
                assert_eq!(episode_id, "qr-cli-006");
                assert_eq!(submitted, 1);
                assert_eq!(submitted_drafts.len(), 1);
            }
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        }
        let staged = DraftStore::new(drafts.path()).list(mimir_librarian::DraftState::Pending)?;
        assert_eq!(staged.len(), 1);
        assert_eq!(
            staged[0].metadata().source_surface,
            DraftSourceSurface::ConsensusQuorum
        );
        assert_eq!(staged[0].metadata().source_agent.as_deref(), Some("quorum"));
        assert_eq!(
            staged[0].metadata().provenance_uri.as_deref(),
            Some("quorum://episode/qr-cli-006")
        );
        assert!(staged[0]
            .metadata()
            .context_tags
            .contains(&"unanimous".to_string()));
        Ok(())
    }

    fn recorded_fixture_create_args(quorum_dir: &std::path::Path) -> Vec<String> {
        vec![
            "create".to_string(),
            "--quorum-dir".to_string(),
            quorum_dir.display().to_string(),
            "--id".to_string(),
            "qr-cli-007".to_string(),
            "--requester".to_string(),
            "operator:AlainDor".to_string(),
            "--question".to_string(),
            "Should recorded quorum fixtures preserve the draft boundary?".to_string(),
            "--target-project".to_string(),
            "buildepicshit/Mimir".to_string(),
            "--participant".to_string(),
            "claude:claude:architect".to_string(),
            "--participant".to_string(),
            "codex:codex:implementation_engineer".to_string(),
        ]
    }

    fn recorded_fixture_adapter_request_args(quorum_dir: &std::path::Path) -> Vec<String> {
        vec![
            "adapter-request".to_string(),
            "--quorum-dir".to_string(),
            quorum_dir.display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--participant-id".to_string(),
            "claude".to_string(),
            "--round".to_string(),
            "independent".to_string(),
        ]
    }

    fn recorded_fixture_append_args(
        quorum_dir: &std::path::Path,
        participant_id: &str,
        response: &str,
    ) -> Vec<String> {
        vec![
            "append-output".to_string(),
            "--quorum-dir".to_string(),
            quorum_dir.display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--output-id".to_string(),
            format!("out-independent-{participant_id}"),
            "--participant-id".to_string(),
            participant_id.to_string(),
            "--round".to_string(),
            "independent".to_string(),
            "--prompt".to_string(),
            "Answer independently from the fixture.".to_string(),
            "--response".to_string(),
            response.to_string(),
        ]
    }

    fn recorded_fixture_synthesize_args(quorum_dir: &std::path::Path) -> Vec<String> {
        vec![
            "synthesize".to_string(),
            "--quorum-dir".to_string(),
            quorum_dir.display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--recommendation".to_string(),
            "Keep recorded quorum fixtures on the draft path.".to_string(),
            "--decision-status".to_string(),
            "recommend".to_string(),
            "--consensus-level".to_string(),
            "strong-majority".to_string(),
            "--confidence".to_string(),
            "0.87".to_string(),
            "--participant-vote".to_string(),
            "claude:agree:0.9:Audit trail is preserved".to_string(),
            "--participant-vote".to_string(),
            "codex:agree:0.84:Draft boundary remains intact".to_string(),
            "--proposed-memory-draft".to_string(),
            "Recorded quorum fixture outputs must enter Mimir through consensus_quorum drafts."
                .to_string(),
        ]
    }

    fn recorded_fixture_synthesize_without_drafts_args(
        quorum_dir: &std::path::Path,
    ) -> Vec<String> {
        vec![
            "synthesize".to_string(),
            "--quorum-dir".to_string(),
            quorum_dir.display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--recommendation".to_string(),
            "Keep recorded quorum fixtures reviewable.".to_string(),
            "--decision-status".to_string(),
            "recommend".to_string(),
            "--consensus-level".to_string(),
            "strong-majority".to_string(),
            "--confidence".to_string(),
            "0.87".to_string(),
            "--participant-vote".to_string(),
            "claude:agree:0.9:Audit trail is preserved".to_string(),
            "--participant-vote".to_string(),
            "codex:agree:0.84:Draft boundary remains intact".to_string(),
        ]
    }

    fn recorded_fixture_submit_args(
        quorum_dir: &std::path::Path,
        drafts_dir: &std::path::Path,
    ) -> Vec<String> {
        vec![
            "submit-drafts".to_string(),
            "--quorum-dir".to_string(),
            quorum_dir.display().to_string(),
            "--drafts-dir".to_string(),
            drafts_dir.display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--operator".to_string(),
            "AlainDor".to_string(),
        ]
    }

    fn create_test_pilot_plan(
        quorum_dir: &std::path::Path,
        out_dir: &std::path::Path,
        drafts_dir: &std::path::Path,
        through_round: &str,
    ) -> Result<Box<QuorumPilotPlan>, Box<dyn std::error::Error>> {
        create_test_pilot_plan_with_args(
            quorum_dir,
            out_dir,
            drafts_dir,
            through_round,
            "codex",
            Vec::new(),
        )
    }

    fn create_test_pilot_plan_with_synthesizer(
        quorum_dir: &std::path::Path,
        out_dir: &std::path::Path,
        drafts_dir: &std::path::Path,
        synthesizer_binary: &std::path::Path,
    ) -> Result<Box<QuorumPilotPlan>, Box<dyn std::error::Error>> {
        create_test_pilot_plan_with_args(
            quorum_dir,
            out_dir,
            drafts_dir,
            "independent",
            "codex",
            vec![
                "--synthesizer-binary".to_string(),
                synthesizer_binary.display().to_string(),
            ],
        )
    }

    fn create_test_pilot_plan_with_runtime_binaries(
        quorum_dir: &std::path::Path,
        out_dir: &std::path::Path,
        drafts_dir: &std::path::Path,
        claude_binary: &std::path::Path,
        codex_binary: &std::path::Path,
        synthesizer_binary: &std::path::Path,
    ) -> Result<Box<QuorumPilotPlan>, Box<dyn std::error::Error>> {
        create_test_pilot_plan_with_args(
            quorum_dir,
            out_dir,
            drafts_dir,
            "independent",
            "codex",
            vec![
                "--adapter-binary".to_string(),
                format!("claude={}", claude_binary.display()),
                "--adapter-binary".to_string(),
                format!("codex={}", codex_binary.display()),
                "--synthesizer-binary".to_string(),
                synthesizer_binary.display().to_string(),
            ],
        )
    }

    fn create_test_pilot_plan_with_args(
        quorum_dir: &std::path::Path,
        out_dir: &std::path::Path,
        drafts_dir: &std::path::Path,
        through_round: &str,
        synthesizer: &str,
        extra_args: Vec<String>,
    ) -> Result<Box<QuorumPilotPlan>, Box<dyn std::error::Error>> {
        let mut plan_args = vec![
            "pilot-plan".to_string(),
            "--quorum-dir".to_string(),
            quorum_dir.display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--through-round".to_string(),
            through_round.to_string(),
            "--out-dir".to_string(),
            out_dir.display().to_string(),
            "--drafts-dir".to_string(),
            drafts_dir.display().to_string(),
            "--synthesizer".to_string(),
            synthesizer.to_string(),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];
        plan_args.extend(extra_args);
        match quorum_from_args(&plan_args, SystemTime::UNIX_EPOCH)? {
            QuorumCliOutcome::PilotPlan { plan } => Ok(plan),
            other => Err(format!("unexpected quorum outcome: {other:?}").into()),
        }
    }

    fn seed_completed_independent_pilot_artifacts(
        plan: &QuorumPilotPlan,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum_dir = std::path::Path::new(&plan.quorum_dir);
        let out_dir = std::path::Path::new(&plan.out_dir);
        let drafts_dir = std::path::Path::new(&plan.drafts_dir);
        seed_independent_round_artifacts(plan, quorum_dir, out_dir)?;
        let synthesis_status_path = seed_synthesis_artifacts(plan, quorum_dir, out_dir)?;
        quorum_from_args(
            &[
                "accept-synthesis".to_string(),
                "--quorum-dir".to_string(),
                plan.quorum_dir.clone(),
                "--episode-id".to_string(),
                plan.episode_id.clone(),
                "--status-file".to_string(),
                synthesis_status_path,
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        quorum_from_args(
            &recorded_fixture_submit_args(quorum_dir, drafts_dir),
            SystemTime::UNIX_EPOCH,
        )?;
        Ok(())
    }

    fn seed_independent_round_artifacts(
        plan: &QuorumPilotPlan,
        quorum_dir: &std::path::Path,
        out_dir: &std::path::Path,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let round = QuorumRound::Independent;
        let mut statuses = Vec::with_capacity(plan.participants.len());

        for participant in &plan.participants {
            let adapter_plan = build_quorum_adapter_plan(&QuorumAdapterPlanArgs {
                quorum_dir: quorum_dir.to_path_buf(),
                episode_id: plan.episode_id.clone(),
                participant_id: participant.id.clone(),
                round,
                adapter: participant.adapter.clone(),
                out_dir: out_dir.to_path_buf(),
                binary: Some("should-not-run".to_string()),
                output_id: None,
            })?;
            let response = format!("Seeded {} independent response.", participant.id);
            fs::write(&adapter_plan.response_path, response.as_bytes())?;
            if adapter_plan.stdout_capture_path != adapter_plan.response_path {
                fs::write(&adapter_plan.stdout_capture_path, [])?;
            }
            fs::write(&adapter_plan.stderr_capture_path, [])?;
            let response_bytes = u64::try_from(response.len())?;
            let stdout_bytes = if adapter_plan.stdout_capture_path == adapter_plan.response_path {
                response_bytes
            } else {
                0
            };
            statuses.push(QuorumAdapterRunStatus {
                schema_version: QUORUM_SCHEMA_VERSION,
                adapter: adapter_plan.adapter,
                episode_id: adapter_plan.episode_id,
                participant_id: adapter_plan.participant_id,
                round: adapter_plan.round,
                status_path: adapter_plan.status_path,
                request_path: adapter_plan.request_path,
                prompt_path: adapter_plan.prompt_path,
                response_path: adapter_plan.response_path,
                stdout_capture_path: adapter_plan.stdout_capture_path,
                stderr_capture_path: adapter_plan.stderr_capture_path,
                success: true,
                timed_out: false,
                exit_code: Some(0),
                duration_ms: 0,
                response_bytes,
                stdout_bytes,
                stderr_bytes: 0,
                append_output_argv: adapter_plan.append_output_argv,
            });
        }

        let round_status_path = adapter_round_status_path(out_dir, &plan.episode_id, round);
        let round_run = QuorumAdapterRoundRun {
            schema_version: QUORUM_SCHEMA_VERSION,
            episode_id: plan.episode_id.clone(),
            round: quorum_round_name(round).to_string(),
            status_path: round_status_path.display().to_string(),
            success: true,
            completed: statuses.len(),
            failed: 0,
            timed_out: 0,
            statuses,
        };
        write_adapter_round_status(&round_run)?;
        quorum_from_args(
            &[
                "append-status-output".to_string(),
                "--status-file".to_string(),
                round_run.status_path.clone(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        Ok(())
    }

    fn seed_synthesis_artifacts(
        plan: &QuorumPilotPlan,
        quorum_dir: &std::path::Path,
        out_dir: &std::path::Path,
    ) -> Result<String, Box<dyn std::error::Error>> {
        let synthesis_plan = build_quorum_synthesis_plan(&QuorumSynthesisPlanArgs {
            quorum_dir: quorum_dir.to_path_buf(),
            episode_id: plan.episode_id.clone(),
            adapter: plan.synthesizer_adapter.clone(),
            out_dir: out_dir.to_path_buf(),
            binary: Some("should-not-run".to_string()),
        })?;
        let participant_votes = plan
            .participants
            .iter()
            .map(|participant| {
                serde_json::json!({
                    "participant_id": participant.id,
                    "vote": "agree",
                    "confidence": 0.82,
                    "rationale": "Seeded completed pilot artifact",
                })
            })
            .collect::<Vec<_>>();
        let proposed = serde_json::json!({
            "schema_version": QUORUM_SCHEMA_VERSION,
            "episode_id": plan.episode_id,
            "recommendation": "Rerun complete manifests by skipping completed gates.",
            "decision_status": "recommend",
            "consensus_level": "strong_majority",
            "confidence": 0.84,
            "supporting_points": ["The completed artifacts satisfy every pilot gate."],
            "dissenting_points": [],
            "unresolved_questions": [],
            "evidence_used": [],
            "participant_votes": participant_votes,
            "proposed_memory_drafts": [
                "Pilot manifest reruns should skip gates that already have complete recorded artifacts."
            ],
        });
        let proposed_bytes = serde_json::to_vec_pretty(&proposed)?;
        fs::write(&synthesis_plan.result_path, &proposed_bytes)?;
        fs::write(&synthesis_plan.stdout_capture_path, [])?;
        fs::write(&synthesis_plan.stderr_capture_path, [])?;
        let synthesis_status = QuorumSynthesisRunStatus {
            schema_version: QUORUM_SCHEMA_VERSION,
            adapter: synthesis_plan.adapter,
            episode_id: synthesis_plan.episode_id,
            status_path: synthesis_plan.status_path,
            transcript_path: synthesis_plan.transcript_path,
            prompt_path: synthesis_plan.prompt_path,
            result_path: synthesis_plan.result_path,
            stdout_capture_path: synthesis_plan.stdout_capture_path,
            stderr_capture_path: synthesis_plan.stderr_capture_path,
            success: true,
            process_status: Some(QuorumProcessStatus::Succeeded),
            timed_out: false,
            exit_code: Some(0),
            duration_ms: 0,
            result_valid: true,
            validation_error: None,
            result_bytes: u64::try_from(proposed_bytes.len())?,
            stdout_bytes: 0,
            stderr_bytes: 0,
            accept_synthesis_argv: synthesis_plan.accept_synthesis_argv,
        };
        let status_path = synthesis_status.status_path.clone();
        write_synthesis_run_status(&synthesis_status)?;
        Ok(status_path)
    }

    #[test]
    fn quorum_recorded_fixture_smoke_test_runs_to_pending_draft(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;

        let outcome = quorum_from_args(
            &recorded_fixture_adapter_request_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        match outcome {
            QuorumCliOutcome::AdapterRequest { request } => {
                assert_eq!(request.episode_id, "qr-cli-007");
                assert_eq!(request.participant.id, "claude");
                assert!(request.visible_prior_outputs.is_empty());
            }
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        }

        for (participant_id, response) in [
            ("claude", "Preserve the draft boundary and audit trail."),
            (
                "codex",
                "Submit fixture-derived memories through consensus_quorum drafts.",
            ),
        ] {
            quorum_from_args(
                &recorded_fixture_append_args(quorum.path(), participant_id, response),
                SystemTime::UNIX_EPOCH,
            )?;
        }

        quorum_from_args(
            &recorded_fixture_synthesize_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let outcome = quorum_from_args(
            &recorded_fixture_submit_args(quorum.path(), drafts.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        match outcome {
            QuorumCliOutcome::DraftsSubmitted {
                episode_id,
                submitted,
                drafts: submitted_drafts,
            } => {
                assert_eq!(episode_id, "qr-cli-007");
                assert_eq!(submitted, 1);
                assert_eq!(submitted_drafts.len(), 1);
            }
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        }

        let result = QuorumStore::new(quorum.path()).load_result("qr-cli-007")?;
        assert_eq!(result.participant_votes.len(), 2);
        assert!(result
            .evidence_used
            .iter()
            .any(|item| item.contains("out-independent-codex")));
        let staged = DraftStore::new(drafts.path()).list(mimir_librarian::DraftState::Pending)?;
        assert_eq!(staged.len(), 1);
        assert_eq!(
            staged[0].metadata().source_project.as_deref(),
            Some("buildepicshit/Mimir")
        );
        assert!(staged[0]
            .metadata()
            .context_tags
            .contains(&"strong_majority".to_string()));
        Ok(())
    }

    #[test]
    fn quorum_pilot_plan_writes_replayable_manifest() -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;

        let plan_args = vec![
            "pilot-plan".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--through-round".to_string(),
            "critique".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--synthesizer".to_string(),
            "codex".to_string(),
            "--adapter-binary".to_string(),
            "claude=/bin/claude-fixture".to_string(),
            "--adapter-binary".to_string(),
            "codex=/bin/codex-fixture".to_string(),
            "--timeout-secs".to_string(),
            "7".to_string(),
            "--require-proposed-drafts".to_string(),
            "1".to_string(),
            "--project".to_string(),
            "buildepicshit/Mimir".to_string(),
            "--operator".to_string(),
            "operator:AlainDor".to_string(),
            "--tag".to_string(),
            "pilot".to_string(),
        ];
        let outcome = quorum_from_args(&plan_args, SystemTime::UNIX_EPOCH)?;
        let plan = match outcome {
            QuorumCliOutcome::PilotPlan { plan } => plan,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert_eq!(plan.episode_id, "qr-cli-007");
        assert_eq!(plan.through_round, "critique");
        assert_eq!(plan.synthesizer_adapter, "codex");
        assert_eq!(plan.timeout_secs, 7);
        assert_eq!(plan.required_proposed_drafts, 1);
        assert_eq!(plan.participants.len(), 2);
        assert_eq!(plan.steps.len(), 4);
        assert_eq!(plan.steps[0].name, "run_rounds");
        assert_eq!(plan.steps[0].argv[2], "adapter-run-rounds");
        assert!(plan.steps[0]
            .argv
            .windows(2)
            .any(|pair| pair == ["--adapter-binary", "claude=/bin/claude-fixture"]));
        assert_eq!(plan.steps[1].name, "run_synthesis");
        assert_eq!(plan.steps[1].argv[2], "synthesize-run");
        assert!(plan.steps[1]
            .argv
            .windows(2)
            .any(|pair| pair == ["--binary", "/bin/codex-fixture"]));
        assert_eq!(plan.steps[2].name, "accept_synthesis");
        assert_eq!(plan.steps[2].argv[2], "accept-synthesis");
        assert!(plan.steps[2]
            .argv
            .windows(2)
            .any(|pair| pair[0] == "--status-file" && pair[1].ends_with("-synthesis-status.json")));
        assert_eq!(plan.steps[3].name, "submit_drafts");
        assert_eq!(plan.steps[3].argv[2], "submit-drafts");
        assert_eq!(plan.artifacts.round_statuses.len(), 2);
        assert!(std::path::Path::new(&plan.manifest_path).exists());

        let manifest_bytes = std::fs::read(&plan.manifest_path)?;
        let manifest: serde_json::Value = serde_json::from_slice(&manifest_bytes)?;
        assert_eq!(manifest["episode_id"], "qr-cli-007");
        assert_eq!(manifest["required_proposed_drafts"], 1);
        assert_eq!(manifest["steps"].as_array().map(Vec::len), Some(4));
        Ok(())
    }

    #[test]
    fn quorum_pilot_status_reports_pending_manifest() -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let plan = create_test_pilot_plan(quorum.path(), out.path(), drafts.path(), "independent")?;

        let outcome = quorum_from_args(
            &[
                "pilot-status".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        let status = match outcome {
            QuorumCliOutcome::PilotStatus { status } => status,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert_eq!(status.episode_id, "qr-cli-007");
        assert_eq!(status.overall_status, QuorumPilotGateStatus::Pending);
        assert!(!status.complete);
        assert_eq!(status.gates.len(), 4);
        assert!(
            status
                .gates
                .iter()
                .any(|gate| gate.name == "run_rounds"
                    && gate.status == QuorumPilotGateStatus::Pending)
        );
        Ok(())
    }

    #[test]
    fn quorum_pilot_status_reports_complete_after_manifest_gates(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        let (_claude_dir, claude_shim) = make_quorum_shim(
            "claude-pilot-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Pilot Claude response.'\n",
        )?;
        let (_codex_dir, codex_shim) = make_quorum_shim(
            "codex-pilot-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Pilot Codex response.' > \"$3\"\n",
        )?;
        let (_synth_dir, synth_shim) = make_quorum_shim(
            "codex-pilot-synth-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf '{\"schema_version\":1,\"episode_id\":\"qr-cli-007\",\"recommendation\":\"Run the pilot through explicit gates.\",\"decision_status\":\"recommend\",\"consensus_level\":\"strong_majority\",\"confidence\":0.84,\"supporting_points\":[\"Both participants completed the independent round.\"],\"dissenting_points\":[],\"unresolved_questions\":[],\"evidence_used\":[],\"participant_votes\":[{\"participant_id\":\"claude\",\"vote\":\"agree\",\"confidence\":0.86,\"rationale\":\"The pilot preserves audit gates\"},{\"participant_id\":\"codex\",\"vote\":\"agree\",\"confidence\":0.82,\"rationale\":\"The manifest is replayable\"}],\"proposed_memory_drafts\":[\"Replayable quorum pilots must complete adapter, synthesis, acceptance, and draft gates before being treated as submitted evidence.\"]}' > \"$3\"\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let plan = create_test_pilot_plan_with_synthesizer(
            quorum.path(),
            out.path(),
            drafts.path(),
            &synth_shim,
        )?;

        quorum_from_args(
            &[
                "adapter-run-rounds".to_string(),
                "--quorum-dir".to_string(),
                quorum.path().display().to_string(),
                "--episode-id".to_string(),
                "qr-cli-007".to_string(),
                "--through-round".to_string(),
                "independent".to_string(),
                "--out-dir".to_string(),
                out.path().display().to_string(),
                "--adapter-binary".to_string(),
                format!("claude={}", claude_shim.display()),
                "--adapter-binary".to_string(),
                format!("codex={}", codex_shim.display()),
                "--timeout-secs".to_string(),
                "5".to_string(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        quorum_from_args(
            &[
                "synthesize-run".to_string(),
                "--quorum-dir".to_string(),
                quorum.path().display().to_string(),
                "--episode-id".to_string(),
                "qr-cli-007".to_string(),
                "--adapter".to_string(),
                "codex".to_string(),
                "--out-dir".to_string(),
                out.path().display().to_string(),
                "--binary".to_string(),
                synth_shim.display().to_string(),
                "--timeout-secs".to_string(),
                "5".to_string(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        quorum_from_args(
            &[
                "accept-synthesis".to_string(),
                "--quorum-dir".to_string(),
                quorum.path().display().to_string(),
                "--episode-id".to_string(),
                "qr-cli-007".to_string(),
                "--status-file".to_string(),
                plan.artifacts.synthesis_status_path.clone(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        quorum_from_args(
            &recorded_fixture_submit_args(quorum.path(), drafts.path()),
            SystemTime::UNIX_EPOCH,
        )?;

        let outcome = quorum_from_args(
            &[
                "pilot-status".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        let status = match outcome {
            QuorumCliOutcome::PilotStatus { status } => status,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert_eq!(status.overall_status, QuorumPilotGateStatus::Complete);
        assert!(status.complete);
        assert!(status
            .gates
            .iter()
            .all(|gate| gate.status == QuorumPilotGateStatus::Complete));
        Ok(())
    }

    #[test]
    fn quorum_pilot_run_executes_manifest_gates() -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        let (_claude_dir, claude_shim) = make_quorum_shim(
            "claude-pilot-run-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Pilot-run Claude response.'\n",
        )?;
        let (_codex_dir, codex_shim) = make_quorum_shim(
            "codex-pilot-run-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Pilot-run Codex response.' > \"$3\"\n",
        )?;
        let (_synth_dir, synth_shim) = make_quorum_shim(
            "codex-pilot-run-synth-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf '{\"schema_version\":1,\"episode_id\":\"qr-cli-007\",\"recommendation\":\"Execute the pilot manifest through explicit gates.\",\"decision_status\":\"recommend\",\"consensus_level\":\"strong_majority\",\"confidence\":0.84,\"supporting_points\":[\"The manifest executor used the existing gates.\"],\"dissenting_points\":[],\"unresolved_questions\":[],\"evidence_used\":[],\"participant_votes\":[{\"participant_id\":\"claude\",\"vote\":\"agree\",\"confidence\":0.86,\"rationale\":\"Round output was recorded\"},{\"participant_id\":\"codex\",\"vote\":\"agree\",\"confidence\":0.82,\"rationale\":\"Synthesis was accepted explicitly\"}],\"proposed_memory_drafts\":[\"Pilot manifests may be executed only by replaying the existing quorum gates in order.\"]}' > \"$3\"\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let plan = create_test_pilot_plan_with_runtime_binaries(
            quorum.path(),
            out.path(),
            drafts.path(),
            &claude_shim,
            &codex_shim,
            &synth_shim,
        )?;

        let outcome = quorum_from_args(
            &[
                "pilot-run".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        let run = match outcome {
            QuorumCliOutcome::PilotRun { run } => run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert!(run.success);
        assert!(run.skipped_steps.is_empty());
        assert_eq!(run.failed_step, None);
        assert_eq!(run.error, None);
        assert_eq!(
            run.executed_steps,
            vec![
                "run_rounds".to_string(),
                "run_synthesis".to_string(),
                "accept_synthesis".to_string(),
                "submit_drafts".to_string(),
            ]
        );
        assert_eq!(
            run.final_status.overall_status,
            QuorumPilotGateStatus::Complete
        );
        assert!(QuorumStore::new(quorum.path())
            .load_result("qr-cli-007")
            .is_ok());
        let staged = DraftStore::new(drafts.path()).list(mimir_librarian::DraftState::Pending)?;
        assert_eq!(staged.len(), 1);
        Ok(())
    }

    #[test]
    fn quorum_pilot_run_reports_failed_step_with_final_status(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        let (_claude_dir, claude_shim) = make_quorum_shim(
            "claude-pilot-run-fail-report-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Pilot failure report Claude response.'\n",
        )?;
        let (_codex_dir, codex_shim) = make_quorum_shim(
            "codex-pilot-run-fail-report-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Pilot failure report Codex response.' > \"$3\"\n",
        )?;
        let (_synth_dir, synth_shim) = make_quorum_shim(
            "codex-pilot-run-invalid-synth-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf '{\"schema_version\":1,\"episode_id\":\"qr-cli-007\",\"recommendation\":\"Incomplete synthesis.\",\"decision_status\":\"recommend\",\"consensus_level\":\"strong_majority\",\"confidence\":0.8,\"participant_votes\":[{\"participant_id\":\"claude\",\"vote\":\"agree\",\"confidence\":0.8,\"rationale\":\"Only one vote\"}]}' > \"$3\"\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let plan = create_test_pilot_plan_with_runtime_binaries(
            quorum.path(),
            out.path(),
            drafts.path(),
            &claude_shim,
            &codex_shim,
            &synth_shim,
        )?;

        let outcome = quorum_from_args(
            &[
                "pilot-run".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        let run = match outcome {
            QuorumCliOutcome::PilotRun { run } => run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert!(!run.success);
        assert_eq!(run.executed_steps, vec!["run_rounds".to_string()]);
        assert!(run.skipped_steps.is_empty());
        assert_eq!(run.failed_step.as_deref(), Some("run_synthesis"));
        assert!(run
            .error
            .as_deref()
            .is_some_and(|message| message.contains("run_synthesis")));
        assert_eq!(
            run.final_status.overall_status,
            QuorumPilotGateStatus::Failed
        );
        assert!(run.final_status.gates.iter().any(|gate| {
            gate.name == "run_synthesis" && gate.status == QuorumPilotGateStatus::Failed
        }));
        assert!(QuorumStore::new(quorum.path())
            .load_result("qr-cli-007")
            .is_err());
        Ok(())
    }

    #[test]
    fn quorum_pilot_run_skips_completed_gates_on_rerun() -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let plan = create_test_pilot_plan_with_args(
            quorum.path(),
            out.path(),
            drafts.path(),
            "independent",
            "codex",
            vec![
                "--adapter-binary".to_string(),
                "claude=should-not-run-claude".to_string(),
                "--adapter-binary".to_string(),
                "codex=should-not-run-codex".to_string(),
                "--synthesizer-binary".to_string(),
                "should-not-run-synthesizer".to_string(),
            ],
        )?;
        seed_completed_independent_pilot_artifacts(&plan)?;

        let second = quorum_from_args(
            &[
                "pilot-run".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        let run = match second {
            QuorumCliOutcome::PilotRun { run } => run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert!(run.success);
        assert!(run.executed_steps.is_empty());
        assert_eq!(
            run.skipped_steps,
            vec![
                "run_rounds".to_string(),
                "run_synthesis".to_string(),
                "accept_synthesis".to_string(),
                "submit_drafts".to_string(),
            ]
        );
        assert_eq!(run.failed_step, None);
        assert_eq!(run.error, None);
        let staged = DraftStore::new(drafts.path()).list(mimir_librarian::DraftState::Pending)?;
        assert_eq!(staged.len(), 1);
        Ok(())
    }

    #[test]
    fn quorum_pilot_status_fails_when_required_proposed_drafts_are_missing(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let plan = create_test_pilot_plan_with_args(
            quorum.path(),
            out.path(),
            drafts.path(),
            "independent",
            "codex",
            vec!["--require-proposed-drafts".to_string(), "1".to_string()],
        )?;
        quorum_from_args(
            &recorded_fixture_synthesize_without_drafts_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;

        let outcome = quorum_from_args(
            &[
                "pilot-status".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        let status = match outcome {
            QuorumCliOutcome::PilotStatus { status } => status,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert_eq!(status.overall_status, QuorumPilotGateStatus::Failed);
        assert!(status.gates.iter().any(|gate| {
            gate.name == "accept_synthesis"
                && gate.status == QuorumPilotGateStatus::Failed
                && gate.detail.contains("requires at least 1")
        }));

        let err = match quorum_from_args(
            &[
                "pilot-review".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
                "--reviewer".to_string(),
                "operator:AlainDor".to_string(),
                "--decision".to_string(),
                "pass".to_string(),
                "--summary".to_string(),
                "Insufficient proposed drafts cannot be certified.".to_string(),
            ],
            SystemTime::UNIX_EPOCH,
        ) {
            Ok(outcome) => {
                return Err(format!("required-draft pass must reject, got {outcome:?}").into())
            }
            Err(err) => err,
        };
        assert!(
            err.to_string().contains("complete pilot-status"),
            "unexpected error: {err}"
        );
        Ok(())
    }

    #[test]
    fn quorum_pilot_run_fails_when_required_proposed_drafts_are_missing(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        let (_claude_dir, claude_shim) = make_quorum_shim(
            "claude-pilot-required-drafts-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Required-drafts Claude response.'\n",
        )?;
        let (_codex_dir, codex_shim) = make_quorum_shim(
            "codex-pilot-required-drafts-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Required-drafts Codex response.' > \"$3\"\n",
        )?;
        let (_synth_dir, synth_shim) = make_quorum_shim(
            "codex-pilot-required-drafts-synth-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf '{\"schema_version\":1,\"episode_id\":\"qr-cli-007\",\"recommendation\":\"Synthesize without drafts despite the pilot requirement.\",\"decision_status\":\"recommend\",\"consensus_level\":\"strong_majority\",\"confidence\":0.84,\"supporting_points\":[\"The model produced a valid result shape.\"],\"dissenting_points\":[],\"unresolved_questions\":[],\"evidence_used\":[],\"participant_votes\":[{\"participant_id\":\"claude\",\"vote\":\"agree\",\"confidence\":0.86,\"rationale\":\"Round output was recorded\"},{\"participant_id\":\"codex\",\"vote\":\"agree\",\"confidence\":0.82,\"rationale\":\"Synthesis accepted the evidence\"}],\"proposed_memory_drafts\":[]}' > \"$3\"\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let plan = create_test_pilot_plan_with_args(
            quorum.path(),
            out.path(),
            drafts.path(),
            "independent",
            "codex",
            vec![
                "--adapter-binary".to_string(),
                format!("claude={}", claude_shim.display()),
                "--adapter-binary".to_string(),
                format!("codex={}", codex_shim.display()),
                "--synthesizer-binary".to_string(),
                synth_shim.display().to_string(),
                "--require-proposed-drafts".to_string(),
                "1".to_string(),
            ],
        )?;

        let outcome = quorum_from_args(
            &[
                "pilot-run".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        let run = match outcome {
            QuorumCliOutcome::PilotRun { run } => run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert!(!run.success);
        assert_eq!(run.failed_step.as_deref(), Some("accept_synthesis"));
        assert!(run
            .error
            .as_deref()
            .is_some_and(|message| message.contains("accept_synthesis")));
        assert_eq!(
            run.executed_steps,
            vec![
                "run_rounds".to_string(),
                "run_synthesis".to_string(),
                "accept_synthesis".to_string(),
            ]
        );
        let staged = DraftStore::new(drafts.path()).list(mimir_librarian::DraftState::Pending)?;
        assert!(staged.is_empty());
        Ok(())
    }

    #[test]
    fn quorum_pilot_review_rejects_pass_for_incomplete_manifest(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let plan = create_test_pilot_plan(quorum.path(), out.path(), drafts.path(), "independent")?;

        let err = match quorum_from_args(
            &[
                "pilot-review".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
                "--reviewer".to_string(),
                "operator:AlainDor".to_string(),
                "--decision".to_string(),
                "pass".to_string(),
                "--summary".to_string(),
                "Incomplete pilots cannot be certified.".to_string(),
            ],
            SystemTime::UNIX_EPOCH,
        ) {
            Ok(outcome) => {
                return Err(format!("incomplete pass must reject, got {outcome:?}").into())
            }
            Err(err) => err,
        };

        assert!(
            err.to_string().contains("complete pilot-status"),
            "unexpected error: {err}"
        );
        Ok(())
    }

    #[test]
    fn quorum_pilot_review_writes_certification_artifact() -> Result<(), Box<dyn std::error::Error>>
    {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        let (_claude_dir, claude_shim) = make_quorum_shim(
            "claude-pilot-review-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Pilot review Claude response.'\n",
        )?;
        let (_codex_dir, codex_shim) = make_quorum_shim(
            "codex-pilot-review-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Pilot review Codex response.' > \"$3\"\n",
        )?;
        let (_synth_dir, synth_shim) = make_quorum_shim(
            "codex-pilot-review-synth-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf '{\"schema_version\":1,\"episode_id\":\"qr-cli-007\",\"recommendation\":\"Certify the manifest after reviewing recorded gates.\",\"decision_status\":\"recommend\",\"consensus_level\":\"strong_majority\",\"confidence\":0.84,\"supporting_points\":[\"All gates completed before review.\"],\"dissenting_points\":[],\"unresolved_questions\":[],\"evidence_used\":[],\"participant_votes\":[{\"participant_id\":\"claude\",\"vote\":\"agree\",\"confidence\":0.86,\"rationale\":\"Review sees completed artifacts\"},{\"participant_id\":\"codex\",\"vote\":\"agree\",\"confidence\":0.82,\"rationale\":\"Certification is separate from participation\"}],\"proposed_memory_drafts\":[\"Live pilot certification should be recorded as a non-participant review artifact before service adapter design.\"]}' > \"$3\"\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let plan = create_test_pilot_plan_with_runtime_binaries(
            quorum.path(),
            out.path(),
            drafts.path(),
            &claude_shim,
            &codex_shim,
            &synth_shim,
        )?;
        match quorum_from_args(
            &[
                "pilot-run".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
            ],
            SystemTime::UNIX_EPOCH,
        )? {
            QuorumCliOutcome::PilotRun { run } => assert!(run.success),
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        }

        let outcome = quorum_from_args(
            &[
                "pilot-review".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
                "--reviewer".to_string(),
                "operator:AlainDor".to_string(),
                "--decision".to_string(),
                "pass".to_string(),
                "--summary".to_string(),
                "Recorded gates satisfy the local pilot certification criteria.".to_string(),
                "--finding".to_string(),
                "info:All manifest gates completed before certification.".to_string(),
                "--next-action".to_string(),
                "Use the certified artifact to shape service adapter contracts.".to_string(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        let review = match outcome {
            QuorumCliOutcome::PilotReview { review } => review,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert_eq!(review.episode_id, "qr-cli-007");
        assert_eq!(review.decision, QuorumPilotReviewDecision::Pass);
        assert!(review.status_at_review.complete);
        assert_eq!(review.findings.len(), 1);
        assert_eq!(review.next_actions.len(), 1);
        assert!(std::path::Path::new(&review.review_path).exists());
        let bytes = std::fs::read(&review.review_path)?;
        let artifact: serde_json::Value = serde_json::from_slice(&bytes)?;
        assert_eq!(artifact["reviewer"], "operator:AlainDor");
        assert_eq!(artifact["decision"], "pass");
        assert_eq!(artifact["status_at_review"]["overall_status"], "complete");
        Ok(())
    }

    #[test]
    fn quorum_pilot_summary_reports_certified_pilot_state() -> Result<(), Box<dyn std::error::Error>>
    {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let drafts = tempfile::tempdir()?;
        let (_claude_dir, claude_shim) = make_quorum_shim(
            "claude-pilot-summary-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Pilot summary Claude response.'\n",
        )?;
        let (_codex_dir, codex_shim) = make_quorum_shim(
            "codex-pilot-summary-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Pilot summary Codex response.' > \"$3\"\n",
        )?;
        let (_synth_dir, synth_shim) = make_quorum_shim(
            "codex-pilot-summary-synth-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf '{\"schema_version\":1,\"episode_id\":\"qr-cli-007\",\"recommendation\":\"Summarize certified pilots for the operator.\",\"decision_status\":\"recommend\",\"consensus_level\":\"strong_majority\",\"confidence\":0.84,\"supporting_points\":[\"All gates completed before review.\"],\"dissenting_points\":[],\"unresolved_questions\":[],\"evidence_used\":[],\"participant_votes\":[{\"participant_id\":\"claude\",\"vote\":\"agree\",\"confidence\":0.86,\"rationale\":\"Summary sees completed artifacts\"},{\"participant_id\":\"codex\",\"vote\":\"agree\",\"confidence\":0.82,\"rationale\":\"Summary preserves draft count\"}],\"proposed_memory_drafts\":[\"Certified pilot summaries should report accepted results, staged drafts, and review decisions.\"]}' > \"$3\"\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let plan = create_test_pilot_plan_with_runtime_binaries(
            quorum.path(),
            out.path(),
            drafts.path(),
            &claude_shim,
            &codex_shim,
            &synth_shim,
        )?;
        match quorum_from_args(
            &[
                "pilot-run".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
            ],
            SystemTime::UNIX_EPOCH,
        )? {
            QuorumCliOutcome::PilotRun { run } => assert!(run.success),
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        }
        quorum_from_args(
            &[
                "pilot-review".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
                "--reviewer".to_string(),
                "operator:AlainDor".to_string(),
                "--decision".to_string(),
                "pass".to_string(),
                "--summary".to_string(),
                "Recorded gates satisfy the local pilot certification criteria.".to_string(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;

        let outcome = quorum_from_args(
            &[
                "pilot-summary".to_string(),
                "--manifest-file".to_string(),
                plan.manifest_path.clone(),
            ],
            SystemTime::UNIX_EPOCH,
        )?;
        let summary = match outcome {
            QuorumCliOutcome::PilotSummary { summary } => summary,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert!(summary.complete);
        assert_eq!(summary.overall_status, QuorumPilotGateStatus::Complete);
        assert_eq!(summary.proposed_memory_drafts, 1);
        assert_eq!(summary.submitted_drafts, 1);
        assert_eq!(summary.result_status, "present");
        assert!(summary
            .result_path
            .as_deref()
            .is_some_and(|path| std::path::Path::new(path).exists()));
        assert_eq!(summary.review_status, "present");
        assert_eq!(
            summary.review_decision,
            Some(QuorumPilotReviewDecision::Pass)
        );
        assert_eq!(summary.next_action, "none");
        assert_eq!(summary.gates.len(), 4);
        Ok(())
    }

    #[test]
    fn quorum_adapter_plan_materializes_native_claude_contract(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let plan_args = vec![
            "adapter-plan".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--participant-id".to_string(),
            "claude".to_string(),
            "--round".to_string(),
            "independent".to_string(),
            "--adapter".to_string(),
            "claude".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
        ];

        let outcome = quorum_from_args(&plan_args, SystemTime::UNIX_EPOCH)?;
        let plan = match outcome {
            QuorumCliOutcome::AdapterPlan { plan } => plan,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert_eq!(plan.adapter, "claude");
        assert_eq!(plan.argv, vec!["claude".to_string(), "-p".to_string()]);
        assert_eq!(plan.stdin_path, plan.prompt_path);
        assert_eq!(
            plan.stdout_path.as_deref(),
            Some(plan.response_path.as_str())
        );
        assert!(std::path::Path::new(&plan.request_path).exists());
        let prompt = std::fs::read_to_string(&plan.prompt_path)?;
        assert!(prompt.contains("Should recorded quorum fixtures preserve the draft boundary?"));
        assert!(prompt.contains("persona: architect"));

        std::fs::write(&plan.response_path, "Claude fixture response.")?;
        let append_args = plan
            .append_output_argv
            .iter()
            .skip(2)
            .cloned()
            .collect::<Vec<_>>();
        quorum_from_args(&append_args, SystemTime::UNIX_EPOCH)?;
        let outputs = QuorumStore::new(quorum.path())
            .load_round_outputs("qr-cli-007", QuorumRound::Independent)?;
        assert_eq!(outputs.len(), 1);
        assert_eq!(outputs[0].response, "Claude fixture response.");
        Ok(())
    }

    #[test]
    fn quorum_adapter_plan_materializes_native_codex_contract(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let plan_args = vec![
            "adapter-plan".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--participant-id".to_string(),
            "codex".to_string(),
            "--round".to_string(),
            "independent".to_string(),
            "--adapter".to_string(),
            "codex".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
        ];

        let outcome = quorum_from_args(&plan_args, SystemTime::UNIX_EPOCH)?;
        let plan = match outcome {
            QuorumCliOutcome::AdapterPlan { plan } => plan,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert_eq!(plan.adapter, "codex");
        assert_eq!(
            plan.argv,
            vec![
                "codex".to_string(),
                "exec".to_string(),
                "--output-last-message".to_string(),
                plan.response_path.clone(),
                "-".to_string()
            ]
        );
        assert_eq!(plan.stdin_path, plan.prompt_path);
        assert_eq!(plan.stdout_path, None);
        let prompt = std::fs::read_to_string(&plan.prompt_path)?;
        assert!(prompt.contains("persona: implementation_engineer"));

        std::fs::write(&plan.response_path, "Codex fixture response.")?;
        let append_args = plan
            .append_output_argv
            .iter()
            .skip(2)
            .cloned()
            .collect::<Vec<_>>();
        quorum_from_args(&append_args, SystemTime::UNIX_EPOCH)?;
        let outputs = QuorumStore::new(quorum.path())
            .load_round_outputs("qr-cli-007", QuorumRound::Independent)?;
        assert_eq!(outputs.len(), 1);
        assert_eq!(outputs[0].response, "Codex fixture response.");
        Ok(())
    }

    #[test]
    fn quorum_adapter_run_executes_claude_plan_without_appending_output(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let (_shim_dir, shim) = make_quorum_shim(
            "claude-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Claude run response.'\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let run_args = vec![
            "adapter-run".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--participant-id".to_string(),
            "claude".to_string(),
            "--round".to_string(),
            "independent".to_string(),
            "--adapter".to_string(),
            "claude".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--binary".to_string(),
            shim.display().to_string(),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];

        let outcome = quorum_from_args(&run_args, SystemTime::UNIX_EPOCH)?;
        let run = match outcome {
            QuorumCliOutcome::AdapterRun { run } => run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert!(run.status.success);
        assert!(!run.status.timed_out);
        assert_eq!(run.status.exit_code, Some(0));
        assert!(std::path::Path::new(&run.status.status_path).exists());
        assert_eq!(
            std::fs::read_to_string(&run.status.response_path)?,
            "Claude run response."
        );
        assert!(
            QuorumStore::new(quorum.path())
                .load_round_outputs("qr-cli-007", QuorumRound::Independent)?
                .is_empty(),
            "adapter-run must not append participant outputs directly",
        );

        let append_args = run
            .status
            .append_output_argv
            .iter()
            .skip(2)
            .cloned()
            .collect::<Vec<_>>();
        quorum_from_args(&append_args, SystemTime::UNIX_EPOCH)?;
        let outputs = QuorumStore::new(quorum.path())
            .load_round_outputs("qr-cli-007", QuorumRound::Independent)?;
        assert_eq!(outputs.len(), 1);
        assert_eq!(outputs[0].response, "Claude run response.");
        Ok(())
    }

    #[test]
    fn quorum_adapter_run_executes_codex_plan_without_appending_output(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let (_shim_dir, shim) = make_quorum_shim(
            "codex-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Codex run response.' > \"$3\"\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let run_args = vec![
            "adapter-run".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--participant-id".to_string(),
            "codex".to_string(),
            "--round".to_string(),
            "independent".to_string(),
            "--adapter".to_string(),
            "codex".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--binary".to_string(),
            shim.display().to_string(),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];

        let outcome = quorum_from_args(&run_args, SystemTime::UNIX_EPOCH)?;
        let run = match outcome {
            QuorumCliOutcome::AdapterRun { run } => run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert!(run.status.success);
        assert_eq!(
            std::fs::read_to_string(&run.status.response_path)?,
            "Codex run response."
        );
        assert!(
            QuorumStore::new(quorum.path())
                .load_round_outputs("qr-cli-007", QuorumRound::Independent)?
                .is_empty(),
            "adapter-run must not append participant outputs directly",
        );

        let append_args = run
            .status
            .append_output_argv
            .iter()
            .skip(2)
            .cloned()
            .collect::<Vec<_>>();
        quorum_from_args(&append_args, SystemTime::UNIX_EPOCH)?;
        let outputs = QuorumStore::new(quorum.path())
            .load_round_outputs("qr-cli-007", QuorumRound::Independent)?;
        assert_eq!(outputs.len(), 1);
        assert_eq!(outputs[0].response, "Codex run response.");
        Ok(())
    }

    #[test]
    fn quorum_append_status_output_records_successful_adapter_run_status(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let (_shim_dir, shim) = make_quorum_shim(
            "claude-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Claude status response.'\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let run_args = vec![
            "adapter-run".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--participant-id".to_string(),
            "claude".to_string(),
            "--round".to_string(),
            "independent".to_string(),
            "--adapter".to_string(),
            "claude".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--binary".to_string(),
            shim.display().to_string(),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];
        let run = match quorum_from_args(&run_args, SystemTime::UNIX_EPOCH)? {
            QuorumCliOutcome::AdapterRun { run } => run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        let append_args = vec![
            "append-status-output".to_string(),
            "--status-file".to_string(),
            run.status.status_path.clone(),
        ];
        let outcome = quorum_from_args(&append_args, SystemTime::UNIX_EPOCH)?;
        match outcome {
            QuorumCliOutcome::StatusOutputsAppended {
                status_path,
                appended,
                outputs,
            } => {
                assert_eq!(status_path, run.status.status_path);
                assert_eq!(appended, 1);
                assert_eq!(outputs.len(), 1);
                assert_eq!(outputs[0].participant_id, "claude");
            }
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        }
        let outputs = QuorumStore::new(quorum.path())
            .load_round_outputs("qr-cli-007", QuorumRound::Independent)?;
        assert_eq!(outputs.len(), 1);
        assert_eq!(outputs[0].response, "Claude status response.");
        Ok(())
    }

    #[test]
    fn quorum_append_status_output_rejects_failed_adapter_status(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let (_shim_dir, shim) = make_quorum_shim(
            "claude-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Failed response.'\nexit 2\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let run_args = vec![
            "adapter-run".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--participant-id".to_string(),
            "claude".to_string(),
            "--round".to_string(),
            "independent".to_string(),
            "--adapter".to_string(),
            "claude".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--binary".to_string(),
            shim.display().to_string(),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];
        let run = match quorum_from_args(&run_args, SystemTime::UNIX_EPOCH)? {
            QuorumCliOutcome::AdapterRun { run } => run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };
        assert!(!run.status.success);

        let append_args = vec![
            "append-status-output".to_string(),
            "--status-file".to_string(),
            run.status.status_path,
        ];
        let err = match quorum_from_args(&append_args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => {
                return Err(format!("failed status must not append, got {outcome:?}").into());
            }
            Err(err) => err,
        };
        assert!(
            err.to_string().contains("not successful"),
            "unexpected error: {err}"
        );
        assert!(
            QuorumStore::new(quorum.path())
                .load_round_outputs("qr-cli-007", QuorumRound::Independent)?
                .is_empty(),
            "failed status must not append participant output",
        );
        Ok(())
    }

    #[test]
    fn quorum_adapter_run_round_executes_independent_participants_without_appending(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let (_claude_dir, claude_shim) = make_quorum_shim(
            "claude-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Round Claude response.'\n",
        )?;
        let (_codex_dir, codex_shim) = make_quorum_shim(
            "codex-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Round Codex response.' > \"$3\"\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let run_args = vec![
            "adapter-run-round".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--round".to_string(),
            "independent".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--adapter-binary".to_string(),
            format!("claude={}", claude_shim.display()),
            "--adapter-binary".to_string(),
            format!("codex={}", codex_shim.display()),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];

        let outcome = quorum_from_args(&run_args, SystemTime::UNIX_EPOCH)?;
        let round_run = match outcome {
            QuorumCliOutcome::AdapterRoundRun { round_run } => round_run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert!(round_run.success);
        assert_eq!(round_run.completed, 2);
        assert_eq!(round_run.failed, 0);
        assert_eq!(round_run.statuses.len(), 2);
        assert!(std::path::Path::new(&round_run.status_path).exists());
        assert!(
            QuorumStore::new(quorum.path())
                .load_round_outputs("qr-cli-007", QuorumRound::Independent)?
                .is_empty(),
            "adapter-run-round must not append participant outputs directly",
        );

        for status in &round_run.statuses {
            let append_args = status
                .append_output_argv
                .iter()
                .skip(2)
                .cloned()
                .collect::<Vec<_>>();
            quorum_from_args(&append_args, SystemTime::UNIX_EPOCH)?;
        }
        let outputs = QuorumStore::new(quorum.path())
            .load_round_outputs("qr-cli-007", QuorumRound::Independent)?;
        let responses = outputs
            .iter()
            .map(|output| output.response.as_str())
            .collect::<Vec<_>>();
        assert_eq!(
            responses,
            vec!["Round Claude response.", "Round Codex response."]
        );
        Ok(())
    }

    #[test]
    fn quorum_append_status_output_records_successful_round_status(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let (_claude_dir, claude_shim) = make_quorum_shim(
            "claude-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Round status Claude response.'\n",
        )?;
        let (_codex_dir, codex_shim) = make_quorum_shim(
            "codex-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Round status Codex response.' > \"$3\"\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let run_args = vec![
            "adapter-run-round".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--round".to_string(),
            "independent".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--adapter-binary".to_string(),
            format!("claude={}", claude_shim.display()),
            "--adapter-binary".to_string(),
            format!("codex={}", codex_shim.display()),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];
        let round_run = match quorum_from_args(&run_args, SystemTime::UNIX_EPOCH)? {
            QuorumCliOutcome::AdapterRoundRun { round_run } => round_run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        let append_args = vec![
            "append-status-output".to_string(),
            "--status-file".to_string(),
            round_run.status_path.clone(),
        ];
        let outcome = quorum_from_args(&append_args, SystemTime::UNIX_EPOCH)?;
        match outcome {
            QuorumCliOutcome::StatusOutputsAppended {
                status_path,
                appended,
                outputs,
            } => {
                assert_eq!(status_path, round_run.status_path);
                assert_eq!(appended, 2);
                let participants = outputs
                    .iter()
                    .map(|output| output.participant_id.as_str())
                    .collect::<Vec<_>>();
                assert_eq!(participants, vec!["claude", "codex"]);
            }
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        }
        let outputs = QuorumStore::new(quorum.path())
            .load_round_outputs("qr-cli-007", QuorumRound::Independent)?;
        let responses = outputs
            .iter()
            .map(|output| output.response.as_str())
            .collect::<Vec<_>>();
        assert_eq!(
            responses,
            vec![
                "Round status Claude response.",
                "Round status Codex response."
            ]
        );
        Ok(())
    }

    #[test]
    fn quorum_adapter_run_rounds_sequences_critique_after_append_gate(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let (_claude_dir, claude_shim) = make_quorum_shim(
            "claude-shim",
            "#!/bin/sh\nprompt=$(cat)\nif printf '%s' \"$prompt\" | grep -q 'output_id: out-independent-claude'; then printf 'Claude critique saw prior outputs.'; else printf 'Claude independent response.'; fi\n",
        )?;
        let (_codex_dir, codex_shim) = make_quorum_shim(
            "codex-shim",
            "#!/bin/sh\nprompt=$(cat)\nif printf '%s' \"$prompt\" | grep -q 'output_id: out-independent-claude'; then printf 'Codex critique saw prior outputs.' > \"$3\"; else printf 'Codex independent response.' > \"$3\"; fi\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let run_args = vec![
            "adapter-run-rounds".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--through-round".to_string(),
            "critique".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--adapter-binary".to_string(),
            format!("claude={}", claude_shim.display()),
            "--adapter-binary".to_string(),
            format!("codex={}", codex_shim.display()),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];

        let outcome = quorum_from_args(&run_args, SystemTime::UNIX_EPOCH)?;
        let rounds_run = match outcome {
            QuorumCliOutcome::AdapterRoundsRun { rounds_run } => rounds_run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert!(rounds_run.success);
        assert_eq!(rounds_run.through_round, "critique");
        assert_eq!(rounds_run.rounds.len(), 2);
        assert_eq!(rounds_run.appended.len(), 4);
        let independent = QuorumStore::new(quorum.path())
            .load_round_outputs("qr-cli-007", QuorumRound::Independent)?;
        let critique = QuorumStore::new(quorum.path())
            .load_round_outputs("qr-cli-007", QuorumRound::Critique)?;
        assert_eq!(independent.len(), 2);
        assert_eq!(critique.len(), 2);
        assert!(critique.iter().all(|output| output.visible_prior_output_ids
            == vec![
                "out-independent-claude".to_string(),
                "out-independent-codex".to_string()
            ]));
        assert!(critique
            .iter()
            .all(|output| output.prompt.contains("Claude independent response.")));
        let critique_responses = critique
            .iter()
            .map(|output| output.response.as_str())
            .collect::<Vec<_>>();
        assert_eq!(
            critique_responses,
            vec![
                "Claude critique saw prior outputs.",
                "Codex critique saw prior outputs."
            ]
        );
        Ok(())
    }

    #[test]
    fn quorum_synthesize_plan_materializes_native_codex_contract_from_outputs(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        for (participant_id, response) in [
            (
                "claude",
                "Claude fixture output supports the draft boundary.",
            ),
            (
                "codex",
                "Codex fixture output preserves explicit acceptance.",
            ),
        ] {
            quorum_from_args(
                &recorded_fixture_append_args(quorum.path(), participant_id, response),
                SystemTime::UNIX_EPOCH,
            )?;
        }

        let plan_args = vec![
            "synthesize-plan".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--adapter".to_string(),
            "codex".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
        ];
        let plan = match quorum_from_args(&plan_args, SystemTime::UNIX_EPOCH)? {
            QuorumCliOutcome::SynthesisPlan { plan } => plan,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert_eq!(plan.adapter, "codex");
        assert_eq!(
            plan.argv,
            vec![
                "codex".to_string(),
                "exec".to_string(),
                "--output-last-message".to_string(),
                plan.result_path.clone(),
                "-".to_string()
            ]
        );
        assert_eq!(plan.stdin_path, plan.prompt_path);
        assert_eq!(plan.stdout_path, None);
        assert!(std::path::Path::new(&plan.transcript_path).exists());
        let prompt = std::fs::read_to_string(&plan.prompt_path)?;
        assert!(prompt.contains("Should recorded quorum fixtures preserve the draft boundary?"));
        assert!(prompt.contains("Claude fixture output supports the draft boundary."));
        assert!(prompt.contains("\"proposed_memory_drafts\""));
        assert_eq!(plan.accept_synthesis_argv[2], "accept-synthesis");
        Ok(())
    }

    #[test]
    fn quorum_synthesize_run_executes_model_without_saving_result(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let (_shim_dir, shim) = make_quorum_shim(
            "claude-synth-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf '{\"schema_version\":1,\"episode_id\":\"qr-cli-007\",\"recommendation\":\"Keep synthesis explicit.\",\"decision_status\":\"recommend\",\"consensus_level\":\"strong_majority\",\"confidence\":0.84,\"supporting_points\":[\"Both outputs support the draft boundary.\"],\"dissenting_points\":[],\"unresolved_questions\":[],\"evidence_used\":[],\"participant_votes\":[{\"participant_id\":\"claude\",\"vote\":\"agree\",\"confidence\":0.86,\"rationale\":\"Audit trail remains intact\"},{\"participant_id\":\"codex\",\"vote\":\"agree\",\"confidence\":0.82,\"rationale\":\"Acceptance remains explicit\"}],\"proposed_memory_drafts\":[\"Synthesis adapters must emit proposed quorum results for explicit acceptance.\"]}'\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        for (participant_id, response) in [
            ("claude", "Claude supports explicit synthesis acceptance."),
            ("codex", "Codex supports synthesis as evidence."),
        ] {
            quorum_from_args(
                &recorded_fixture_append_args(quorum.path(), participant_id, response),
                SystemTime::UNIX_EPOCH,
            )?;
        }

        let run_args = vec![
            "synthesize-run".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--adapter".to_string(),
            "claude".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--binary".to_string(),
            shim.display().to_string(),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];
        let run = match quorum_from_args(&run_args, SystemTime::UNIX_EPOCH)? {
            QuorumCliOutcome::SynthesisRun { run } => run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert!(run.status.success);
        assert!(run.status.result_valid);
        assert_eq!(run.status.validation_error, None);
        assert!(!run.status.timed_out);
        assert!(std::path::Path::new(&run.status.result_path).exists());
        assert!(
            QuorumStore::new(quorum.path())
                .load_result("qr-cli-007")
                .is_err(),
            "synthesize-run must not save a quorum result directly",
        );
        assert_eq!(run.status.accept_synthesis_argv[2], "accept-synthesis");
        Ok(())
    }

    #[test]
    fn quorum_synthesize_run_reports_invalid_result_in_status(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let (_shim_dir, shim) = make_quorum_shim(
            "claude-invalid-synth-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf '{\"schema_version\":1,\"episode_id\":\"qr-cli-007\",\"recommendation\":\"Incomplete synthesis.\",\"decision_status\":\"recommend\",\"consensus_level\":\"strong_majority\",\"confidence\":0.8,\"participant_votes\":[{\"participant_id\":\"claude\",\"vote\":\"agree\",\"confidence\":0.8,\"rationale\":\"Only one vote\"}]}'\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        for (participant_id, response) in [
            ("claude", "Claude supports explicit synthesis acceptance."),
            ("codex", "Codex supports synthesis as evidence."),
        ] {
            quorum_from_args(
                &recorded_fixture_append_args(quorum.path(), participant_id, response),
                SystemTime::UNIX_EPOCH,
            )?;
        }

        let run_args = vec![
            "synthesize-run".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--adapter".to_string(),
            "claude".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--binary".to_string(),
            shim.display().to_string(),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];
        let run = match quorum_from_args(&run_args, SystemTime::UNIX_EPOCH)? {
            QuorumCliOutcome::SynthesisRun { run } => run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };

        assert!(!run.status.success);
        assert_eq!(
            run.status.process_status,
            Some(QuorumProcessStatus::Succeeded)
        );
        assert!(!run.status.result_valid);
        assert!(run
            .status
            .validation_error
            .as_deref()
            .is_some_and(|message| message.contains("participant vote")));
        assert!(std::path::Path::new(&run.status.status_path).exists());
        assert!(
            QuorumStore::new(quorum.path())
                .load_result("qr-cli-007")
                .is_err(),
            "invalid synthesize-run output must not save a quorum result",
        );
        Ok(())
    }

    #[test]
    fn quorum_accept_synthesis_records_result_through_existing_synthesize_path(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let result_file = tempfile::NamedTempFile::new()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        for (participant_id, response) in [
            ("claude", "Claude wants dissent preserved."),
            ("codex", "Codex wants provenance attached."),
        ] {
            quorum_from_args(
                &recorded_fixture_append_args(quorum.path(), participant_id, response),
                SystemTime::UNIX_EPOCH,
            )?;
        }
        std::fs::write(
            result_file.path(),
            r#"{
  "schema_version": 1,
  "episode_id": "qr-cli-007",
  "recommendation": "Accept synthesis only through the recorded result path.",
  "decision_status": "recommend",
  "consensus_level": "strong_majority",
  "confidence": 0.88,
  "supporting_points": ["Both participants require provenance."],
  "dissenting_points": ["No dissent, but acceptance remains explicit."],
  "unresolved_questions": [],
  "evidence_used": ["operator://fixture"],
  "participant_votes": [
    {"participant_id":"claude","vote":"agree","confidence":0.9,"rationale":"Dissent is visible"},
    {"participant_id":"codex","vote":"agree","confidence":0.86,"rationale":"Provenance is attached"}
  ],
  "proposed_memory_drafts": [
    "Accepted quorum synthesis must preserve participant evidence and enter drafts separately."
  ]
}"#,
        )?;

        let accept_args = vec![
            "accept-synthesis".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--result-file".to_string(),
            result_file.path().display().to_string(),
        ];
        let outcome = quorum_from_args(&accept_args, SystemTime::UNIX_EPOCH)?;
        match outcome {
            QuorumCliOutcome::ResultSaved { episode_id, .. } => {
                assert_eq!(episode_id, "qr-cli-007");
            }
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        }
        let result = QuorumStore::new(quorum.path()).load_result("qr-cli-007")?;
        assert_eq!(result.participant_votes.len(), 2);
        assert!(result
            .evidence_used
            .iter()
            .any(|item| item == "operator://fixture"));
        assert!(result
            .evidence_used
            .iter()
            .any(|item| item.contains("out-independent-codex")));
        Ok(())
    }

    #[test]
    fn quorum_accept_synthesis_records_result_from_successful_status(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let (_shim_dir, shim) = make_quorum_shim(
            "claude-status-synth-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf '{\"schema_version\":1,\"episode_id\":\"qr-cli-007\",\"recommendation\":\"Accept status-backed synthesis.\",\"decision_status\":\"recommend\",\"consensus_level\":\"strong_majority\",\"confidence\":0.84,\"supporting_points\":[\"Status artifacts preserve replayability.\"],\"dissenting_points\":[],\"unresolved_questions\":[],\"evidence_used\":[],\"participant_votes\":[{\"participant_id\":\"claude\",\"vote\":\"agree\",\"confidence\":0.86,\"rationale\":\"Status is valid\"},{\"participant_id\":\"codex\",\"vote\":\"agree\",\"confidence\":0.82,\"rationale\":\"Acceptance remains explicit\"}],\"proposed_memory_drafts\":[\"Status-backed synthesis acceptance must still enter through explicit result recording.\"]}'\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        for (participant_id, response) in [
            (
                "claude",
                "Claude supports status-backed synthesis acceptance.",
            ),
            ("codex", "Codex supports replayable synthesis status."),
        ] {
            quorum_from_args(
                &recorded_fixture_append_args(quorum.path(), participant_id, response),
                SystemTime::UNIX_EPOCH,
            )?;
        }
        let run_args = vec![
            "synthesize-run".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--adapter".to_string(),
            "claude".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--binary".to_string(),
            shim.display().to_string(),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];
        let run = match quorum_from_args(&run_args, SystemTime::UNIX_EPOCH)? {
            QuorumCliOutcome::SynthesisRun { run } => run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };
        assert!(run.status.success);

        let accept_args = vec![
            "accept-synthesis".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--status-file".to_string(),
            run.status.status_path.clone(),
        ];
        let outcome = quorum_from_args(&accept_args, SystemTime::UNIX_EPOCH)?;
        match outcome {
            QuorumCliOutcome::ResultSaved { episode_id, .. } => {
                assert_eq!(episode_id, "qr-cli-007");
            }
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        }
        let result = QuorumStore::new(quorum.path()).load_result("qr-cli-007")?;
        assert_eq!(result.participant_votes.len(), 2);
        assert!(result
            .evidence_used
            .iter()
            .any(|item| item.contains("out-independent-codex")));
        Ok(())
    }

    #[test]
    fn quorum_accept_synthesis_rejects_failed_status() -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let (_shim_dir, shim) = make_quorum_shim(
            "claude-invalid-status-synth-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf '{\"schema_version\":1,\"episode_id\":\"qr-cli-007\",\"recommendation\":\"Incomplete synthesis.\",\"decision_status\":\"recommend\",\"consensus_level\":\"strong_majority\",\"confidence\":0.8,\"participant_votes\":[{\"participant_id\":\"claude\",\"vote\":\"agree\",\"confidence\":0.8,\"rationale\":\"Only one vote\"}]}'\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        for (participant_id, response) in [
            ("claude", "Claude supports explicit synthesis acceptance."),
            ("codex", "Codex supports synthesis as evidence."),
        ] {
            quorum_from_args(
                &recorded_fixture_append_args(quorum.path(), participant_id, response),
                SystemTime::UNIX_EPOCH,
            )?;
        }
        let run_args = vec![
            "synthesize-run".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--adapter".to_string(),
            "claude".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--binary".to_string(),
            shim.display().to_string(),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];
        let run = match quorum_from_args(&run_args, SystemTime::UNIX_EPOCH)? {
            QuorumCliOutcome::SynthesisRun { run } => run,
            other => return Err(format!("unexpected quorum outcome: {other:?}").into()),
        };
        assert!(!run.status.success);

        let accept_args = vec![
            "accept-synthesis".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--status-file".to_string(),
            run.status.status_path,
        ];
        let err = match quorum_from_args(&accept_args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => return Err(format!("failed status must reject, got {outcome:?}").into()),
            Err(err) => err,
        };
        assert!(
            err.to_string().contains("not successful"),
            "unexpected error: {err}"
        );
        assert!(QuorumStore::new(quorum.path())
            .load_result("qr-cli-007")
            .is_err());
        Ok(())
    }

    #[test]
    fn quorum_accept_synthesis_rejects_missing_participant_votes(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let result_file = tempfile::NamedTempFile::new()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        std::fs::write(
            result_file.path(),
            r#"{
  "schema_version": 1,
  "episode_id": "qr-cli-007",
  "recommendation": "Incomplete synthesis must not be accepted.",
  "decision_status": "recommend",
  "consensus_level": "strong_majority",
  "confidence": 0.8,
  "participant_votes": [
    {"participant_id":"claude","vote":"agree","confidence":0.8,"rationale":"Codex vote is missing"}
  ]
}"#,
        )?;

        let accept_args = vec![
            "accept-synthesis".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--result-file".to_string(),
            result_file.path().display().to_string(),
        ];
        let err = match quorum_from_args(&accept_args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => return Err(format!("missing vote must reject, got {outcome:?}").into()),
            Err(err) => err,
        };
        assert!(
            err.to_string().contains("participant vote"),
            "unexpected error: {err}"
        );
        assert!(QuorumStore::new(quorum.path())
            .load_result("qr-cli-007")
            .is_err());
        Ok(())
    }

    #[test]
    fn quorum_accept_synthesis_rejects_duplicate_participant_votes(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let result_file = tempfile::NamedTempFile::new()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        std::fs::write(
            result_file.path(),
            r#"{
  "schema_version": 1,
  "episode_id": "qr-cli-007",
  "recommendation": "Duplicate votes must not be accepted.",
  "decision_status": "recommend",
  "consensus_level": "strong_majority",
  "confidence": 0.8,
  "participant_votes": [
    {"participant_id":"claude","vote":"agree","confidence":0.8,"rationale":"First vote"},
    {"participant_id":"claude","vote":"agree","confidence":0.7,"rationale":"Duplicate vote"},
    {"participant_id":"codex","vote":"agree","confidence":0.8,"rationale":"Codex vote"}
  ]
}"#,
        )?;

        let accept_args = vec![
            "accept-synthesis".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--result-file".to_string(),
            result_file.path().display().to_string(),
        ];
        let err = match quorum_from_args(&accept_args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => {
                return Err(format!("duplicate vote must reject, got {outcome:?}").into());
            }
            Err(err) => err,
        };
        assert!(
            err.to_string().contains("duplicate participant vote"),
            "unexpected error: {err}"
        );
        assert!(QuorumStore::new(quorum.path())
            .load_result("qr-cli-007")
            .is_err());
        Ok(())
    }

    #[test]
    fn quorum_adapter_run_rounds_rejects_failed_round_without_appending(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        let (_claude_dir, claude_shim) = make_quorum_shim(
            "claude-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Failed independent response.'\nexit 2\n",
        )?;
        let (_codex_dir, codex_shim) = make_quorum_shim(
            "codex-shim",
            "#!/bin/sh\ncat >/dev/null\nprintf 'Codex independent response.' > \"$3\"\n",
        )?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let run_args = vec![
            "adapter-run-rounds".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--through-round".to_string(),
            "independent".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
            "--adapter-binary".to_string(),
            format!("claude={}", claude_shim.display()),
            "--adapter-binary".to_string(),
            format!("codex={}", codex_shim.display()),
            "--timeout-secs".to_string(),
            "5".to_string(),
        ];

        let err = match quorum_from_args(&run_args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => {
                return Err(format!("failed round must not append, got {outcome:?}").into());
            }
            Err(err) => err,
        };
        assert!(
            err.to_string().contains("not successful"),
            "unexpected error: {err}"
        );
        assert!(
            QuorumStore::new(quorum.path())
                .load_round_outputs("qr-cli-007", QuorumRound::Independent)?
                .is_empty(),
            "failed round must not append participant outputs",
        );
        Ok(())
    }

    #[test]
    fn quorum_adapter_run_round_respects_critique_visibility_gate(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let quorum = tempfile::tempdir()?;
        let out = tempfile::tempdir()?;
        quorum_from_args(
            &recorded_fixture_create_args(quorum.path()),
            SystemTime::UNIX_EPOCH,
        )?;
        let run_args = vec![
            "adapter-run-round".to_string(),
            "--quorum-dir".to_string(),
            quorum.path().display().to_string(),
            "--episode-id".to_string(),
            "qr-cli-007".to_string(),
            "--round".to_string(),
            "critique".to_string(),
            "--out-dir".to_string(),
            out.path().display().to_string(),
        ];

        let err = match quorum_from_args(&run_args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => {
                return Err(format!(
                    "critique round must wait for independent outputs, got {outcome:?}"
                )
                .into());
            }
            Err(err) => err,
        };
        assert!(
            err.to_string().contains("round is incomplete"),
            "unexpected error: {err}"
        );
        Ok(())
    }

    #[test]
    fn sweep_writes_pending_drafts_for_text_files() -> Result<(), Box<dyn std::error::Error>> {
        let drafts = tempfile::tempdir()?;
        let source = tempfile::tempdir()?;
        std::fs::write(source.path().join("codex.md"), "Codex remembers Mimir.\n")?;
        std::fs::create_dir(source.path().join("nested"))?;
        std::fs::write(
            source.path().join("nested").join("claude.txt"),
            "Claude remembers the quorum boundary.\n",
        )?;
        std::fs::write(source.path().join("ignored.bin"), "not a memory file")?;

        let args = vec![
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--path".to_string(),
            source.path().display().to_string(),
            "--source-surface".to_string(),
            "codex-memory".to_string(),
            "--project".to_string(),
            "buildepicshit/Mimir".to_string(),
            "--operator".to_string(),
            "AlainDor".to_string(),
            "--tag".to_string(),
            "sweep-test".to_string(),
        ];

        let outcome = match sweep_from_args(&args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("sweep failed: {err:?}").into()),
        };

        assert_eq!(outcome.submitted, 2);
        assert_eq!(outcome.skipped_empty, 0);
        assert_eq!(outcome.drafts.len(), 2);

        let store = DraftStore::new(drafts.path());
        let staged = store.list(mimir_librarian::DraftState::Pending)?;
        assert_eq!(staged.len(), 2);
        assert!(staged
            .iter()
            .all(|draft| draft.metadata().source_surface == DraftSourceSurface::CodexMemory));
        assert!(staged
            .iter()
            .all(|draft| draft.metadata().source_agent.as_deref() == Some("codex")));
        assert!(staged
            .iter()
            .all(|draft| draft.metadata().operator.as_deref() == Some("AlainDor")));
        Ok(())
    }

    #[test]
    fn sweep_from_args_skips_empty_files() -> Result<(), Box<dyn std::error::Error>> {
        let drafts = tempfile::tempdir()?;
        let source = tempfile::NamedTempFile::new()?;
        std::fs::write(source.path(), " \n\t")?;
        let args = vec![
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--path".to_string(),
            source.path().display().to_string(),
            "--source-surface".to_string(),
            "claude-memory".to_string(),
        ];

        let outcome = match sweep_from_args(&args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("sweep failed: {err:?}").into()),
        };

        assert_eq!(outcome.submitted, 0);
        assert_eq!(outcome.skipped_empty, 1);
        assert!(outcome.drafts.is_empty());
        Ok(())
    }

    #[test]
    fn sweep_from_args_rejects_missing_path() {
        let args = vec!["--source-surface".to_string(), "codex-memory".to_string()];
        match sweep_from_args(&args, SystemTime::UNIX_EPOCH) {
            Err(CliError::Usage(message)) => assert!(message.contains("--path is required")),
            other => assert!(
                matches!(other, Err(CliError::Usage(_))),
                "expected usage error, got {other:?}"
            ),
        }
    }

    #[test]
    fn sweep_from_args_rejects_missing_source_surface() {
        let args = vec!["--path".to_string(), ".".to_string()];
        match sweep_from_args(&args, SystemTime::UNIX_EPOCH) {
            Err(CliError::Usage(message)) => {
                assert!(message.contains("--source-surface is required"));
            }
            other => assert!(
                matches!(other, Err(CliError::Usage(_))),
                "expected usage error, got {other:?}"
            ),
        }
    }

    #[test]
    fn sweep_from_args_rejects_non_sweep_source_surface() {
        let args = vec![
            "--path".to_string(),
            ".".to_string(),
            "--source-surface".to_string(),
            "consensus-quorum".to_string(),
        ];
        match sweep_from_args(&args, SystemTime::UNIX_EPOCH) {
            Err(CliError::Usage(message)) => assert!(message.contains("not valid for sweep")),
            other => assert!(
                matches!(other, Err(CliError::Usage(_))),
                "expected usage error, got {other:?}"
            ),
        }
    }

    #[test]
    fn run_from_args_reports_empty_store() -> Result<(), Box<dyn std::error::Error>> {
        let drafts = tempfile::tempdir()?;
        let workspace = tempfile::tempdir()?;
        let workspace_log = workspace.path().join("canonical.log");
        let args = vec![
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--workspace".to_string(),
            workspace_log.display().to_string(),
        ];

        let outcome = match run_from_args(&args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("run failed: {err:?}").into()),
        };

        assert_eq!(outcome.processor, "retrying_llm");
        assert_eq!(outcome.summary.pending_seen, 0);
        assert_eq!(outcome.summary.claimed, 0);
        assert_eq!(outcome.summary.deferred, 0);
        Ok(())
    }

    #[test]
    fn run_from_args_accepts_review_conflicts_flag() -> Result<(), Box<dyn std::error::Error>> {
        let drafts = tempfile::tempdir()?;
        let workspace = tempfile::tempdir()?;
        let workspace_log = workspace.path().join("canonical.log");
        let args = vec![
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--workspace".to_string(),
            workspace_log.display().to_string(),
            "--review-conflicts".to_string(),
        ];

        let outcome = match run_from_args(&args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("run failed: {err:?}").into()),
        };

        assert_eq!(outcome.processor, "retrying_llm");
        assert_eq!(outcome.summary.pending_seen, 0);
        assert_eq!(outcome.summary.quarantined, 0);
        Ok(())
    }

    #[test]
    fn run_from_args_accepts_dedup_window_flag() -> Result<(), Box<dyn std::error::Error>> {
        let drafts = tempfile::tempdir()?;
        let workspace = tempfile::tempdir()?;
        let workspace_log = workspace.path().join("canonical.log");
        let args = vec![
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--workspace".to_string(),
            workspace_log.display().to_string(),
            "--dedup-valid-at-window-secs".to_string(),
            "0".to_string(),
        ];

        let outcome = match run_from_args(&args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("run failed: {err:?}").into()),
        };

        assert_eq!(outcome.processor, "retrying_llm");
        assert_eq!(outcome.summary.pending_seen, 0);
        Ok(())
    }

    #[test]
    fn run_from_args_rejects_invalid_dedup_window() {
        let args = vec![
            "--dedup-valid-at-window-secs".to_string(),
            "soon".to_string(),
        ];
        match run_from_args(&args, SystemTime::UNIX_EPOCH) {
            Err(CliError::Usage(message)) => {
                assert!(message.contains("--dedup-valid-at-window-secs must be an integer"));
            }
            other => assert!(
                matches!(other, Err(CliError::Usage(_))),
                "expected usage error, got {other:?}"
            ),
        }
    }

    #[test]
    fn watch_from_args_runs_bounded_iteration() -> Result<(), Box<dyn std::error::Error>> {
        let drafts = tempfile::tempdir()?;
        let workspace = tempfile::tempdir()?;
        let workspace_log = workspace.path().join("canonical.log");
        let args = vec![
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--workspace".to_string(),
            workspace_log.display().to_string(),
            "--iterations".to_string(),
            "1".to_string(),
        ];
        let mut outcomes = Vec::new();

        let outcome = match watch_from_args(&args, |run| outcomes.push(run.clone())) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("watch failed: {err:?}").into()),
        };

        assert_eq!(outcome.iterations, 1);
        assert_eq!(outcomes.len(), 1);
        assert_eq!(outcomes[0].summary.pending_seen, 0);
        Ok(())
    }

    #[test]
    fn watch_from_args_supports_defer_mode() -> Result<(), Box<dyn std::error::Error>> {
        let drafts = tempfile::tempdir()?;
        let store = DraftStore::new(drafts.path());
        let draft = Draft::with_metadata(
            "watch should return this draft to pending".to_string(),
            DraftMetadata::new(DraftSourceSurface::Cli, SystemTime::UNIX_EPOCH),
        );
        store.submit(&draft)?;
        let args = vec![
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--defer".to_string(),
            "--iterations".to_string(),
            "1".to_string(),
        ];
        let mut outcomes = Vec::new();

        let outcome = match watch_from_args(&args, |run| outcomes.push(run.clone())) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("watch failed: {err:?}").into()),
        };

        assert_eq!(outcome.iterations, 1);
        assert_eq!(outcomes[0].summary.deferred, 1);
        assert_eq!(store.list(mimir_librarian::DraftState::Pending)?.len(), 1);
        Ok(())
    }

    #[test]
    fn watch_from_args_rejects_invalid_poll_secs() {
        let args = vec!["--poll-secs".to_string(), "often".to_string()];
        match watch_from_args(&args, |_| {}) {
            Err(CliError::Usage(message)) => {
                assert!(message.contains("--poll-secs must be an integer"));
            }
            other => assert!(
                matches!(other, Err(CliError::Usage(_))),
                "expected usage error, got {other:?}"
            ),
        }
    }

    #[test]
    fn run_from_args_defers_pending_without_losing_drafts() -> Result<(), Box<dyn std::error::Error>>
    {
        let drafts = tempfile::tempdir()?;
        let store = DraftStore::new(drafts.path());
        let draft = Draft::with_metadata(
            "The real processor lands after the run skeleton.".to_string(),
            DraftMetadata::new(DraftSourceSurface::Cli, SystemTime::UNIX_EPOCH),
        );
        store.submit(&draft)?;
        let args = vec![
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--defer".to_string(),
        ];

        let outcome = match run_from_args(&args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("run failed: {err:?}").into()),
        };

        assert_eq!(outcome.summary.pending_seen, 1);
        assert_eq!(outcome.summary.claimed, 1);
        assert_eq!(outcome.summary.deferred, 1);
        assert_eq!(store.list(mimir_librarian::DraftState::Pending)?.len(), 1);
        assert_eq!(
            store.list(mimir_librarian::DraftState::Processing)?.len(),
            0
        );
        Ok(())
    }

    #[test]
    fn run_from_args_recovers_stale_processing_before_deferring(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let drafts = tempfile::tempdir()?;
        let store = DraftStore::new(drafts.path());
        let draft = Draft::with_metadata(
            "A crashed run should not leave this stuck forever.".to_string(),
            DraftMetadata::new(DraftSourceSurface::Cli, SystemTime::UNIX_EPOCH),
        );
        store.submit(&draft)?;
        store.transition(
            draft.id(),
            mimir_librarian::DraftState::Pending,
            mimir_librarian::DraftState::Processing,
        )?;
        let args = vec![
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--stale-processing-secs".to_string(),
            "0".to_string(),
            "--defer".to_string(),
        ];

        let outcome = match run_from_args(&args, SystemTime::now() + Duration::from_secs(60)) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("run failed: {err:?}").into()),
        };

        assert_eq!(outcome.summary.recovered_processing, 1);
        assert_eq!(outcome.summary.pending_seen, 1);
        assert_eq!(outcome.summary.deferred, 1);
        assert_eq!(store.list(mimir_librarian::DraftState::Pending)?.len(), 1);
        assert_eq!(
            store.list(mimir_librarian::DraftState::Processing)?.len(),
            0
        );
        Ok(())
    }

    #[test]
    fn run_from_args_archive_raw_commits_pending_without_llm(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let drafts = tempfile::tempdir()?;
        let workspace = tempfile::tempdir()?;
        let workspace_log = workspace.path().join("canonical.log");
        let store = DraftStore::new(drafts.path());
        let draft = Draft::with_metadata(
            "Archive this raw checkpoint quickly.".to_string(),
            DraftMetadata::new(DraftSourceSurface::Cli, SystemTime::UNIX_EPOCH),
        );
        store.submit(&draft)?;
        let args = vec![
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--workspace".to_string(),
            workspace_log.display().to_string(),
            "--archive-raw".to_string(),
        ];

        let outcome = match run_from_args(&args, SystemTime::UNIX_EPOCH) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("run failed: {err:?}").into()),
        };

        assert_eq!(outcome.processor, "archive_raw");
        assert_eq!(outcome.summary.pending_seen, 1);
        assert_eq!(outcome.summary.claimed, 1);
        assert_eq!(outcome.summary.accepted, 1);
        assert_eq!(store.list(mimir_librarian::DraftState::Accepted)?.len(), 1);
        let reopened = mimir_core::Store::open(&workspace_log)?;
        assert_eq!(reopened.pipeline().semantic_records().len(), 6);
        Ok(())
    }

    #[test]
    fn run_from_args_archive_raw_recovers_stale_processing(
    ) -> Result<(), Box<dyn std::error::Error>> {
        let drafts = tempfile::tempdir()?;
        let workspace = tempfile::tempdir()?;
        let workspace_log = workspace.path().join("canonical.log");
        let store = DraftStore::new(drafts.path());
        let draft = Draft::with_metadata(
            "A crashed archive run should drain on the next pass.".to_string(),
            DraftMetadata::new(DraftSourceSurface::Cli, SystemTime::UNIX_EPOCH),
        );
        store.submit(&draft)?;
        store.transition(
            draft.id(),
            mimir_librarian::DraftState::Pending,
            mimir_librarian::DraftState::Processing,
        )?;
        let args = vec![
            "--drafts-dir".to_string(),
            drafts.path().display().to_string(),
            "--workspace".to_string(),
            workspace_log.display().to_string(),
            "--stale-processing-secs".to_string(),
            "0".to_string(),
            "--archive-raw".to_string(),
        ];

        let outcome = match run_from_args(&args, SystemTime::now() + Duration::from_secs(60)) {
            Ok(outcome) => outcome,
            Err(err) => return Err(format!("run failed: {err:?}").into()),
        };

        assert_eq!(outcome.processor, "archive_raw");
        assert_eq!(outcome.summary.recovered_processing, 1);
        assert_eq!(outcome.summary.pending_seen, 1);
        assert_eq!(outcome.summary.accepted, 1);
        assert_eq!(
            store.list(mimir_librarian::DraftState::Processing)?.len(),
            0
        );
        let reopened = mimir_core::Store::open(&workspace_log)?;
        assert_eq!(reopened.pipeline().semantic_records().len(), 6);
        Ok(())
    }

    #[test]
    fn run_from_args_rejects_archive_raw_with_defer() {
        let args = vec!["--archive-raw".to_string(), "--defer".to_string()];
        match run_from_args(&args, SystemTime::UNIX_EPOCH) {
            Err(CliError::Usage(message)) => {
                assert!(message.contains("--archive-raw cannot be combined"));
            }
            other => assert!(
                matches!(other, Err(CliError::Usage(_))),
                "expected usage error, got {other:?}"
            ),
        }
    }

    #[test]
    fn run_from_args_rejects_bad_stale_processing_secs() {
        let args = vec![
            "--stale-processing-secs".to_string(),
            "not-a-number".to_string(),
        ];
        match run_from_args(&args, SystemTime::UNIX_EPOCH) {
            Err(CliError::Usage(message)) => {
                assert!(message.contains("--stale-processing-secs must be an integer"));
            }
            other => assert!(
                matches!(other, Err(CliError::Usage(_))),
                "expected usage error, got {other:?}"
            ),
        }
    }

    #[test]
    fn run_from_args_rejects_bad_max_retries() {
        let args = vec!["--max-retries".to_string(), "not-a-number".to_string()];
        match run_from_args(&args, SystemTime::UNIX_EPOCH) {
            Err(CliError::Usage(message)) => {
                assert!(message.contains("--max-retries must be an integer"));
            }
            other => assert!(
                matches!(other, Err(CliError::Usage(_))),
                "expected usage error, got {other:?}"
            ),
        }
    }

    #[test]
    fn run_from_args_rejects_bad_llm_timeout_secs() {
        let args = vec!["--llm-timeout-secs".to_string(), "not-a-number".to_string()];
        match run_from_args(&args, SystemTime::UNIX_EPOCH) {
            Err(CliError::Usage(message)) => {
                assert!(message.contains("--llm-timeout-secs must be an integer"));
            }
            other => assert!(
                matches!(other, Err(CliError::Usage(_))),
                "expected usage error, got {other:?}"
            ),
        }
    }
}
