diff --git a/TASKS.md b/TASKS.md index 1cb82c4..a568c09 100644 --- a/TASKS.md +++ b/TASKS.md @@ -145,5 +145,6 @@ Progress: - Phase 0 / task `0.1`: создан `crank-core::ext` module skeleton - Phase 0 / task `0.2`: `MachineCredentialVerifier` и связанные типы вынесены из `apps/mcp-server` в public seam `crank_core::ext::auth` - Phase 0 / task `0.5`: введены `PolicyEngine`, `SessionActor`, `PolicyAction`, `PolicyScope`, `PolicyDecision` и default `OwnerOnlyPolicyEngine` в public seam `crank_core::ext::access` + - Phase 0 / task `0.6`: введены `AuditSink`, `NoopAuditSink`, `AuditEventId` и базовые audit-типы в public seam `crank_core::ext::audit` - Phase 0 / task `0.7`: введены `CapabilityProfile` и `CommunityCapabilityProfile`, а `admin-api` capability payload теперь собирается через public seam вместо локального literal - backward-compatible alias `CommunityMachineCredentialVerifier` сохранен, поведение Community не изменено diff --git a/crates/crank-core/src/ext/audit.rs b/crates/crank-core/src/ext/audit.rs index 8b13789..93984cb 100644 --- a/crates/crank-core/src/ext/audit.rs +++ b/crates/crank-core/src/ext/audit.rs @@ -1 +1,94 @@ +use std::sync::Arc; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use time::OffsetDateTime; + +use crate::{AuditEventId, UserId, UserSessionId, WorkspaceId}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct AuditActor { + pub user_id: UserId, + pub email: String, + pub session_id: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum AuditTargetKind { + Workspace, + Membership, + Invitation, + Operation, + Agent, + PlatformApiKey, + Secret, + AuthProfile, + Other, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct AuditTarget { + pub workspace_id: WorkspaceId, + pub kind: AuditTargetKind, + pub id: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AuditEvent { + pub id: AuditEventId, + #[serde(with = "time::serde::rfc3339")] + pub occurred_at: OffsetDateTime, + pub actor: AuditActor, + pub action: String, + pub target: AuditTarget, + pub payload: Value, + pub source_ip: Option, + pub user_agent: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct AuditQuery { + pub workspace_id: Option, + pub action: Option, + pub cursor: Option, + pub limit: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct AuditEventPage { + pub items: Vec, + pub next_cursor: Option, +} + +#[derive(Debug, thiserror::Error)] +pub enum AuditError { + #[error("audit sink failed: {0}")] + Backend(String), +} + +#[async_trait] +pub trait AuditSink: Send + Sync { + async fn record(&self, event: AuditEvent) -> Result<(), AuditError>; + + async fn list(&self, _query: AuditQuery) -> Result { + Ok(AuditEventPage::default()) + } + + async fn get(&self, _id: &AuditEventId) -> Result, AuditError> { + Ok(None) + } +} + +pub type SharedAuditSink = Arc; + +#[derive(Clone, Copy, Debug, Default)] +pub struct NoopAuditSink; + +#[async_trait] +impl AuditSink for NoopAuditSink { + async fn record(&self, _event: AuditEvent) -> Result<(), AuditError> { + Ok(()) + } +} diff --git a/crates/crank-core/src/ids.rs b/crates/crank-core/src/ids.rs index 99b5e8e..6942a56 100644 --- a/crates/crank-core/src/ids.rs +++ b/crates/crank-core/src/ids.rs @@ -54,6 +54,7 @@ define_id!(AgentId); define_id!(InvitationId); define_id!(PlatformApiKeyId); define_id!(InvocationLogId); +define_id!(AuditEventId); define_id!(SecretId); define_id!(StreamSessionId); define_id!(AsyncJobId); diff --git a/crates/crank-core/src/lib.rs b/crates/crank-core/src/lib.rs index a38af30..0ceba0a 100644 --- a/crates/crank-core/src/lib.rs +++ b/crates/crank-core/src/lib.rs @@ -35,15 +35,19 @@ pub use edition::{ pub use ext::access::{ OwnerOnlyPolicyEngine, PolicyAction, PolicyDecision, PolicyEngine, PolicyScope, SessionActor, }; +pub use ext::audit::{ + AuditActor, AuditError, AuditEvent, AuditEventPage, AuditQuery, AuditSink, AuditTarget, + AuditTargetKind, NoopAuditSink, SharedAuditSink, +}; pub use ext::auth::{ MachineCredentialVerifier, MachineCredentialVerifierError, SharedMachineCredentialVerifier, VerifiedMachineCredential, }; pub use ext::capability::{CapabilityProfile, CommunityCapabilityProfile}; pub use ids::{ - AgentId, AsyncJobId, AuthProfileId, DescriptorId, InvitationId, InvocationLogId, OperationId, - PlatformApiKeyId, SampleId, SecretId, StreamSessionId, ToolId, UserId, UserSessionId, - WorkspaceId, + AgentId, AsyncJobId, AuditEventId, AuthProfileId, DescriptorId, InvitationId, InvocationLogId, + OperationId, PlatformApiKeyId, SampleId, SecretId, StreamSessionId, ToolId, UserId, + UserSessionId, WorkspaceId, }; pub use observability::{ InvocationLevel, InvocationLog, InvocationSource, InvocationStatus, UsagePeriod, UsageRollup,