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