diff --git a/TASKS.md b/TASKS.md index 6e7811b..28ac6cb 100644 --- a/TASKS.md +++ b/TASKS.md @@ -166,4 +166,5 @@ Progress: - Phase 2 dependency slice: `IdentityProvider` seam widened to cover `SSO/TOTP` with default `NotSupportedForProvider` methods and shared public DTOs for authorize/callback/two-factor flows; workspace version bumped to `0.3.0` for downstream enterprise consumption - Phase 2 dependency slice: `apps/admin-api` now exposes a reusable lib target (`src/lib.rs`), so downstream private repos can depend on `build_app`, `AppState`, `AdminServiceBuilder`, and auth/state modules without copying the Community admin app - Phase 3 dependency slice: added public tenancy seam in `crank-core` — `TenantId`, `TenantResolutionContext`, `TenantController`, `TenancyError`, and `SingleTenantController` — so `cloud` can build hosted tenant routing without bypassing the shared extension model + - Phase 3 dependency slice: added public metering seam in `crank-core` — `MeteringEvent`, `MeteringSink`, `SharedMeteringSink`, and `NoopMeteringSink` — so `cloud` can add hosted usage capture without introducing private-only contracts - backward-compatible alias `CommunityMachineCredentialVerifier` сохранен, поведение Community не изменено diff --git a/crates/crank-core/src/ext/metering.rs b/crates/crank-core/src/ext/metering.rs new file mode 100644 index 0000000..62225d1 --- /dev/null +++ b/crates/crank-core/src/ext/metering.rs @@ -0,0 +1,55 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use time::OffsetDateTime; + +use crate::{AgentId, InvocationSource, InvocationStatus, OperationId, WorkspaceId}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MeteringEvent { + pub workspace_id: WorkspaceId, + pub agent_id: Option, + pub operation_id: OperationId, + pub source: InvocationSource, + pub status: InvocationStatus, + pub duration_ms: u64, + pub created_at: OffsetDateTime, +} + +#[async_trait] +pub trait MeteringSink: Send + Sync { + async fn record(&self, event: MeteringEvent); +} + +pub type SharedMeteringSink = Arc; + +#[derive(Clone, Copy, Debug, Default)] +pub struct NoopMeteringSink; + +#[async_trait] +impl MeteringSink for NoopMeteringSink { + async fn record(&self, _event: MeteringEvent) {} +} + +#[cfg(test)] +mod tests { + use time::OffsetDateTime; + + use super::{MeteringEvent, MeteringSink, NoopMeteringSink}; + use crate::{InvocationSource, InvocationStatus, OperationId, WorkspaceId}; + + #[tokio::test] + async fn noop_metering_sink_accepts_event() { + NoopMeteringSink + .record(MeteringEvent { + workspace_id: WorkspaceId::new("ws_01"), + agent_id: None, + operation_id: OperationId::new("op_01"), + source: InvocationSource::AgentToolCall, + status: InvocationStatus::Ok, + duration_ms: 42, + created_at: OffsetDateTime::now_utc(), + }) + .await; + } +} diff --git a/crates/crank-core/src/ext/mod.rs b/crates/crank-core/src/ext/mod.rs index a655379..ad9c978 100644 --- a/crates/crank-core/src/ext/mod.rs +++ b/crates/crank-core/src/ext/mod.rs @@ -2,5 +2,6 @@ pub mod access; pub mod audit; pub mod auth; pub mod capability; +pub mod metering; pub mod protocol; pub mod tenancy; diff --git a/crates/crank-core/src/lib.rs b/crates/crank-core/src/lib.rs index 457aa35..99ab6c4 100644 --- a/crates/crank-core/src/lib.rs +++ b/crates/crank-core/src/lib.rs @@ -48,6 +48,7 @@ pub use ext::auth::{ TwoFactorPendingIdentity, TwoFactorSetup, TwoFactorStatus, VerifiedMachineCredential, }; pub use ext::capability::{CapabilityProfile, CommunityCapabilityProfile}; +pub use ext::metering::{MeteringEvent, MeteringSink, NoopMeteringSink, SharedMeteringSink}; pub use ext::protocol::{ AdapterRegistry, AdapterResponse, PreparedRequest, ProtocolAdapter, ProtocolAdapterError, ResponseCacheScope, RuntimeRequestContext, SharedProtocolAdapter, WindowExecutionResult,