trusty-mpm 1.8.2

trusty-mpm: unified multi-agent orchestration platform (core, daemon, CLI, TUI, Telegram)
//! `tm issue epic sync` — regenerate a tracker's `phases` block (#8447).
//!
//! Why: the block is the one zone of a tracker body that is machine-owned, and
//! until this verb existed it was regenerated by a session retyping a markdown
//! table — the drift the pattern's second rule exists to prevent, reintroduced
//! as the implementation. Regenerating it from live child state is the whole
//! point: a row somebody hand-edited inside the block is DISCARDED, because the
//! children are the truth and the block is a rendering of them.
//! What: [`sync`] reads the tracker body, reads its native sub-issues, renders
//! the table, and replaces the whole marker region through
//! [`super::render::replace_block`]. A body already carrying the right block is
//! left alone rather than rewritten, so the verb is idempotent and a no-op run
//! costs no write.
//!
//! # Fail-closed
//!
//! Every refusal [`super::render::BlockError`] can raise reaches the caller and
//! nothing is written: no marker pair, a duplicated marker, an inverted pair.
//! The empty-body case cannot arise here — `replace_block` copies both marker
//! segments through — and the live refusal for it sits one layer down in
//! [`super::backend::EpicBackend::set_body`], which is what any future caller
//! not routed through `replace_block` would meet. That is the failure that
//! wiped [#8445](https://github.com/bobmatnyc/trusty-tools/issues/8445) when
//! the hand-run procedure accepted an empty `awk` result as a body.
//!
//! Test: `sync_replaces_the_whole_block_and_discards_a_hand_edited_row`,
//! `sync_leaves_every_byte_outside_the_markers_identical`,
//! `sync_refuses_a_body_with_no_markers_and_writes_nothing`,
//! `sync_refuses_a_body_with_two_start_markers_and_writes_nothing`,
//! `sync_is_a_no_op_when_the_block_already_matches`,
//! `sync_propagates_a_failed_body_write`.

use crate::commands::issue::config::StateModel;

use super::backend::EpicBackend;
use super::render::{self, PHASES_END, PHASES_START};

/// What one `sync` run did.
///
/// Test: `sync_is_a_no_op_when_the_block_already_matches`.
#[derive(Debug, Clone)]
pub(crate) struct SyncReport {
    /// The tracker synced.
    pub(crate) tracker: u64,
    /// How many phase rows the regenerated block carries.
    pub(crate) rows: usize,
    /// Whether the body already matched and no write was made.
    pub(crate) unchanged: bool,
}

/// Regenerate `tracker`'s `phases` block from its children.
///
/// Why: one code path renders that block, so `create`'s final step, the
/// transition hook and the standalone verb cannot disagree about what it
/// should contain.
/// What: reads the body and the children, renders the table (its State cell
/// resolves each child's labels through `model`'s states, #8448/#8696),
/// replaces the marker region, and writes the result only when it differs.
/// Every refusal is propagated with the tracker named, and no write happens
/// on any of them.
/// Test: see the module doc.
pub(crate) fn sync<B: EpicBackend>(
    backend: &B,
    tracker: u64,
    model: &StateModel,
) -> anyhow::Result<SyncReport> {
    let body = backend.body(tracker)?;
    let children = backend.children(tracker)?;
    let rows = children
        .iter()
        .filter(|c| render::phase_number_of(&c.title).is_some())
        .count();
    // #8696: the whole model, not its prefix — see `render::state_cell`.
    let table = render::phases_table(&children, model);
    let next = render::replace_block(&body, PHASES_START, PHASES_END, &table)
        .map_err(|e| anyhow::anyhow!("#{tracker}: {e}"))?;
    if next == body {
        return Ok(SyncReport {
            tracker,
            rows,
            unchanged: true,
        });
    }
    backend.set_body(tracker, &next)?;
    Ok(SyncReport {
        tracker,
        rows,
        unchanged: false,
    })
}