use crate::error::ServerError;
use super::{required_node_cache_budget, required_transcript_batch_policy};
pub(crate) struct RequiredFieldDefault {
pub(crate) path: &'static str,
pub(crate) section: &'static str,
pub(crate) key: &'static str,
pub(crate) requirement_message: &'static str,
pub(crate) default_toml: &'static str,
pub(crate) env_override: &'static str,
pub(crate) teaching_comment: &'static [&'static str],
pub(crate) is_absent: fn(&crate::config::ServerConfig) -> bool,
pub(crate) satisfy_in_memory: fn(&mut crate::config::ServerConfig),
}
pub(crate) const BOOT_REQUIRED_FIELD_DEFAULTS: [RequiredFieldDefault; 3] = [
RequiredFieldDefault {
path: "store.node_cache_budget",
section: "store",
key: "node_cache_budget",
requirement_message: crate::config::STORE_NODE_CACHE_BUDGET_REQUIRED,
default_toml: "\"unlimited\"",
env_override: "AION_STORE_NODE_CACHE_BUDGET",
teaching_comment: &[
"The node cache's BYTE ceiling. Required: a node is not a fixed-size thing",
"(a leaf reaches the ~96KB class), so a cache bounded only by its entry",
"count is unbounded in bytes. `unlimited` is the pre-budget behaviour,",
"stated out loud; set a ceiling once you know your box, e.g.:",
" node_cache_budget = { bytes = 1073741824 } # 1 GiB",
],
is_absent: |config| config.store.node_cache_budget.is_none(),
satisfy_in_memory: |config| {
config.store.node_cache_budget = Some(haematite::NodeCacheBudget::Unlimited);
},
},
RequiredFieldDefault {
path: "observability.max_batch_events",
section: "observability",
key: "max_batch_events",
requirement_message: crate::config::OBSERVABILITY_MAX_BATCH_EVENTS_REQUIRED,
default_toml: "64",
env_override: "AION_OBSERVABILITY_MAX_BATCH_EVENTS",
teaching_comment: &[
"The most transcript events one durable commit carries. Every commit",
"re-persists its whole containing storage leaf, so bigger means fewer",
"commits (less store) — and more events lost by a single refused commit.",
],
is_absent: |config| config.observability.max_batch_events.is_none(),
satisfy_in_memory: |config| config.observability.max_batch_events = Some(64),
},
RequiredFieldDefault {
path: "observability.max_batch_hold_ms",
section: "observability",
key: "max_batch_hold_ms",
requirement_message: crate::config::OBSERVABILITY_MAX_BATCH_HOLD_MS_REQUIRED,
default_toml: "200",
env_override: "AION_OBSERVABILITY_MAX_BATCH_HOLD_MS",
teaching_comment: &[
"How long a PARTIAL batch may be held open waiting to fill, in",
"milliseconds. Bounds how much not-yet-durable transcript a crash can",
"cost; 0 means never wait — commit whatever is already queued.",
],
is_absent: |config| config.observability.max_batch_hold_ms.is_none(),
satisfy_in_memory: |config| config.observability.max_batch_hold_ms = Some(200),
},
];
pub(crate) const NOT_UPGRADE_HEALABLE: [(&str, &str); 3] = [
(
crate::config::OUTBOX_RECONCILE_INTERVAL_REQUIRED,
"required only when live outbox reconciliation is being enabled — \
both reconcile knobs absent means reconciliation is OFF by the \
operator's standing choice, and minting an interval would silently \
commission a sweep no one asked for",
),
(
crate::config::OUTBOX_RECONCILE_STALE_AFTER_REQUIRED,
"the second half of the reconcile pair: same reasoning — absence \
selects the feature-off state, so there is no upgrade gap to heal",
),
(
crate::config::AUTHORING_PROJECT_ROOT_REQUIRED,
"required only once authoring.gleam_path commissions the Gleam \
authoring loop, and it names an operator-provisioned project \
directory (gleam.toml, aion_flow, schemas/) that no default can \
invent — a minted path would point the loop at nothing",
),
];
pub(crate) fn boot_required_probe(config: &crate::config::ServerConfig) -> Result<(), ServerError> {
let (store, runtime) = config.clone().into_parts();
if matches!(store.backend, crate::config::StoreBackend::Haematite) {
required_node_cache_budget(&store)?;
}
required_transcript_batch_policy(&runtime)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::{BOOT_REQUIRED_FIELD_DEFAULTS, NOT_UPGRADE_HEALABLE};
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[test]
fn every_required_no_default_message_is_declared_healable_or_not() -> TestResult {
let source_path = format!("{}/src/config/defaults.rs", env!("CARGO_MANIFEST_DIR"));
let source = std::fs::read_to_string(&source_path)
.map_err(|error| format!("cannot read `{source_path}`: {error}"))?;
let required: Vec<String> = string_literals(&source)
.into_iter()
.filter(|literal| literal.contains("is required and has no default"))
.collect();
assert!(
!required.is_empty(),
"the census found no required-no-default messages in {source_path}; \
the extraction is broken, not the config surface"
);
for declared in BOOT_REQUIRED_FIELD_DEFAULTS
.iter()
.map(|entry| entry.requirement_message)
.chain(NOT_UPGRADE_HEALABLE.iter().map(|(message, _)| *message))
{
assert!(
required.iter().any(|found| found == declared),
"the census scanner did not find a declared message in \
{source_path}; the extraction has gone blind: {declared}"
);
}
for message in &required {
let healable = BOOT_REQUIRED_FIELD_DEFAULTS
.iter()
.filter(|entry| entry.requirement_message == message)
.count();
let declared_not = NOT_UPGRADE_HEALABLE
.iter()
.filter(|(unhealable, _reason)| unhealable == message)
.count();
assert_eq!(
healable + declared_not,
1,
"a required-no-default message must appear in exactly one of \
BOOT_REQUIRED_FIELD_DEFAULTS and NOT_UPGRADE_HEALABLE \
(found in {healable} + {declared_not}): {message}"
);
}
Ok(())
}
#[test]
fn requirement_messages_are_unique_across_both_declared_lists() {
let mut seen: Vec<&str> = Vec::new();
for message in BOOT_REQUIRED_FIELD_DEFAULTS
.iter()
.map(|entry| entry.requirement_message)
.chain(NOT_UPGRADE_HEALABLE.iter().map(|(message, _)| *message))
{
assert!(
!seen.contains(&message),
"duplicate requirement message across the declared lists: {message}"
);
seen.push(message);
}
}
#[test]
fn every_declared_env_override_is_an_overlay_arm() -> TestResult {
let source_path = format!("{}/src/config/env.rs", env!("CARGO_MANIFEST_DIR"));
let source = std::fs::read_to_string(&source_path)
.map_err(|error| format!("cannot read `{source_path}`: {error}"))?;
let non_test = source
.split("#[cfg(test)]")
.next()
.ok_or("splitting a string yields at least one piece")?;
let literals = string_literals(non_test);
assert!(
literals.iter().any(|found| found == "AION_STORE_BACKEND"),
"the census scanner did not find the AION_STORE_BACKEND arm in \
{source_path}; the extraction has gone blind"
);
assert!(
!literals
.iter()
.any(|found| found == "AION_CENSUS_BOGUS_CONTROL"),
"the census scanner claims to find a name that exists nowhere in \
{source_path}; the extraction cannot discriminate"
);
for entry in &BOOT_REQUIRED_FIELD_DEFAULTS {
assert!(
literals.iter().any(|found| found == entry.env_override),
"env_override `{}` declared for {} is not an overlay arm \
literal in {source_path}: the H1 override warning keys on it \
and would silently stop firing — rename the table entry to \
match the arm",
entry.env_override,
entry.path
);
}
Ok(())
}
fn string_literals(source: &str) -> Vec<String> {
let mut literals = Vec::new();
let mut chars = source.chars().peekable();
while let Some(c) = chars.next() {
if c != '"' {
continue;
}
let mut literal = String::new();
let mut closed = false;
while let Some(inner) = chars.next() {
match inner {
'"' => {
closed = true;
break;
}
'\\' => match chars.next() {
Some('n') => literal.push('\n'),
Some('t') => literal.push('\t'),
Some('r') => literal.push('\r'),
Some('0') => literal.push('\0'),
Some('\n') => {
while matches!(chars.peek(), Some(' ' | '\t')) {
chars.next();
}
}
Some(escaped) => literal.push(escaped),
None => break,
},
other => literal.push(other),
}
}
if closed {
literals.push(literal);
}
}
literals
}
}