kernel/consts_external.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! External-fact constants: values that follow the
7//! outside world. Changing one requires evidence that the world changed,
8//! never our own preference.
9//! The part `crates/kernel/spec/ConstsExternal.lean` specifies this module.
10//!
11//! Data, plus the classifications that read that data and nothing else:
12//! [`readable_log_v`] answers a question only [`EVENT_LOG_V`] can answer
13//! and every home it could have had instead would have been a second
14//! place to decide which ledgers this build opens.
15
16/// Provider-side explicit cache breakpoint ceiling.
17pub const CACHE_BREAKPOINTS_MAX: u32 = 4;
18
19/// Prompt-cache lifetime treated as the async-discipline horizon (6.4).
20pub const PROMPT_CACHE_TTL_SECS: u64 = 300;
21
22/// How long before the end of a prompt-cache lifetime a keep-warm
23/// renewal must already be due, beyond the lead the caller measured.
24///
25/// Not an external fact: it is subtracted from [`PROMPT_CACHE_TTL_SECS`]
26/// and nowhere else, so it lives beside it. It covers the delays the lead
27/// cannot measure - a timer that wakes a little late, a renewal thread
28/// that starts a little late - and costs that many seconds of a warm
29/// window that lasts 300 (`crates/kernel/spec/KeepWarm.lean`, kernel D62).
30pub const PROMPT_CACHE_RENEWAL_MARGIN_SECS: u64 = 5;
31
32/// EventRecord `v`: one monotonic integer, no major/minor split (3.1).
33/// 2: the four lines a turn waits for record their own moment
34/// (kernel D10).
35pub const EVENT_LOG_V: u32 = 2;
36
37/// Where a ledger line's `v` stands against the version this build
38/// writes. The one authority for "which ledgers open"; every reader that
39/// compares a `v` asks [`readable_log_v`] rather than comparing again.
40///
41/// Exhaustive on purpose: a reader that handles three of these and
42/// defaults the fourth is how an unversioned line came to be reported as
43/// a broken chain.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum LogVersion {
46 /// Exactly what this build writes. Readable, and appendable.
47 Current,
48 /// Written by an older build, at or above the first version.
49 /// Readable: the ledger is append-only and its history stays on
50 /// disk, so every version from 1 upwards still parses.
51 Older,
52 /// Written by a newer build. Refused whole rather than read in part:
53 /// a line this build does not fully understand must not become the
54 /// prefix of a chain it then extends.
55 Ahead,
56 /// Below the first version anybody ever wrote, `0` included. No
57 /// build produced this, so it is a damaged or foreign line rather
58 /// than an old one.
59 NotAVersion,
60}
61
62/// The first `v` any build of this ledger format wrote. A line below it
63/// was never written by us.
64const EVENT_LOG_V_FIRST: u64 = 1;
65
66/// Classifies one ledger line's `v`.
67///
68/// Takes `u64` because that is what JSON hands a reader before anything
69/// has been validated; a `v` too large for [`EVENT_LOG_V`]'s type is
70/// [`LogVersion::Ahead`] like any other future version.
71#[must_use]
72pub fn readable_log_v(v: u64) -> LogVersion {
73 let current = u64::from(EVENT_LOG_V);
74 if v < EVENT_LOG_V_FIRST {
75 LogVersion::NotAVersion
76 } else if v > current {
77 LogVersion::Ahead
78 } else if v == current {
79 LogVersion::Current
80 } else {
81 LogVersion::Older
82 }
83}
84
85/// The L0 tool set: always present, never discovered (5.1).
86pub const L0_TOOLS: [&str; 3] = [crate::ToolName::EXEC, "edit", "status"];
87
88/// One secret shape: prefix + charset + length window. The table is
89/// data, not code; it grows with providers, and `kernel::secret::scan`
90/// plus `xtask secret` are its only consumers.
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct SecretShape {
93 pub provider: &'static str,
94 pub prefix: &'static str,
95 pub charset: SecretCharset,
96 /// Closed interval over the length of the whole token, prefix
97 /// included. `scan` measures from the first byte of the prefix to
98 /// the last byte the charset admits, so a window written against the
99 /// body alone is short by `prefix.len()`.
100 pub len: (u16, u16),
101}
102
103/// Character sets that public token formats draw from.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum SecretCharset {
106 /// `[A-Za-z0-9]`
107 Base62,
108 /// `[A-Za-z0-9_-]`
109 Base64Url,
110 /// `[0-9a-f]`
111 HexLower,
112 /// `[A-Z0-9]`
113 UpperBase36,
114}
115
116/// Public provider token shapes, per each provider's published format.
117pub const SECRET_SHAPES: [SecretShape; 11] = [
118 SecretShape {
119 provider: "anthropic",
120 prefix: "sk-ant-",
121 charset: SecretCharset::Base64Url,
122 len: (24, 120),
123 },
124 SecretShape {
125 provider: "openai",
126 prefix: "sk-proj-",
127 charset: SecretCharset::Base64Url,
128 len: (24, 200),
129 },
130 SecretShape {
131 provider: "github",
132 prefix: "ghp_",
133 charset: SecretCharset::Base62,
134 len: (36, 40),
135 },
136 SecretShape {
137 provider: "github-oauth",
138 prefix: "gho_",
139 charset: SecretCharset::Base62,
140 len: (36, 40),
141 },
142 SecretShape {
143 provider: "aws",
144 prefix: "AKIA",
145 charset: SecretCharset::UpperBase36,
146 len: (16, 16),
147 },
148 SecretShape {
149 provider: "gitlab",
150 prefix: "glpat-",
151 charset: SecretCharset::Base64Url,
152 len: (20, 22),
153 },
154 SecretShape {
155 provider: "slack",
156 prefix: "xoxb-",
157 charset: SecretCharset::Base62,
158 len: (24, 80),
159 },
160 SecretShape {
161 provider: "google",
162 prefix: "AIza",
163 charset: SecretCharset::Base64Url,
164 len: (35, 35),
165 },
166 // The three below are aggregators, and they share one property that
167 // decides they must be listed: their bodies are all-lowercase hex or
168 // single-case base62, so `secret::scan`'s entropy detector - which
169 // fires only on mixed upper/lower/digit runs, so that the city's own
170 // blake3 hashes stay quiet - cannot reach them. For these, the shape
171 // table is not the primary net; it is the only one.
172 SecretShape {
173 provider: "openrouter",
174 prefix: "sk-or-v1-",
175 charset: SecretCharset::HexLower,
176 len: (40, 80),
177 },
178 SecretShape {
179 provider: "zenmux",
180 prefix: "sk-ai-v1-",
181 charset: SecretCharset::HexLower,
182 len: (40, 80),
183 },
184 SecretShape {
185 provider: "groq",
186 prefix: "gsk_",
187 charset: SecretCharset::Base62,
188 len: (32, 96),
189 },
190];
191
192#[cfg(test)]
193#[allow(
194 clippy::unwrap_used,
195 clippy::expect_used,
196 clippy::panic,
197 clippy::indexing_slicing,
198 reason = "test code"
199)]
200mod tests {
201 use super::*;
202 use std::collections::BTreeSet;
203
204 #[test]
205 fn the_five_facts_hold_their_documented_values() {
206 assert_eq!(CACHE_BREAKPOINTS_MAX, 4);
207 assert_eq!(PROMPT_CACHE_TTL_SECS, 300);
208 assert_eq!(EVENT_LOG_V, 2);
209 assert_eq!(L0_TOOLS, ["exec", "edit", "status"]);
210 assert_eq!(SECRET_SHAPES.len(), 11);
211 }
212
213 #[test]
214 fn every_version_a_line_can_carry_lands_in_exactly_one_class() {
215 assert_eq!(readable_log_v(0), LogVersion::NotAVersion);
216 assert_eq!(readable_log_v(u64::from(EVENT_LOG_V)), LogVersion::Current);
217 assert_eq!(
218 readable_log_v(u64::from(EVENT_LOG_V).saturating_add(1)),
219 LogVersion::Ahead
220 );
221 assert_eq!(readable_log_v(u64::MAX), LogVersion::Ahead);
222 for v in EVENT_LOG_V_FIRST..u64::from(EVENT_LOG_V) {
223 assert_eq!(readable_log_v(v), LogVersion::Older, "v{v}");
224 }
225 }
226
227 #[test]
228 fn secret_shapes_are_wellformed_data() {
229 let prefixes: BTreeSet<&str> = SECRET_SHAPES.iter().map(|s| s.prefix).collect();
230 assert_eq!(
231 prefixes.len(),
232 SECRET_SHAPES.len(),
233 "prefixes must be unique"
234 );
235 for shape in &SECRET_SHAPES {
236 assert!(!shape.prefix.is_empty());
237 assert!(!shape.provider.is_empty());
238 let (lo, hi) = shape.len;
239 assert!(
240 lo > 0 && lo <= hi,
241 "{}: empty or inverted window",
242 shape.provider
243 );
244 }
245 }
246}