core: add tenancy extension seam
This commit is contained in:
@@ -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 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: `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 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 не изменено
|
- backward-compatible alias `CommunityMachineCredentialVerifier` сохранен, поведение Community не изменено
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ pub mod audit;
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod capability;
|
pub mod capability;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
|
pub mod tenancy;
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,6 +48,7 @@ define_id!(ToolId);
|
|||||||
define_id!(SampleId);
|
define_id!(SampleId);
|
||||||
define_id!(AuthProfileId);
|
define_id!(AuthProfileId);
|
||||||
define_id!(WorkspaceId);
|
define_id!(WorkspaceId);
|
||||||
|
define_id!(TenantId);
|
||||||
define_id!(UserId);
|
define_id!(UserId);
|
||||||
define_id!(UserSessionId);
|
define_id!(UserSessionId);
|
||||||
define_id!(AgentId);
|
define_id!(AgentId);
|
||||||
|
|||||||
@@ -52,9 +52,13 @@ pub use ext::protocol::{
|
|||||||
AdapterRegistry, AdapterResponse, PreparedRequest, ProtocolAdapter, ProtocolAdapterError,
|
AdapterRegistry, AdapterResponse, PreparedRequest, ProtocolAdapter, ProtocolAdapterError,
|
||||||
ResponseCacheScope, RuntimeRequestContext, SharedProtocolAdapter, WindowExecutionResult,
|
ResponseCacheScope, RuntimeRequestContext, SharedProtocolAdapter, WindowExecutionResult,
|
||||||
};
|
};
|
||||||
|
pub use ext::tenancy::{
|
||||||
|
SharedTenantController, SingleTenantController, TenantController, TenantResolutionContext,
|
||||||
|
TenancyError,
|
||||||
|
};
|
||||||
pub use ids::{
|
pub use ids::{
|
||||||
AgentId, AsyncJobId, AuditEventId, AuthProfileId, DescriptorId, InvitationId, InvocationLogId,
|
AgentId, AsyncJobId, AuditEventId, AuthProfileId, DescriptorId, InvitationId, InvocationLogId,
|
||||||
OperationId, PlatformApiKeyId, SampleId, SecretId, StreamSessionId, ToolId, UserId,
|
OperationId, PlatformApiKeyId, SampleId, SecretId, StreamSessionId, TenantId, ToolId, UserId,
|
||||||
UserSessionId, WorkspaceId,
|
UserSessionId, WorkspaceId,
|
||||||
};
|
};
|
||||||
pub use observability::{
|
pub use observability::{
|
||||||
|
|||||||
Reference in New Issue
Block a user