core: add tenancy extension seam

This commit is contained in:
github-ops
2026-05-15 09:02:28 +00:00
parent 7ebe8cae22
commit 0e6d81b109
5 changed files with 89 additions and 1 deletions
+1
View File
@@ -165,4 +165,5 @@ Progress:
- Phase 1 / task `1.7`: workspace version bumped to `0.2.0`; release gate пройден (`just verify`, `npm run build`, `npm run e2e`), release commit и tag `v0.2.0` подготовлены в `crank-community`
- 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
- backward-compatible alias `CommunityMachineCredentialVerifier` сохранен, поведение Community не изменено
+1
View File
@@ -3,3 +3,4 @@ pub mod audit;
pub mod auth;
pub mod capability;
pub mod protocol;
pub mod tenancy;
+81
View File
@@ -0,0 +1,81 @@
use std::sync::Arc;
use async_trait::async_trait;
use crate::{TenantId, WorkspaceId};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TenantResolutionContext {
pub workspace_id: Option<WorkspaceId>,
pub workspace_slug: Option<String>,
pub host: Option<String>,
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum TenancyError {
#[error("tenant lookup is not supported in this edition")]
UnsupportedLookup,
#[error("internal tenancy failure: {0}")]
Internal(String),
}
#[async_trait]
pub trait TenantController: Send + Sync {
fn resolve_tenant(&self, request: &TenantResolutionContext) -> Result<TenantId, TenancyError>;
async fn list_workspaces_for_tenant(
&self,
tenant: &TenantId,
) -> Result<Vec<WorkspaceId>, TenancyError>;
}
pub type SharedTenantController = Arc<dyn TenantController>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SingleTenantController {
tenant_id: TenantId,
}
impl Default for SingleTenantController {
fn default() -> Self {
Self {
tenant_id: TenantId::new("default"),
}
}
}
#[async_trait]
impl TenantController for SingleTenantController {
fn resolve_tenant(
&self,
_request: &TenantResolutionContext,
) -> Result<TenantId, TenancyError> {
Ok(self.tenant_id.clone())
}
async fn list_workspaces_for_tenant(
&self,
_tenant: &TenantId,
) -> Result<Vec<WorkspaceId>, TenancyError> {
Ok(Vec::new())
}
}
#[cfg(test)]
mod tests {
use super::{SingleTenantController, TenantController, TenantResolutionContext};
use crate::TenantId;
#[tokio::test]
async fn single_tenant_controller_returns_default_tenant() {
let controller = SingleTenantController::default();
let tenant = controller
.resolve_tenant(&TenantResolutionContext::default())
.unwrap();
let workspaces = controller.list_workspaces_for_tenant(&tenant).await.unwrap();
assert_eq!(tenant, TenantId::new("default"));
assert!(workspaces.is_empty());
}
}
+1
View File
@@ -48,6 +48,7 @@ define_id!(ToolId);
define_id!(SampleId);
define_id!(AuthProfileId);
define_id!(WorkspaceId);
define_id!(TenantId);
define_id!(UserId);
define_id!(UserSessionId);
define_id!(AgentId);
+5 -1
View File
@@ -52,9 +52,13 @@ pub use ext::protocol::{
AdapterRegistry, AdapterResponse, PreparedRequest, ProtocolAdapter, ProtocolAdapterError,
ResponseCacheScope, RuntimeRequestContext, SharedProtocolAdapter, WindowExecutionResult,
};
pub use ext::tenancy::{
SharedTenantController, SingleTenantController, TenantController, TenantResolutionContext,
TenancyError,
};
pub use ids::{
AgentId, AsyncJobId, AuditEventId, AuthProfileId, DescriptorId, InvitationId, InvocationLogId,
OperationId, PlatformApiKeyId, SampleId, SecretId, StreamSessionId, ToolId, UserId,
OperationId, PlatformApiKeyId, SampleId, SecretId, StreamSessionId, TenantId, ToolId, UserId,
UserSessionId, WorkspaceId,
};
pub use observability::{