core: add billing extension seam

This commit is contained in:
github-ops
2026-05-15 10:16:47 +00:00
parent 530cd5f588
commit ad61350c2e
4 changed files with 80 additions and 0 deletions
+1
View File
@@ -167,4 +167,5 @@ Progress:
- 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
- Phase 3 dependency slice: added public billing seam in `crank-core``BillingHook`, `BillingGate`, `BillingError`, `SharedBillingHook`, and `NoopBillingHook` — so `cloud` can layer billing policy without private-only contracts in the base repo
- backward-compatible alias `CommunityMachineCredentialVerifier` сохранен, поведение Community не изменено
+75
View File
@@ -0,0 +1,75 @@
use std::sync::Arc;
use async_trait::async_trait;
use crate::{TenantId, UsageRollup};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BillingGate {
Allow,
Deny { reason: String },
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum BillingError {
#[error("billing integration is not available in this edition")]
NotSupportedInEdition,
#[error("billing provider failure: {0}")]
Provider(String),
}
#[async_trait]
pub trait BillingHook: Send + Sync {
async fn on_rollup(&self, rollup: UsageRollup) -> Result<(), BillingError>;
async fn check_billing_gate(&self, tenant: &TenantId) -> Result<BillingGate, BillingError>;
}
pub type SharedBillingHook = Arc<dyn BillingHook>;
#[derive(Clone, Copy, Debug, Default)]
pub struct NoopBillingHook;
#[async_trait]
impl BillingHook for NoopBillingHook {
async fn on_rollup(&self, _rollup: UsageRollup) -> Result<(), BillingError> {
Ok(())
}
async fn check_billing_gate(&self, _tenant: &TenantId) -> Result<BillingGate, BillingError> {
Ok(BillingGate::Allow)
}
}
#[cfg(test)]
mod tests {
use super::{BillingGate, BillingHook, NoopBillingHook};
use crate::{TenantId, UsagePeriod, UsageRollup, WorkspaceId};
#[tokio::test]
async fn noop_billing_hook_allows_default_gate_and_rollups() {
let hook = NoopBillingHook;
hook.on_rollup(UsageRollup {
workspace_id: WorkspaceId::new("ws_01"),
agent_id: None,
operation_id: None,
period: UsagePeriod::Last24Hours,
calls_total: 10,
calls_ok: 9,
calls_error: 1,
p50_ms: 12,
p95_ms: 42,
p99_ms: 77,
})
.await
.unwrap();
let gate = hook
.check_billing_gate(&TenantId::new("tenant_01"))
.await
.unwrap();
assert_eq!(gate, BillingGate::Allow);
}
}
+1
View File
@@ -1,6 +1,7 @@
pub mod access;
pub mod audit;
pub mod auth;
pub mod billing;
pub mod capability;
pub mod metering;
pub mod protocol;
+3
View File
@@ -47,6 +47,9 @@ pub use ext::auth::{
TokenIssuerActor, TokenIssuerError, TwoFactorActivation, TwoFactorChallenge,
TwoFactorPendingIdentity, TwoFactorSetup, TwoFactorStatus, VerifiedMachineCredential,
};
pub use ext::billing::{
BillingError, BillingGate, BillingHook, NoopBillingHook, SharedBillingHook,
};
pub use ext::capability::{CapabilityProfile, CommunityCapabilityProfile};
pub use ext::metering::{MeteringEvent, MeteringSink, NoopMeteringSink, SharedMeteringSink};
pub use ext::protocol::{