admin: add service seam builder

This commit is contained in:
github-ops
2026-05-14 19:06:23 +00:00
parent 5926206e67
commit 82deaeec63
3 changed files with 135 additions and 18 deletions
+1
View File
@@ -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 не изменено
+4 -3
View File
@@ -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<dyn std::error::Error>> {
.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?;
+130 -15
View File
@@ -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<Arc<dyn IdentityProvider>>,
policy_engine: Arc<dyn PolicyEngine>,
audit_sink: Arc<dyn AuditSink>,
token_issuer: Arc<dyn MachineTokenIssuer>,
capability_profile: Arc<dyn CapabilityProfile>,
}
pub struct AdminServiceBuilder {
registry: PostgresRegistry,
storage_root: PathBuf,
auth_settings: AuthSettings,
secret_crypto: SecretCrypto,
runtime: RuntimeExecutor,
identity_provider: Option<Arc<dyn IdentityProvider>>,
policy_engine: Option<Arc<dyn PolicyEngine>>,
audit_sink: Option<Arc<dyn AuditSink>>,
token_issuer: Option<Arc<dyn MachineTokenIssuer>>,
capability_profile: Option<Arc<dyn CapabilityProfile>>,
}
#[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<dyn IdentityProvider>> {
self.identity_provider.as_ref()
}
pub fn policy_engine(&self) -> &Arc<dyn PolicyEngine> {
&self.policy_engine
}
pub fn audit_sink(&self) -> &Arc<dyn AuditSink> {
&self.audit_sink
}
pub fn token_issuer(&self) -> &Arc<dyn MachineTokenIssuer> {
&self.token_issuer
}
pub fn capability_profile(&self) -> &Arc<dyn CapabilityProfile> {
&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<dyn IdentityProvider>) -> Self {
self.identity_provider = Some(provider);
self
}
pub fn with_policy_engine(mut self, policy_engine: Arc<dyn PolicyEngine>) -> Self {
self.policy_engine = Some(policy_engine);
self
}
pub fn with_audit_sink(mut self, audit_sink: Arc<dyn AuditSink>) -> Self {
self.audit_sink = Some(audit_sink);
self
}
pub fn with_token_issuer(mut self, token_issuer: Arc<dyn MachineTokenIssuer>) -> Self {
self.token_issuer = Some(token_issuer);
self
}
pub fn with_capability_profile(
mut self,
capability_profile: Arc<dyn CapabilityProfile>,
) -> 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(