faculties 0.21.0

An office suite for AI agents: kanban, wiki, files, messaging, and a Lissajous-backed viewer — all persisted in a TribleSpace pile.
//! Native, fallible presentation of completed Decide observations.
use super::operations::common_snapshot;
use super::{
    AddedFactor, DecisionDetail, DecisionSummary, FactorDetail, FactorSide, IntervalValue,
    ProposedDecision, Resolution, ResolutionReceipt, ResolutionSnapshot,
};
use crate::out::Out;
use anyhow::{anyhow, Result};
use hifitime::Epoch;
use triblespace::prelude::*;

fn fmt_result(id: Id) -> String {
    match super::result_name(id) {
        Some(name) => format!("{name} ({id:x})"),
        None => format!("{id:x}"),
    }
}

fn fmt_id(id: Id) -> String {
    format!("{id:x}")
}

fn format_interval(interval: IntervalValue) -> String {
    let (lower, _): (Epoch, Epoch) = interval
        .try_from_inline()
        .expect("validated point interval");
    format!("{lower}")
}

fn truncate(value: &str, max: usize) -> String {
    if value.chars().count() <= max {
        value.to_owned()
    } else {
        format!(
            "{}…",
            value
                .chars()
                .take(max.saturating_sub(1))
                .collect::<String>()
        )
    }
}

fn list_status(resolution: &Resolution) -> String {
    match resolution {
        Resolution::Missing => "open".to_owned(),
        Resolution::Unique(snapshot) if snapshot.forced => "resolved [forced]".to_owned(),
        Resolution::Unique(_) => "resolved".to_owned(),
        Resolution::Agreed(snapshots) if snapshots[0].forced => {
            format!("resolved [forced agreement: {} heads]", snapshots.len())
        }
        Resolution::Agreed(snapshots) => {
            format!("resolved [agreement: {} heads]", snapshots.len())
        }
        Resolution::Forked(snapshots) => format!("FORKED: {} divergent heads", snapshots.len()),
        Resolution::Invalid(reason) => format!("INVALID: {reason}"),
    }
}

