Skip to main content

aion_server/config/
defaults.rs

1//! Default values and operator-facing validation messages for the server config.
2//!
3//! This module holds the pure constants that back the config surface: the
4//! compile-time default listener addresses, every `DEFAULT_*` tuning-knob
5//! default, and the `*_REQUIRED` / `*_EMPTY` / `*_INVALID` operator-facing
6//! validation messages. They are re-exported from the `config` module so every
7//! existing `crate::config::X` path resolves identically.
8
9use std::net::SocketAddr;
10
11pub(super) const DEFAULT_HTTP_ADDRESS: SocketAddr =
12    SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST), 8080);
13pub(super) const DEFAULT_GRPC_ADDRESS: SocketAddr =
14    SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST), 50051);
15
16/// Default SS-5b failover poll interval (milliseconds) when `[store.cluster]`
17/// does not set `failover_poll_interval_ms`.
18pub const DEFAULT_FAILOVER_POLL_INTERVAL_MS: u64 = 500;
19
20/// Default SS-5b debounce count when `[store.cluster]` does not set
21/// `failover_confirmations`.
22pub const DEFAULT_FAILOVER_CONFIRMATIONS: u32 = 3;
23
24/// Generous platform default for `[namespaces] max_in_flight_activities`: the
25/// cluster-wide concurrent in-flight-activity ceiling applied to a namespace
26/// that sets no explicit override. A generous power-of-two (Control-Plane
27/// Phase 2 §6.1 / Open Decision 4) so the default is HEADROOM, not a low hard
28/// cap — a tenant only ever hits a ceiling it (or the operator) raised. Nothing
29/// enforces it yet (P2-Q1 is config + record field only).
30pub const DEFAULT_MAX_IN_FLIGHT_ACTIVITIES: u32 = 1024;
31
32/// Default `event_broadcast_capacity` applied when omitted, so a minimal/empty
33/// config boots without forcing the operator to size a tuning knob. Sized for
34/// global event volume across namespaces; override for high-throughput fleets.
35pub const DEFAULT_EVENT_BROADCAST_CAPACITY: usize = 256;
36
37/// Default `cluster_broadcast_capacity` applied when omitted. Cluster topology
38/// events are low-rate, so a small lag buffer is ample.
39pub const DEFAULT_CLUSTER_BROADCAST_CAPACITY: usize = 64;
40
41/// Operator-facing message for an explicitly zero `event_broadcast_capacity`
42/// (omitting the key uses [`DEFAULT_EVENT_BROADCAST_CAPACITY`]; an explicit zero
43/// is a genuine misconfiguration — a zero-capacity channel streams nothing).
44pub(crate) const EVENT_BROADCAST_CAPACITY_REQUIRED: &str = "websocket.event_broadcast_capacity must be a positive integer when set (the server always mounts /events/stream, so a zero-capacity channel would stream nothing); omit websocket.event_broadcast_capacity (or AION_WEBSOCKET_EVENT_BROADCAST_CAPACITY) to use the default, or set it to a positive integer";
45
46/// Operator-facing message for an explicitly zero `cluster_broadcast_capacity`
47/// (omitting the key uses [`DEFAULT_CLUSTER_BROADCAST_CAPACITY`]).
48pub(crate) const CLUSTER_BROADCAST_CAPACITY_REQUIRED: &str = "websocket.cluster_broadcast_capacity must be a positive integer when set (the server always mounts the WS3 cluster subscription on /events/stream); omit websocket.cluster_broadcast_capacity (or AION_WEBSOCKET_CLUSTER_BROADCAST_CAPACITY) to use the default, or set it to a positive integer";
49
50/// Default `deploy.max_archive_bytes` applied when omitted and deploy is
51/// enabled. A conservative 64 MiB upload ceiling: large enough for real
52/// workflow packages, small enough to bound a single upload. Overridable.
53pub const DEFAULT_DEPLOY_MAX_ARCHIVE_BYTES: u64 = 64 * 1024 * 1024;
54
55/// Default `deploy.max_inflated_bytes` applied when omitted and deploy is
56/// enabled. A conservative 256 MiB decompressed-contents ceiling (4x the
57/// archive ceiling) that still hard-caps DEFLATE-bomb inflation. Overridable.
58pub const DEFAULT_DEPLOY_MAX_INFLATED_BYTES: u64 = 256 * 1024 * 1024;
59
60/// Operator-facing message for an explicitly zero `deploy.max_archive_bytes`
61/// (omitting the key uses [`DEFAULT_DEPLOY_MAX_ARCHIVE_BYTES`]; an explicit zero
62/// is a genuine misconfiguration — a zero ceiling refuses every upload).
63pub(crate) const DEPLOY_MAX_ARCHIVE_BYTES_REQUIRED: &str = "deploy.max_archive_bytes must be a positive number of bytes when set (a zero archive ceiling would refuse every upload); omit deploy.max_archive_bytes (or AION_DEPLOY_MAX_ARCHIVE_BYTES) to use the conservative default, or set it to a positive number of bytes sized for the deployment's packages";
64
65/// Operator-facing message for an explicitly zero `deploy.max_inflated_bytes`
66/// (omitting the key uses [`DEFAULT_DEPLOY_MAX_INFLATED_BYTES`]; an explicit
67/// zero is a genuine misconfiguration — a compressed upload under
68/// `deploy.max_archive_bytes` can inflate ~1000:1).
69pub(crate) const DEPLOY_MAX_INFLATED_BYTES_REQUIRED: &str = "deploy.max_inflated_bytes must be a positive number of bytes when set (a compressed upload under deploy.max_archive_bytes can inflate ~1000:1, so a zero inflate ceiling is incoherent); omit deploy.max_inflated_bytes (or AION_DEPLOY_MAX_INFLATED_BYTES) to use the conservative default, or set it to a positive number of bytes no smaller than deploy.max_archive_bytes";
70
71/// Default `query_timeout_ms` applied when omitted, so a minimal/empty config
72/// boots with a sane workflow-query reply deadline instead of failing startup.
73pub const DEFAULT_QUERY_TIMEOUT_MS: u64 = 10_000;
74
75/// Operator-facing message for an explicitly zero `query_timeout_ms` (omitting
76/// the key uses [`DEFAULT_QUERY_TIMEOUT_MS`]; an explicit zero is a genuine
77/// misconfiguration — a zero deadline would fail every query immediately).
78pub(crate) const QUERY_TIMEOUT_REQUIRED: &str = "runtime.query_timeout_ms must be a positive integer when set (the server always mounts /workflows/query, so a zero deadline would fail every query immediately); omit runtime.query_timeout_ms (or AION_RUNTIME_QUERY_TIMEOUT_MS) to use the default, or set it to a positive number of milliseconds";
79
80/// Default `outbox.poll_interval_ms` applied when omitted and the dispatcher is
81/// enabled. A tight 20ms claim cadence keeps fan-out latency low; raise it for
82/// lower-rate, larger-batch sweeps.
83pub const DEFAULT_OUTBOX_POLL_INTERVAL_MS: u64 = 20;
84
85/// Default `outbox.batch_size` applied when omitted and the dispatcher is
86/// enabled: rows claimed per sweep.
87pub const DEFAULT_OUTBOX_BATCH_SIZE: u32 = 16;
88
89/// Default `outbox.max_attempts` applied when omitted and the dispatcher is
90/// enabled: dispatch attempts before a row is dead-lettered to `failed`.
91pub const DEFAULT_OUTBOX_MAX_ATTEMPTS: u32 = 5;
92
93/// Default `outbox.backoff_base_ms` applied when omitted and the dispatcher is
94/// enabled: the first retry's backoff, in milliseconds.
95pub const DEFAULT_OUTBOX_BACKOFF_BASE_MS: u64 = 50;
96
97/// Default `outbox.backoff_multiplier` applied when omitted and the dispatcher
98/// is enabled: geometric growth factor per prior attempt (must be >= 1).
99pub const DEFAULT_OUTBOX_BACKOFF_MULTIPLIER: u32 = 2;
100
101/// Default `outbox.backoff_max_ms` applied when omitted and the dispatcher is
102/// enabled: the per-retry backoff ceiling, in milliseconds.
103pub const DEFAULT_OUTBOX_BACKOFF_MAX_MS: u64 = 1_000;
104
105/// Operator-facing message for an explicitly zero `outbox.poll_interval_ms`
106/// (omitting the key uses [`DEFAULT_OUTBOX_POLL_INTERVAL_MS`]).
107pub(crate) const OUTBOX_POLL_INTERVAL_REQUIRED: &str = "outbox.poll_interval_ms must be a positive number of milliseconds when set (a zero claim cadence is invalid); omit outbox.poll_interval_ms (or AION_OUTBOX_POLL_INTERVAL_MS) to use the default, or set it to a positive number of milliseconds sized for fan-out volume and latency";
108
109/// Operator-facing message for an explicitly zero `outbox.batch_size`
110/// (omitting the key uses [`DEFAULT_OUTBOX_BATCH_SIZE`]).
111pub(crate) const OUTBOX_BATCH_SIZE_REQUIRED: &str = "outbox.batch_size must be a positive integer when set (a zero per-sweep claim ceiling claims nothing); omit outbox.batch_size (or AION_OUTBOX_BATCH_SIZE) to use the default, or set it to a positive integer";
112
113/// Operator-facing message for an explicitly zero `outbox.max_attempts`
114/// (omitting the key uses [`DEFAULT_OUTBOX_MAX_ATTEMPTS`]).
115pub(crate) const OUTBOX_MAX_ATTEMPTS_REQUIRED: &str = "outbox.max_attempts must be a positive integer when set (a zero retry budget dead-letters before the first attempt); omit outbox.max_attempts (or AION_OUTBOX_MAX_ATTEMPTS) to use the default, or set it to a positive integer";
116
117/// Operator-facing message for an explicitly zero `outbox.backoff_base_ms`
118/// (omitting the key uses [`DEFAULT_OUTBOX_BACKOFF_BASE_MS`]).
119pub(crate) const OUTBOX_BACKOFF_BASE_REQUIRED: &str = "outbox.backoff_base_ms must be a positive number of milliseconds when set (a zero first-retry backoff is invalid); omit outbox.backoff_base_ms (or AION_OUTBOX_BACKOFF_BASE_MS) to use the default, or set it to a positive number of milliseconds";
120
121/// Operator-facing message for an explicitly zero `outbox.backoff_multiplier`
122/// (omitting the key uses [`DEFAULT_OUTBOX_BACKOFF_MULTIPLIER`]).
123pub(crate) const OUTBOX_BACKOFF_MULTIPLIER_REQUIRED: &str = "outbox.backoff_multiplier must be at least one when set so backoff never shrinks (a zero multiplier collapses the backoff curve); omit outbox.backoff_multiplier (or AION_OUTBOX_BACKOFF_MULTIPLIER) to use the default, or set it to a positive integer";
124
125/// Operator-facing message for an explicitly undersized `outbox.backoff_max_ms`
126/// (omitting the key uses [`DEFAULT_OUTBOX_BACKOFF_MAX_MS`]; an explicit value
127/// must be at least `outbox.backoff_base_ms`).
128pub(crate) const OUTBOX_BACKOFF_MAX_REQUIRED: &str = "outbox.backoff_max_ms must be at least outbox.backoff_base_ms when set (a ceiling below the base would cap the very first retry below its own backoff); omit outbox.backoff_max_ms (or AION_OUTBOX_BACKOFF_MAX_MS) to use the default, or set it to a positive number of milliseconds no smaller than outbox.backoff_base_ms";
129
130/// Operator-facing message for an absent or zero `outbox.reconcile_interval_ms`.
131pub(crate) const OUTBOX_RECONCILE_INTERVAL_REQUIRED: &str = "outbox.reconcile_interval_ms is required and has no default when live outbox reconciliation is enabled: set both outbox.reconcile_interval_ms and outbox.reconcile_stale_after_ms (or AION_OUTBOX_RECONCILE_INTERVAL_MS / AION_OUTBOX_RECONCILE_STALE_AFTER_MS) to positive millisecond values, or omit both to leave reconciliation disabled";
132
133/// Operator-facing message for an absent or zero `outbox.reconcile_stale_after_ms`.
134pub(crate) const OUTBOX_RECONCILE_STALE_AFTER_REQUIRED: &str = "outbox.reconcile_stale_after_ms is required and has no default when live outbox reconciliation is enabled: set both outbox.reconcile_interval_ms and outbox.reconcile_stale_after_ms (or AION_OUTBOX_RECONCILE_INTERVAL_MS / AION_OUTBOX_RECONCILE_STALE_AFTER_MS) to positive millisecond values, or omit both to leave reconciliation disabled";
135
136/// Operator-facing message for an absent or empty `authoring.gleam_path` value.
137pub(crate) const AUTHORING_GLEAM_PATH_EMPTY: &str = "authoring.gleam_path must not be empty when set: it names the external gleam binary the authoring loop spawns; set authoring.gleam_path (or AION_AUTHORING_GLEAM_PATH) to the path of a runnable gleam binary, or remove it to leave the authoring surface dark";
138
139/// Operator-facing message for an absent `authoring.project_root` when the
140/// authoring surface is commissioned.
141pub(crate) const AUTHORING_PROJECT_ROOT_REQUIRED: &str = "authoring.project_root is required and has no default when authoring.gleam_path is set: submitted Gleam source is written into and packaged from a built project, so the operator must provision and name the project root (a directory with gleam.toml, the aion_flow dependency, workflow.toml, and schemas/); set authoring.project_root (or AION_AUTHORING_PROJECT_ROOT)";
142
143/// Default `observability.max_event_bytes`: the ceiling on one persisted
144/// transcript event's serialized size. 256 KiB comfortably holds real
145/// tool-result payloads while bounding what one hostile/verbose harness line
146/// can write to the durable `O` keyspace. Overridable per deployment.
147pub const DEFAULT_OBSERVABILITY_MAX_EVENT_BYTES: usize = 256 * 1024;
148
149/// Default `observability.max_stream_events`: the ceiling on retained events
150/// per `(workflow, activity, attempt)` transcript stream. Past it one marker
151/// record is retained and further events stay live-only. Overridable.
152pub const DEFAULT_OBSERVABILITY_MAX_STREAM_EVENTS: u64 = 20_000;
153
154/// Operator-facing message for an explicitly zero `observability.max_event_bytes`
155/// (omitting the key uses [`DEFAULT_OBSERVABILITY_MAX_EVENT_BYTES`]; a zero
156/// ceiling would truncate every transcript event to nothing).
157pub(crate) const OBSERVABILITY_MAX_EVENT_BYTES_REQUIRED: &str = "observability.max_event_bytes must be a positive number of bytes when set (a zero ceiling would truncate every retained transcript event to nothing); omit observability.max_event_bytes (or AION_OBSERVABILITY_MAX_EVENT_BYTES) to use the default, or set it to a positive number of bytes";
158
159/// Operator-facing message for an explicitly zero `observability.max_stream_events`
160/// (omitting the key uses [`DEFAULT_OBSERVABILITY_MAX_STREAM_EVENTS`]; a zero
161/// cap would retain no transcript at all).
162pub(crate) const OBSERVABILITY_MAX_STREAM_EVENTS_REQUIRED: &str = "observability.max_stream_events must be a positive integer when set (a zero per-stream cap would retain no transcript at all); omit observability.max_stream_events (or AION_OBSERVABILITY_MAX_STREAM_EVENTS) to use the default, or set it to a positive integer";
163
164/// Default haematite data directory for the unconfigured durable backend.
165///
166/// An empty `[store]` section (or no config file at all) now selects the ablative
167/// stack's haematite event store rooted here, so a stock server is durable out of
168/// the box. `validate()` requires a non-empty `data_dir` when the backend is
169/// haematite, so the default must supply one or an otherwise-empty config would
170/// fail validation. Operators override it with `store.data_dir` /
171/// `AION_STORE_DATA_DIR`, or opt out with `backend = "memory"` / `"libsql"`.
172pub const DEFAULT_HAEMATITE_DATA_DIR: &str = "aion-data";
173
174/// Operator-facing message for an empty or malformed `cors_allowed_origins`
175/// entry.
176pub(crate) const CORS_ALLOWED_ORIGIN_INVALID: &str = "server.cors_allowed_origins entries must each be a valid HTTP origin (scheme://host[:port], e.g. http://localhost:5173) with no path or trailing slash";