diff --git a/TASKS.md b/TASKS.md index 7319818..eb6550d 100644 --- a/TASKS.md +++ b/TASKS.md @@ -155,4 +155,5 @@ Progress: - Phase 0 / task `0.11`: добавлены `RuntimeExecutorBuilder` и `community_default()`, а default community runtime wiring вынесен из `RuntimeExecutor` в отдельный builder layer - Phase 0 / task `0.12`: `apps/admin-api` и `apps/mcp-server` переведены на `community_default().with_limits(...).with_response_cache(...).build()` вместо прямой сборки runtime через `RuntimeExecutor::with_limits(...)` - Phase 0 / task `0.13`: введены `RegistryExtension`, `ExtensionMigration` и `apply_extension_migrations(...)` в `crank-registry` как отдельный public seam для additive private migrations + - Phase 0 / task `0.14`: добавлены `AdminServiceBuilder`, seam slots (`identity_provider`, `policy_engine`, `audit_sink`, `token_issuer`, `capability_profile`) и community defaults для них; `apps/admin-api/src/main.rs` переведен на builder - backward-compatible alias `CommunityMachineCredentialVerifier` сохранен, поведение Community не изменено diff --git a/apps/admin-api/src/main.rs b/apps/admin-api/src/main.rs index b8adf5f..a7604b8 100644 --- a/apps/admin-api/src/main.rs +++ b/apps/admin-api/src/main.rs @@ -18,7 +18,7 @@ use tracing::info; use crate::{ app::build_app, auth::{AuthSettings, BootstrapAdminConfig}, - service::AdminService, + service::AdminServiceBuilder, state::AppState, }; use crank_runtime::{ @@ -70,13 +70,14 @@ async fn main() -> Result<(), Box> { .with_limits(runtime_limits) .with_response_cache(cache_stores.response.clone()) .build(); - let service = AdminService::new_with_runtime( + let service = AdminServiceBuilder::new( registry, storage_root, auth_settings, secret_crypto, runtime, - ); + ) + .build(); service.bootstrap_admin_user().await?; if env_flag("CRANK_DEMO_SEED") { service.seed_demo_assets().await?; diff --git a/apps/admin-api/src/service.rs b/apps/admin-api/src/service.rs index 297ca14..a4878c9 100644 --- a/apps/admin-api/src/service.rs +++ b/apps/admin-api/src/service.rs @@ -1,19 +1,21 @@ use std::collections::BTreeMap; use std::path::PathBuf; +use std::sync::Arc; use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; use crank_core::{ Agent, AgentId, AgentOperationBinding, AgentStatus, AgentVersion, AggregationMode, - AsyncJobHandle, AuthConfig, AuthKind, AuthProfile, AuthProfileId, CapabilityProfile, - ConfigExport, EditionCapabilities, ExecutionMode, ExportMode, GeneratedDraft, - GeneratedDraftStatus, InvitationId, InvitationStatus, InvitationToken, InvocationLevel, - InvocationLog, InvocationLogId, InvocationSource, InvocationStatus, IssueAgentTokenRequest, - IssueOneTimeAgentTokenRequest, IssuedAgentTokenResponse, JobStatus, MachineAccessMode, - MembershipRole, OperationId, OperationSecurityLevel, OperationStatus, PlatformApiKey, - PlatformApiKeyId, PlatformApiKeyScope, PlatformApiKeyStatus, ProductEdition, Protocol, - ResponseCachePolicy, SampleId, Samples, Secret, SecretId, SecretKind, SecretStatus, - StreamSession, StreamStatus, Target, TransportBehavior, UsagePeriod, UserId, UserSessionId, - Workspace, WorkspaceId, WorkspaceStatus, + AsyncJobHandle, AuditSink, AuthConfig, AuthKind, AuthProfile, AuthProfileId, CapabilityProfile, + CommunityCapabilityProfile, ConfigExport, EditionCapabilities, ExecutionMode, ExportMode, + GeneratedDraft, GeneratedDraftStatus, IdentityProvider, InvitationId, InvitationStatus, + InvitationToken, InvocationLevel, InvocationLog, InvocationLogId, InvocationSource, + InvocationStatus, IssueAgentTokenRequest, IssueOneTimeAgentTokenRequest, + IssuedAgentTokenResponse, JobStatus, MachineAccessMode, MachineTokenIssuer, MembershipRole, + NoMachineTokenIssuer, NoopAuditSink, OperationId, OperationSecurityLevel, OperationStatus, + OwnerOnlyPolicyEngine, PlatformApiKey, PlatformApiKeyId, PlatformApiKeyScope, + PlatformApiKeyStatus, PolicyEngine, ProductEdition, Protocol, ResponseCachePolicy, SampleId, + Samples, Secret, SecretId, SecretKind, SecretStatus, StreamSession, StreamStatus, Target, + TransportBehavior, UsagePeriod, UserId, UserSessionId, Workspace, WorkspaceId, WorkspaceStatus, }; use crank_mapping::{JsonPathRoot, MappingSet, infer_mapping_from_samples}; use crank_registry::{ @@ -57,6 +59,24 @@ pub struct AdminService { storage: LocalArtifactStorage, auth_settings: AuthSettings, secret_crypto: SecretCrypto, + identity_provider: Option>, + policy_engine: Arc, + audit_sink: Arc, + token_issuer: Arc, + capability_profile: Arc, +} + +pub struct AdminServiceBuilder { + registry: PostgresRegistry, + storage_root: PathBuf, + auth_settings: AuthSettings, + secret_crypto: SecretCrypto, + runtime: RuntimeExecutor, + identity_provider: Option>, + policy_engine: Option>, + audit_sink: Option>, + token_issuer: Option>, + capability_profile: Option>, } #[derive(Clone, Debug, Deserialize)] @@ -654,19 +674,114 @@ impl AdminService { secret_crypto: SecretCrypto, runtime: RuntimeExecutor, ) -> Self { - Self { + AdminServiceBuilder::new( registry, - runtime, - storage: LocalArtifactStorage::new(storage_root), + storage_root, auth_settings, secret_crypto, - } + runtime, + ) + .build() } pub fn auth_settings(&self) -> &AuthSettings { &self.auth_settings } + pub fn identity_provider(&self) -> Option<&Arc> { + self.identity_provider.as_ref() + } + + pub fn policy_engine(&self) -> &Arc { + &self.policy_engine + } + + pub fn audit_sink(&self) -> &Arc { + &self.audit_sink + } + + pub fn token_issuer(&self) -> &Arc { + &self.token_issuer + } + + pub fn capability_profile(&self) -> &Arc { + &self.capability_profile + } +} + +impl AdminServiceBuilder { + pub fn new( + registry: PostgresRegistry, + storage_root: PathBuf, + auth_settings: AuthSettings, + secret_crypto: SecretCrypto, + runtime: RuntimeExecutor, + ) -> Self { + Self { + registry, + storage_root, + auth_settings, + secret_crypto, + runtime, + identity_provider: None, + policy_engine: None, + audit_sink: None, + token_issuer: None, + capability_profile: None, + } + } + + pub fn with_identity_provider(mut self, provider: Arc) -> Self { + self.identity_provider = Some(provider); + self + } + + pub fn with_policy_engine(mut self, policy_engine: Arc) -> Self { + self.policy_engine = Some(policy_engine); + self + } + + pub fn with_audit_sink(mut self, audit_sink: Arc) -> Self { + self.audit_sink = Some(audit_sink); + self + } + + pub fn with_token_issuer(mut self, token_issuer: Arc) -> Self { + self.token_issuer = Some(token_issuer); + self + } + + pub fn with_capability_profile( + mut self, + capability_profile: Arc, + ) -> Self { + self.capability_profile = Some(capability_profile); + self + } + + pub fn build(self) -> AdminService { + AdminService { + registry: self.registry, + runtime: self.runtime, + storage: LocalArtifactStorage::new(self.storage_root), + auth_settings: self.auth_settings, + secret_crypto: self.secret_crypto, + identity_provider: self.identity_provider, + policy_engine: self + .policy_engine + .unwrap_or_else(|| Arc::new(OwnerOnlyPolicyEngine)), + audit_sink: self.audit_sink.unwrap_or_else(|| Arc::new(NoopAuditSink)), + token_issuer: self + .token_issuer + .unwrap_or_else(|| Arc::new(NoMachineTokenIssuer)), + capability_profile: self + .capability_profile + .unwrap_or_else(|| Arc::new(CommunityCapabilityProfile)), + } + } +} + +impl AdminService { pub async fn bootstrap_admin_user(&self) -> Result<(), ApiError> { let password_hash = hash_password( &self.auth_settings.bootstrap_admin.password, @@ -1496,7 +1611,7 @@ impl AdminService { } pub async fn get_capabilities(&self) -> EditionCapabilities { - crank_core::CommunityCapabilityProfile.capabilities() + self.capability_profile().capabilities() } pub async fn issue_agent_token(