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//! Six entries carry a type rather than a plain number (`crates/kernel/spec/ConstsPolicy.lean`
12//! §8-8): AUTONOMY_DEFAULT, CLOCK_STAMP_DEFAULT, DEFAULT_EFFORT, 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/// The thinking level a request takes when nobody stated one, if the
221/// (Endpoint, model) offers it: a balanced level for a person who never
222/// opened the setting. Whether it is used is the answer of the gateway's
223/// thinking ladder, which falls back to the upstream's own default when
224/// this level is not offered (`crates/kernel/spec/Model.lean` D56).
225pub const DEFAULT_EFFORT: crate::model::Effort = crate::model::Effort::High;
226
227/// Where a city listens when nobody says otherwise: the loopback
228/// interface, so a city started by a double-click is reachable from the
229/// browser on that machine and from nowhere else.
230///
231/// `up`, `serve`, the first screen, and every command that talks to a
232/// served city read this one value, and the installer script and README
233/// quote it, so the address a person is told is the address that binds.
234pub const DEFAULT_AT: &str = "127.0.0.1:8787";
235
236/// The building raised with every city, which holds the city's own plan
237/// and the two residents that serve every other building.
238pub const HALL_BUILDING: &str = "hall";
239
240/// The city's planner. It writes Markdown and plans; it does not build.
241pub const HALL_MAYOR: &str = "hall/mayor";
242
243/// Who answers approvals when the person delegated them. Genesis writes
244/// the delegation as an event rather than baking it into
245/// `AUTONOMY_DEFAULT`: who answers is a decision this city made, and a
246/// decision the person can change needs a line of history to change.
247pub const HALL_CLERK: &str = "hall/clerk";
248
249#[cfg(test)]
250#[allow(
251 clippy::unwrap_used,
252 clippy::expect_used,
253 clippy::panic,
254 clippy::indexing_slicing,
255 reason = "test code"
256)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn the_landed_policies_hold_their_documented_values() {
262 assert_eq!(STARTUP_BUDGET_TOKENS, 2000);
263 assert_eq!(CTX_REMINDER_FIRST_PERCENT, 30);
264 assert_eq!(CTX_REMINDER_SECOND_DEFAULT, 65);
265 assert_eq!(CTX_REMINDER_SECOND_MIN, 31);
266 assert_eq!(CTX_REMINDER_SECOND_MAX, 90);
267 assert_eq!(LOOP_REPEAT_THRESHOLD, 3);
268 assert_eq!(OFFLOAD_MIN_BYTES, 16_384);
269 assert_eq!(DRAFT_HELD_ESCALATE, 3);
270 assert_eq!(EDIT_WAR_FREEZE, 2);
271 assert_eq!(SECRET_ENTROPY_MIN, Ratio { num: 7, den: 2 });
272 assert_eq!(DISCARD_FILES_MAX, 16);
273 assert_eq!(DISCARD_RETENTION_DAYS, 30);
274 assert_eq!(CLOCK_ZONES_MAX, crate::policy_limit::ClockZonesMax::new(4));
275 assert_eq!(SANDBOX_FUEL_DEFAULT, 200_000_000);
276 assert_eq!(
277 IMAGE_MAX_BYTES,
278 crate::policy_limit::ImageMaxBytes::new(2_097_152)
279 );
280 assert_eq!(IMAGES_PER_TURN, crate::policy_limit::ImagesPerTurn::new(4));
281 assert_eq!(OUTPUT_CEILING_DEFAULT, 8_192);
282 }
283
284 /// Zero is not a ceiling: the Anthropic wire refuses it and the
285 /// OpenAI wire sends `max_tokens: 0`, which answers with no content
286 /// at all. The ladder's last rung has to be a number that calls.
287 #[test]
288 fn the_last_rung_of_the_ceiling_ladder_is_a_ceiling() {
289 const { assert!(OUTPUT_CEILING_DEFAULT > 0) };
290 }
291
292 #[test]
293 fn ratios_never_divide_by_zero() {
294 const { assert!(SECRET_ENTROPY_MIN.den > 0) };
295 }
296
297 /// The default sits inside the domain the one construction point
298 /// enforces, or a run with no configured layer would have no legal
299 /// rung.
300 #[test]
301 fn the_default_second_rung_sits_inside_its_domain() {
302 const { assert!(CTX_REMINDER_SECOND_MIN <= CTX_REMINDER_SECOND_DEFAULT) };
303 const { assert!(CTX_REMINDER_SECOND_DEFAULT <= CTX_REMINDER_SECOND_MAX) };
304 }
305}