sprawling-kernel 0.0.9

Every decision of a sprawling city - addresses, events, gates, plans and errors - with nothing that touches a disk.
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Copyright (c) 2026 2youg1 and the sprawling contributors

//! External-fact constants: values that follow the
//! outside world. Changing one requires evidence that the world changed,
//! never our own preference.
//! The part `crates/kernel/spec/ConstsExternal.lean` specifies this module.
//!
//! Data, plus the classifications that read that data and nothing else:
//! [`readable_log_v`] answers a question only [`EVENT_LOG_V`] can answer
//! and every home it could have had instead would have been a second
//! place to decide which ledgers this build opens.

/// Provider-side explicit cache breakpoint ceiling.
pub const CACHE_BREAKPOINTS_MAX: u32 = 4;

/// Prompt-cache lifetime treated as the async-discipline horizon (6.4).
pub const PROMPT_CACHE_TTL_SECS: u64 = 300;

/// EventRecord `v`: one monotonic integer, no major/minor split (3.1).
/// 2: the four lines a turn waits for record their own moment
/// (kernel D10).
pub const EVENT_LOG_V: u32 = 2;

/// Where a ledger line's `v` stands against the version this build
/// writes. The one authority for "which ledgers open"; every reader that
/// compares a `v` asks [`readable_log_v`] rather than comparing again.
///
/// Exhaustive on purpose: a reader that handles three of these and
/// defaults the fourth is how an unversioned line came to be reported as
/// a broken chain.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogVersion {
    /// Exactly what this build writes. Readable, and appendable.
    Current,
    /// Written by an older build, at or above the first version.
    /// Readable: the ledger is append-only and its history stays on
    /// disk, so every version from 1 upwards still parses.
    Older,
    /// Written by a newer build. Refused whole rather than read in part:
    /// a line this build does not fully understand must not become the
    /// prefix of a chain it then extends.
    Ahead,
    /// Below the first version anybody ever wrote, `0` included. No
    /// build produced this, so it is a damaged or foreign line rather
    /// than an old one.
    NotAVersion,
}

/// The first `v` any build of this ledger format wrote. A line below it
/// was never written by us.
const EVENT_LOG_V_FIRST: u64 = 1;

/// Classifies one ledger line's `v`.
///
/// Takes `u64` because that is what JSON hands a reader before anything
/// has been validated; a `v` too large for [`EVENT_LOG_V`]'s type is
/// [`LogVersion::Ahead`] like any other future version.
#[must_use]
pub fn readable_log_v(v: u64) -> LogVersion {
    let current = u64::from(EVENT_LOG_V);
    if v < EVENT_LOG_V_FIRST {
        LogVersion::NotAVersion
    } else if v > current {
        LogVersion::Ahead
    } else if v == current {
        LogVersion::Current
    } else {
        LogVersion::Older
    }
}

/// The L0 tool set: always present, never discovered (5.1).
pub const L0_TOOLS: [&str; 3] = [crate::ToolName::EXEC, "edit", "status"];

/// One secret shape: prefix + charset + length window. The table is
/// data, not code; it grows with providers, and `kernel::secret::scan`
/// plus `xtask secret` are its only consumers.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SecretShape {
    pub provider: &'static str,
    pub prefix: &'static str,
    pub charset: SecretCharset,
    /// Closed interval over the length of the whole token, prefix
    /// included. `scan` measures from the first byte of the prefix to
    /// the last byte the charset admits, so a window written against the
    /// body alone is short by `prefix.len()`.
    pub len: (u16, u16),
}

/// Character sets that public token formats draw from.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SecretCharset {
    /// `[A-Za-z0-9]`
    Base62,
    /// `[A-Za-z0-9_-]`
    Base64Url,
    /// `[0-9a-f]`
    HexLower,
    /// `[A-Z0-9]`
    UpperBase36,
}

