kernel/consts_policy.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//! Policy constants: our choices. Changing one
7//! changes behavior and therefore requires EVAL evidence or an
8//! explicit ruling. Data only — zero branches by charter.
9//! The part `crates/kernel/spec/ConstsPolicy.lean` specifies this module.
10//!
11//! Five entries carry a type rather than a plain number (`crates/kernel/spec/ConstsPolicy.lean`
12//! §8-8): AUTONOMY_DEFAULT, CLOCK_STAMP_DEFAULT, and the three limits
13//! whose refusal derives from their type (`crates/kernel/spec/PolicyLimit.lean` §8-73).
14
15/// Exact ratio as an integer pair: kernel decision paths never touch
16/// floats (determinism rule 6). Kept unreduced so the spelling mirrors the
17/// value as it was decided.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct Ratio {
20 pub num: u32,
21 pub den: u32,
22}
23
24pub const STARTUP_BUDGET_TOKENS: u64 = 2000;
25
26/// Bytes per token, typical: the rate every budget that crosses the two
27/// units is converted at. It is an estimate for English prose and code,
28/// and it is deliberately not the worst-case ratio
29/// [`INTERVAL_CAP_BYTES`] reasons from - a budget that has to hold is
30/// sized by the worst case, a budget that has to be spent is sized by
31/// the typical one.
32pub const BYTES_PER_TOKEN: u64 = 4;
33
34/// How many slots a whole-prefix budget divides evenly across.
35/// `runtime::prefix::SegmentSlot` is the authority on how many slots
36/// there are, and `runtime::prefix::tests` holds this divisor against
37/// that enum so the two cannot drift apart.
38pub const PREFIX_SLOTS: std::num::NonZeroU64 = match std::num::NonZeroU64::new(4) {
39 Some(slots) => slots,
40 None => std::num::NonZeroU64::MIN,
41};
42
43/// Where the context reminder's two rungs sit, as whole percents of the
44/// window. The first is not adjustable: one number, one meaning. The
45/// second is the one a layer may move, and these three numbers are the
46/// whole of what it may move it between — `kernel::config::SecondThreshold`
47/// enforces the domain at its one construction point.
48///
49/// The domain starts one point above the first rung, derived rather than
50/// written: two rungs on one percent would always sound as one, and the
51/// first would never be heard (`crates/kernel/Spec.lean` D19).
52pub const CTX_REMINDER_FIRST_PERCENT: u64 = 30;
53pub const CTX_REMINDER_SECOND_DEFAULT: u64 = 65;
54pub const CTX_REMINDER_SECOND_MIN: u64 = match CTX_REMINDER_FIRST_PERCENT.checked_add(1) {
55 Some(floor) => floor,
56 None => u64::MAX,
57};
58pub const CTX_REMINDER_SECOND_MAX: u64 = 90;
59
60pub const LOOP_REPEAT_THRESHOLD: u32 = 3;
61
62/// Floor, not threshold: the effective offload bound is derived by
63/// `pipeline` from window headroom (4.4).
64pub const OFFLOAD_MIN_BYTES: u64 = 16_384;
65
66/// What one interval of a document may carry into the window: 64 KiB,
67/// cut on a line end, continued by the offset the answer reports.
68///
69/// A different subject from a command's cap, which is why it is a
70/// different number. `exec` output cannot be continued, so its ceiling
71/// is where loss becomes acceptable and the remainder goes to CAS.
72/// `read` and `search` hand back an interval of something the caller can
73/// ask for again, so their ceiling only paces delivery.
74///
75/// Derived, not chosen: the context reminders sound at 30% and at the
76/// second rung — `CTX_REMINDER_SECOND_DEFAULT` unless a layer moves it
77/// between `CTX_REMINDER_SECOND_MIN` and `CTX_REMINDER_SECOND_MAX` — and
78/// a ladder means nothing if one result can clear a rung unseen. The gaps
79/// one result must not cover are the second rung minus 30 into the first
80/// rung, and 100 minus the second rung past the last: 35 and 35 points at
81/// the default pair, so one result stays under 35% of the window. At 2.5
82/// bytes per token, the worst realistic ratio (base64, hash-dense text),
83/// 64 KiB is 26.2K tokens: 20.5% of a 128K window, with 1.7x to spare.
84/// 128 KiB would be 41% and out. A rung moved off the default narrows one
85/// gap below that: under 65 one result can spend the first rung unseen,
86/// and over 65 one result can fill the window past the second while its
87/// "still enough to write a handoff" claim is still fixed text — which is
88/// the arithmetic the 90 ceiling of the legal domain is priced from.
89/// Measured against this repository's 928 tracked text files, the
90/// bytes in their first 512 lines run p50 6,275 / p95 16,055 / p99
91/// 49,373 / max 73,978, so 64 KiB binds on one of them: it is a guard
92/// against input that is not line-structured - a minified bundle, a
93/// lockfile, a generated table - and not a tax on ordinary reading.
94///
95/// A `usize` because both readers compare it with the length of a text
96/// in memory: with no conversion to write, there is no failed one to
97/// read as "no ceiling at all".
98pub const INTERVAL_CAP_BYTES: usize = 65_536;
99
100/// What one turn's exchange — its assistant reply and its wave's
101/// results — may occupy in the window, in bytes. Shared evenly across
102/// the exchange's texts when the whole does not fit; which end of a text
103/// survives is `runtime::compaction`'s decision, and this file only says
104/// how much there is.
105///
106/// Derived, not chosen: one reply at the city's default output ceiling,
107/// converted at the rate every two-unit budget crosses at. A reply is
108/// the one part of an exchange no landing-time mechanism has bounded
109/// yet — a tool result is capped where it lands and its original is
110/// pinned first — so the exchange's budget is priced at exactly the
111/// largest thing the model is allowed to say. The `checked_mul` is
112/// const-evaluated over two literals and cannot fail; the match is the
113/// const-safe spelling of that product.
114pub const EXCHANGE_BUDGET_BYTES: u64 = match OUTPUT_CEILING_DEFAULT.checked_mul(BYTES_PER_TOKEN) {
115 Some(bytes) => bytes,
116 None => BYTES_PER_TOKEN,
117};
118
119pub const DRAFT_HELD_ESCALATE: u32 = 3;
120
121pub const EDIT_WAR_FREEZE: u32 = 2;
122
123/// 3.5 bits/char. Second constant scheduled for re-estimation; evidence =
124/// false-positive rate on repository and fixture corpora.
125pub const SECRET_ENTROPY_MIN: Ratio = Ratio { num: 7, den: 2 };
126
127pub const DISCARD_FILES_MAX: u32 = 16;
128
129pub const DISCARD_RETENTION_DAYS: u32 = 30;
130
131pub const CLOCK_ZONES_MAX: crate::policy_limit::ClockZonesMax =
132 crate::policy_limit::ClockZonesMax::new(4);
133
134/// Instruction budget for one sandboxed call when no layer states one.
135/// Large enough that ordinary work finishes, small enough that a loop
136/// stops rather than runs until somebody notices: the point of
137/// fuel is that exhaustion is a verdict the city writes down, not a
138/// machine that gets slow.
139pub const SANDBOX_FUEL_DEFAULT: u64 = 200_000_000;
140
141/// Words that make an environment variable name read as a credential
142/// (11.1). Matched as a case-insensitive substring, so `AWS_SECRET_KEY`
143/// and `npm_password` are both caught.
144///
145/// Deliberately over-inclusive: `KEYBOARD` is refused along with
146/// `API_KEY`, and that is the direction to err in. A refused name comes
147/// back as a three-part refusal a person can act on; an admitted one is
148/// inherited by a child process that cannot be asked to forget it.
149pub const CREDENTIAL_NAME_MARKERS: [&str; 11] = [
150 "secret",
151 "token",
152 "key",
153 "password",
154 "passwd",
155 "credential",
156 "auth",
157 "session",
158 "cookie",
159 "private",
160 "signature",
161];
162
163/// 2 GiB per node's working tree. A ceiling rather than a free-space
164/// probe: free space is a moving fact about one machine, while this is
165/// the number a refusal can state and a person can raise. Checked
166/// before a tree is created, so an over-large city is refused rather
167/// than half-copied (11.4).
168pub const WORKTREE_MAX_BYTES: u64 = 2_147_483_648;
169
170/// 2 MiB per picture. A ceiling a refusal can state and a person can
171/// raise, sized so a base64 body (4/3 of this) stays inside what both
172/// providers accept.
173pub const IMAGE_MAX_BYTES: crate::policy_limit::ImageMaxBytes =
174 crate::policy_limit::ImageMaxBytes::new(2_097_152);
175
176/// Four pictures in one turn. Past that the window is being spent on
177/// pixels rather than on the work, and a limit stated once is what a
178/// refusal can name.
179pub const IMAGES_PER_TURN: crate::policy_limit::ImagesPerTurn =
180 crate::policy_limit::ImagesPerTurn::new(4);
181
182/// 100 is the whole domain a lossy encoder has. Refused rather than
183/// clamped where a caller names one: `quality: 120` is a caller that
184/// believes it asked for something better, and an answer clamped to 100
185/// would agree with it.
186pub const IMAGE_QUALITY: crate::policy_limit::ImageQuality =
187 crate::policy_limit::ImageQuality::new(100);
188
189/// How long one answer may be on the messages face when nobody has said:
190/// 8_192 tokens.
191///
192/// The last rung of the output-ceiling ladder on the one face that needs
193/// a figure in every request, reached only when the person stated no
194/// ceiling, the provider's model list stated none, and the preset table
195/// has no row for the model. The three rungs above it carry real
196/// statements, so this number is never a figure that outranks one
197/// somebody made. The chat and responses faces never reach it: there a
198/// ceiling nobody stated is left to the provider (`crates/gateway/Spec.lean` §8-17).
199///
200/// Chosen at the width every provider this city calls accepts for every
201/// model it serves, which is what a number used in place of knowledge
202/// has to be. It truncates a long answer rather than refusing the call,
203/// and `model_selected` records `ceiling_from: policy` so a truncated
204/// run is read off the account rather than guessed at.
205pub const OUTPUT_CEILING_DEFAULT: u64 = 8_192;
206
207/// Every `Timestamped` result carries the clock line, and a `Timeless`
208/// one at most once a minute, until a layer writes `[clock] stamp`: a
209/// model that cannot tell when a command ran cannot tell a stale answer
210/// from a fresh one (runtime D8).
211pub const CLOCK_STAMP_DEFAULT: crate::config::ClockStampGranularity =
212 crate::config::ClockStampGranularity::Minute;
213
214/// Who reads the Approval Inbox in a city nobody has configured: the
215/// person, because the inbox holds design questions and a design
216/// question is the kind a person wants. What a run may *do* is settled
217/// by the gates, which answer from the rules and never ask.
218pub const AUTONOMY_DEFAULT: crate::approval::Autonomy = crate::approval::Autonomy::Owner;
219
220/// Where a city listens when nobody says otherwise: the loopback
221/// interface, so a city started by a double-click is reachable from the
222/// browser on that machine and from nowhere else.
223///
224/// `up`, `serve`, the first screen, and every command that talks to a
225/// served city read this one value, and the installer script and README
226/// quote it, so the address a person is told is the address that binds.
227pub const DEFAULT_AT: &str = "127.0.0.1:8787";
228
229/// The building raised with every city, which holds the city's own plan
230/// and the two residents that serve every other building.
231pub const HALL_BUILDING: &str = "hall";
232
233/// The city's planner. It writes Markdown and plans; it does not build.
234pub const HALL_MAYOR: &str = "hall/mayor";
235
236/// Who answers approvals when the person delegated them. Genesis writes
237/// the delegation as an event rather than baking it into
238/// `AUTONOMY_DEFAULT`: who answers is a decision this city made, and a
239/// decision the person can change needs a line of history to change.
240pub const HALL_CLERK: &str = "hall/clerk";
241
242#[cfg(test)]
243#[allow(
244 clippy::unwrap_used,
245 clippy::expect_used,
246 clippy::panic,
247 clippy::indexing_slicing,
248 reason = "test code"
249)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn the_landed_policies_hold_their_documented_values() {
255 assert_eq!(STARTUP_BUDGET_TOKENS, 2000);
256 assert_eq!(CTX_REMINDER_FIRST_PERCENT, 30);
257 assert_eq!(CTX_REMINDER_SECOND_DEFAULT, 65);
258 assert_eq!(CTX_REMINDER_SECOND_MIN, 31);
259 assert_eq!(CTX_REMINDER_SECOND_MAX, 90);
260 assert_eq!(LOOP_REPEAT_THRESHOLD, 3);
261 assert_eq!(OFFLOAD_MIN_BYTES, 16_384);
262 assert_eq!(DRAFT_HELD_ESCALATE, 3);
263 assert_eq!(EDIT_WAR_FREEZE, 2);
264 assert_eq!(SECRET_ENTROPY_MIN, Ratio { num: 7, den: 2 });
265 assert_eq!(DISCARD_FILES_MAX, 16);
266 assert_eq!(DISCARD_RETENTION_DAYS, 30);
267 assert_eq!(CLOCK_ZONES_MAX, crate::policy_limit::ClockZonesMax::new(4));
268 assert_eq!(SANDBOX_FUEL_DEFAULT, 200_000_000);
269 assert_eq!(
270 IMAGE_MAX_BYTES,
271 crate::policy_limit::ImageMaxBytes::new(2_097_152)
272 );
273 assert_eq!(IMAGES_PER_TURN, crate::policy_limit::ImagesPerTurn::new(4));
274 assert_eq!(OUTPUT_CEILING_DEFAULT, 8_192);
275 }
276
277 /// Zero is not a ceiling: the Anthropic wire refuses it and the
278 /// OpenAI wire sends `max_tokens: 0`, which answers with no content
279 /// at all. The ladder's last rung has to be a number that calls.
280 #[test]
281 fn the_last_rung_of_the_ceiling_ladder_is_a_ceiling() {
282 const { assert!(OUTPUT_CEILING_DEFAULT > 0) };
283 }
284
285 #[test]
286 fn ratios_never_divide_by_zero() {
287 const { assert!(SECRET_ENTROPY_MIN.den > 0) };
288 }
289
290 /// The default sits inside the domain the one construction point
291 /// enforces, or a run with no configured layer would have no legal
292 /// rung.
293 #[test]
294 fn the_default_second_rung_sits_inside_its_domain() {
295 const { assert!(CTX_REMINDER_SECOND_MIN <= CTX_REMINDER_SECOND_DEFAULT) };
296 const { assert!(CTX_REMINDER_SECOND_DEFAULT <= CTX_REMINDER_SECOND_MAX) };
297 }
298}