docs(core): document runtime guardrails
This commit is contained in:
@@ -1,19 +1,37 @@
|
|||||||
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
|
|
||||||
|
//! Shared production primitives for AWatch-rus.
|
||||||
|
//!
|
||||||
|
//! This crate intentionally stays small and dependency-light. It contains the
|
||||||
|
//! status, exit-code and runtime-configuration guardrails that are reused by
|
||||||
|
//! operational binaries and health/check tooling. Keep business-specific portal,
|
||||||
|
//! DLP or workforce logic out of this crate.
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use chrono::{DateTime, SecondsFormat, Utc};
|
use chrono::{DateTime, SecondsFormat, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Normalized health/check status used by CLI tools, probes and JSON payloads.
|
||||||
|
///
|
||||||
|
/// CONTRACT: serialized values are uppercase and must remain stable because
|
||||||
|
/// deployment scripts, smoke checks and dashboards can key off these strings.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "UPPERCASE")]
|
#[serde(rename_all = "UPPERCASE")]
|
||||||
pub enum StatusLevel {
|
pub enum StatusLevel {
|
||||||
|
/// Component is healthy and the check passed.
|
||||||
Ok,
|
Ok,
|
||||||
|
/// Component works, but a risk or degraded condition needs attention.
|
||||||
Warn,
|
Warn,
|
||||||
|
/// Component check failed or a required dependency is unavailable.
|
||||||
Fail,
|
Fail,
|
||||||
|
/// Component did not provide enough information for a reliable status.
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatusLevel {
|
impl StatusLevel {
|
||||||
|
/// Return the stable uppercase representation used in human and JSON output.
|
||||||
pub fn as_str(self) -> &'static str {
|
pub fn as_str(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Ok => "OK",
|
Self::Ok => "OK",
|
||||||
@@ -23,6 +41,10 @@ impl StatusLevel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Map status to the process exit code expected by operational checks.
|
||||||
|
///
|
||||||
|
/// CONTRACT: `WARN` exits as a failed check rather than success so that
|
||||||
|
/// automation does not silently ignore degraded production state.
|
||||||
pub fn exit_code(self) -> i32 {
|
pub fn exit_code(self) -> i32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Ok => exit_codes::OK,
|
Self::Ok => exit_codes::OK,
|
||||||
@@ -48,23 +70,39 @@ impl From<&str> for StatusLevel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stable process exit codes for AWatch-rus operational binaries.
|
||||||
|
///
|
||||||
|
/// CONTRACT: keep these numeric values stable. Shell scripts, systemd units,
|
||||||
|
/// smoke tests and runbooks can depend on them.
|
||||||
pub mod exit_codes {
|
pub mod exit_codes {
|
||||||
|
/// Successful execution.
|
||||||
pub const OK: i32 = 0;
|
pub const OK: i32 = 0;
|
||||||
|
/// Unexpected runtime or IO error.
|
||||||
pub const ERROR: i32 = 1;
|
pub const ERROR: i32 = 1;
|
||||||
|
/// Health/check policy failed or returned a degraded status.
|
||||||
pub const CHECK_FAILED: i32 = 2;
|
pub const CHECK_FAILED: i32 = 2;
|
||||||
|
/// A safety policy denied a requested action.
|
||||||
pub const POLICY_DENIED: i32 = 3;
|
pub const POLICY_DENIED: i32 = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the current UTC timestamp in compact RFC3339/Zulu format.
|
||||||
pub fn now_utc_rfc3339() -> String {
|
pub fn now_utc_rfc3339() -> String {
|
||||||
Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true)
|
Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an RFC3339 timestamp and normalize it to UTC.
|
||||||
pub fn parse_utc_rfc3339(value: &str) -> Result<DateTime<Utc>> {
|
pub fn parse_utc_rfc3339(value: &str) -> Result<DateTime<Utc>> {
|
||||||
DateTime::parse_from_rfc3339(value)
|
DateTime::parse_from_rfc3339(value)
|
||||||
.with_context(|| format!("invalid RFC3339 timestamp: {value}"))
|
.with_context(|| format!("invalid RFC3339 timestamp: {value}"))
|
||||||
.map(|ts| ts.with_timezone(&Utc))
|
.map(|ts| ts.with_timezone(&Utc))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runtime configuration guardrails.
|
||||||
|
///
|
||||||
|
/// SECURITY: these helpers are deliberately conservative. They reject empty,
|
||||||
|
/// documentation, TEST-NET and common placeholder values before a component is
|
||||||
|
/// allowed to run in production mode. This prevents demo-safe examples from
|
||||||
|
/// accidentally becoming live runtime configuration.
|
||||||
pub mod runtime_guard {
|
pub mod runtime_guard {
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
|
|
||||||
@@ -82,6 +120,11 @@ pub mod runtime_guard {
|
|||||||
"PASSWORD",
|
"PASSWORD",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Return true when a value looks like a public/demo placeholder.
|
||||||
|
///
|
||||||
|
/// RATIONALE: AWatch-rus documentation intentionally uses TEST-NET ranges
|
||||||
|
/// and HOST-EXAMPLE markers. Production binaries should fail closed when
|
||||||
|
/// such values reach runtime configuration.
|
||||||
pub fn is_runtime_placeholder(value: &str) -> bool {
|
pub fn is_runtime_placeholder(value: &str) -> bool {
|
||||||
let trimmed = value.trim();
|
let trimmed = value.trim();
|
||||||
if trimmed.is_empty() {
|
if trimmed.is_empty() {
|
||||||
@@ -106,6 +149,7 @@ pub mod runtime_guard {
|
|||||||
|| (normalized.starts_with('<') && normalized.ends_with('>'))
|
|| (normalized.starts_with('<') && normalized.ends_with('>'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return true when a value is unsafe for a secret-like configuration field.
|
||||||
pub fn is_secret_placeholder(value: &str) -> bool {
|
pub fn is_secret_placeholder(value: &str) -> bool {
|
||||||
is_runtime_placeholder(value)
|
is_runtime_placeholder(value)
|
||||||
|| matches!(
|
|| matches!(
|
||||||
@@ -114,6 +158,10 @@ pub mod runtime_guard {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure a required runtime value is not empty or demo-only.
|
||||||
|
///
|
||||||
|
/// SECURITY: callers should invoke this before opening network connections,
|
||||||
|
/// starting ingestion or enabling exporters in production mode.
|
||||||
pub fn ensure_runtime_value(name: &str, value: &str, context: &str) -> Result<()> {
|
pub fn ensure_runtime_value(name: &str, value: &str, context: &str) -> Result<()> {
|
||||||
if is_runtime_placeholder(value) {
|
if is_runtime_placeholder(value) {
|
||||||
bail!("{name} contains an empty/example/TEST-NET value while {context}");
|
bail!("{name} contains an empty/example/TEST-NET value while {context}");
|
||||||
@@ -121,6 +169,7 @@ pub mod runtime_guard {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure a required secret is not empty or an obvious placeholder.
|
||||||
pub fn ensure_secret_value(name: &str, value: &str, context: &str) -> Result<()> {
|
pub fn ensure_secret_value(name: &str, value: &str, context: &str) -> Result<()> {
|
||||||
if is_secret_placeholder(value) {
|
if is_secret_placeholder(value) {
|
||||||
bail!("{name} contains an empty/example secret value while {context}");
|
bail!("{name} contains an empty/example secret value while {context}");
|
||||||
@@ -128,6 +177,7 @@ pub mod runtime_guard {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure an iterator of runtime values is non-empty and production-safe.
|
||||||
pub fn ensure_runtime_values<'a>(
|
pub fn ensure_runtime_values<'a>(
|
||||||
name: &str,
|
name: &str,
|
||||||
values: impl IntoIterator<Item = &'a String>,
|
values: impl IntoIterator<Item = &'a String>,
|
||||||
@@ -144,6 +194,12 @@ pub mod runtime_guard {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validate a complete InfluxDB exporter configuration block.
|
||||||
|
///
|
||||||
|
/// CONTRACT: when an exporter is enabled, URL, org, bucket, token and host
|
||||||
|
/// list must all be real runtime values. A partial/demo exporter config is
|
||||||
|
/// more dangerous than a disabled exporter because it creates false
|
||||||
|
/// confidence in monitoring readiness.
|
||||||
pub fn ensure_influx_runtime_config(
|
pub fn ensure_influx_runtime_config(
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
url: &str,
|
url: &str,
|
||||||
|
|||||||
Reference in New Issue
Block a user