наблюдаемость: завершить базовый контур Community
CI / Rust Checks (push) Failing after 4m28s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Community Image Smoke (push) Has been skipped
CI / Deploy (push) Has been skipped

Добавить структурированные журналы, метрики, трассировку и безопасный канал критических ошибок. Усилить границы рантайма, тесты, проверку зависимостей и сценарии развёртывания.
This commit is contained in:
2026-07-31 01:01:14 +03:00
parent 99bd05c145
commit 0e8f1ca03a
160 changed files with 13506 additions and 1499 deletions
+198
View File
@@ -0,0 +1,198 @@
//! Закрытый семантический контракт spans Crank.
//!
//! Этот crate не настраивает subscriber и не знает об OTLP. Он ограничивает
//! имена и атрибуты стадий статическим словарём, чтобы продуктовые crate не
//! могли случайно экспортировать пользовательские данные.
use std::future::Future;
use tracing::{Instrument, Span, field::Empty, info_span};
macro_rules! stage_span {
($name:literal) => {
info_span!(
target: "crank::trace",
$name,
outcome = Empty,
error.category = Empty,
)
};
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Stage {
McpRateLimit,
McpAccessCheck,
McpCatalogLoad,
McpToolsResolve,
ApprovalCheck,
RuntimeExecute,
RuntimeArgumentsMap,
RuntimeIdempotency,
UpstreamHttp,
RuntimeResponseTransform,
AuthResolve,
ApprovalRecovery,
HistoryWrite,
DbQuery,
}
impl Stage {
pub fn span(self) -> Span {
match self {
Self::McpRateLimit => stage_span!("mcp.rate_limit"),
Self::McpAccessCheck => stage_span!("mcp.access.check"),
Self::McpCatalogLoad => stage_span!("mcp.catalog.load"),
Self::McpToolsResolve => stage_span!("mcp.tools.resolve"),
Self::ApprovalCheck => stage_span!("approval.check"),
Self::RuntimeExecute => stage_span!("runtime.execute"),
Self::RuntimeArgumentsMap => stage_span!("runtime.arguments.map"),
Self::RuntimeIdempotency => stage_span!("runtime.idempotency"),
Self::UpstreamHttp => stage_span!("upstream.http"),
Self::RuntimeResponseTransform => stage_span!("runtime.response.transform"),
Self::AuthResolve => stage_span!("auth.resolve"),
Self::ApprovalRecovery => stage_span!("approval.recovery"),
Self::HistoryWrite => stage_span!("history.write"),
Self::DbQuery => info_span!(
target: "crank::trace",
"db.query",
outcome = Empty,
error.category = Empty,
db.system = "postgresql",
db.operation = Empty,
),
}
}
pub fn db_span(self, operation: DbOperation) -> Option<Span> {
if self != Self::DbQuery {
return None;
}
let span = self.span();
span.record("db.operation", operation.as_str());
Some(span)
}
}
pub async fn observe_db_query<T, E>(
operation: DbOperation,
future: impl Future<Output = Result<T, E>>,
) -> Result<T, E> {
let span = Stage::DbQuery
.db_span(operation)
.expect("database operation requires db.query stage");
let result = future.instrument(span.clone()).await;
match &result {
Ok(_) => StageOutcome::Success.record(&span),
Err(_) => {
StageOutcome::Error.record(&span);
ErrorCategory::Database.record(&span);
}
}
result
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StageOutcome {
Success,
Error,
Allowed,
Denied,
Required,
Replay,
Execute,
Skipped,
CacheHit,
}
impl StageOutcome {
pub fn record(self, span: &Span) {
span.record("outcome", self.as_str());
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Success => "success",
Self::Error => "error",
Self::Allowed => "allowed",
Self::Denied => "denied",
Self::Required => "required",
Self::Replay => "replay",
Self::Execute => "execute",
Self::Skipped => "skipped",
Self::CacheHit => "cache_hit",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ErrorCategory {
Access,
RateLimit,
Catalog,
Approval,
Idempotency,
Schema,
Mapping,
Upstream,
Transformation,
History,
Database,
Concurrency,
Configuration,
Internal,
}
impl ErrorCategory {
pub fn record(self, span: &Span) {
span.record("error.category", self.as_str());
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Access => "access",
Self::RateLimit => "rate_limit",
Self::Catalog => "catalog",
Self::Approval => "approval",
Self::Idempotency => "idempotency",
Self::Schema => "schema",
Self::Mapping => "mapping",
Self::Upstream => "upstream",
Self::Transformation => "transformation",
Self::History => "history",
Self::Database => "database",
Self::Concurrency => "concurrency",
Self::Configuration => "configuration",
Self::Internal => "internal",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DbOperation {
MachineAccessRead,
MachineAccessTouch,
CatalogLoad,
ApprovalRead,
ApprovalWrite,
AuthProfileRead,
SecretRead,
SecretTouch,
InvocationHistoryWrite,
}
impl DbOperation {
pub const fn as_str(self) -> &'static str {
match self {
Self::MachineAccessRead => "machine_access.read",
Self::MachineAccessTouch => "machine_access.touch",
Self::CatalogLoad => "catalog.load",
Self::ApprovalRead => "approval.read",
Self::ApprovalWrite => "approval.write",
Self::AuthProfileRead => "auth_profile.read",
Self::SecretRead => "secret.read",
Self::SecretTouch => "secret.touch",
Self::InvocationHistoryWrite => "invocation_history.write",
}
}
}