1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! Configuration exists so the app can answer three questions consistently:
//! what keys are legal, which source wins, and what file edits are allowed.
//!
//! The mental model is:
//!
//! - [`crate::config::LoaderPipeline`] materializes source layers from files,
//! environment, and in-memory overrides.
//! - [`crate::config::ConfigResolver`] applies source precedence, scope
//! precedence, interpolation, and schema adaptation.
//! - [`crate::config::RuntimeConfig`] lowers the resolved config into the
//! smaller runtime view used by the app and REPL.
//! - [`crate::config::set_scoped_value_in_toml`] edits TOML-backed config files
//! while preserving the same schema and scope rules used at runtime.
//!
//! Quick starts:
//!
//! In-memory or test-only config resolution:
//!
//! ```
//! use osp_cli::config::{ConfigLayer, LoaderPipeline, ResolveOptions, StaticLayerLoader};
//!
//! let mut defaults = ConfigLayer::default();
//! defaults.set("profile.default", "default");
//! defaults.set("theme.name", "dracula");
//!
//! let resolved = LoaderPipeline::new(StaticLayerLoader::new(defaults))
//! .resolve(ResolveOptions::new().with_terminal("cli"))
//! .unwrap();
//!
//! assert_eq!(resolved.terminal(), Some("cli"));
//! assert_eq!(resolved.get_string("theme.name"), Some("dracula"));
//! ```
//!
//! Host-style bootstrap with runtime defaults plus standard path discovery:
//!
//! ```no_run
//! use osp_cli::config::{
//! ResolveOptions, RuntimeConfig, RuntimeConfigPaths, RuntimeDefaults,
//! RuntimeLoadOptions, build_runtime_pipeline,
//! };
//!
//! let defaults = RuntimeDefaults::from_process_env("dracula", "osp> ").to_layer();
//! let paths = RuntimeConfigPaths::discover();
//! let presentation = None;
//! let cli = None;
//! let session = None;
//!
//! let resolved = build_runtime_pipeline(
//! defaults,
//! presentation,
//! &paths,
//! RuntimeLoadOptions::default(),
//! cli,
//! session,
//! )
//! .resolve(ResolveOptions::new().with_terminal("cli"))?;
//!
//! let runtime = RuntimeConfig::from_resolved(&resolved);
//! assert_eq!(runtime.active_profile, resolved.active_profile());
//! # Ok::<(), osp_cli::config::ConfigError>(())
//! ```
//!
//! On-disk config mutation:
//!
//! - use [`crate::config::set_scoped_value_in_toml`] and
//! [`crate::config::unset_scoped_value_in_toml`] instead of editing TOML by
//! hand
//! - use [`crate::config::ConfigResolver::explain_key`] when the main question
//! is "why did this value win?"
//!
//! Broad-strokes flow:
//!
//! ```text
//! files / env / session overrides
//! │
//! ▼ [ LoaderPipeline ]
//! ConfigLayer values + scope metadata
//! │
//! ▼ [ ConfigResolver ]
//! precedence + interpolation + type adaptation
//! │
//! ├── ResolvedConfig (full provenance-aware map)
//! ├── RuntimeConfig (smaller runtime view used by the host)
//! └── config explain (why this winner won)
//! ```
//!
//! Read this module when you need to answer "where did this config value come
//! from?", "why did this value win?", or "what writes are legal for this key?".
//!
//! Most callers should not be assembling bespoke config logic. The normal path
//! is:
//!
//! - load layers through [`crate::config::LoaderPipeline`]
//! - resolve once through [`crate::config::ConfigResolver`]
//! - consume the result as [`crate::config::ResolvedConfig`] or
//! [`crate::config::RuntimeConfig`]
//! - for host-style startup, prefer [`crate::config::RuntimeDefaults`],
//! [`crate::config::RuntimeConfigPaths`], and
//! [`crate::config::build_runtime_pipeline`] over hand-rolled file/env
//! discovery
//!
//! The same discipline should hold for writes. File edits belong through the
//! store helpers here so `config set`, runtime resolution, and `config explain`
//! stay aligned. A hand-rolled file edit path almost always creates drift.
//!
//! Contract:
//!
//! - source precedence and scope precedence are defined here, not in callers
//! - schema validation and config-store editing should stay aligned
//! - other modules should not hand-roll config merging or scope filtering
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;