tix-tracker 0.11.0

A terminal issue tracker: markdown + YAML frontmatter issues with a kanban TUI and CLI
Documentation
//! The versioned health policy (mission `project-issue-health-status-01M3C8HX`,
//! WP01 T002/T004).
//!
//! # A formula with a name, not a rule in six places
//!
//! A parent's health is derived from what contributes to it, and *how* is a
//! decision: which state wins when children disagree, and what an empty scope or an
//! unresolvable reference resolves to. Those three answers live here, behind a
//! version string, so a rollup can report **which** policy produced it and a reader
//! can tell whether two numbers are comparable (C-005's "policy version", the
//! data-model's `policy_version`).
//!
//! `health-v1` is: the worst contributing state wins, where "worst" is
//! `at_risk > needs_attention > on_track`; an **empty** scope is `unknown`; and a
//! **missing reference** contributes `unknown`, which is never a healthy
//! contribution (C-005). Nothing here reads a file or an issue: it is the formula,
//! and the caller supplies the contributions.
//!
//! # Changing it is an escalation
//!
//! A fifth state, or a different answer for a missing reference, changes what every
//! stored value *means* — so it cannot be a quiet edit. The version is a closed set
//! ([`HealthPolicy::for_version`] refuses anything this build does not know), a
//! policy that disagrees with a stored rollup is a `policy_mismatch` diagnostic
//! (WP04), and adding a version is a plan of its own (C-003).

use super::state::HealthState;

/// The policy version this build implements.
pub const HEALTH_POLICY_VERSION: &str = "health-v1";

/// The three answers that make up a policy, under a version string.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HealthPolicy {
    /// The version, e.g. `health-v1`. Reported by every rollup.
    pub version: String,
    /// The precedence, **worst first**: the first state in this list that any
    /// contribution carries is the rollup's state.
    pub precedence: Vec<HealthState>,
    /// What an empty scope resolves to.
    pub empty_scope: HealthState,
    /// What an unresolvable reference contributes. Never a *healthy* value: a
    /// missing child must not read as `on_track` (C-005).
    pub missing_reference: HealthState,
}

impl Default for HealthPolicy {
    fn default() -> Self {
        Self::v1()
    }
}

impl HealthPolicy {
    /// `health-v1`: worst-wins over `at_risk > needs_attention > on_track`, with
    /// `unknown` for an empty scope and for a missing reference.
    pub fn v1() -> Self {
        Self {
            version: HEALTH_POLICY_VERSION.to_string(),
            precedence: vec![
                HealthState::AtRisk,
                HealthState::NeedsAttention,
                HealthState::OnTrack,
            ],
            empty_scope: HealthState::Unknown,
            missing_reference: HealthState::Unknown,
        }
    }

    /// The policy a stored version names (T002).
    ///
    /// A closed set: an unknown version is refused by name rather than treated as
    /// "current", because a rollup computed under one formula and reported as
    /// another is exactly the confusion the version exists to prevent.
    pub fn for_version(version: &str) -> std::result::Result<Self, HealthPolicyError> {
        match version.trim() {
            HEALTH_POLICY_VERSION => Ok(Self::v1()),
            other => Err(HealthPolicyError {
                version: other.to_string(),
            }),
        }
    }

    /// This policy's version.
    pub fn version(&self) -> &str {
        &self.version
    }

    /// Every version this build knows, for a refusal to quote.
    pub const KNOWN: [&'static str; 1] = [HEALTH_POLICY_VERSION];

    /// The **worst** of `states`, per this precedence (T002).
    ///
    /// Contributions that are not in the precedence — only `unknown`, since the
    /// list holds the three real states — are ignored here: the caller decides what
    /// they mean (the missing-reference rule above), because "nobody has said" and
    /// "this child is at risk" are different facts and this function only answers
    /// "which of these is worse".
    pub fn worst(&self, states: impl IntoIterator<Item = HealthState>) -> Option<HealthState> {
        let mut worst: Option<HealthState> = None;
        for state in states {
            if !self.precedence.contains(&state) {
                continue;
            }
            let replace = match worst {
                None => true,
                Some(current) => {
                    let rank = |state: HealthState| {
                        self.precedence
                            .iter()
                            .position(|candidate| *candidate == state)
                            .unwrap_or(usize::MAX)
                    };
                    rank(state) < rank(current)
                }
            };
            if replace {
                worst = Some(state);
            }
        }
        worst
    }

    /// The state a scope with no contributions resolves to.
    pub const fn empty_scope(&self) -> HealthState {
        self.empty_scope
    }

    /// What a missing reference contributes.
    pub const fn missing_reference(&self) -> HealthState {
        self.missing_reference
    }

    /// The provenance line a rollup reports: the version, and the formula in words.
    pub fn provenance(&self) -> String {
        format!(
            "{} (worst of {}; empty scope is {}; a missing reference contributes {})",
            self.version,
            self.precedence
                .iter()
                .map(|state| state.as_str())
                .collect::<Vec<_>>()
                .join(" > "),
            self.empty_scope.as_str(),
            self.missing_reference.as_str()
        )
    }
}

/// A policy version this build does not implement.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HealthPolicyError {
    /// The version that was asked for.
    pub version: String,
}

impl HealthPolicyError {
    /// The one sentence a surface shows.
    pub fn render(&self) -> String {
        format!(
            "`{}` is not a health policy this build implements; known versions: {}",
            self.version,
            HealthPolicy::KNOWN.join(", ")
        )
    }
}

impl std::fmt::Display for HealthPolicyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.render())
    }
}

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

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

    #[test]
    fn health_policy_v1_is_the_documented_formula() {
        let policy = HealthPolicy::v1();
        assert_eq!(policy.version(), "health-v1");
        assert_eq!(
            policy.precedence,
            vec![
                HealthState::AtRisk,
                HealthState::NeedsAttention,
                HealthState::OnTrack
            ],
            "worst first"
        );
        assert_eq!(policy.empty_scope(), HealthState::Unknown);
        assert_eq!(policy.missing_reference(), HealthState::Unknown);
        let provenance = policy.provenance();
        assert!(provenance.starts_with("health-v1"), "{provenance}");
        assert!(
            provenance.contains("at_risk > needs_attention > on_track"),
            "{provenance}"
        );
    }

    #[test]
    fn health_policy_worst_picks_by_precedence() {
        let policy = HealthPolicy::v1();
        assert_eq!(policy.worst([]), None);
        assert_eq!(
            policy.worst([HealthState::OnTrack, HealthState::NeedsAttention]),
            Some(HealthState::NeedsAttention)
        );
        assert_eq!(
            policy.worst([
                HealthState::AtRisk,
                HealthState::NeedsAttention,
                HealthState::OnTrack
            ]),
            Some(HealthState::AtRisk)
        );
        // `unknown` is not in the precedence: it is not a *worse* health, and the
        // missing-reference rule is what gives it meaning.
        assert_eq!(
            policy.worst([HealthState::Unknown, HealthState::OnTrack]),
            Some(HealthState::OnTrack)
        );
        assert_eq!(policy.worst([HealthState::Unknown]), None);
    }

    #[test]
    fn health_policy_versions_are_a_closed_set() {
        assert_eq!(
            HealthPolicy::for_version("health-v1").expect("the current version"),
            HealthPolicy::v1()
        );
        let error = HealthPolicy::for_version("health-v2").expect_err("not implemented");
        assert!(error.render().contains("health-v1"), "{}", error.render());
        assert!(error.render().contains("health-v2"), "{}", error.render());
        assert!(HealthPolicy::for_version("").is_err());
    }
}