polyc-runtime 2026.7.1

Shared Unix-coherence runtime for polychrome binaries: logging, health/metrics side-server, signals.
Documentation
//! The one place an available update is worded for a person.
//!
//! Every surface that tells someone "an update is ready" — the CLI `status`
//! line, the local dashboard banner, and the chat-edge approval ask — renders
//! the text this module returns, so the same update never reads two different
//! ways on two surfaces. [`update_copy`] is pure: it maps an update
//! [`Compatibility`] class plus the target version to a small [`UpdateCopy`]
//! value, and nothing about an available update is worded anywhere else.
//!
//! The copy is honest per class, in plain terms:
//!
//! - [`Compatibility::Hot`] — a config-as-data change reaches new conversations
//!   right away, with no restart. Carries the [`APPLY_NOW`] verb.
//! - [`Compatibility::Warm`] — a binary swap restarts the service; conversations
//!   already underway finish first. Carries the [`APPLY_NOW`] verb.
//! - [`Compatibility::Cold`] — a format change needs a coordinated deploy across
//!   the whole deployment, so it points at that path and carries **no** apply
//!   verb (there is no one-click apply for a cold change).
//! - [`Compatibility::Incompatible`] — the release was built for a different
//!   runtime and cannot be applied here; it names the mismatch and carries no
//!   apply verb.

use crate::compat::Compatibility;

/// The exact verb a surface renders on the apply affordance for an update that
/// can be applied in place — never "Yes" or "OK". Shared so no surface
/// hand-writes it.
pub const APPLY_NOW: &str = "Apply now";

/// The wording for one available update: a headline, a plain-language detail
/// sentence, and — only when the update can be applied in place — the apply
/// verb a surface puts on its button.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdateCopy {
    /// The one-line headline naming the update and, for a cold or incompatible
    /// change, why it is not a one-click apply.
    pub headline: String,
    /// A complete sentence saying what applying the update does (or, for a cold
    /// or incompatible change, what to do instead).
    pub detail: String,
    /// The apply verb ([`APPLY_NOW`]) for a hot or warm update that a surface
    /// renders on its button, or `None` for a cold or incompatible change that
    /// routes to a coordinated deploy rather than a one-click apply.
    pub action: Option<&'static str>,
}

/// Word an available `version` for a person, given how it relates to the running
/// build ([`Compatibility`]). Pure — the same inputs always yield the same
/// [`UpdateCopy`], with no I/O.
///
/// A hot or warm update carries [`APPLY_NOW`]; a cold or incompatible one
/// carries no apply verb and its detail routes to a coordinated deploy instead.
#[must_use]
pub fn update_copy(class: &Compatibility, version: &str) -> UpdateCopy {
    match class {
        Compatibility::Hot => UpdateCopy {
            headline: format!("Update {version} is ready to apply"),
            detail: "It reaches new conversations right away, with no restart.".to_owned(),
            action: Some(APPLY_NOW),
        },
        Compatibility::Warm => UpdateCopy {
            headline: format!("Update {version} is ready to apply"),
            detail: "Applying it restarts the service; conversations already underway finish \
                     first."
                .to_owned(),
            action: Some(APPLY_NOW),
        },
        Compatibility::Cold => UpdateCopy {
            headline: format!("Update {version} needs a coordinated deploy"),
            detail: "It changes how the parts talk to each other, so the whole deployment moves \
                     together. Reach out to whoever runs your deployment to schedule it."
                .to_owned(),
            action: None,
        },
        Compatibility::Incompatible(reason) => UpdateCopy {
            headline: format!("Update {version} can't be applied here"),
            detail: format!(
                "This build was made for a different setup: {reason}. Reach out to whoever runs \
                 your deployment."
            ),
            action: None,
        },
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::pedantic, clippy::nursery, missing_docs)]

    use super::*;
    use crate::compat::Incompatibility;

    /// Every class, for the copy sweeps below.
    fn all_classes() -> Vec<Compatibility> {
        vec![
            Compatibility::Hot,
            Compatibility::Warm,
            Compatibility::Cold,
            Compatibility::Incompatible(Incompatibility::Wire),
            Compatibility::Incompatible(Incompatibility::EventLog),
            Compatibility::Incompatible(Incompatibility::Crd),
        ]
    }

    #[test]
    fn hot_and_warm_carry_the_apply_verb() {
        assert_eq!(
            update_copy(&Compatibility::Hot, "0.2.0").action,
            Some(APPLY_NOW)
        );
        assert_eq!(
            update_copy(&Compatibility::Warm, "0.2.0").action,
            Some(APPLY_NOW)
        );
    }

    #[test]
    fn cold_and_incompatible_carry_no_apply_verb() {
        assert_eq!(update_copy(&Compatibility::Cold, "0.2.0").action, None);
        assert_eq!(
            update_copy(&Compatibility::Incompatible(Incompatibility::Wire), "0.2.0").action,
            None,
        );
    }

    #[test]
    fn each_class_speaks_plainly_and_distinctly() {
        // Hot: no restart. Warm: a restart. Cold: a coordinated deploy.
        assert!(
            update_copy(&Compatibility::Hot, "0.2.0")
                .detail
                .contains("no restart"),
            "hot copy must say it needs no restart"
        );
        assert!(
            update_copy(&Compatibility::Warm, "0.2.0")
                .detail
                .contains("restarts the service"),
            "warm copy must say it restarts the service"
        );
        let cold = update_copy(&Compatibility::Cold, "0.2.0");
        assert!(
            cold.detail.contains("coordinated deploy")
                || cold.headline.contains("coordinated deploy"),
            "cold copy must route to a coordinated deploy"
        );
    }

    #[test]
    fn the_version_rides_every_headline() {
        for class in all_classes() {
            assert!(
                update_copy(&class, "1.4.2").headline.contains("1.4.2"),
                "the version must appear in the headline for {class:?}"
            );
        }
    }

    #[test]
    fn no_banned_jargon_and_no_apologising_anywhere() {
        // The user-facing copy rules (CLAUDE.md): no internal jargon, no
        // please/sorry/unfortunately — checked across every class.
        const BANNED: &[&str] = &[
            "operator",
            "trifecta",
            "rule-of-two",
            "context budget",
            "sub-agent",
            "state-changing action",
            "lethal-trifecta",
            "please",
            "sorry",
            "unfortunately",
        ];
        for class in all_classes() {
            let copy = update_copy(&class, "0.2.0");
            let surface = format!(
                "{} {} {}",
                copy.headline,
                copy.detail,
                copy.action.unwrap_or_default()
            )
            .to_lowercase();
            for banned in BANNED {
                assert!(
                    !surface.contains(banned),
                    "class {class:?} copy contains banned term {banned:?}: {surface}"
                );
            }
        }
    }

    #[test]
    fn incompatible_names_the_mismatched_axis() {
        let copy = update_copy(
            &Compatibility::Incompatible(Incompatibility::EventLog),
            "0.2.0",
        );
        assert!(
            copy.detail.contains("event-log schema"),
            "an incompatible update names the axis that differs: {}",
            copy.detail
        );
    }
}