ontoref-code/crates/ontoref-daemon/src/config.rs

221 lines
6 KiB
Rust
Raw Normal View History

feat: config surface, NCL contracts, override-layer mutation, on+re update Config surface — per-project config introspection, coherence verification, and audited mutation without destroying NCL structure (ADR-008): - crates/ontoref-daemon/src/config.rs — typed DaemonNclConfig (parse-at-boundary pattern); all section structs derive ConfigFields + config_section(id, ncl_file) emitting inventory::submit!(ConfigFieldsEntry{...}) at link time - crates/ontoref-derive/src/lib.rs — #[derive(ConfigFields)] proc-macro; serde rename support; serde_rename_of() helper extracted to fix excessive_nesting - crates/ontoref-daemon/src/main.rs — 3-tuple bootstrap block (nickel_import_path, loaded_ncl_config: Option<DaemonNclConfig>, stdin_raw); apply_ui_config takes &UiConfig; NATS call site typed; resolve_asset_dir cfg(feature = "ui") - crates/ontoref-daemon/src/api.rs — config GET/PUT endpoints, quickref, coherence, cross-project comparison; index_section_fields() extracted (excessive_nesting) - crates/ontoref-daemon/src/config_coherence.rs — multi-consumer coherence; merge_meta_into_section() extracted; and() replaces unnecessary and_then NCL contracts for ontoref's own config: - .ontoref/contracts.ncl — LogConfig (LogLevel, LogRotation, PositiveInt) and DaemonConfig (Port, optional overrides); std.contract.from_validator throughout - .ontoref/config.ncl — log | C.LogConfig applied - .ontology/manifest.ncl — contracts_path, log/daemon contract refs, daemon section with DaemonRuntimeConfig consumer and 7 declared fields Protocol: - adrs/adr-008-ncl-first-config-validation-and-override-layer.ncl — NCL contracts as single validation gate; Rust structs are contract-trusted; override-layer mutation writes {section}.overrides.ncl + _overrides_meta, never touches source on+re update: - .ontology/core.ncl — config-surface node (28 practices); adr-lifecycle extended to adr-007 + adr-008; 6 new edges (ManifestsIn daemon, DependsOn ontology-crate, Complements api-catalog-surface/dag-formalized/self-describing/adopt-ontoref) - .ontology/state.ncl — protocol-maturity blocker and self-description-coverage catalyst updated for session 2026-03-26 - README.md / CHANGELOG.md updated
2026-03-26 20:20:22 +00:00
/// Typed representation of `.ontoref/config.ncl`.
///
/// Every section that the daemon reads from its own config file is captured
/// here with `#[derive(ConfigFields)]` so that `inventory` registers the
/// consumed fields at link time. The coherence endpoint can then compare
/// these registrations against the live NCL export without needing a
/// hand-maintained `fields` list in `manifest.ncl`.
///
/// All fields carry `#[serde(default)]` — a project may omit any section and
/// the daemon degrades gracefully rather than failing to start.
use ontoref_ontology::ConfigFields;
use serde::Deserialize;
/// Full deserialized view of `.ontoref/config.ncl`.
#[derive(Debug, Deserialize, Default)]
pub struct DaemonNclConfig {
#[serde(default)]
pub nickel_import_paths: Vec<String>,
#[serde(default)]
pub ui: UiConfig,
#[serde(default)]
pub log: LogConfig,
#[serde(default)]
pub mode_run: ModeRunConfig,
#[serde(default)]
pub nats_events: NatsEventsConfig,
#[serde(default)]
pub actor_init: Vec<ActorInit>,
#[serde(default)]
pub quick_actions: Vec<QuickAction>,
#[serde(default)]
pub daemon: DaemonRuntimeConfig,
#[cfg(feature = "db")]
#[serde(default)]
pub db: DbConfig,
}
/// `ui` section — template and asset paths, optional TLS cert overrides.
#[derive(Debug, Deserialize, Default, ConfigFields)]
#[config_section(id = "ui", ncl_file = ".ontoref/config.ncl")]
pub struct UiConfig {
#[serde(default)]
pub templates_dir: String,
#[serde(default)]
pub public_dir: String,
#[serde(default)]
pub tls_cert: String,
#[serde(default)]
pub tls_key: String,
#[serde(default)]
pub logo: String,
#[serde(default)]
pub logo_dark: String,
}
/// `log` section — structured logging policy.
#[derive(Debug, Deserialize, ConfigFields)]
#[config_section(id = "log", ncl_file = ".ontoref/config.ncl")]
pub struct LogConfig {
#[serde(default = "default_log_level")]
pub level: String,
#[serde(default = "default_log_path")]
pub path: String,
#[serde(default = "default_rotation")]
pub rotation: String,
#[serde(default)]
pub compress: bool,
#[serde(default = "default_log_archive")]
pub archive: String,
#[serde(default = "default_max_files")]
pub max_files: u32,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
level: default_log_level(),
path: default_log_path(),
rotation: default_rotation(),
compress: false,
archive: default_log_archive(),
max_files: default_max_files(),
}
}
}
fn default_log_level() -> String {
"info".into()
}
fn default_log_path() -> String {
"logs".into()
}
fn default_rotation() -> String {
"daily".into()
}
fn default_log_archive() -> String {
"logs-archive".into()
}
fn default_max_files() -> u32 {
7
}
/// `nats_events` section — NATS JetStream integration.
#[derive(Debug, Deserialize, Default, ConfigFields)]
#[config_section(id = "nats_events", ncl_file = ".ontoref/config.ncl")]
pub struct NatsEventsConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_nats_url")]
pub url: String,
#[serde(default)]
pub emit: Vec<String>,
#[serde(default)]
pub subscribe: Vec<String>,
#[serde(default)]
pub handlers_dir: String,
#[serde(default)]
pub nkey_seed: Option<String>,
#[serde(default)]
pub require_signed_messages: bool,
#[serde(default)]
pub trusted_nkeys: Vec<String>,
#[serde(default)]
pub streams_config: String,
}
fn default_nats_url() -> String {
"nats://localhost:4222".into()
}
/// `mode_run` section — per-actor mode execution ACL.
#[derive(Debug, Deserialize, Default, ConfigFields)]
#[config_section(id = "mode_run", ncl_file = ".ontoref/config.ncl")]
pub struct ModeRunConfig {
#[serde(default)]
pub rules: Vec<ModeRunRule>,
}
#[derive(Debug, Deserialize, Default)]
pub struct ModeRunRule {
#[serde(default)]
pub when: ModeRunWhen,
#[serde(default)]
pub allow: bool,
#[serde(default)]
pub reason: String,
}
#[derive(Debug, Deserialize, Default)]
pub struct ModeRunWhen {
#[serde(default)]
pub mode_id: Option<String>,
#[serde(default)]
pub actor: Option<String>,
}
/// One entry in the `actor_init` array.
#[derive(Debug, Deserialize, Default)]
pub struct ActorInit {
#[serde(default)]
pub actor: String,
#[serde(default)]
pub mode: String,
#[serde(default)]
pub auto_run: bool,
}
/// One entry in the `quick_actions` array.
#[derive(Debug, Deserialize, Default)]
pub struct QuickAction {
#[serde(default)]
pub id: String,
#[serde(default)]
pub label: String,
#[serde(default)]
pub icon: String,
#[serde(default)]
pub category: String,
#[serde(default)]
pub mode: String,
#[serde(default)]
pub actors: Vec<String>,
}
/// `daemon` section — overrides for CLI defaults set at startup.
#[derive(Debug, Deserialize, Default, ConfigFields)]
#[config_section(id = "daemon", ncl_file = ".ontoref/config.ncl")]
pub struct DaemonRuntimeConfig {
#[serde(default)]
pub port: Option<u16>,
#[serde(default)]
pub idle_timeout: Option<u64>,
#[serde(default)]
pub invalidation_interval: Option<u64>,
#[serde(default)]
pub actor_sweep_interval: Option<u64>,
#[serde(default)]
pub actor_stale_timeout: Option<u64>,
#[serde(default)]
pub max_notifications: Option<usize>,
#[serde(default)]
pub notification_ack_required: Vec<String>,
}
/// `db` section — SurrealDB connection (feature-gated).
#[cfg(feature = "db")]
#[derive(Debug, Deserialize, Default, ConfigFields)]
#[config_section(id = "db", ncl_file = ".ontoref/config.ncl")]
pub struct DbConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub url: String,
#[serde(default)]
pub namespace: String,
#[serde(default)]
pub username: String,
#[serde(default)]
pub password: String,
}