Remove enterprise leftovers from community
CI / Rust Checks (push) Failing after 2m6s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Deployment Manifests (push) Has been skipped
Deploy / deploy (push) Successful in 2m26s

This commit is contained in:
github-ops
2026-06-20 12:04:46 +00:00
parent 5f8c208409
commit 0af60b1693
46 changed files with 46 additions and 7096 deletions
+1 -1450
View File
File diff suppressed because it is too large Load Diff
+1 -62
View File
@@ -109,13 +109,6 @@ impl ApiError {
}
}
pub(crate) fn forbidden_with_context(message: impl Into<String>, context: Value) -> Self {
Self::Forbidden {
message: message.into(),
context: Some(context),
}
}
fn status_code(&self) -> StatusCode {
match self {
Self::Unauthorized { .. } => StatusCode::UNAUTHORIZED,
@@ -224,14 +217,6 @@ impl From<RegistryError> for ApiError {
format!("secret {secret_id} was not found"),
json!({ "secret_id": secret_id }),
),
RegistryError::StreamSessionNotFound { session_id } => Self::not_found_with_context(
format!("stream session {session_id} was not found"),
json!({ "session_id": session_id }),
),
RegistryError::AsyncJobNotFound { job_id } => Self::not_found_with_context(
format!("async job {job_id} was not found"),
json!({ "job_id": job_id }),
),
RegistryError::InvocationLogNotFound { log_id } => Self::not_found_with_context(
format!("invocation log {log_id} was not found"),
json!({ "log_id": log_id }),
@@ -299,28 +284,6 @@ impl From<RegistryError> for ApiError {
"auth_profile_id": auth_profile_id,
}),
),
RegistryError::InvalidStreamSessionTransition {
session_id,
from,
to,
} => Self::conflict_with_context(
format!("invalid stream session transition for {session_id}: {from} -> {to}"),
json!({
"session_id": session_id,
"from": from,
"to": to,
}),
),
RegistryError::InvalidAsyncJobTransition { job_id, from, to } => {
Self::conflict_with_context(
format!("invalid async job transition for {job_id}: {from} -> {to}"),
json!({
"job_id": job_id,
"from": from,
"to": to,
}),
)
}
RegistryError::UserEmailAlreadyExists { email } => Self::conflict_with_context(
format!("user with email {email} already exists"),
json!({ "email": email }),
@@ -490,8 +453,7 @@ pub fn runtime_error_context(error: &RuntimeError) -> Option<Value> {
mod tests {
use serde_json::json;
use super::{ApiError, runtime_error_context, runtime_test_failure};
use crank_registry::RegistryError;
use super::{runtime_error_context, runtime_test_failure};
use crank_runtime::RuntimeError;
#[test]
@@ -544,27 +506,4 @@ mod tests {
})
);
}
#[test]
fn registry_errors_preserve_structured_context_in_api_error() {
let error = ApiError::from(RegistryError::InvalidAsyncJobTransition {
job_id: "job_123".to_owned(),
from: "running".to_owned(),
to: "completed".to_owned(),
});
match error {
ApiError::Conflict { context, .. } => {
assert_eq!(
context,
Some(json!({
"job_id": "job_123",
"from": "running",
"to": "completed"
}))
);
}
other => panic!("unexpected error variant: {other:?}"),
}
}
}
-2
View File
@@ -3,11 +3,9 @@ pub mod agents;
pub mod auth;
pub mod auth_profiles;
pub mod capabilities;
pub mod machine_auth;
pub mod observability;
pub mod operations;
pub mod secrets;
pub mod streaming;
pub mod upstreams;
pub mod workspaces;
-124
View File
@@ -1,124 +0,0 @@
use axum::{Json, extract::State};
use crank_core::{
IssueAgentTokenRequest, IssueOneTimeAgentTokenRequest, MachineAccessMode, MembershipRole,
TokenIssuerActor, TokenIssuerError, UserId, WorkspaceId,
};
use serde_json::json;
use crate::{error::ApiError, state::AppState};
pub async fn issue_agent_token(
State(state): State<AppState>,
Json(payload): Json<IssueAgentTokenRequest>,
) -> Result<Json<serde_json::Value>, ApiError> {
let edition = state.service.capability_profile().capabilities().edition;
let grant_type = payload.grant_type.clone();
let response = state
.service
.token_issuer()
.issue_short_lived(payload, &community_token_issuer_actor())
.await
.map_err(|error| {
map_token_issuer_error(
error,
edition,
MachineAccessMode::ShortLivedToken,
json!({
"grant_type": grant_type,
"upgrade_required": true,
}),
)
})?;
Ok(Json(serde_json::json!(response)))
}
pub async fn issue_one_time_agent_token(
State(state): State<AppState>,
Json(payload): Json<IssueOneTimeAgentTokenRequest>,
) -> Result<Json<serde_json::Value>, ApiError> {
let edition = state.service.capability_profile().capabilities().edition;
let operation_id = payload.operation_id.as_str().to_owned();
let response = state
.service
.token_issuer()
.issue_one_time(payload, &community_token_issuer_actor())
.await
.map_err(|error| {
map_token_issuer_error(
error,
edition,
MachineAccessMode::OneTimeToken,
json!({
"operation_id": operation_id,
"upgrade_required": true,
}),
)
})?;
Ok(Json(serde_json::json!(response)))
}
fn community_token_issuer_actor() -> TokenIssuerActor {
TokenIssuerActor {
user_id: UserId::new("user_community_public"),
workspace_id: WorkspaceId::new("ws_community_public"),
role: MembershipRole::Viewer,
}
}
fn map_token_issuer_error(
error: TokenIssuerError,
edition: crank_core::ProductEdition,
machine_access_mode: MachineAccessMode,
extra_context: serde_json::Value,
) -> ApiError {
match error {
TokenIssuerError::NotSupportedInEdition => ApiError::forbidden_with_context(
match machine_access_mode {
MachineAccessMode::ShortLivedToken => {
"short-lived machine access is not available in Community"
}
MachineAccessMode::OneTimeToken => {
"one-time machine access is not available in Community"
}
MachineAccessMode::StaticAgentKey => {
"static agent key machine access is not available for token issue"
}
},
merge_machine_auth_context(edition, machine_access_mode, extra_context),
),
TokenIssuerError::InvalidGrant(reason) => ApiError::validation_with_context(
"invalid machine token grant",
json!({ "reason": reason }),
),
TokenIssuerError::AgentKeyUnknown => ApiError::validation("agent key is unknown"),
TokenIssuerError::OperationNotStrict => ApiError::validation("operation is not strict"),
TokenIssuerError::OperationNotPublishedForAgent => {
ApiError::validation("operation is not published for agent")
}
TokenIssuerError::RegistryFailure(details) => {
ApiError::internal(format!("token issuer registry failure: {details}"))
}
TokenIssuerError::ReplayGuardFailure(details) => {
ApiError::internal(format!("token issuer replay guard failure: {details}"))
}
}
}
fn merge_machine_auth_context(
edition: crank_core::ProductEdition,
machine_access_mode: MachineAccessMode,
extra_context: serde_json::Value,
) -> serde_json::Value {
let mut context = json!({
"edition": edition,
"machine_access_mode": machine_access_mode,
});
if let (Some(base), Some(extra)) = (context.as_object_mut(), extra_context.as_object()) {
for (key, value) in extra {
base.insert(key.clone(), value.clone());
}
}
context
}
-16
View File
@@ -1,16 +0,0 @@
use axum::{
Json,
extract::{Path, State},
};
use serde_json::{Value, json};
use crate::{error::ApiError, routes::access::WorkspacePath, state::AppState};
pub async fn list_protocol_capabilities(
Path(_path): Path<WorkspacePath>,
State(state): State<AppState>,
) -> Result<Json<Value>, ApiError> {
Ok(Json(json!({
"items": state.service.list_protocol_capabilities().await
})))
}
+8 -242
View File
@@ -8,12 +8,12 @@ use crank_core::{
AuthKind, AuthProfile, AuthProfileId, CapabilityProfile, CommunityCapabilityProfile,
ConfigExport, EditionCapabilities, ExecutionMode, ExportMode, GeneratedDraft,
GeneratedDraftStatus, IdentityError, IdentityProvider, InvocationLevel, InvocationLog,
InvocationLogId, InvocationSource, InvocationStatus, LoginOutcome, MachineTokenIssuer,
MembershipRole, NoMachineTokenIssuer, NoopAuditSink, OperationId, OperationSecurityLevel,
OperationStatus, OwnerOnlyPolicyEngine, PlatformApiKey, PlatformApiKeyId, PlatformApiKeyScope,
PlatformApiKeyStatus, PolicyEngine, ProductEdition, Protocol, ResponseCachePolicy, SampleId,
Samples, Secret, SecretId, SecretKind, SecretStatus, Target, UsagePeriod, UserId,
UserSessionId, WizardState, Workspace, WorkspaceId, WorkspaceStatus,
InvocationLogId, InvocationSource, InvocationStatus, LoginOutcome, MembershipRole,
NoopAuditSink, OperationId, OperationSecurityLevel, OperationStatus, OwnerOnlyPolicyEngine,
PlatformApiKey, PlatformApiKeyId, PlatformApiKeyScope, PlatformApiKeyStatus, PolicyEngine,
ProductEdition, Protocol, ResponseCachePolicy, SampleId, Samples, Secret, SecretId, SecretKind,
SecretStatus, Target, UsagePeriod, UserId, UserSessionId, WizardState, Workspace, WorkspaceId,
WorkspaceStatus,
};
use crank_mapping::{JsonPathRoot, MappingSet, infer_mapping_from_samples};
use crank_registry::{
@@ -59,7 +59,6 @@ pub struct AdminService {
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>,
}
@@ -72,7 +71,6 @@ pub struct AdminServiceBuilder {
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>>,
}
@@ -508,21 +506,6 @@ pub struct DescriptorUploadResponse {
pub version: u32,
}
#[derive(Clone, Debug, Serialize)]
pub struct GrpcServiceSummary {
pub package: String,
pub service: String,
pub methods: Vec<GrpcMethodSummary>,
}
#[derive(Clone, Debug, Serialize)]
pub struct GrpcMethodSummary {
pub name: String,
pub kind: String,
pub input_schema: Schema,
pub output_schema: Schema,
}
fn default_operation_category() -> String {
"general".to_owned()
}
@@ -574,10 +557,6 @@ impl AdminService {
&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
}
@@ -600,7 +579,6 @@ impl AdminServiceBuilder {
identity_provider: None,
policy_engine: None,
audit_sink: None,
token_issuer: None,
capability_profile: None,
}
}
@@ -622,12 +600,6 @@ impl AdminServiceBuilder {
self
}
#[allow(dead_code)]
pub fn with_token_issuer(mut self, token_issuer: Arc<dyn MachineTokenIssuer>) -> Self {
self.token_issuer = Some(token_issuer);
self
}
#[allow(dead_code)]
pub fn with_capability_profile(
mut self,
@@ -649,9 +621,6 @@ impl AdminServiceBuilder {
.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)),
@@ -789,9 +758,6 @@ impl AdminService {
.await
{
Ok(LoginOutcome::Authenticated(identity)) => Ok(identity),
Ok(LoginOutcome::TwoFactorRequired(_)) => Err(ApiError::internal(
"two-factor login flow is not configured for this service",
)),
Err(error) => Err(map_identity_error(error)),
};
}
@@ -1818,204 +1784,6 @@ impl AdminService {
.map(Some)
}
#[cfg(any())]
async fn start_stream_session_test(
&self,
workspace_id: &WorkspaceId,
operation: &RegistryOperation,
runtime: &RuntimeOperation,
input: &Value,
resolved_auth: Option<&ResolvedAuth>,
request_context: &RuntimeRequestContext,
) -> Result<StreamSessionStartView, RuntimeError> {
let streaming = runtime.execution_config.streaming.as_ref().ok_or_else(|| {
RuntimeError::MissingStreamingConfig {
operation_id: runtime.operation_id.as_str().to_owned(),
}
})?;
let seed = self
.runtime
.execute_session_seed_with_auth_and_context(
runtime,
input,
resolved_auth,
Some(request_context),
)
.await?;
let batch_size = streaming.max_items.unwrap_or(10).max(1) as usize;
let preview_count = seed.items.len().min(batch_size);
let preview_items = seed.items[..preview_count].to_vec();
let next_index = preview_count;
let created_at = OffsetDateTime::now_utc();
let expires_at = created_at
.checked_add(time::Duration::milliseconds(
streaming.max_session_lifetime_ms.unwrap_or(60_000) as i64,
))
.ok_or_else(|| RuntimeError::SecretCrypto {
operation: "compute stream session expiration",
details: "failed to compute stream session expiration".to_owned(),
})?;
let session = StreamSession {
id: crank_core::StreamSessionId::new(new_prefixed_id("sess")),
workspace_id: workspace_id.clone(),
agent_id: None,
operation_id: operation.id.clone(),
protocol: operation.protocol,
mode: ExecutionMode::Session,
status: StreamStatus::Running,
cursor: (next_index < seed.items.len()).then(|| json!(next_index)),
state: json!(StoredSessionState {
input: input.clone(),
summary: seed.summary.clone(),
items: seed.items.clone(),
next_index,
batch_size,
}),
expires_at,
last_poll_at: None,
created_at,
closed_at: None,
};
self.registry
.create_stream_session(CreateStreamSessionRequest { session: &session })
.await
.map_err(|error| RuntimeError::SecretCrypto {
operation: "persist stream session",
details: error.to_string(),
})?;
Ok(StreamSessionStartView {
session_id: session.id.as_str().to_owned(),
status: session.status,
expires_at,
poll_after_ms: streaming.poll_interval_ms.unwrap_or(1_000),
preview: json!({
"summary": seed.summary,
"items": preview_items,
}),
})
}
#[cfg(any())]
async fn start_async_job_test(
&self,
workspace_id: &WorkspaceId,
operation: &RegistryOperation,
runtime: &RuntimeOperation,
input: &Value,
request_context: &RuntimeRequestContext,
) -> Result<AsyncJobStartView, RuntimeError> {
let created_at = OffsetDateTime::now_utc();
let streaming = runtime.execution_config.streaming.as_ref().ok_or_else(|| {
RuntimeError::MissingStreamingConfig {
operation_id: runtime.operation_id.as_str().to_owned(),
}
})?;
let expires_at = created_at
.checked_add(time::Duration::milliseconds(
streaming.max_session_lifetime_ms.unwrap_or(300_000) as i64,
))
.ok_or_else(|| RuntimeError::SecretCrypto {
operation: "compute async job expiration",
details: "failed to compute async job expiration".to_owned(),
})?;
let job = AsyncJobHandle {
id: crank_core::AsyncJobId::new(new_prefixed_id("job")),
workspace_id: workspace_id.clone(),
agent_id: None,
operation_id: operation.id.clone(),
status: JobStatus::Running,
progress: json!({ "pct": 0 }),
result: None,
error: None,
expires_at: Some(expires_at),
last_poll_at: None,
created_at,
updated_at: created_at,
finished_at: None,
};
self.registry
.create_async_job(CreateAsyncJobRequest { job: &job })
.await
.map_err(|error| RuntimeError::SecretCrypto {
operation: "persist async job",
details: error.to_string(),
})?;
let registry = self.registry.clone();
let secret_crypto = self.secret_crypto.clone();
let runtime_for_task = self.runtime.clone();
let workspace_for_task = workspace_id.clone();
let operation_for_task = runtime.clone();
let input_for_task = input.clone();
let request_context_for_task = request_context.clone();
let job_id = job.id.clone();
tokio::spawn(async move {
let resolved_auth = resolve_runtime_auth_for_task(
&registry,
&secret_crypto,
&workspace_for_task,
&operation_for_task.execution_config,
)
.await;
let result = match resolved_auth {
Ok(resolved_auth) => {
runtime_for_task
.execute_with_auth_and_context(
&operation_for_task,
&input_for_task,
resolved_auth.as_ref(),
Some(&request_context_for_task),
)
.await
}
Err(error) => Err(error),
};
let finished_at = OffsetDateTime::now_utc();
let _ = match result {
Ok(output) => {
registry
.update_async_job_status(UpdateAsyncJobStatusRequest {
job_id: &job_id,
current_status: JobStatus::Running,
next_status: JobStatus::Completed,
progress: &json!({ "pct": 100 }),
result: Some(&output),
error: None,
expires_at: None,
updated_at: &finished_at,
finished_at: Some(&finished_at),
})
.await
}
Err(error) => {
registry
.update_async_job_status(UpdateAsyncJobStatusRequest {
job_id: &job_id,
current_status: JobStatus::Running,
next_status: JobStatus::Failed,
progress: &json!({ "pct": 100 }),
result: None,
error: Some(&json!({
"code": runtime_error_code(&error),
"message": error.to_string(),
})),
expires_at: None,
updated_at: &finished_at,
finished_at: Some(&finished_at),
})
.await
}
};
});
Ok(AsyncJobStartView {
job_id: job.id.as_str().to_owned(),
status: job.status,
progress: job.progress,
})
}
async fn resolve_auth_profile(
&self,
workspace_id: &WorkspaceId,
@@ -3419,8 +3187,6 @@ impl AdminService {
"path": {},
"query": {},
"headers": { "x-demo-source": "crank-seed" },
"variables": null,
"grpc": null,
"body": demo_rest_request_sample()
}),
response_preview: demo_rest_response_sample(),
@@ -3602,9 +3368,9 @@ fn demo_revops_agent_payload() -> AgentPayload {
AgentPayload {
slug: "revops-copilot".to_owned(),
display_name: "RevOps Copilot".to_owned(),
description: "Revenue operations assistant with CRM and billing tools.".to_owned(),
description: "Sales operations assistant with CRM tools.".to_owned(),
instructions: json!({
"system": "Prefer CRM mutations first, then billing lookups for confirmation."
"system": "Prefer CRM mutations first, then lookup tools for confirmation."
}),
tool_selection_policy: json!({
"max_tools": 8,