pub fn proposed(receipt: &ProposedDecision, out: &mut Out<'_>) -> Result<()> {
    out.line(format!("Proposed decision {:x}", receipt.decision))
}
pub fn factor(receipt: &AddedFactor, out: &mut Out<'_>) -> Result<()> {
    out.line(format!(
        "Added {} factor {:x} to {:x}",
        receipt.side.label(),
        receipt.factor,
        receipt.decision
    ))
}
pub fn resolved(receipt: &ResolutionReceipt, reconciled: bool, out: &mut Out<'_>) -> Result<()> {
    if reconciled {
        out.line(format!(
            "Reconciled {} resolution heads for {:x} at {:x}",
            receipt.predecessors.len(),
            receipt.decision,
            receipt.head
        ))?;
    } else {
        out.line(format!(
            "Resolved decision {:x} at {:x}",
            receipt.decision, receipt.head
        ))?;
    }
    match receipt.result {
        Some(result) => out.line(format!("Result: {}", fmt_result(result)))?,
        None => out.line("Result: none — no gate will read this outcome")?,
    }
    if receipt.forced {
        out.line(if reconciled {
            "Reconciliation is explicitly forced"
        } else {
            "Resolution is explicitly forced"
        })?;
    }
    Ok(())
}
pub fn list(rows: &[DecisionSummary], out: &mut Out<'_>) -> Result<()> {
    if rows.is_empty() {
        return out.line("(no decisions)");
    }
    for row in rows {
        let mut line = format!(
            "  {} [{}] +{}/-{} {}",
            &fmt_id(row.id)[..8],
            list_status(&row.resolution),
            row.pros,
            row.cons,
            row.title
        );
        if let Some(snapshot) = common_snapshot(&row.resolution) {
            if let Some(result) = snapshot.result {
                line.push_str(&format!(
                    "  → [{}]",
                    super::result_name(result)
                        .map(str::to_owned)
                        .unwrap_or_else(|| format!("{result:x}"))
                ));
            } else {
                line.push_str("  →");
            }
            if let Some(outcome) = &row.outcome {
                line.push_str(&format!(
                    " {}",
                    truncate(outcome.lines().next().unwrap_or(""), 60)
                ));
            }
        }
        out.line(line)?;
    }
    Ok(())
}
fn show_factor(factor: &FactorDetail, out: &mut Out<'_>) -> Result<()> {
    let sign = match factor.record.side {
        FactorSide::Pro => '+',
        FactorSide::Con => '-',
    };
    out.line(format!(
        "    {sign} [{}] {} ({})",
        fmt_id(factor.record.id),
        factor.text,
        format_interval(factor.record.created_at)
    ))
}
fn snapshot(
    report: &DecisionDetail,
    snapshot: &ResolutionSnapshot,
    out: &mut Out<'_>,
) -> Result<()> {
    let outcome = report
        .outcomes
        .iter()
        .find(|outcome| outcome.head == snapshot.id)
        .ok_or_else(|| anyhow!("resolution head {:x} has no prepared outcome", snapshot.id))?;
    out.line(format!("    head {}", fmt_id(snapshot.id)))?;
    out.line(format!(
        "      result: {}",
        snapshot
            .result
            .map(fmt_result)
            .unwrap_or_else(|| "(none - prose only, no gate reads it)".to_owned())
    ))?;
    out.line(format!("      forced: {}", snapshot.forced))?;
    out.line(format!(
        "      finished: {}",
        format_interval(snapshot.finished_at)
    ))?;
    out.line(format!(
        "      evidence: {}",
        if snapshot.evidence.is_empty() {
            "(none)".to_owned()
        } else {
            snapshot
                .evidence
                .iter()
                .map(|id| fmt_id(*id))
                .collect::<Vec<_>>()
                .join(", ")
        }
    ))?;
    if !snapshot.predecessors.is_empty() {
        out.line(format!(
            "      supersedes: {}",
            snapshot
                .predecessors
                .iter()
                .map(|id| fmt_id(*id))
                .collect::<Vec<_>>()
                .join(", ")
        ))?;
    }
    out.line("      outcome:")?;
    for line in outcome.outcome.lines() {
        out.line(format!("        {line}"))?;
    }
    Ok(())
}
pub fn show(report: &DecisionDetail, out: &mut Out<'_>) -> Result<()> {
    out.line(format!("decision {:x}", report.id))?;
    out.line(format!("  title: {}", report.title))?;
    out.line(format!(
        "  created: {}",
        format_interval(report.genesis.created_at)
    ))?;
    if let Some(context) = &report.context {
        out.line("  context:")?;
        for line in context.lines() {
            out.line(format!("    {line}"))?;
        }
    }
    if let Some(about) = report.genesis.about {
        out.line(format!("  about: {about:x}"))?;
    }
    for (side, name) in [(FactorSide::Pro, "pros"), (FactorSide::Con, "cons")] {
        let factors = report
            .factors
            .iter()
            .filter(|factor| factor.record.side == side)
            .collect::<Vec<_>>();
        out.line(format!("  {name} ({}):", factors.len()))?;
        for factor in factors {
            show_factor(factor, out)?;
        }
    }
    match &report.resolution {
        Resolution::Missing => out.line("  resolution: MISSING (open)")?,
        Resolution::Unique(head) => {
            out.line("  resolution: UNIQUE")?;
            snapshot(report, head, out)?;
        }
        Resolution::Agreed(heads) => {
            out.line(format!(
                "  resolution: AGREED ({} concurrent heads; all remain join obligations)",
                heads.len()
            ))?;
            for head in heads {
                snapshot(report, head, out)?;
            }
        }
        Resolution::Forked(heads) => {
            out.line(format!(
                "  resolution: FORKED ({} divergent heads; no outcome selected)",
                heads.len()
            ))?;
            for head in heads {
                snapshot(report, head, out)?;
            }
        }
        Resolution::Invalid(reason) => out.line(format!("  resolution: INVALID ({reason})"))?,
    }
    Ok(())
}