/// Public provider token shapes, per each provider's published format.
pub const SECRET_SHAPES: [SecretShape; 11] = [
    SecretShape {
        provider: "anthropic",
        prefix: "sk-ant-",
        charset: SecretCharset::Base64Url,
        len: (24, 120),
    },
    SecretShape {
        provider: "openai",
        prefix: "sk-proj-",
        charset: SecretCharset::Base64Url,
        len: (24, 200),
    },
    SecretShape {
        provider: "github",
        prefix: "ghp_",
        charset: SecretCharset::Base62,
        len: (36, 40),
    },
    SecretShape {
        provider: "github-oauth",
        prefix: "gho_",
        charset: SecretCharset::Base62,
        len: (36, 40),
    },
    SecretShape {
        provider: "aws",
        prefix: "AKIA",
        charset: SecretCharset::UpperBase36,
        len: (16, 16),
    },
    SecretShape {
        provider: "gitlab",
        prefix: "glpat-",
        charset: SecretCharset::Base64Url,
        len: (20, 22),
    },
    SecretShape {
        provider: "slack",
        prefix: "xoxb-",
        charset: SecretCharset::Base62,
        len: (24, 80),
    },
    SecretShape {
        provider: "google",
        prefix: "AIza",
        charset: SecretCharset::Base64Url,
        len: (35, 35),
    },
    // The three below are aggregators, and they share one property that
    // decides they must be listed: their bodies are all-lowercase hex or
    // single-case base62, so `secret::scan`'s entropy detector - which
    // fires only on mixed upper/lower/digit runs, so that the city's own
    // blake3 hashes stay quiet - cannot reach them. For these, the shape
    // table is not the primary net; it is the only one.
    SecretShape {
        provider: "openrouter",
        prefix: "sk-or-v1-",
        charset: SecretCharset::HexLower,
        len: (40, 80),
    },
    SecretShape {
        provider: "zenmux",
        prefix: "sk-ai-v1-",
        charset: SecretCharset::HexLower,
        len: (40, 80),
    },
    SecretShape {
        provider: "groq",
        prefix: "gsk_",
        charset: SecretCharset::Base62,
        len: (32, 96),
    },
];

#[cfg(test)]
#[allow(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic,
    clippy::indexing_slicing,
    reason = "test code"
)]
mod tests {
    use super::*;
    use std::collections::BTreeSet;

    #[test]
    fn the_five_facts_hold_their_documented_values() {
        assert_eq!(CACHE_BREAKPOINTS_MAX, 4);
        assert_eq!(PROMPT_CACHE_TTL_SECS, 300);
        assert_eq!(EVENT_LOG_V, 2);
        assert_eq!(L0_TOOLS, ["exec", "edit", "status"]);
        assert_eq!(SECRET_SHAPES.len(), 11);
    }

    #[test]
    fn every_version_a_line_can_carry_lands_in_exactly_one_class() {
        assert_eq!(readable_log_v(0), LogVersion::NotAVersion);
        assert_eq!(readable_log_v(u64::from(EVENT_LOG_V)), LogVersion::Current);
        assert_eq!(
            readable_log_v(u64::from(EVENT_LOG_V).saturating_add(1)),
            LogVersion::Ahead
        );
        assert_eq!(readable_log_v(u64::MAX), LogVersion::Ahead);
        for v in EVENT_LOG_V_FIRST..u64::from(EVENT_LOG_V) {
            assert_eq!(readable_log_v(v), LogVersion::Older, "v{v}");
        }
    }

    #[test]
    fn secret_shapes_are_wellformed_data() {
        let prefixes: BTreeSet<&str> = SECRET_SHAPES.iter().map(|s| s.prefix).collect();
        assert_eq!(
            prefixes.len(),
            SECRET_SHAPES.len(),
            "prefixes must be unique"
        );
        for shape in &SECRET_SHAPES {
            assert!(!shape.prefix.is_empty());
            assert!(!shape.provider.is_empty());
            let (lo, hi) = shape.len;
            assert!(
                lo > 0 && lo <= hi,
                "{}: empty or inverted window",
                shape.provider
            );
        }
    }
}