Skip to main content

pharos_runtime_core/
config.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3use std::sync::Arc;
4
5use serde_json::Value;
6/// Implementation status marker for the shared config module.
7///
8/// # Examples
9///
10/// ```rust,ignore
11/// let _ = pharos_runtime_core::config::MODULE_STATUS;
12/// ```
13///
14/// # Related
15///
16/// - [`crate::config`] for the surrounding module surface.
17pub const MODULE_STATUS: &str = "COMPLETE";
18
19#[derive(Debug, Clone, Eq, PartialEq)]
20/// Runtime resource ceilings applied to responses, queries, jobs, and integrations.
21///
22/// # Fields
23///
24/// - `max_response_bytes`: Stored field on `ResourceBudgets`.
25/// - `max_query_result_bytes`: Stored field on `ResourceBudgets`.
26/// - `max_report_bytes`: Stored field on `ResourceBudgets`.
27/// - `max_state_section_items`: Stored field on `ResourceBudgets`.
28/// - `max_workflow_effects`: Stored field on `ResourceBudgets`.
29/// - `max_jobs_per_poll`: Stored field on `ResourceBudgets`.
30/// - `integration_connect_timeout_ms`: Stored field on `ResourceBudgets`.
31/// - `integration_operation_timeout_ms`: Stored field on `ResourceBudgets`.
32/// - `integration_max_response_bytes`: Stored field on `ResourceBudgets`.
33///
34/// # Examples
35///
36/// ```rust,ignore
37/// let _ = std::mem::size_of::<pharos_runtime_core::config::ResourceBudgets>();
38/// ```
39///
40/// # Related
41///
42/// - [`crate::config`] for constructors, helpers, and neighboring runtime types.
43pub struct ResourceBudgets {
44    pub max_response_bytes: usize,
45    pub max_query_result_bytes: usize,
46    pub max_report_bytes: usize,
47    pub max_state_section_items: usize,
48    pub max_workflow_effects: usize,
49    pub max_jobs_per_poll: usize,
50    pub integration_connect_timeout_ms: u64,
51    pub integration_operation_timeout_ms: u64,
52    pub integration_max_response_bytes: usize,
53}
54
55impl Default for ResourceBudgets {
56    /// Returns the default default.
57    fn default() -> Self {
58        Self {
59            max_response_bytes: 16 * 1024 * 1024,
60            max_query_result_bytes: 8 * 1024 * 1024,
61            max_report_bytes: 8 * 1024 * 1024,
62            max_state_section_items: 10_000,
63            max_workflow_effects: 1_024,
64            max_jobs_per_poll: 128,
65            integration_connect_timeout_ms: 5_000,
66            integration_operation_timeout_ms: 15_000,
67            integration_max_response_bytes: 8 * 1024,
68        }
69    }
70}
71
72#[derive(Debug, Clone, Default, Eq, PartialEq)]
73/// Parsed top-level runtime launch configuration.
74///
75/// # Fields
76///
77/// - `kind`: Stored field on `ServeConfig`.
78/// - `target_id`: Stored field on `ServeConfig`.
79/// - `conf_path`: Stored field on `ServeConfig`.
80/// - `app_dir`: Stored field on `ServeConfig`.
81/// - `build_dir`: Stored field on `ServeConfig`.
82/// - `build_dir_from_conf`: Stored field on `ServeConfig`.
83/// - `clean`: Stored field on `ServeConfig`.
84/// - `state_dir`: Stored field on `ServeConfig`.
85/// - `state_dir_from_conf`: Stored field on `ServeConfig`.
86/// - `state_path`: Stored field on `ServeConfig`.
87/// - `state_path_from_conf`: Stored field on `ServeConfig`.
88/// - `base_path`: Stored field on `ServeConfig`.
89/// - `base_path_from_conf`: Stored field on `ServeConfig`.
90/// - `log_path`: Stored field on `ServeConfig`.
91/// - `log_path_from_conf`: Stored field on `ServeConfig`.
92/// - `log_level`: Stored field on `ServeConfig`.
93/// - `log_level_from_conf`: Stored field on `ServeConfig`.
94/// - `trusted_proxy_cidrs`: Stored field on `ServeConfig`.
95/// - `trusted_proxy_cidrs_from_conf`: Stored field on `ServeConfig`.
96/// - `redis_enabled`: Stored field on `ServeConfig`.
97/// - `redis_enabled_from_conf`: Stored field on `ServeConfig`.
98/// - `redis_host`: Stored field on `ServeConfig`.
99/// - `redis_host_from_conf`: Stored field on `ServeConfig`.
100/// - `redis_port`: Stored field on `ServeConfig`.
101/// - `redis_port_from_conf`: Stored field on `ServeConfig`.
102/// - `redis_auth_user`: Stored field on `ServeConfig`.
103/// - `redis_auth_user_from_conf`: Stored field on `ServeConfig`.
104/// - `redis_auth_token_env`: Stored field on `ServeConfig`.
105/// - `redis_auth_token_env_from_conf`: Stored field on `ServeConfig`.
106/// - `redis_db_index`: Stored field on `ServeConfig`.
107/// - `redis_db_index_from_conf`: Stored field on `ServeConfig`.
108/// - `redis_connect_timeout_ms`: Stored field on `ServeConfig`.
109/// - `redis_connect_timeout_ms_from_conf`: Stored field on `ServeConfig`.
110/// - `redis_read_timeout_ms`: Stored field on `ServeConfig`.
111/// - `redis_read_timeout_ms_from_conf`: Stored field on `ServeConfig`.
112/// - `redis_cache_prefix`: Stored field on `ServeConfig`.
113/// - `redis_cache_prefix_from_conf`: Stored field on `ServeConfig`.
114/// - `redis_backoff_seconds`: Stored field on `ServeConfig`.
115/// - `redis_backoff_seconds_from_conf`: Stored field on `ServeConfig`.
116/// - `redis_backoff_max_seconds`: Stored field on `ServeConfig`.
117/// - `redis_backoff_max_seconds_from_conf`: Stored field on `ServeConfig`.
118/// - `redis_tls_mode`: Stored field on `ServeConfig`.
119/// - `redis_tls_mode_from_conf`: Stored field on `ServeConfig`.
120/// - `redis_tls_root_cert`: Stored field on `ServeConfig`.
121/// - `redis_tls_root_cert_from_conf`: Stored field on `ServeConfig`.
122/// - `redis_tls_cert`: Stored field on `ServeConfig`.
123/// - `redis_tls_cert_from_conf`: Stored field on `ServeConfig`.
124/// - `redis_tls_key`: Stored field on `ServeConfig`.
125/// - `redis_tls_key_from_conf`: Stored field on `ServeConfig`.
126/// - `redis_require_peer_name_match`: Stored field on `ServeConfig`.
127/// - `redis_require_peer_name_match_from_conf`: Stored field on `ServeConfig`.
128/// - `redis_cache_sessions_enabled`: Stored field on `ServeConfig`.
129/// - `redis_cache_sessions_enabled_from_conf`: Stored field on `ServeConfig`.
130/// - `redis_cache_template_source_enabled`: Stored field on `ServeConfig`.
131/// - `redis_cache_template_source_enabled_from_conf`: Stored field on `ServeConfig`.
132/// - `redis_cache_route_match_enabled`: Stored field on `ServeConfig`.
133/// - `redis_cache_route_match_enabled_from_conf`: Stored field on `ServeConfig`.
134/// - `redis_cache_config_allowlist_enabled`: Stored field on `ServeConfig`.
135/// - `redis_cache_config_allowlist_enabled_from_conf`: Stored field on `ServeConfig`.
136/// - `socket_path`: Stored field on `ServeConfig`.
137/// - `socket_path_from_conf`: Stored field on `ServeConfig`.
138/// - `host`: Stored field on `ServeConfig`.
139/// - `host_from_conf`: Stored field on `ServeConfig`.
140/// - `port`: Stored field on `ServeConfig`.
141/// - `port_from_conf`: Stored field on `ServeConfig`.
142/// - `root`: Stored field on `ServeConfig`.
143/// - `seed_state`: Stored field on `ServeConfig`.
144///
145/// # Examples
146///
147/// ```rust,ignore
148/// let _ = std::mem::size_of::<pharos_runtime_core::config::ServeConfig>();
149/// ```
150///
151/// # Related
152///
153/// - [`crate::config`] for constructors, helpers, and neighboring runtime types.
154pub struct ServeConfig {
155    pub kind: Option<String>,
156    pub target_id: Option<String>,
157    pub conf_path: Option<String>,
158    pub app_dir: Option<String>,
159    pub build_dir: Option<String>,
160    pub build_dir_from_conf: bool,
161    pub clean: bool,
162    pub state_dir: Option<String>,
163    pub state_dir_from_conf: bool,
164    pub state_path: Option<String>,
165    pub state_path_from_conf: bool,
166    pub base_path: Option<String>,
167    pub base_path_from_conf: bool,
168    pub log_path: Option<String>,
169    pub log_path_from_conf: bool,
170    pub log_level: Option<String>,
171    pub log_level_from_conf: bool,
172    pub trusted_proxy_cidrs: Option<String>,
173    pub trusted_proxy_cidrs_from_conf: bool,
174    pub redis_enabled: bool,
175    pub redis_enabled_from_conf: bool,
176    pub redis_host: Option<String>,
177    pub redis_host_from_conf: bool,
178    pub redis_port: Option<String>,
179    pub redis_port_from_conf: bool,
180    pub redis_auth_user: Option<String>,
181    pub redis_auth_user_from_conf: bool,
182    pub redis_auth_token_env: Option<String>,
183    pub redis_auth_token_env_from_conf: bool,
184    pub redis_db_index: Option<String>,
185    pub redis_db_index_from_conf: bool,
186    pub redis_connect_timeout_ms: Option<String>,
187    pub redis_connect_timeout_ms_from_conf: bool,
188    pub redis_read_timeout_ms: Option<String>,
189    pub redis_read_timeout_ms_from_conf: bool,
190    pub redis_cache_prefix: Option<String>,
191    pub redis_cache_prefix_from_conf: bool,
192    pub redis_backoff_seconds: Option<String>,
193    pub redis_backoff_seconds_from_conf: bool,
194    pub redis_backoff_max_seconds: Option<String>,
195    pub redis_backoff_max_seconds_from_conf: bool,
196    pub redis_tls_mode: Option<String>,
197    pub redis_tls_mode_from_conf: bool,
198    pub redis_tls_root_cert: Option<String>,
199    pub redis_tls_root_cert_from_conf: bool,
200    pub redis_tls_cert: Option<String>,
201    pub redis_tls_cert_from_conf: bool,
202    pub redis_tls_key: Option<String>,
203    pub redis_tls_key_from_conf: bool,
204    pub redis_require_peer_name_match: bool,
205    pub redis_require_peer_name_match_from_conf: bool,
206    pub redis_cache_sessions_enabled: bool,
207    pub redis_cache_sessions_enabled_from_conf: bool,
208    pub redis_cache_template_source_enabled: bool,
209    pub redis_cache_template_source_enabled_from_conf: bool,
210    pub redis_cache_route_match_enabled: bool,
211    pub redis_cache_route_match_enabled_from_conf: bool,
212    pub redis_cache_config_allowlist_enabled: bool,
213    pub redis_cache_config_allowlist_enabled_from_conf: bool,
214    pub socket_path: Option<String>,
215    pub socket_path_from_conf: bool,
216    pub host: Option<String>,
217    pub host_from_conf: bool,
218    pub port: Option<String>,
219    pub port_from_conf: bool,
220    pub root: Option<String>,
221    pub seed_state: bool,
222}
223
224#[derive(Debug, Clone, Default, Eq, PartialEq)]
225/// Resolved app-local config values loaded from `app.conf`.
226///
227/// # Fields
228///
229/// - `conf_path`: Stored field on `AppLocalConfig`.
230/// - `build_dir`: Stored field on `AppLocalConfig`.
231/// - `state_dir`: Stored field on `AppLocalConfig`.
232/// - `state_path`: Stored field on `AppLocalConfig`.
233/// - `base_path`: Stored field on `AppLocalConfig`.
234/// - `log_path`: Stored field on `AppLocalConfig`.
235/// - `socket_path`: Stored field on `AppLocalConfig`.
236/// - `error_report_path`: Stored field on `AppLocalConfig`.
237/// - `log_level`: Stored field on `AppLocalConfig`.
238/// - `trusted_proxy_cidrs`: Stored field on `AppLocalConfig`.
239/// - `manifest_build_artifact_dir`: Stored field on `AppLocalConfig`.
240/// - `manifest_state_path`: Stored field on `AppLocalConfig`.
241/// - `manifest_base_path`: Stored field on `AppLocalConfig`.
242///
243/// # Examples
244///
245/// ```rust,ignore
246/// let _ = std::mem::size_of::<pharos_runtime_core::config::AppLocalConfig>();
247/// ```
248///
249/// # Related
250///
251/// - [`crate::config`] for constructors, helpers, and neighboring runtime types.
252pub struct AppLocalConfig {
253    pub conf_path: Option<String>,
254    pub build_dir: Option<String>,
255    pub state_dir: Option<String>,
256    pub state_path: Option<String>,
257    pub base_path: Option<String>,
258    pub log_path: Option<String>,
259    pub socket_path: Option<String>,
260    pub error_report_path: Option<String>,
261    pub log_level: Option<String>,
262    pub trusted_proxy_cidrs: Option<String>,
263    pub manifest_build_artifact_dir: Option<String>,
264    pub manifest_state_path: Option<String>,
265    pub manifest_base_path: Option<String>,
266}
267
268#[derive(Debug, Clone, Default, Eq, PartialEq)]
269/// Fully resolved runtime configuration for one mounted app.
270///
271/// # Fields
272///
273/// - `application_ir`: Stored field on `AppRuntime`.
274/// - `app_id`: Stored field on `AppRuntime`.
275/// - `app_root`: Stored field on `AppRuntime`.
276/// - `build_dir`: Stored field on `AppRuntime`.
277/// - `artifact_dir`: Stored field on `AppRuntime`.
278/// - `host_profile`: Stored field on `AppRuntime`.
279/// - `state_path`: Stored field on `AppRuntime`.
280/// - `base_path`: Stored field on `AppRuntime`.
281/// - `log_path`: Stored field on `AppRuntime`.
282/// - `log_format`: Stored field on `AppRuntime`.
283/// - `error_report_path`: Stored field on `AppRuntime`.
284/// - `log_level`: Stored field on `AppRuntime`.
285/// - `trusted_proxy_cidrs`: Stored field on `AppRuntime`.
286/// - `runtime_conf_path`: Stored field on `AppRuntime`.
287/// - `queue_enabled`: Stored field on `AppRuntime`.
288/// - `queue_poll_interval_ms`: Stored field on `AppRuntime`.
289/// - `queue_max_attempts`: Stored field on `AppRuntime`.
290/// - `queue_retry_seconds`: Stored field on `AppRuntime`.
291/// - `resource_budgets`: Stored field on `AppRuntime`.
292///
293/// # Examples
294///
295/// ```rust,ignore
296/// let _ = std::mem::size_of::<pharos_runtime_core::config::AppRuntime>();
297/// ```
298///
299/// # Related
300///
301/// - [`crate::config`] for constructors, helpers, and neighboring runtime types.
302pub struct AppRuntime {
303    /// Host-derived deployment identity for external connections; imported app declarations cannot select it.
304    pub connection_namespace: Option<String>,
305    pub application_ir: Option<Arc<crate::application_ir::ApplicationIr>>,
306    pub app_id: Option<String>,
307    pub app_root: Option<String>,
308    pub build_dir: Option<String>,
309    pub artifact_dir: Option<String>,
310    pub host_profile: Option<String>,
311    pub state_path: Option<String>,
312    pub base_path: Option<String>,
313    pub log_path: Option<String>,
314    pub log_format: Option<String>,
315    pub error_report_path: Option<String>,
316    pub log_level: Option<String>,
317    pub trusted_proxy_cidrs: Option<String>,
318    pub runtime_conf_path: Option<String>,
319    /// Set only by Architect when serving an isolated editable app preview.
320    pub is_editor_preview: bool,
321    pub queue_enabled: bool,
322    pub queue_poll_interval_ms: i64,
323    pub queue_max_attempts: i64,
324    pub queue_retry_seconds: i64,
325    pub resource_budgets: ResourceBudgets,
326}
327/// Clones one resolved runtime configuration.
328///
329/// # Parameters
330///
331/// - `app`: Input consumed by [`crate::config::clone_app`].
332///
333/// # Returns
334///
335/// Returns the computed value produced by [`crate::config::clone_app`].
336///
337/// # Examples
338///
339/// ```rust,ignore
340/// let _result = pharos_runtime_core::config::clone_app(todo!());
341/// ```
342///
343/// # Related
344///
345/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
346pub fn clone_app(app: &AppRuntime) -> AppRuntime {
347    app.clone()
348}
349
350/// Reads file text.
351fn read_file_text(path: &Path) -> Option<String> {
352    fs::read_to_string(path).ok()
353}
354
355/// Performs path exists.
356fn path_exists(path: &Path) -> bool {
357    path.exists()
358}
359
360/// Resolves relative to.
361fn resolve_relative_to(base_dir: &Path, value: &str) -> Option<String> {
362    if value.is_empty() {
363        return None;
364    }
365
366    let path = Path::new(value);
367    if path.is_absolute() {
368        return Some(value.to_owned());
369    }
370
371    Some(base_dir.join(path).to_string_lossy().into_owned())
372}
373
374/// Performs current working directory.
375fn current_working_directory() -> String {
376    std::env::current_dir()
377        .unwrap_or_else(|_| PathBuf::from("."))
378        .to_string_lossy()
379        .into_owned()
380}
381
382/// Performs path dirname.
383fn path_dirname(path: &Path) -> Option<PathBuf> {
384    if path.as_os_str().is_empty() {
385        return Some(PathBuf::from("."));
386    }
387
388    match path.parent() {
389        Some(parent) if parent.as_os_str().is_empty() => Some(PathBuf::from(".")),
390        Some(parent) => Some(parent.to_path_buf()),
391        None => Some(PathBuf::from(".")),
392    }
393}
394
395/// Performs string from JSON value.
396fn string_from_json_value(value: &Value) -> Option<String> {
397    value.as_str().map(ToOwned::to_owned)
398}
399
400/// Parses JSON text.
401fn parse_json_text(text: &str) -> Option<Value> {
402    serde_json::from_str(text).ok()
403}
404
405/// Parses YAML text.
406fn parse_yaml_text(text: &str) -> Option<Value> {
407    crate::yaml::parse_yaml_text(text)
408}
409
410/// Loads manifest value.
411fn load_manifest_value(app_root: &Path) -> Option<Value> {
412    let yaml_path = app_root.join("pharos.app.yml");
413    if path_exists(&yaml_path) {
414        let yaml_text = read_file_text(&yaml_path)?;
415        let value = parse_yaml_text(&yaml_text)?;
416        if manifest_has_required_fields(&value) {
417            return Some(value);
418        }
419        return None;
420    }
421
422    let json_path = app_root.join("pharos.app.json");
423    if path_exists(&json_path) {
424        let json_text = read_file_text(&json_path)?;
425        let value = parse_json_text(&json_text)?;
426        if manifest_has_required_fields(&value) {
427            return Some(value);
428        }
429    }
430
431    None
432}
433
434/// Performs manifest has required fields.
435fn manifest_has_required_fields(value: &Value) -> bool {
436    let app_id_ok = value
437        .get("app_id")
438        .and_then(Value::as_str)
439        .is_some_and(|text| !text.is_empty());
440    let host_profile_ok = value
441        .get("runtime")
442        .and_then(Value::as_object)
443        .and_then(|runtime| runtime.get("host_profile"))
444        .and_then(Value::as_str)
445        .is_some_and(|text| !text.is_empty());
446
447    app_id_ok && host_profile_ok
448}
449
450/// Performs manifest app ID.
451fn manifest_app_id(value: &Value) -> Option<&str> {
452    value
453        .get("app_id")
454        .and_then(Value::as_str)
455        .filter(|text| !text.is_empty())
456}
457
458/// Performs canonicalize or original.
459fn canonicalize_or_original(path: &Path) -> String {
460    path.to_string_lossy().into_owned()
461}
462
463/// Returns whether override value.
464fn has_override_value(value: Option<&str>) -> bool {
465    value.is_some_and(|value| !value.is_empty())
466}
467
468/// Returns whether cli override value.
469fn has_cli_override_value(value: Option<&str>, from_conf: bool) -> bool {
470    has_override_value(value) && !from_conf
471}
472
473/// Returns whether runtime default value.
474fn has_runtime_default_value(value: Option<&str>, from_conf: bool) -> bool {
475    has_override_value(value) && from_conf
476}
477
478/// Parses queue enabled.
479fn parse_queue_enabled(value: Option<&str>) -> bool {
480    matches!(value, Some("1" | "true"))
481}
482
483/// Parses config boolean flag.
484fn parse_config_bool(value: Option<&str>, default_value: bool) -> bool {
485    value
486        .map(|value| parse_queue_enabled(Some(value)))
487        .unwrap_or(default_value)
488}
489
490/// Parses positive i64.
491fn parse_positive_i64(value: Option<&str>, default_value: i64) -> i64 {
492    value
493        .and_then(|value| value.parse::<i64>().ok())
494        .filter(|value| *value > 0)
495        .unwrap_or(default_value)
496}
497/// Finds an app root in one directory tree by matching `app_id` from the manifest.
498///
499/// # Parameters
500///
501/// - `base_dir`: Input consumed by [`crate::config::match_app_root_in_directory`].
502/// - `target_id`: Input consumed by [`crate::config::match_app_root_in_directory`].
503///
504/// # Returns
505///
506/// Returns [`Some`] when [`crate::config::match_app_root_in_directory`] can produce a value and [`None`] otherwise.
507///
508/// # Performance
509///
510/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::config::match_app_root_in_directory`].
511///
512/// # Examples
513///
514/// ```rust,ignore
515/// let _result = pharos_runtime_core::config::match_app_root_in_directory(std::path::Path::new("apps"), "dev_docs");
516/// ```
517///
518/// # Related
519///
520/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
521pub fn match_app_root_in_directory(base_dir: &Path, target_id: &str) -> Option<String> {
522    if base_dir.as_os_str().is_empty() || target_id.is_empty() {
523        return None;
524    }
525
526    if let Some(manifest) = load_manifest_value(base_dir)
527        && manifest_app_id(&manifest) == Some(target_id)
528    {
529        return Some(canonicalize_or_original(base_dir));
530    }
531
532    let directory = fs::read_dir(base_dir).ok()?;
533    for entry in directory.flatten() {
534        let child_root = entry.path();
535        if !child_root.is_dir() {
536            continue;
537        }
538        if let Some(manifest) = load_manifest_value(&child_root)
539            && manifest_app_id(&manifest) == Some(target_id)
540        {
541            return Some(canonicalize_or_original(&child_root));
542        }
543    }
544
545    None
546}
547
548/// Finds an app root by checking the explicit app directory, repository root, and binary-relative app directories.
549///
550/// # Parameters
551///
552/// - `target_id`: Input consumed by [`crate::config::find_app_root_for_target`].
553/// - `app_dir_hint`: Input consumed by [`crate::config::find_app_root_for_target`].
554/// - `repo_root`: Input consumed by [`crate::config::find_app_root_for_target`].
555/// - `binary_dir`: Input consumed by [`crate::config::find_app_root_for_target`].
556///
557/// # Returns
558///
559/// Returns [`Some`] when [`crate::config::find_app_root_for_target`] can produce a value and [`None`] otherwise.
560///
561/// # Examples
562///
563/// ```rust,ignore
564/// let _result = pharos_runtime_core::config::find_app_root_for_target("dev_docs", todo!(), std::path::Path::new("."), std::path::Path::new("bin"));
565/// ```
566///
567/// # Related
568///
569/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
570pub fn find_app_root_for_target(
571    target_id: &str,
572    app_dir_hint: Option<&Path>,
573    repo_root: Option<&Path>,
574    binary_dir: Option<&Path>,
575) -> Option<String> {
576    if let Some(app_dir_hint) = app_dir_hint.filter(|path| !path.as_os_str().is_empty())
577        && let Some(matched) = match_app_root_in_directory(app_dir_hint, target_id)
578    {
579        return Some(matched);
580    }
581
582    if let Some(repo_root) = repo_root.filter(|path| !path.as_os_str().is_empty())
583        && let Some(matched) = match_app_root_in_directory(&repo_root.join("apps"), target_id)
584    {
585        return Some(matched);
586    }
587
588    if let Some(binary_dir) = binary_dir.filter(|path| !path.as_os_str().is_empty()) {
589        if let Some(matched) = match_app_root_in_directory(&binary_dir.join("apps"), target_id) {
590            return Some(matched);
591        }
592        if let Some(binary_parent) = path_dirname(binary_dir)
593            && let Some(matched) =
594                match_app_root_in_directory(&binary_parent.join("apps"), target_id)
595        {
596            return Some(matched);
597        }
598    }
599
600    None
601}
602/// Resolves a config value relative to the config file that declared it.
603///
604/// # Parameters
605///
606/// - `conf_path`: Input consumed by [`crate::config::resolve_conf_relative_path`].
607/// - `raw_value`: Input consumed by [`crate::config::resolve_conf_relative_path`].
608///
609/// # Returns
610///
611/// Returns [`Some`] when [`crate::config::resolve_conf_relative_path`] can produce a value and [`None`] otherwise.
612///
613/// # Examples
614///
615/// ```rust,ignore
616/// let _result = pharos_runtime_core::config::resolve_conf_relative_path(std::path::Path::new("app.conf"), "logs/app.log");
617/// ```
618///
619/// # Related
620///
621/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
622pub fn resolve_conf_relative_path(conf_path: &Path, raw_value: &str) -> Option<String> {
623    if raw_value.is_empty() {
624        return Some(String::new());
625    }
626
627    let candidate = Path::new(raw_value);
628    if candidate.is_absolute() {
629        return Some(raw_value.to_owned());
630    }
631
632    let conf_dir = path_dirname(conf_path)?;
633    Some(conf_dir.join(candidate).to_string_lossy().into_owned())
634}
635/// Returns the `app.conf` path for an app when the file exists.
636///
637/// # Parameters
638///
639/// - `app_root`: Input consumed by [`crate::config::app_local_conf_path`].
640///
641/// # Returns
642///
643/// Returns [`Some`] when [`crate::config::app_local_conf_path`] can produce a value and [`None`] otherwise.
644///
645/// # Examples
646///
647/// ```rust,ignore
648/// let _result = pharos_runtime_core::config::app_local_conf_path(std::path::Path::new("apps/dev_docs"));
649/// ```
650///
651/// # Related
652///
653/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
654pub fn app_local_conf_path(app_root: &Path) -> Option<PathBuf> {
655    let path = if app_root.as_os_str().is_empty() {
656        PathBuf::from("app.conf")
657    } else {
658        app_root.join("app.conf")
659    };
660
661    path_exists(&path).then_some(path)
662}
663
664/// Resolves conf value key.
665fn resolve_conf_value_key(conf_path: &Path, key: &str, value: Option<&str>) -> Option<String> {
666    let value = value?;
667
668    match key {
669        "app_dir" | "build_dir" | "state_dir" | "state_path" | "log_path" | "socket_path"
670        | "error_report_path" => resolve_conf_relative_path(conf_path, value),
671        _ => Some(value.to_owned()),
672    }
673}
674
675/// Performs conf lookup value in text.
676fn conf_lookup_value_in_text(text: &str, key: &str) -> Option<String> {
677    text.lines().find_map(|line| {
678        let trimmed = line.trim();
679        if trimmed.is_empty() || trimmed.starts_with('#') {
680            return None;
681        }
682
683        let (raw_key, raw_value) = trimmed.split_once('=')?;
684        let parsed_key = raw_key.trim();
685        if parsed_key != key {
686            return None;
687        }
688
689        Some(raw_value.trim().to_owned())
690    })
691}
692
693/// Performs dynamic config value is set.
694///
695/// # Parameters
696///
697/// - `value`: Input consumed by [`crate::config::dynamic_config_value_is_set`].
698///
699/// # Returns
700///
701/// Returns `true` when the condition checked by [`crate::config::dynamic_config_value_is_set`] holds and `false` otherwise.
702///
703/// # Examples
704///
705/// ```rust,ignore
706/// let _result = pharos_runtime_core::config::dynamic_config_value_is_set("value");
707/// ```
708///
709/// # Related
710///
711/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
712pub fn dynamic_config_value_is_set(value: Option<&str>) -> bool {
713    matches!(value, Some(value) if !value.is_empty() && value != "null")
714}
715
716/// Performs dynamic config key is path.
717///
718/// # Parameters
719///
720/// - `key`: Input consumed by [`crate::config::dynamic_config_key_is_path`].
721///
722/// # Returns
723///
724/// Returns `true` when the condition checked by [`crate::config::dynamic_config_key_is_path`] holds and `false` otherwise.
725///
726/// # Examples
727///
728/// ```rust,ignore
729/// let _result = pharos_runtime_core::config::dynamic_config_key_is_path("key");
730/// ```
731///
732/// # Related
733///
734/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
735pub fn dynamic_config_key_is_path(key: &str) -> bool {
736    matches!(
737        key,
738        "db_path" | "db_tls_root_cert" | "db_tls_cert" | "db_tls_key" | "backup_dir" | "MEDIA_ROOT"
739    )
740}
741
742/// Performs dynamic config resolve value.
743///
744/// # Parameters
745///
746/// - `conf_path`: Input consumed by [`crate::config::dynamic_config_resolve_value`].
747/// - `key`: Input consumed by [`crate::config::dynamic_config_resolve_value`].
748/// - `raw_value`: Input consumed by [`crate::config::dynamic_config_resolve_value`].
749///
750/// # Returns
751///
752/// Returns [`Some`] when [`crate::config::dynamic_config_resolve_value`] can produce a value and [`None`] otherwise.
753///
754/// # Examples
755///
756/// ```rust,ignore
757/// let _result = pharos_runtime_core::config::dynamic_config_resolve_value(std::path::Path::new("app.conf"), "key", "logs/app.log");
758/// ```
759///
760/// # Related
761///
762/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
763pub fn dynamic_config_resolve_value(
764    conf_path: &Path,
765    key: &str,
766    raw_value: &str,
767) -> Option<String> {
768    if dynamic_config_key_is_path(key) {
769        resolve_conf_relative_path(conf_path, raw_value)
770    } else {
771        Some(raw_value.to_owned())
772    }
773}
774
775/// Performs dynamic config lookup raw.
776///
777/// # Parameters
778///
779/// - `conf_path`: Input consumed by [`crate::config::dynamic_config_lookup_raw`].
780/// - `key`: Input consumed by [`crate::config::dynamic_config_lookup_raw`].
781///
782/// # Returns
783///
784/// Returns [`Some`] when [`crate::config::dynamic_config_lookup_raw`] can produce a value and [`None`] otherwise.
785///
786/// # Examples
787///
788/// ```rust,ignore
789/// let _result = pharos_runtime_core::config::dynamic_config_lookup_raw(std::path::Path::new("app.conf"), "key");
790/// ```
791///
792/// # Related
793///
794/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
795pub fn dynamic_config_lookup_raw(conf_path: &Path, key: &str) -> Option<String> {
796    if key.is_empty() || !path_exists(conf_path) {
797        return None;
798    }
799
800    let text = read_file_text(conf_path)?;
801    conf_lookup_value_in_text(&text, key)
802}
803
804/// Performs dynamic config effective value.
805///
806/// # Parameters
807///
808/// - `app_root`: Input consumed by [`crate::config::dynamic_config_effective_value`].
809/// - `runtime_conf_path`: Input consumed by [`crate::config::dynamic_config_effective_value`].
810/// - `key`: Input consumed by [`crate::config::dynamic_config_effective_value`].
811///
812/// # Returns
813///
814/// Returns [`Some`] when [`crate::config::dynamic_config_effective_value`] can produce a value and [`None`] otherwise.
815///
816/// # Examples
817///
818/// ```rust,ignore
819/// let _result = pharos_runtime_core::config::dynamic_config_effective_value(std::path::Path::new("apps/dev_docs"), todo!(), "key");
820/// ```
821///
822/// # Related
823///
824/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
825pub fn dynamic_config_effective_value(
826    app_root: &Path,
827    runtime_conf_path: Option<&Path>,
828    key: &str,
829) -> Option<String> {
830    if key.is_empty() {
831        return None;
832    }
833
834    if let Some(app_conf_path) = app_local_conf_path(app_root) {
835        let raw_value = dynamic_config_lookup_raw(&app_conf_path, key);
836        if dynamic_config_value_is_set(raw_value.as_deref()) {
837            return dynamic_config_resolve_value(&app_conf_path, key, raw_value.as_deref()?);
838        }
839    }
840
841    if let Some(runtime_conf_path) = runtime_conf_path {
842        let raw_value = dynamic_config_lookup_raw(runtime_conf_path, key);
843        if dynamic_config_value_is_set(raw_value.as_deref()) {
844            return dynamic_config_resolve_value(runtime_conf_path, key, raw_value.as_deref()?);
845        }
846    }
847
848    None
849}
850
851/// Resolves configured app media storage, retaining bundled storage for legacy apps.
852/// Configured paths must be absolute after config-relative resolution and cannot traverse parents.
853pub fn app_media_root(app_root: &Path) -> Option<PathBuf> {
854    let configured = dynamic_config_effective_value(app_root, None, "MEDIA_ROOT");
855    match configured {
856        Some(value) => {
857            let path = PathBuf::from(value);
858            (path.is_absolute()
859                && !path
860                    .components()
861                    .any(|part| matches!(part, std::path::Component::ParentDir)))
862            .then_some(path)
863        }
864        None => Some(app_root.join("media")),
865    }
866}
867
868/// Performs duplicate config field.
869///
870/// # Parameters
871///
872/// - `config`: Input consumed by [`crate::config::duplicate_config_field`].
873/// - `key`: Input consumed by [`crate::config::duplicate_config_field`].
874///
875/// # Returns
876///
877/// Returns [`Some`] when [`crate::config::duplicate_config_field`] can produce a value and [`None`] otherwise.
878///
879/// # Examples
880///
881/// ```rust,ignore
882/// let _result = pharos_runtime_core::config::duplicate_config_field(todo!(), "key");
883/// ```
884///
885/// # Related
886///
887/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
888pub fn duplicate_config_field(config: &AppLocalConfig, key: &str) -> Option<String> {
889    match key {
890        "build_dir" => config.build_dir.clone(),
891        "state_dir" => config.state_dir.clone(),
892        "state_path" => config.state_path.clone(),
893        "base_path" => config.base_path.clone(),
894        "log_path" => config.log_path.clone(),
895        "socket_path" => config.socket_path.clone(),
896        "error_report_path" => config.error_report_path.clone(),
897        "log_level" => config.log_level.clone(),
898        "trusted_proxy_cidrs" => config.trusted_proxy_cidrs.clone(),
899        "conf_path" => config.conf_path.clone(),
900        _ => None,
901    }
902}
903
904/// Performs app local conf value.
905///
906/// # Parameters
907///
908/// - `app_root`: Input consumed by [`crate::config::app_local_conf_value`].
909/// - `key`: Input consumed by [`crate::config::app_local_conf_value`].
910///
911/// # Returns
912///
913/// Returns [`Some`] when [`crate::config::app_local_conf_value`] can produce a value and [`None`] otherwise.
914///
915/// # Examples
916///
917/// ```rust,ignore
918/// let _result = pharos_runtime_core::config::app_local_conf_value(std::path::Path::new("apps/dev_docs"), "key");
919/// ```
920///
921/// # Related
922///
923/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
924pub fn app_local_conf_value(app_root: &Path, key: &str) -> Option<String> {
925    if key == "app_dir" {
926        return None;
927    }
928
929    let config = load_app_local_config(app_root)?;
930    duplicate_config_field(&config, key)
931}
932
933/// Loads app local config.
934///
935/// # Parameters
936///
937/// - `app_root`: Input consumed by [`crate::config::load_app_local_config`].
938///
939/// # Returns
940///
941/// Returns [`Some`] when [`crate::config::load_app_local_config`] can produce a value and [`None`] otherwise.
942///
943/// # Examples
944///
945/// ```rust,ignore
946/// let _result = pharos_runtime_core::config::load_app_local_config(std::path::Path::new("apps/dev_docs"));
947/// ```
948///
949/// # Related
950///
951/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
952pub fn load_app_local_config(app_root: &Path) -> Option<AppLocalConfig> {
953    if app_root.as_os_str().is_empty() {
954        return None;
955    }
956
957    let conf_path = app_local_conf_path(app_root)?;
958    let conf_text = read_file_text(&conf_path)?;
959    let manifest = load_manifest_value(app_root);
960
961    let build_dir = resolve_conf_value_key(
962        &conf_path,
963        "build_dir",
964        conf_lookup_value_in_text(&conf_text, "build_dir").as_deref(),
965    );
966    let state_dir = resolve_conf_value_key(
967        &conf_path,
968        "state_dir",
969        conf_lookup_value_in_text(&conf_text, "state_dir").as_deref(),
970    );
971    let state_path = resolve_conf_value_key(
972        &conf_path,
973        "state_path",
974        conf_lookup_value_in_text(&conf_text, "state_path").as_deref(),
975    );
976    let base_path = resolve_conf_value_key(
977        &conf_path,
978        "base_path",
979        conf_lookup_value_in_text(&conf_text, "base_path").as_deref(),
980    );
981    let log_path = resolve_conf_value_key(
982        &conf_path,
983        "log_path",
984        conf_lookup_value_in_text(&conf_text, "log_path").as_deref(),
985    );
986    let socket_path = resolve_conf_value_key(
987        &conf_path,
988        "socket_path",
989        conf_lookup_value_in_text(&conf_text, "socket_path").as_deref(),
990    );
991    let error_report_path = resolve_conf_value_key(
992        &conf_path,
993        "error_report_path",
994        conf_lookup_value_in_text(&conf_text, "error_report_path").as_deref(),
995    );
996    let log_level = resolve_conf_value_key(
997        &conf_path,
998        "log_level",
999        conf_lookup_value_in_text(&conf_text, "log_level").as_deref(),
1000    );
1001    let trusted_proxy_cidrs = resolve_conf_value_key(
1002        &conf_path,
1003        "trusted_proxy_cidrs",
1004        conf_lookup_value_in_text(&conf_text, "trusted_proxy_cidrs").as_deref(),
1005    );
1006
1007    let manifest_build_artifact_dir = manifest
1008        .as_ref()
1009        .and_then(|value| value.get("runtime"))
1010        .and_then(Value::as_object)
1011        .and_then(|runtime| runtime.get("build_artifact_dir"))
1012        .and_then(string_from_json_value);
1013    let manifest_state_path = manifest
1014        .as_ref()
1015        .and_then(|value| value.get("runtime"))
1016        .and_then(Value::as_object)
1017        .and_then(|runtime| runtime.get("state_path"))
1018        .and_then(string_from_json_value);
1019    let manifest_base_path = manifest
1020        .as_ref()
1021        .and_then(|value| value.get("runtime"))
1022        .and_then(Value::as_object)
1023        .and_then(|runtime| runtime.get("base_path"))
1024        .and_then(string_from_json_value);
1025
1026    Some(AppLocalConfig {
1027        conf_path: Some(conf_path.to_string_lossy().into_owned()),
1028        build_dir,
1029        state_dir,
1030        state_path,
1031        base_path,
1032        log_path,
1033        socket_path,
1034        error_report_path,
1035        log_level,
1036        trusted_proxy_cidrs,
1037        manifest_build_artifact_dir,
1038        manifest_state_path,
1039        manifest_base_path,
1040    })
1041}
1042
1043/// Normalizes build dir value.
1044fn normalize_build_dir_value(value: &str) -> Option<String> {
1045    if value.is_empty() {
1046        return None;
1047    }
1048    let mut normalized = PathBuf::new();
1049    let mut previous_was_dist = false;
1050    for component in Path::new(value).components() {
1051        let text = component.as_os_str().to_str()?;
1052        if text == "dist" && previous_was_dist {
1053            continue;
1054        }
1055        previous_was_dist = text == "dist";
1056        normalized.push(component.as_os_str());
1057    }
1058    Some(normalized.to_string_lossy().into_owned())
1059}
1060
1061/// Performs normalized build dir path.
1062fn normalized_build_dir_path(path: &Path) -> PathBuf {
1063    normalize_build_dir_value(&path.to_string_lossy())
1064        .map(PathBuf::from)
1065        .unwrap_or_else(|| path.to_path_buf())
1066}
1067
1068/// Performs app build dir local.
1069///
1070/// # Parameters
1071///
1072/// - `app_root`: Input consumed by [`crate::config::app_build_dir_local`].
1073///
1074/// # Returns
1075///
1076/// Returns [`Some`] when [`crate::config::app_build_dir_local`] can produce a value and [`None`] otherwise.
1077///
1078/// # Examples
1079///
1080/// ```rust,ignore
1081/// let _result = pharos_runtime_core::config::app_build_dir_local(std::path::Path::new("apps/dev_docs"));
1082/// ```
1083///
1084/// # Related
1085///
1086/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1087pub fn app_build_dir_local(app_root: &Path) -> Option<String> {
1088    let config = load_app_local_config(app_root)?;
1089
1090    if let Some(build_dir) = config.build_dir.filter(|value| !value.is_empty()) {
1091        return normalize_build_dir_value(&build_dir);
1092    }
1093
1094    if let Some(manifest_build_artifact_dir) = config.manifest_build_artifact_dir
1095        && !manifest_build_artifact_dir.is_empty()
1096        && manifest_build_artifact_dir != "null"
1097        && manifest_build_artifact_dir != "."
1098    {
1099        let artifact_path = Path::new(&manifest_build_artifact_dir);
1100        if !artifact_path.is_absolute() {
1101            let mut components = artifact_path.components();
1102            if matches!(
1103                components
1104                    .next()
1105                    .and_then(|component| component.as_os_str().to_str()),
1106                Some("apps")
1107            ) {
1108                return Some("dist".to_owned());
1109            }
1110        }
1111
1112        let artifact_parent = artifact_path.parent()?;
1113        return artifact_parent
1114            .parent()
1115            .map(|path| path.to_string_lossy().into_owned())
1116            .or_else(|| Some(PathBuf::from(".").to_string_lossy().into_owned()));
1117    }
1118
1119    if app_root
1120        .parent()
1121        .and_then(Path::file_name)
1122        .and_then(|value| value.to_str())
1123        == Some("apps")
1124        && let Some(repo_root) = app_root.parent().and_then(Path::parent)
1125    {
1126        return Some(repo_root.join("dist").to_string_lossy().into_owned());
1127    }
1128
1129    let result = path_dirname(app_root)
1130        .map(|path| path.to_string_lossy().into_owned())
1131        .filter(|value| !value.is_empty());
1132    if result.is_some() {
1133        return result;
1134    }
1135
1136    Some(app_root.join("dist").to_string_lossy().into_owned())
1137}
1138
1139/// Performs effective app build dir.
1140///
1141/// # Parameters
1142///
1143/// - `app_root`: Input consumed by [`crate::config::effective_app_build_dir`].
1144/// - `build_dir_override`: Input consumed by [`crate::config::effective_app_build_dir`].
1145///
1146/// # Returns
1147///
1148/// Returns [`Some`] when [`crate::config::effective_app_build_dir`] can produce a value and [`None`] otherwise.
1149///
1150/// # Examples
1151///
1152/// ```rust,ignore
1153/// let _result = pharos_runtime_core::config::effective_app_build_dir(std::path::Path::new("apps/dev_docs"), todo!());
1154/// ```
1155///
1156/// # Related
1157///
1158/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1159pub fn effective_app_build_dir(
1160    app_root: &Path,
1161    build_dir_override: Option<&str>,
1162) -> Option<String> {
1163    match build_dir_override {
1164        Some(value) if !value.is_empty() => normalize_build_dir_value(value),
1165        _ => app_build_dir_local(app_root),
1166    }
1167}
1168
1169/// Performs path last segment is apps.
1170fn path_last_segment_is_apps(path: &Path) -> bool {
1171    path.file_name().is_some_and(|segment| segment == "apps")
1172}
1173
1174/// Performs skip leading apps segment.
1175fn skip_leading_apps_segment(relative_path: &str) -> &str {
1176    relative_path
1177        .strip_prefix("apps/")
1178        .or_else(|| relative_path.strip_prefix("apps\\"))
1179        .unwrap_or(relative_path)
1180}
1181
1182/// Performs skip leading build dir segment.
1183fn skip_leading_build_dir_segment<'a>(build_dir: &Path, relative_path: &'a str) -> &'a str {
1184    let Some(build_segment) = build_dir.file_name().and_then(|segment| segment.to_str()) else {
1185        return relative_path;
1186    };
1187
1188    let (leading_segment, remainder) = match relative_path.find(['/', '\\']) {
1189        Some(index) => (&relative_path[..index], Some(&relative_path[index + 1..])),
1190        None => (relative_path, None),
1191    };
1192
1193    if leading_segment == build_segment {
1194        remainder.unwrap_or(relative_path)
1195    } else {
1196        relative_path
1197    }
1198}
1199
1200/// Resolves relative artifact dir.
1201fn resolve_relative_artifact_dir(build_dir: &Path, relative_artifact_dir: &str) -> Option<String> {
1202    if build_dir.as_os_str().is_empty() || relative_artifact_dir.is_empty() {
1203        return None;
1204    }
1205    let build_dir = normalized_build_dir_path(build_dir);
1206
1207    let normalized = if path_last_segment_is_apps(&build_dir) {
1208        skip_leading_apps_segment(relative_artifact_dir)
1209    } else {
1210        skip_leading_build_dir_segment(&build_dir, relative_artifact_dir)
1211    };
1212
1213    Some(build_dir.join(normalized).to_string_lossy().into_owned())
1214}
1215
1216/// Performs app artifact dir from build dir.
1217///
1218/// # Parameters
1219///
1220/// - `build_dir`: Input consumed by [`crate::config::app_artifact_dir_from_build_dir`].
1221/// - `app_id`: Input consumed by [`crate::config::app_artifact_dir_from_build_dir`].
1222///
1223/// # Returns
1224///
1225/// Returns [`Some`] when [`crate::config::app_artifact_dir_from_build_dir`] can produce a value and [`None`] otherwise.
1226///
1227/// # Examples
1228///
1229/// ```rust,ignore
1230/// let _result = pharos_runtime_core::config::app_artifact_dir_from_build_dir(std::path::Path::new("build/dev_docs"), todo!());
1231/// ```
1232///
1233/// # Related
1234///
1235/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1236pub fn app_artifact_dir_from_build_dir(build_dir: &Path, app_id: &str) -> Option<String> {
1237    if app_id.is_empty() {
1238        return None;
1239    }
1240    let build_dir = normalized_build_dir_path(build_dir);
1241
1242    if path_last_segment_is_apps(&build_dir) {
1243        return Some(build_dir.join(app_id).to_string_lossy().into_owned());
1244    }
1245
1246    Some(
1247        build_dir
1248            .join("apps")
1249            .join(app_id)
1250            .to_string_lossy()
1251            .into_owned(),
1252    )
1253}
1254
1255/// Performs effective app artifact dir.
1256///
1257/// # Parameters
1258///
1259/// - `app_root`: Input consumed by [`crate::config::effective_app_artifact_dir`].
1260/// - `build_dir`: Input consumed by [`crate::config::effective_app_artifact_dir`].
1261/// - `app_id`: Input consumed by [`crate::config::effective_app_artifact_dir`].
1262///
1263/// # Returns
1264///
1265/// Returns [`Some`] when [`crate::config::effective_app_artifact_dir`] can produce a value and [`None`] otherwise.
1266///
1267/// # Examples
1268///
1269/// ```rust,ignore
1270/// let _result = pharos_runtime_core::config::effective_app_artifact_dir(std::path::Path::new("apps/dev_docs"), std::path::Path::new("build/dev_docs"), todo!());
1271/// ```
1272///
1273/// # Related
1274///
1275/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1276pub fn effective_app_artifact_dir(
1277    app_root: &Path,
1278    build_dir: &Path,
1279    app_id: &str,
1280) -> Option<String> {
1281    let config = load_app_local_config(app_root)?;
1282
1283    if let Some(manifest_build_artifact_dir) = config.manifest_build_artifact_dir
1284        && !manifest_build_artifact_dir.is_empty()
1285        && manifest_build_artifact_dir != "null"
1286        && manifest_build_artifact_dir != "."
1287    {
1288        let artifact_path = Path::new(&manifest_build_artifact_dir);
1289        if artifact_path.is_absolute() {
1290            return Some(manifest_build_artifact_dir);
1291        }
1292
1293        return resolve_relative_artifact_dir(build_dir, &manifest_build_artifact_dir);
1294    }
1295
1296    app_artifact_dir_from_build_dir(build_dir, app_id)
1297}
1298
1299/// Performs app state path local.
1300///
1301/// # Parameters
1302///
1303/// - `app_root`: Input consumed by [`crate::config::app_state_path_local`].
1304/// - `build_dir`: Input consumed by [`crate::config::app_state_path_local`].
1305/// - `app_id`: Input consumed by [`crate::config::app_state_path_local`].
1306///
1307/// # Returns
1308///
1309/// Returns [`Some`] when [`crate::config::app_state_path_local`] can produce a value and [`None`] otherwise.
1310///
1311/// # Examples
1312///
1313/// ```rust,ignore
1314/// let _result = pharos_runtime_core::config::app_state_path_local(std::path::Path::new("apps/dev_docs"), std::path::Path::new("build/dev_docs"), todo!());
1315/// ```
1316///
1317/// # Related
1318///
1319/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1320pub fn app_state_path_local(app_root: &Path, build_dir: &Path, app_id: &str) -> Option<String> {
1321    let config = load_app_local_config(app_root)?;
1322
1323    if let Some(state_path) = config.state_path.filter(|value| !value.is_empty()) {
1324        return Some(state_path);
1325    }
1326
1327    if let Some(state_dir) = config.state_dir.filter(|value| !value.is_empty()) {
1328        return Some(
1329            Path::new(&state_dir)
1330                .join(app_id)
1331                .join("runtime-state.json")
1332                .to_string_lossy()
1333                .into_owned(),
1334        );
1335    }
1336
1337    if let Some(manifest_state_path) = config.manifest_state_path
1338        && !manifest_state_path.is_empty()
1339        && manifest_state_path != "null"
1340    {
1341        let manifest_state = Path::new(&manifest_state_path);
1342        if manifest_state.is_absolute() {
1343            return Some(manifest_state_path);
1344        }
1345        return Some(app_root.join(manifest_state).to_string_lossy().into_owned());
1346    }
1347
1348    let artifact_dir = effective_app_artifact_dir(app_root, build_dir, app_id)?;
1349    Some(
1350        Path::new(&artifact_dir)
1351            .join("runtime-state.json")
1352            .to_string_lossy()
1353            .into_owned(),
1354    )
1355}
1356
1357/// Performs effective app state path.
1358///
1359/// # Parameters
1360///
1361/// - `app_root`: Input consumed by [`crate::config::effective_app_state_path`].
1362/// - `build_dir`: Input consumed by [`crate::config::effective_app_state_path`].
1363/// - `app_id`: Input consumed by [`crate::config::effective_app_state_path`].
1364/// - `state_path_override`: Input consumed by [`crate::config::effective_app_state_path`].
1365/// - `state_dir_override`: Input consumed by [`crate::config::effective_app_state_path`].
1366///
1367/// # Returns
1368///
1369/// Returns [`Some`] when [`crate::config::effective_app_state_path`] can produce a value and [`None`] otherwise.
1370///
1371/// # Examples
1372///
1373/// ```rust,ignore
1374/// let _result = pharos_runtime_core::config::effective_app_state_path(std::path::Path::new("apps/dev_docs"), std::path::Path::new("build/dev_docs"), todo!(), todo!(), todo!());
1375/// ```
1376///
1377/// # Related
1378///
1379/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1380pub fn effective_app_state_path(
1381    app_root: &Path,
1382    build_dir: &Path,
1383    app_id: &str,
1384    state_path_override: Option<&str>,
1385    state_dir_override: Option<&str>,
1386) -> Option<String> {
1387    if let Some(state_path_override) = state_path_override.filter(|value| !value.is_empty()) {
1388        return Some(state_path_override.to_owned());
1389    }
1390
1391    if let Some(state_dir_override) = state_dir_override.filter(|value| !value.is_empty()) {
1392        return Some(
1393            Path::new(state_dir_override)
1394                .join(app_id)
1395                .join("runtime-state.json")
1396                .to_string_lossy()
1397                .into_owned(),
1398        );
1399    }
1400
1401    app_state_path_local(app_root, build_dir, app_id)
1402}
1403
1404/// Parses serve config.
1405///
1406/// # Parameters
1407///
1408/// - `args`: Input consumed by [`crate::config::parse_serve_config`].
1409///
1410/// # Returns
1411///
1412/// Returns the computed value produced by [`crate::config::parse_serve_config`].
1413///
1414/// # Examples
1415///
1416/// ```rust,ignore
1417/// let _result = pharos_runtime_core::config::parse_serve_config(todo!());
1418/// ```
1419///
1420/// # Related
1421///
1422/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1423pub fn parse_serve_config(args: &[String]) -> ServeConfig {
1424    let mut config = ServeConfig {
1425        host: Some("127.0.0.1".to_owned()),
1426        ..ServeConfig::default()
1427    };
1428    let mut index = 0usize;
1429
1430    while index < args.len() {
1431        let arg = args[index].as_str();
1432        if config.kind.is_none() && matches!(arg, "website" | "app" | "runtime") {
1433            config.kind = Some(arg.to_owned());
1434            if index + 1 < args.len() && !args[index + 1].starts_with('-') {
1435                config.target_id = Some(args[index + 1].clone());
1436                index += 2;
1437                continue;
1438            }
1439            index += 1;
1440            continue;
1441        }
1442
1443        let next_value = |index: usize, args: &[String]| args.get(index + 1).cloned();
1444
1445        match arg {
1446            "--host" => {
1447                if let Some(value) = next_value(index, args) {
1448                    config.host = Some(value);
1449                    index += 2;
1450                    continue;
1451                }
1452            }
1453            "--port" => {
1454                if let Some(value) = next_value(index, args) {
1455                    config.port = Some(value);
1456                    index += 2;
1457                    continue;
1458                }
1459            }
1460            "--root" => {
1461                if let Some(value) = next_value(index, args) {
1462                    config.root = Some(value);
1463                    index += 2;
1464                    continue;
1465                }
1466            }
1467            "--conf" => {
1468                if let Some(value) = next_value(index, args) {
1469                    config.conf_path = Some(value);
1470                    index += 2;
1471                    continue;
1472                }
1473            }
1474            "--app-dir" => {
1475                if let Some(value) = next_value(index, args) {
1476                    config.app_dir = Some(value);
1477                    index += 2;
1478                    continue;
1479                }
1480            }
1481            "--build-dir" => {
1482                if let Some(value) = next_value(index, args) {
1483                    config.build_dir = Some(value);
1484                    index += 2;
1485                    continue;
1486                }
1487            }
1488            "--state-dir" => {
1489                if let Some(value) = next_value(index, args) {
1490                    config.state_dir = Some(value);
1491                    index += 2;
1492                    continue;
1493                }
1494            }
1495            "--state-path" => {
1496                if let Some(value) = next_value(index, args) {
1497                    config.state_path = Some(value);
1498                    index += 2;
1499                    continue;
1500                }
1501            }
1502            "--base-path" => {
1503                if let Some(value) = next_value(index, args) {
1504                    config.base_path = Some(value);
1505                    index += 2;
1506                    continue;
1507                }
1508            }
1509            "--log-path" => {
1510                if let Some(value) = next_value(index, args) {
1511                    config.log_path = Some(value);
1512                    index += 2;
1513                    continue;
1514                }
1515            }
1516            "--log-level" => {
1517                if let Some(value) = next_value(index, args) {
1518                    config.log_level = Some(value);
1519                    index += 2;
1520                    continue;
1521                }
1522            }
1523            "--trusted-proxy-cidrs" => {
1524                if let Some(value) = next_value(index, args) {
1525                    config.trusted_proxy_cidrs = Some(value);
1526                    index += 2;
1527                    continue;
1528                }
1529            }
1530            "--socket-path" => {
1531                if let Some(value) = next_value(index, args) {
1532                    config.socket_path = Some(value);
1533                    index += 2;
1534                    continue;
1535                }
1536            }
1537            "--clean" => {
1538                config.clean = true;
1539                index += 1;
1540                continue;
1541            }
1542            "--seed-state" => {
1543                config.seed_state = true;
1544                index += 1;
1545                continue;
1546            }
1547            _ => {}
1548        }
1549
1550        index += 1;
1551    }
1552
1553    config
1554}
1555
1556/// Clears an owned serve configuration in the same way as dropping a parsed value.
1557///
1558/// # Parameters
1559///
1560/// - `config`: Input consumed by [`crate::config::free_serve_config`].
1561///
1562/// # Returns
1563///
1564/// Returns after [`crate::config::free_serve_config`] applies its in-place effect.
1565///
1566/// # Examples
1567///
1568/// ```rust,ignore
1569/// let _result = pharos_runtime_core::config::free_serve_config(todo!());
1570/// ```
1571///
1572/// # Related
1573///
1574/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1575pub fn free_serve_config(config: &mut ServeConfig) {
1576    *config = ServeConfig::default();
1577}
1578
1579/// Performs apply serve config defaults from conf.
1580///
1581/// # Parameters
1582///
1583/// - `config`: Input consumed by [`crate::config::apply_serve_config_defaults_from_conf`].
1584///
1585/// # Returns
1586///
1587/// Returns after [`crate::config::apply_serve_config_defaults_from_conf`] applies its in-place effect.
1588///
1589/// # Examples
1590///
1591/// ```rust,ignore
1592/// let _result = pharos_runtime_core::config::apply_serve_config_defaults_from_conf(todo!());
1593/// ```
1594///
1595/// # Related
1596///
1597/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1598pub fn apply_serve_config_defaults_from_conf(config: &mut ServeConfig) {
1599    if config.conf_path.as_deref().is_none_or(str::is_empty)
1600        && let Some(env_path) = std::env::var_os("PHAROS_CONFIG")
1601    {
1602        let env_path = env_path.to_string_lossy().into_owned();
1603        if !env_path.is_empty() {
1604            config.conf_path = Some(env_path);
1605        }
1606    }
1607
1608    let Some(conf_path) = config.conf_path.clone() else {
1609        return;
1610    };
1611    if conf_path.is_empty() {
1612        return;
1613    }
1614
1615    let cwd = current_working_directory();
1616    let runtime_conf_path = resolve_relative_to(Path::new(&cwd), &conf_path).unwrap_or(conf_path);
1617    let runtime_conf_dir = Path::new(&runtime_conf_path)
1618        .parent()
1619        .map(Path::to_path_buf)
1620        .unwrap_or_else(|| PathBuf::from("."));
1621    let runtime_conf = Path::new(&runtime_conf_path);
1622
1623    if config.socket_path.as_deref().is_none_or(str::is_empty)
1624        && let Some(socket_value) = dynamic_config_lookup_raw(runtime_conf, "socket_path")
1625        && !socket_value.is_empty()
1626        && let Some(resolved) = resolve_relative_to(&runtime_conf_dir, &socket_value)
1627    {
1628        config.socket_path = Some(resolved);
1629        config.socket_path_from_conf = true;
1630    }
1631
1632    if config.port.as_deref().is_none_or(str::is_empty)
1633        && let Some(port_value) = dynamic_config_lookup_raw(runtime_conf, "port")
1634        && !port_value.is_empty()
1635    {
1636        config.port = Some(port_value);
1637        config.port_from_conf = true;
1638    }
1639
1640    if (config.host.as_deref().is_none_or(str::is_empty)
1641        || config.host.as_deref() == Some("127.0.0.1"))
1642        && let Some(host_value) = dynamic_config_lookup_raw(runtime_conf, "host")
1643        && !host_value.is_empty()
1644    {
1645        config.host = Some(host_value);
1646        config.host_from_conf = true;
1647    }
1648
1649    if config
1650        .trusted_proxy_cidrs
1651        .as_deref()
1652        .is_none_or(str::is_empty)
1653        && let Some(proxy_value) = dynamic_config_lookup_raw(runtime_conf, "trusted_proxy_cidrs")
1654        && !proxy_value.is_empty()
1655    {
1656        config.trusted_proxy_cidrs = Some(proxy_value);
1657        config.trusted_proxy_cidrs_from_conf = true;
1658    }
1659
1660    if let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_enabled") {
1661        config.redis_enabled = parse_config_bool(Some(value.as_str()), false);
1662        config.redis_enabled_from_conf = true;
1663    }
1664    if config.redis_host.as_deref().is_none_or(str::is_empty)
1665        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_host")
1666        && !value.is_empty()
1667    {
1668        config.redis_host = Some(value);
1669        config.redis_host_from_conf = true;
1670    }
1671    if config.redis_port.as_deref().is_none_or(str::is_empty)
1672        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_port")
1673        && !value.is_empty()
1674    {
1675        config.redis_port = Some(value);
1676        config.redis_port_from_conf = true;
1677    }
1678    if config.redis_auth_user.as_deref().is_none_or(str::is_empty)
1679        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_auth_user")
1680        && !value.is_empty()
1681    {
1682        config.redis_auth_user = Some(value);
1683        config.redis_auth_user_from_conf = true;
1684    }
1685    if config
1686        .redis_auth_token_env
1687        .as_deref()
1688        .is_none_or(str::is_empty)
1689        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_auth_token_env")
1690        && !value.is_empty()
1691    {
1692        config.redis_auth_token_env = Some(value);
1693        config.redis_auth_token_env_from_conf = true;
1694    }
1695    if config.redis_db_index.as_deref().is_none_or(str::is_empty)
1696        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_db_index")
1697        && !value.is_empty()
1698    {
1699        config.redis_db_index = Some(value);
1700        config.redis_db_index_from_conf = true;
1701    }
1702    if config
1703        .redis_connect_timeout_ms
1704        .as_deref()
1705        .is_none_or(str::is_empty)
1706        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_connect_timeout_ms")
1707        && !value.is_empty()
1708    {
1709        config.redis_connect_timeout_ms = Some(value);
1710        config.redis_connect_timeout_ms_from_conf = true;
1711    }
1712    if config
1713        .redis_read_timeout_ms
1714        .as_deref()
1715        .is_none_or(str::is_empty)
1716        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_read_timeout_ms")
1717        && !value.is_empty()
1718    {
1719        config.redis_read_timeout_ms = Some(value);
1720        config.redis_read_timeout_ms_from_conf = true;
1721    }
1722    if config
1723        .redis_cache_prefix
1724        .as_deref()
1725        .is_none_or(str::is_empty)
1726        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_cache_prefix")
1727        && !value.is_empty()
1728    {
1729        config.redis_cache_prefix = Some(value);
1730        config.redis_cache_prefix_from_conf = true;
1731    }
1732    if config
1733        .redis_backoff_seconds
1734        .as_deref()
1735        .is_none_or(str::is_empty)
1736        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_backoff_seconds")
1737        && !value.is_empty()
1738    {
1739        config.redis_backoff_seconds = Some(value);
1740        config.redis_backoff_seconds_from_conf = true;
1741    }
1742    if config
1743        .redis_backoff_max_seconds
1744        .as_deref()
1745        .is_none_or(str::is_empty)
1746        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_backoff_max_seconds")
1747        && !value.is_empty()
1748    {
1749        config.redis_backoff_max_seconds = Some(value);
1750        config.redis_backoff_max_seconds_from_conf = true;
1751    }
1752    if config.redis_tls_mode.as_deref().is_none_or(str::is_empty)
1753        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_tls_mode")
1754        && !value.is_empty()
1755    {
1756        config.redis_tls_mode = Some(value);
1757        config.redis_tls_mode_from_conf = true;
1758    }
1759    if config
1760        .redis_tls_root_cert
1761        .as_deref()
1762        .is_none_or(str::is_empty)
1763        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_tls_root_cert")
1764        && !value.is_empty()
1765        && let Some(resolved) = resolve_relative_to(&runtime_conf_dir, &value)
1766    {
1767        config.redis_tls_root_cert = Some(resolved);
1768        config.redis_tls_root_cert_from_conf = true;
1769    }
1770    if config.redis_tls_cert.as_deref().is_none_or(str::is_empty)
1771        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_tls_cert")
1772        && !value.is_empty()
1773        && let Some(resolved) = resolve_relative_to(&runtime_conf_dir, &value)
1774    {
1775        config.redis_tls_cert = Some(resolved);
1776        config.redis_tls_cert_from_conf = true;
1777    }
1778    if config.redis_tls_key.as_deref().is_none_or(str::is_empty)
1779        && let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_tls_key")
1780        && !value.is_empty()
1781        && let Some(resolved) = resolve_relative_to(&runtime_conf_dir, &value)
1782    {
1783        config.redis_tls_key = Some(resolved);
1784        config.redis_tls_key_from_conf = true;
1785    }
1786    if let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_require_peer_name_match") {
1787        config.redis_require_peer_name_match = parse_config_bool(Some(value.as_str()), true);
1788        config.redis_require_peer_name_match_from_conf = true;
1789    }
1790    if let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_cache_sessions_enabled") {
1791        config.redis_cache_sessions_enabled = parse_config_bool(Some(value.as_str()), true);
1792        config.redis_cache_sessions_enabled_from_conf = true;
1793    }
1794    if let Some(value) =
1795        dynamic_config_lookup_raw(runtime_conf, "redis_cache_template_source_enabled")
1796    {
1797        config.redis_cache_template_source_enabled = parse_config_bool(Some(value.as_str()), false);
1798        config.redis_cache_template_source_enabled_from_conf = true;
1799    }
1800    if let Some(value) = dynamic_config_lookup_raw(runtime_conf, "redis_cache_route_match_enabled")
1801    {
1802        config.redis_cache_route_match_enabled = parse_config_bool(Some(value.as_str()), false);
1803        config.redis_cache_route_match_enabled_from_conf = true;
1804    }
1805    if let Some(value) =
1806        dynamic_config_lookup_raw(runtime_conf, "redis_cache_config_allowlist_enabled")
1807    {
1808        config.redis_cache_config_allowlist_enabled =
1809            parse_config_bool(Some(value.as_str()), false);
1810        config.redis_cache_config_allowlist_enabled_from_conf = true;
1811    }
1812
1813    if config.log_path.as_deref().is_none_or(str::is_empty)
1814        && let Some(log_path_value) = dynamic_config_lookup_raw(runtime_conf, "log_path")
1815        && !log_path_value.is_empty()
1816        && let Some(resolved) = resolve_relative_to(&runtime_conf_dir, &log_path_value)
1817    {
1818        config.log_path = Some(resolved);
1819        config.log_path_from_conf = true;
1820    }
1821
1822    if config.log_level.as_deref().is_none_or(str::is_empty)
1823        && let Some(level_value) = dynamic_config_lookup_raw(runtime_conf, "log_level")
1824        && !level_value.is_empty()
1825    {
1826        config.log_level = Some(level_value);
1827        config.log_level_from_conf = true;
1828    }
1829}
1830
1831/// Performs app base path local.
1832///
1833/// # Parameters
1834///
1835/// - `app_root`: Input consumed by [`crate::config::app_base_path_local`].
1836///
1837/// # Returns
1838///
1839/// Returns [`Some`] when [`crate::config::app_base_path_local`] can produce a value and [`None`] otherwise.
1840///
1841/// # Examples
1842///
1843/// ```rust,ignore
1844/// let _result = pharos_runtime_core::config::app_base_path_local(std::path::Path::new("apps/dev_docs"));
1845/// ```
1846///
1847/// # Related
1848///
1849/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1850pub fn app_base_path_local(app_root: &Path) -> Option<String> {
1851    let config = load_app_local_config(app_root)?;
1852    let value = config
1853        .base_path
1854        .filter(|value| !value.is_empty())
1855        .or(config.manifest_base_path)
1856        .unwrap_or_default();
1857
1858    normalize_base_path(Some(&value))
1859}
1860
1861/// Performs effective app base path.
1862///
1863/// # Parameters
1864///
1865/// - `app_root`: Input consumed by [`crate::config::effective_app_base_path`].
1866/// - `base_path_override`: Input consumed by [`crate::config::effective_app_base_path`].
1867///
1868/// # Returns
1869///
1870/// Returns [`Some`] when [`crate::config::effective_app_base_path`] can produce a value and [`None`] otherwise.
1871///
1872/// # Examples
1873///
1874/// ```rust,ignore
1875/// let _result = pharos_runtime_core::config::effective_app_base_path(std::path::Path::new("apps/dev_docs"), todo!());
1876/// ```
1877///
1878/// # Related
1879///
1880/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1881pub fn effective_app_base_path(
1882    app_root: &Path,
1883    base_path_override: Option<&str>,
1884) -> Option<String> {
1885    match base_path_override {
1886        Some(value) if !value.is_empty() => normalize_base_path(Some(value)),
1887        _ => app_base_path_local(app_root),
1888    }
1889}
1890
1891/// Performs app log path local.
1892///
1893/// # Parameters
1894///
1895/// - `app_root`: Input consumed by [`crate::config::app_log_path_local`].
1896/// - `app_id`: Input consumed by [`crate::config::app_log_path_local`].
1897///
1898/// # Returns
1899///
1900/// Returns [`Some`] when [`crate::config::app_log_path_local`] can produce a value and [`None`] otherwise.
1901///
1902/// # Examples
1903///
1904/// ```rust,ignore
1905/// let _result = pharos_runtime_core::config::app_log_path_local(std::path::Path::new("apps/dev_docs"), todo!());
1906/// ```
1907///
1908/// # Related
1909///
1910/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1911pub fn app_log_path_local(app_root: &Path, app_id: &str) -> Option<String> {
1912    let config = load_app_local_config(app_root)?;
1913
1914    if let Some(value) = config.log_path.filter(|value| !value.is_empty()) {
1915        return Some(value);
1916    }
1917
1918    Some(format!("/var/log/pharos/{app_id}.log"))
1919}
1920
1921/// Performs effective app log path.
1922///
1923/// # Parameters
1924///
1925/// - `app_root`: Input consumed by [`crate::config::effective_app_log_path`].
1926/// - `app_id`: Input consumed by [`crate::config::effective_app_log_path`].
1927/// - `log_path_override`: Input consumed by [`crate::config::effective_app_log_path`].
1928///
1929/// # Returns
1930///
1931/// Returns [`Some`] when [`crate::config::effective_app_log_path`] can produce a value and [`None`] otherwise.
1932///
1933/// # Examples
1934///
1935/// ```rust,ignore
1936/// let _result = pharos_runtime_core::config::effective_app_log_path(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
1937/// ```
1938///
1939/// # Related
1940///
1941/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1942pub fn effective_app_log_path(
1943    app_root: &Path,
1944    app_id: &str,
1945    log_path_override: Option<&str>,
1946) -> Option<String> {
1947    match log_path_override {
1948        Some(value) if !value.is_empty() => Some(value.to_owned()),
1949        _ => app_log_path_local(app_root, app_id),
1950    }
1951}
1952
1953/// Performs app log format local.
1954///
1955/// # Parameters
1956///
1957/// - `_app_root`: Input consumed by [`crate::config::app_log_format_local`].
1958///
1959/// # Returns
1960///
1961/// Returns [`Some`] when [`crate::config::app_log_format_local`] can produce a value and [`None`] otherwise.
1962///
1963/// # Examples
1964///
1965/// ```rust,ignore
1966/// let _result = pharos_runtime_core::config::app_log_format_local(todo!());
1967/// ```
1968///
1969/// # Related
1970///
1971/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1972pub fn app_log_format_local(_app_root: &Path) -> Option<String> {
1973    Some("service".to_owned())
1974}
1975
1976/// Performs effective app log format.
1977///
1978/// # Parameters
1979///
1980/// - `app_root`: Input consumed by [`crate::config::effective_app_log_format`].
1981///
1982/// # Returns
1983///
1984/// Returns [`Some`] when [`crate::config::effective_app_log_format`] can produce a value and [`None`] otherwise.
1985///
1986/// # Examples
1987///
1988/// ```rust,ignore
1989/// let _result = pharos_runtime_core::config::effective_app_log_format(std::path::Path::new("apps/dev_docs"));
1990/// ```
1991///
1992/// # Related
1993///
1994/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
1995pub fn effective_app_log_format(app_root: &Path) -> Option<String> {
1996    app_log_format_local(app_root)
1997}
1998
1999/// Performs app error report path local.
2000///
2001/// # Parameters
2002///
2003/// - `app_root`: Input consumed by [`crate::config::app_error_report_path_local`].
2004///
2005/// # Returns
2006///
2007/// Returns [`Some`] when [`crate::config::app_error_report_path_local`] can produce a value and [`None`] otherwise.
2008///
2009/// # Examples
2010///
2011/// ```rust,ignore
2012/// let _result = pharos_runtime_core::config::app_error_report_path_local(std::path::Path::new("apps/dev_docs"));
2013/// ```
2014///
2015/// # Related
2016///
2017/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
2018pub fn app_error_report_path_local(app_root: &Path) -> Option<String> {
2019    let config = load_app_local_config(app_root)?;
2020
2021    if let Some(value) = config.error_report_path.filter(|value| !value.is_empty()) {
2022        return Some(value);
2023    }
2024
2025    Some(String::new())
2026}
2027
2028/// Performs effective app error report path.
2029///
2030/// # Parameters
2031///
2032/// - `app_root`: Input consumed by [`crate::config::effective_app_error_report_path`].
2033/// - `app_id`: Input consumed by [`crate::config::effective_app_error_report_path`].
2034///
2035/// # Returns
2036///
2037/// Returns [`Some`] when [`crate::config::effective_app_error_report_path`] can produce a value and [`None`] otherwise.
2038///
2039/// # Examples
2040///
2041/// ```rust,ignore
2042/// let _result = pharos_runtime_core::config::effective_app_error_report_path(std::path::Path::new("apps/dev_docs"), todo!());
2043/// ```
2044///
2045/// # Related
2046///
2047/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
2048pub fn effective_app_error_report_path(app_root: &Path, app_id: Option<&str>) -> Option<String> {
2049    let value = app_error_report_path_local(app_root)?;
2050    if !value.is_empty() {
2051        return Some(value);
2052    }
2053
2054    let effective_app_id = app_id.filter(|value| !value.is_empty()).unwrap_or("app");
2055    Some(format!("/var/log/pharos/{effective_app_id}.error.jsonl"))
2056}
2057
2058/// Performs app log level local.
2059///
2060/// # Parameters
2061///
2062/// - `app_root`: Input consumed by [`crate::config::app_log_level_local`].
2063///
2064/// # Returns
2065///
2066/// Returns [`Some`] when [`crate::config::app_log_level_local`] can produce a value and [`None`] otherwise.
2067///
2068/// # Examples
2069///
2070/// ```rust,ignore
2071/// let _result = pharos_runtime_core::config::app_log_level_local(std::path::Path::new("apps/dev_docs"));
2072/// ```
2073///
2074/// # Related
2075///
2076/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
2077pub fn app_log_level_local(app_root: &Path) -> Option<String> {
2078    let config = load_app_local_config(app_root)?;
2079
2080    if let Some(value) = config.log_level.filter(|value| !value.is_empty()) {
2081        return Some(value);
2082    }
2083
2084    Some("WARN".to_owned())
2085}
2086
2087/// Performs effective app log level.
2088///
2089/// # Parameters
2090///
2091/// - `app_root`: Input consumed by [`crate::config::effective_app_log_level`].
2092/// - `log_level_override`: Input consumed by [`crate::config::effective_app_log_level`].
2093///
2094/// # Returns
2095///
2096/// Returns [`Some`] when [`crate::config::effective_app_log_level`] can produce a value and [`None`] otherwise.
2097///
2098/// # Examples
2099///
2100/// ```rust,ignore
2101/// let _result = pharos_runtime_core::config::effective_app_log_level(std::path::Path::new("apps/dev_docs"), todo!());
2102/// ```
2103///
2104/// # Related
2105///
2106/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
2107pub fn effective_app_log_level(
2108    app_root: &Path,
2109    log_level_override: Option<&str>,
2110) -> Option<String> {
2111    match log_level_override {
2112        Some(value) if !value.is_empty() => Some(value.to_owned()),
2113        _ => app_log_level_local(app_root),
2114    }
2115}
2116
2117/// Performs app log level explicit.
2118///
2119/// # Parameters
2120///
2121/// - `app_root`: Input consumed by [`crate::config::app_log_level_explicit`].
2122///
2123/// # Returns
2124///
2125/// Returns [`Some`] when [`crate::config::app_log_level_explicit`] can produce a value and [`None`] otherwise.
2126///
2127/// # Examples
2128///
2129/// ```rust,ignore
2130/// let _result = pharos_runtime_core::config::app_log_level_explicit(std::path::Path::new("apps/dev_docs"));
2131/// ```
2132///
2133/// # Related
2134///
2135/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
2136pub fn app_log_level_explicit(app_root: &Path) -> Option<String> {
2137    load_app_local_config(app_root)?
2138        .log_level
2139        .filter(|value| !value.is_empty())
2140}
2141
2142/// Normalizes base path.
2143///
2144/// # Parameters
2145///
2146/// - `raw_value`: Input consumed by [`crate::config::normalize_base_path`].
2147///
2148/// # Returns
2149///
2150/// Returns [`Some`] when [`crate::config::normalize_base_path`] can produce a value and [`None`] otherwise.
2151///
2152/// # Examples
2153///
2154/// ```rust,ignore
2155/// let _result = pharos_runtime_core::config::normalize_base_path("logs/app.log");
2156/// ```
2157///
2158/// # Related
2159///
2160/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
2161pub fn normalize_base_path(raw_value: Option<&str>) -> Option<String> {
2162    let raw_value = match raw_value {
2163        None | Some("") | Some("/") | Some("null") => return Some(String::new()),
2164        Some(value) => value,
2165    };
2166
2167    let trimmed = raw_value.trim_matches('/');
2168    if trimmed.is_empty() {
2169        return Some(String::new());
2170    }
2171
2172    Some(format!("/{trimmed}"))
2173}
2174
2175/// Performs finalize state path.
2176fn finalize_state_path(
2177    app: &AppRuntime,
2178    config: Option<&ServeConfig>,
2179    app_root: &Path,
2180    build_dir: &Path,
2181) -> Option<String> {
2182    if let Some(config) = config {
2183        if has_cli_override_value(config.state_path.as_deref(), config.state_path_from_conf) {
2184            return config.state_path.clone();
2185        }
2186        if has_cli_override_value(config.state_dir.as_deref(), config.state_dir_from_conf) {
2187            return effective_app_state_path(
2188                app_root,
2189                build_dir,
2190                app.app_id.as_deref()?,
2191                None,
2192                config.state_dir.as_deref(),
2193            );
2194        }
2195    }
2196
2197    let app_local_state_path = app_local_conf_value(app_root, "state_path");
2198    if has_override_value(app_local_state_path.as_deref()) {
2199        return app_local_state_path;
2200    }
2201
2202    let app_local_state_dir = app_local_conf_value(app_root, "state_dir");
2203    if has_override_value(app_local_state_dir.as_deref()) {
2204        return effective_app_state_path(
2205            app_root,
2206            build_dir,
2207            app.app_id.as_deref()?,
2208            None,
2209            app_local_state_dir.as_deref(),
2210        );
2211    }
2212
2213    if has_override_value(app.state_path.as_deref()) {
2214        return app.state_path.clone();
2215    }
2216
2217    if let Some(config) = config {
2218        if has_runtime_default_value(config.state_path.as_deref(), config.state_path_from_conf) {
2219            return config.state_path.clone();
2220        }
2221        if has_runtime_default_value(config.state_dir.as_deref(), config.state_dir_from_conf) {
2222            return effective_app_state_path(
2223                app_root,
2224                build_dir,
2225                app.app_id.as_deref()?,
2226                None,
2227                config.state_dir.as_deref(),
2228            );
2229        }
2230    }
2231
2232    effective_app_state_path(app_root, build_dir, app.app_id.as_deref()?, None, None)
2233}
2234
2235/// Performs finalize base path.
2236fn finalize_base_path(
2237    app: &AppRuntime,
2238    config: Option<&ServeConfig>,
2239    app_root: &Path,
2240) -> Option<String> {
2241    if let Some(config) = config
2242        && has_cli_override_value(config.base_path.as_deref(), config.base_path_from_conf)
2243    {
2244        return effective_app_base_path(app_root, config.base_path.as_deref());
2245    }
2246
2247    let app_local_base_path = app_local_conf_value(app_root, "base_path");
2248    if app_local_base_path.is_some() {
2249        return effective_app_base_path(app_root, None);
2250    }
2251
2252    if app.base_path.is_some() {
2253        return app.base_path.clone();
2254    }
2255
2256    if let Some(config) = config
2257        && has_runtime_default_value(config.base_path.as_deref(), config.base_path_from_conf)
2258    {
2259        return effective_app_base_path(app_root, config.base_path.as_deref());
2260    }
2261
2262    effective_app_base_path(app_root, None)
2263}
2264
2265/// Performs finalize log path.
2266fn finalize_log_path(
2267    app: &AppRuntime,
2268    config: Option<&ServeConfig>,
2269    app_root: &Path,
2270) -> Option<String> {
2271    let app_local_log_path = app
2272        .app_id
2273        .as_deref()
2274        .and_then(|app_id| app_log_path_local(app_root, app_id));
2275    if let Some(config) = config
2276        && has_cli_override_value(config.log_path.as_deref(), config.log_path_from_conf)
2277    {
2278        return effective_app_log_path(
2279            app_root,
2280            app.app_id.as_deref()?,
2281            config.log_path.as_deref(),
2282        );
2283    }
2284
2285    if has_override_value(app_local_log_path.as_deref()) {
2286        return app_local_log_path;
2287    }
2288
2289    if has_override_value(app.log_path.as_deref()) {
2290        return app.log_path.clone();
2291    }
2292
2293    if let Some(config) = config
2294        && has_runtime_default_value(config.log_path.as_deref(), config.log_path_from_conf)
2295    {
2296        return config.log_path.clone();
2297    }
2298
2299    app_local_log_path
2300}
2301
2302/// Performs finalize log format.
2303fn finalize_log_format(app: &AppRuntime, app_root: &Path) -> Option<String> {
2304    if has_override_value(app.log_format.as_deref()) {
2305        return app.log_format.clone();
2306    }
2307
2308    effective_app_log_format(app_root)
2309}
2310
2311/// Performs finalize error report path.
2312fn finalize_error_report_path(app: &AppRuntime, app_root: &Path) -> Option<String> {
2313    let app_local_error_report_path = app_error_report_path_local(app_root);
2314    if has_override_value(app_local_error_report_path.as_deref()) {
2315        return app_local_error_report_path;
2316    }
2317
2318    if app.error_report_path.is_some() {
2319        return app.error_report_path.clone();
2320    }
2321
2322    effective_app_error_report_path(app_root, app.app_id.as_deref())
2323}
2324
2325/// Performs finalize log level.
2326fn finalize_log_level(config: Option<&ServeConfig>, app_root: &Path) -> Option<String> {
2327    if let Some(config) = config
2328        && has_cli_override_value(config.log_level.as_deref(), config.log_level_from_conf)
2329    {
2330        return config.log_level.clone();
2331    }
2332
2333    if let Some(app_value) = app_log_level_explicit(app_root) {
2334        return Some(app_value);
2335    }
2336
2337    if let Some(config) = config
2338        && has_runtime_default_value(config.log_level.as_deref(), config.log_level_from_conf)
2339    {
2340        return config.log_level.clone();
2341    }
2342
2343    Some("WARN".to_owned())
2344}
2345
2346/// Performs finalize trusted proxy cidrs.
2347fn finalize_trusted_proxy_cidrs(app: &AppRuntime, config: Option<&ServeConfig>) -> Option<String> {
2348    if let Some(config) = config
2349        && has_cli_override_value(
2350            config.trusted_proxy_cidrs.as_deref(),
2351            config.trusted_proxy_cidrs_from_conf,
2352        )
2353    {
2354        return config.trusted_proxy_cidrs.clone();
2355    }
2356
2357    if has_override_value(app.trusted_proxy_cidrs.as_deref()) {
2358        return app.trusted_proxy_cidrs.clone();
2359    }
2360
2361    if let Some(config) = config
2362        && has_runtime_default_value(
2363            config.trusted_proxy_cidrs.as_deref(),
2364            config.trusted_proxy_cidrs_from_conf,
2365        )
2366    {
2367        return config.trusted_proxy_cidrs.clone();
2368    }
2369
2370    None
2371}
2372
2373/// Performs resolved runtime conf path from config.
2374///
2375/// # Parameters
2376///
2377/// - `config`: Input consumed by [`crate::config::resolved_runtime_conf_path_from_config`].
2378/// - `app_root`: Input consumed by [`crate::config::resolved_runtime_conf_path_from_config`].
2379///
2380/// # Returns
2381///
2382/// Returns [`Some`] when [`crate::config::resolved_runtime_conf_path_from_config`] can produce a value and [`None`] otherwise.
2383///
2384/// # Examples
2385///
2386/// ```rust,ignore
2387/// let _result = pharos_runtime_core::config::resolved_runtime_conf_path_from_config(todo!(), std::path::Path::new("apps/dev_docs"));
2388/// ```
2389///
2390/// # Related
2391///
2392/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
2393pub fn resolved_runtime_conf_path_from_config(
2394    config: Option<&ServeConfig>,
2395    app_root: &Path,
2396) -> Option<String> {
2397    if let Some(config) = config
2398        && let Some(conf_path) = config
2399            .conf_path
2400            .as_deref()
2401            .filter(|value| !value.is_empty())
2402    {
2403        return resolve_relative_to(app_root, conf_path).or_else(|| Some(conf_path.to_owned()));
2404    }
2405
2406    app_local_conf_path(app_root).map(|path| path.to_string_lossy().into_owned())
2407}
2408
2409/// Performs finalize runtime conf path.
2410fn finalize_runtime_conf_path(
2411    app: &AppRuntime,
2412    config: Option<&ServeConfig>,
2413    app_root: &Path,
2414) -> Option<String> {
2415    if let Some(config) = config
2416        && has_override_value(config.conf_path.as_deref())
2417    {
2418        return resolved_runtime_conf_path_from_config(Some(config), app_root);
2419    }
2420
2421    if has_override_value(app.runtime_conf_path.as_deref()) {
2422        return app.runtime_conf_path.clone();
2423    }
2424
2425    resolved_runtime_conf_path_from_config(config, app_root)
2426}
2427
2428/// Performs finalize queue enabled.
2429fn finalize_queue_enabled(app_root: &Path) -> bool {
2430    parse_queue_enabled(dynamic_config_effective_value(app_root, None, "queue_enabled").as_deref())
2431}
2432
2433/// Performs finalize queue long field.
2434fn finalize_queue_long_field(app_root: &Path, key: &str, default_value: i64) -> i64 {
2435    parse_positive_i64(
2436        dynamic_config_effective_value(app_root, None, key).as_deref(),
2437        default_value,
2438    )
2439}
2440
2441/// Loads one bounded positive resource limit from app or runtime configuration.
2442fn resource_limit(
2443    app_root: &Path,
2444    runtime_conf_path: Option<&Path>,
2445    key: &str,
2446    default_value: usize,
2447    maximum: usize,
2448) -> usize {
2449    dynamic_config_effective_value(app_root, runtime_conf_path, key)
2450        .and_then(|value| value.parse::<usize>().ok())
2451        .filter(|value| *value > 0 && *value <= maximum)
2452        .unwrap_or(default_value)
2453}
2454
2455/// Resolves the complete first-class resource budget contract.
2456fn finalize_resource_budgets(app_root: &Path, runtime_conf_path: Option<&Path>) -> ResourceBudgets {
2457    let defaults = ResourceBudgets::default();
2458    ResourceBudgets {
2459        max_response_bytes: resource_limit(
2460            app_root,
2461            runtime_conf_path,
2462            "max_response_bytes",
2463            defaults.max_response_bytes,
2464            64 * 1024 * 1024,
2465        ),
2466        max_query_result_bytes: resource_limit(
2467            app_root,
2468            runtime_conf_path,
2469            "max_query_result_bytes",
2470            defaults.max_query_result_bytes,
2471            64 * 1024 * 1024,
2472        ),
2473        max_report_bytes: resource_limit(
2474            app_root,
2475            runtime_conf_path,
2476            "max_report_bytes",
2477            defaults.max_report_bytes,
2478            64 * 1024 * 1024,
2479        ),
2480        max_state_section_items: resource_limit(
2481            app_root,
2482            runtime_conf_path,
2483            "max_state_section_items",
2484            defaults.max_state_section_items,
2485            1_000_000,
2486        ),
2487        max_workflow_effects: resource_limit(
2488            app_root,
2489            runtime_conf_path,
2490            "max_workflow_effects",
2491            defaults.max_workflow_effects,
2492            100_000,
2493        ),
2494        max_jobs_per_poll: resource_limit(
2495            app_root,
2496            runtime_conf_path,
2497            "max_jobs_per_poll",
2498            defaults.max_jobs_per_poll,
2499            10_000,
2500        ),
2501        integration_connect_timeout_ms: resource_limit(
2502            app_root,
2503            runtime_conf_path,
2504            "integration_connect_timeout_ms",
2505            defaults.integration_connect_timeout_ms as usize,
2506            120_000,
2507        ) as u64,
2508        integration_operation_timeout_ms: resource_limit(
2509            app_root,
2510            runtime_conf_path,
2511            "integration_operation_timeout_ms",
2512            defaults.integration_operation_timeout_ms as usize,
2513            300_000,
2514        ) as u64,
2515        integration_max_response_bytes: resource_limit(
2516            app_root,
2517            runtime_conf_path,
2518            "integration_max_response_bytes",
2519            defaults.integration_max_response_bytes,
2520            16 * 1024 * 1024,
2521        ),
2522    }
2523}
2524
2525/// Rejects compiled actions whose declared effect set exceeds the configured work budget.
2526fn application_effects_within_budget(app: &AppRuntime, budgets: &ResourceBudgets) -> bool {
2527    app.application_ir.as_ref().is_none_or(|ir| {
2528        ir.actions.values().all(|action| {
2529            action
2530                .effects
2531                .reads
2532                .len()
2533                .saturating_add(action.effects.writes.len())
2534                .saturating_add(action.effects.emits.len())
2535                .saturating_add(action.effects.enqueues.len())
2536                .saturating_add(action.effects.external_calls.len())
2537                <= budgets.max_workflow_effects
2538        })
2539    })
2540}
2541
2542/// Rejects an existing state document containing an unbounded top-level collection.
2543fn state_sections_within_budget(path: &Path, budgets: &ResourceBudgets) -> bool {
2544    if !path.is_file() {
2545        return true;
2546    }
2547    let Ok(metadata) = fs::metadata(path) else {
2548        return false;
2549    };
2550    if metadata.len() > budgets.max_query_result_bytes as u64 {
2551        return false;
2552    }
2553    fs::read_to_string(path)
2554        .ok()
2555        .and_then(|text| serde_json::from_str::<Value>(&text).ok())
2556        .and_then(|value| value.as_object().cloned())
2557        .is_some_and(|object| {
2558            object.values().all(|value| {
2559                value
2560                    .as_array()
2561                    .is_none_or(|rows| rows.len() <= budgets.max_state_section_items)
2562            })
2563        })
2564}
2565
2566/// Performs finalize loaded app.
2567///
2568/// # Parameters
2569///
2570/// - `app`: Input consumed by [`crate::config::finalize_loaded_app`].
2571/// - `config`: Input consumed by [`crate::config::finalize_loaded_app`].
2572/// - `app_root_override`: Input consumed by [`crate::config::finalize_loaded_app`].
2573/// - `build_dir_override`: Input consumed by [`crate::config::finalize_loaded_app`].
2574/// - `) -> Result<(`: Input consumed by [`crate::config::finalize_loaded_app`].
2575///
2576/// # Returns
2577///
2578/// Returns the successful value produced by [`crate::config::finalize_loaded_app`] or an error when the operation fails.
2579///
2580/// # Errors
2581///
2582/// Returns an error value when [`crate::config::finalize_loaded_app`] cannot complete successfully.
2583///
2584/// # Examples
2585///
2586/// ```rust,ignore
2587/// let _result = pharos_runtime_core::config::finalize_loaded_app(todo!(), todo!(), todo!(), todo!(), todo!());
2588/// ```
2589///
2590/// # Related
2591///
2592/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
2593pub fn finalize_loaded_app(
2594    app: &mut AppRuntime,
2595    config: Option<&ServeConfig>,
2596    app_root_override: Option<&Path>,
2597    build_dir_override: Option<&Path>,
2598) -> Result<(), &'static str> {
2599    let app_root_value = app_root_override
2600        .map(|path| path.to_string_lossy().into_owned())
2601        .or_else(|| app.app_root.clone())
2602        .ok_or("missing app_root")?;
2603    let build_dir_value = build_dir_override
2604        .map(|path| path.to_string_lossy().into_owned())
2605        .or_else(|| app.build_dir.clone())
2606        .ok_or("missing build_dir")?;
2607
2608    let app_root_path = PathBuf::from(&app_root_value);
2609    let build_dir_path = PathBuf::from(&build_dir_value);
2610    let state_path = finalize_state_path(app, config, &app_root_path, &build_dir_path)
2611        .ok_or("missing state_path")?;
2612    let base_path = finalize_base_path(app, config, &app_root_path).unwrap_or_default();
2613    let log_path = finalize_log_path(app, config, &app_root_path).ok_or("missing log_path")?;
2614    let log_format = finalize_log_format(app, &app_root_path).ok_or("missing log_format")?;
2615    let error_report_path =
2616        finalize_error_report_path(app, &app_root_path).ok_or("missing error_report_path")?;
2617    let log_level = finalize_log_level(config, &app_root_path).unwrap_or_else(|| "WARN".to_owned());
2618    let trusted_proxy_cidrs = finalize_trusted_proxy_cidrs(app, config);
2619    let runtime_conf_path = finalize_runtime_conf_path(app, config, &app_root_path);
2620    let resource_budgets =
2621        finalize_resource_budgets(&app_root_path, runtime_conf_path.as_deref().map(Path::new));
2622    if !application_effects_within_budget(app, &resource_budgets)
2623        || !state_sections_within_budget(Path::new(&state_path), &resource_budgets)
2624    {
2625        return Err("runtime resource budget exceeded");
2626    }
2627
2628    app.app_root = Some(app_root_value);
2629    app.build_dir = Some(build_dir_value);
2630    app.state_path = Some(state_path);
2631    app.base_path = Some(base_path);
2632    app.log_path = Some(log_path);
2633    app.log_format = Some(log_format);
2634    app.error_report_path = Some(error_report_path);
2635    app.log_level = Some(log_level);
2636    app.trusted_proxy_cidrs = trusted_proxy_cidrs;
2637    app.runtime_conf_path = runtime_conf_path;
2638    app.queue_enabled = finalize_queue_enabled(&app_root_path);
2639    app.queue_poll_interval_ms =
2640        finalize_queue_long_field(&app_root_path, "queue_poll_interval_ms", 5000);
2641    app.queue_max_attempts = finalize_queue_long_field(&app_root_path, "queue_max_attempts", 3);
2642    app.queue_retry_seconds = finalize_queue_long_field(&app_root_path, "queue_retry_seconds", 30);
2643    app.resource_budgets = resource_budgets;
2644
2645    Ok(())
2646}
2647
2648/// Clears an app-local configuration value without retaining owned strings.
2649///
2650/// # Parameters
2651///
2652/// - `config`: Input consumed by [`crate::config::free_app_local_config`].
2653///
2654/// # Returns
2655///
2656/// Returns after [`crate::config::free_app_local_config`] applies its in-place effect.
2657///
2658/// # Examples
2659///
2660/// ```rust,ignore
2661/// let _result = pharos_runtime_core::config::free_app_local_config(todo!());
2662/// ```
2663///
2664/// # Related
2665///
2666/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
2667pub fn free_app_local_config(config: &mut AppLocalConfig) {
2668    *config = AppLocalConfig::default();
2669}
2670
2671/// Resolves an artifact directory relative to a build directory while preserving `dist/apps`.
2672///
2673/// # Parameters
2674///
2675/// - `build_dir`: Input consumed by [`crate::config::resolve_relative_artifact_dir_runtime_local`].
2676/// - `artifact_dir`: Input consumed by [`crate::config::resolve_relative_artifact_dir_runtime_local`].
2677///
2678/// # Returns
2679///
2680/// Returns [`Some`] when [`crate::config::resolve_relative_artifact_dir_runtime_local`] can produce a value and [`None`] otherwise.
2681///
2682/// # Examples
2683///
2684/// ```rust,ignore
2685/// let _result = pharos_runtime_core::config::resolve_relative_artifact_dir_runtime_local(std::path::Path::new("build/dev_docs"), std::path::Path::new("build/dev_docs"));
2686/// ```
2687///
2688/// # Related
2689///
2690/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
2691pub fn resolve_relative_artifact_dir_runtime_local(
2692    build_dir: &Path,
2693    artifact_dir: &str,
2694) -> Option<String> {
2695    if build_dir.as_os_str().is_empty()
2696        || artifact_dir.is_empty()
2697        || artifact_dir.contains(['\0', '\r', '\n'])
2698    {
2699        return None;
2700    }
2701    let relative = Path::new(artifact_dir);
2702    if relative.is_absolute()
2703        || relative
2704            .components()
2705            .any(|component| matches!(component, std::path::Component::ParentDir))
2706    {
2707        return None;
2708    }
2709    Some(
2710        build_dir
2711            .join(relative)
2712            .to_string_lossy()
2713            .replace("dist/dist/", "dist/"),
2714    )
2715}
2716
2717/// Tests whether the final path component is the protected runtime directory.
2718///
2719/// # Parameters
2720///
2721/// - `path`: Input consumed by [`crate::config::path_last_segment_is_apps_runtime_local`].
2722///
2723/// # Returns
2724///
2725/// Returns `true` when the condition checked by [`crate::config::path_last_segment_is_apps_runtime_local`] holds and `false` otherwise.
2726///
2727/// # Examples
2728///
2729/// ```rust,ignore
2730/// let _result = pharos_runtime_core::config::path_last_segment_is_apps_runtime_local(std::path::Path::new("fixture.json"));
2731/// ```
2732///
2733/// # Related
2734///
2735/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
2736pub fn path_last_segment_is_apps_runtime_local(path: &Path) -> bool {
2737    path.file_name().and_then(|value| value.to_str()) == Some("apps")
2738}
2739
2740/// Skips a leading `apps` path component without allowing traversal.
2741///
2742/// # Parameters
2743///
2744/// - `path`: Input consumed by [`crate::config::skip_leading_apps_segment_runtime_local`].
2745///
2746/// # Returns
2747///
2748/// Returns the computed value produced by [`crate::config::skip_leading_apps_segment_runtime_local`].
2749///
2750/// # Examples
2751///
2752/// ```rust,ignore
2753/// let _result = pharos_runtime_core::config::skip_leading_apps_segment_runtime_local(std::path::Path::new("fixture.json"));
2754/// ```
2755///
2756/// # Related
2757///
2758/// - [`crate::config`] for adjacent helpers that share the same runtime surface.
2759pub fn skip_leading_apps_segment_runtime_local(path: &Path) -> PathBuf {
2760    let mut components = path.components();
2761    if matches!(components.next(), Some(std::path::Component::Normal(value)) if value == "apps") {
2762        components.as_path().to_path_buf()
2763    } else {
2764        path.to_path_buf()
2765    }
2766}
2767
2768#[cfg(test)]
2769mod tests {
2770    use std::fs;
2771    use std::path::Path;
2772
2773    #[test]
2774    fn app_local_state_path_resolves_relative_to_app_root() {
2775        let root =
2776            std::env::temp_dir().join(format!("pharos-config-state-path-{}", std::process::id()));
2777        let app_root = root.join("apps").join("architect");
2778        fs::create_dir_all(&app_root).expect("app root");
2779        fs::write(
2780            app_root.join("app.conf"),
2781            "base_path=/architect\nstate_path=var/state/runtime-state.json\n",
2782        )
2783        .expect("app conf");
2784
2785        let resolved = super::app_state_path_local(&app_root, Path::new("dist"), "architect")
2786            .expect("state path");
2787
2788        assert_eq!(
2789            resolved,
2790            app_root
2791                .join("var/state/runtime-state.json")
2792                .to_string_lossy()
2793                .into_owned()
2794        );
2795
2796        fs::remove_dir_all(root).expect("cleanup");
2797    }
2798}