byteor_replay/
options.rs

1//! Replay options and shared configuration helpers.
2
3use std::path::PathBuf;
4use std::time::Duration;
5
6use byteor_core::config::Environment;
7
8use crate::stages::ReplayDataGuardConfig;
9
10/// Replay execution mode.
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum ReplayMode {
13    /// Do not execute side effects (side-effect stages become identity).
14    DryRun,
15    /// Execute side effects (subject to policy + allowlists).
16    Execute,
17}
18
19/// Options for bundle-based replay.
20#[derive(Clone, Debug)]
21pub struct ReplayOptions {
22    /// Bundle directory containing at least `spec.kv`.
23    pub bundle_dir: PathBuf,
24    /// For LaneGraph specs: SHM directory for replay run (per-lane mmap files).
25    pub lane_graph_shm_dir: Option<PathBuf>,
26    /// For SingleRing specs: SHM lane path for the replay run.
27    pub single_ring_lane_path: Option<PathBuf>,
28    /// Journal lane name within the bundle to replay.
29    pub journal_lane: Option<String>,
30    /// Select the last N messages.
31    pub last_n: usize,
32    /// Replay mode.
33    pub mode: ReplayMode,
34    /// Optional explicit DataGuard resolver configuration for replay.
35    pub dataguard: Option<ReplayDataGuardConfig>,
36    /// Environment for policy evaluation.
37    pub env: Environment,
38    /// Optional approval token.
39    pub approval: Option<String>,
40    /// Output path for the JSON audit artifact.
41    pub out_audit_path: PathBuf,
42    /// Best-effort remove existing lane backing files before replay.
43    pub clean: bool,
44    /// Optional drain-detection timeout. Defaults to 5 seconds when unset.
45    pub drain_timeout: Option<Duration>,
46    /// Optional max journal record buffer size. Defaults to 1 MiB when unset; capped at 64 MiB.
47    pub journal_buffer_limit_bytes: Option<usize>,
48}
49
50pub(crate) fn accumulate_replay_accounting(
51    current: byteor_policy::ReplayAccounting,
52    selected_actions: u64,
53    selected_bytes: u64,
54) -> byteor_policy::ReplayAccounting {
55    byteor_policy::ReplayAccounting {
56        actions_so_far: current.actions_so_far.saturating_add(selected_actions),
57        input_bytes_so_far: current.input_bytes_so_far.saturating_add(selected_bytes),
58    }
59}
60
61const DEFAULT_DRAIN_TIMEOUT: Duration = Duration::from_secs(5);
62
63pub(crate) fn replay_drain_timeout(opts: &ReplayOptions) -> Duration {
64    opts.drain_timeout.unwrap_or(DEFAULT_DRAIN_TIMEOUT)
65}
66
67pub(crate) fn replay_journal_buffer_limit_bytes(opts: &ReplayOptions) -> Result<usize, String> {
68    let limit = opts
69        .journal_buffer_limit_bytes
70        .unwrap_or(crate::journal::DEFAULT_JOURNAL_BUFFER_LIMIT_BYTES);
71    if limit == 0 {
72        return Err("replay: journal buffer limit must be > 0".to_string());
73    }
74    if limit > crate::journal::MAX_JOURNAL_BUFFER_LIMIT_BYTES {
75        return Err(format!(
76            "replay: journal buffer limit exceeds max: requested={} max={}",
77            limit,
78            crate::journal::MAX_JOURNAL_BUFFER_LIMIT_BYTES
79        ));
80    }
81    Ok(limit)
82}