From 2c0416e1c52f26d1828136d88b0a7349bbae2d27 Mon Sep 17 00:00:00 2001 From: "fabro-sh-0530[bot]" <281434857+fabro-sh-0530[bot]@users.noreply.github.com> Date: Sun, 24 May 2026 16:49:05 -0400 Subject: [PATCH] feat: add server sandbox provider enablement policy (#389) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operators can now disable individual sandbox providers at the server level via `[server.sandbox.providers.]` in `settings.toml`, without breaking existing deployments that omit the section entirely. ## What changed **Config layer & resolution** (`fabro-config`, `fabro-types`): new sparse `ServerSandboxLayer` / `ServerSandboxProvidersLayer` / `ServerSandboxProviderLayer` structs with `deny_unknown_fields` parse validation. Resolution defaults every missing level to `enabled = true`. The resolved `ServerSandboxSettings` / `ServerSandboxProvidersSettings` / `ServerSandboxProviderSettings` types live in `fabro-types` and are shared by all consumers. **Policy enforcement** (`fabro-server`): three check points enforce the effective provider (after dry-run Local coercion): 1. `POST /api/v1/runs` — 400 at admission. 2. `POST /api/v1/runs/preflight` — `ok: false` with a `Sandbox Provider Policy` error check. 3. Launch (`execute_run_in_process` / `execute_run_subprocess`) — fail-before-execution with a `LaunchFailed` reason. The dry-run coercion logic was extracted into `SandboxProvider::effective_for(mode)` on the type itself and reused across `fabro-server` and `fabro-workflow`. **Installer** (`fabro-install`): `write_sandbox_settings` now always writes all three provider policy tables with `enabled = true`, so generated `settings.toml` files are self-documenting. **API schema & clients**: `ServerNamespace` gains a required `sandbox` field in the OpenAPI spec; three new TypeScript model files were regenerated accordingly. ### Plan Summary - Task 1: config layer structs → resolved types → resolver helpers → tests - Task 2: `effective_sandbox_provider` + `sandbox_provider_policy_error` helpers; admission, preflight, and launch checks + integration tests - Task 3: installer writes all three provider entries; install finish tests updated - Task 4: OpenAPI schema, `fabro-api` build mappings, TS client regeneration, docs ### Fabro Details
Ran 8 stages in 59m 15s for $45.70 | Stage | Duration | Cost | Retries | |---|---|---|---| | start | 0s | – | 0 | | toolchain | 1s | – | 0 | | preflight_compile | 2m 5s | – | 0 | | preflight_lint | 2m 21s | – | 0 | | implement | 27m 55s | $38.88 | 0 | | simplify_opus | 14m 20s | $4.93 | 0 | | simplify_gpt | 3m 14s | $1.88 | 0 | | verify | 8m 49s | – | 0 | | **Total** | **59m 15s** | **$45.70** | **0** |
Ran ImplementPlan.fabro (11 nodes and 14 edges) ```dot digraph ImplementPlan { graph [ goal="Implement and simplify", model_stylesheet=" * { model: claude-opus-4-7; } " ] rankdir=LR start [shape=Mdiamond, label="Start"] exit [shape=Msquare, label="Exit"] toolchain [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0] preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0] preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", max_retries=0] fix_lints [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3] implement [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD.", model="gpt-55", reasoning_effort="xhigh"] simplify_opus [label="Simplify (Opus)", prompt="@prompts/simplify.md"] simplify_gpt [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"] verify [label="Verify", shape=parallelogram, script="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && { command -v rg >/dev/null 2>&1 || { echo 'rg is required for verify'; exit 127; }; } && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 2>&1", goal_gate=true, retry_target="fixup"] fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.", max_visits=3] start -> toolchain toolchain -> preflight_compile [condition="outcome=succeeded"] toolchain -> exit preflight_compile -> preflight_lint [condition="outcome=succeeded"] preflight_compile -> exit preflight_lint -> implement [condition="outcome=succeeded"] preflight_lint -> fix_lints fix_lints -> preflight_lint implement -> simplify_opus -> simplify_gpt -> verify verify -> exit [condition="outcome=succeeded"] verify -> fixup fixup -> verify } ```
⚒️ Generated with [Fabro](https://fabro.sh) --------- Co-authored-by: Fabro Co-authored-by: Bryan Helmkamp --- docs/public/administration/sandboxing.mdx | 5 ++ .../administration/server-configuration.mdx | 29 ++++++- docs/public/api-reference/fabro-api.yaml | 28 +++++++ lib/crates/fabro-api/build.rs | 15 ++++ lib/crates/fabro-api/src/lib.rs | 3 +- .../tests/server_settings_round_trip.rs | 28 ++++++- lib/crates/fabro-config/src/layers/mod.rs | 5 +- lib/crates/fabro-config/src/layers/server.rs | 28 +++++++ lib/crates/fabro-config/src/lib.rs | 1 + lib/crates/fabro-config/src/resolve/server.rs | 33 +++++++- .../fabro-config/src/tests/resolve_server.rs | 60 ++++++++++++++ lib/crates/fabro-install/src/lib.rs | 36 +++++++++ lib/crates/fabro-server/src/run_manifest.rs | 52 ++++++++++--- lib/crates/fabro-server/src/server.rs | 37 +++++++++ .../fabro-server/src/server/handler/runs.rs | 6 ++ lib/crates/fabro-server/src/server/tests.rs | 23 ++++++ .../fabro-server/tests/it/api/install.rs | 18 +++++ lib/crates/fabro-server/tests/it/api/runs.rs | 78 +++++++++++++++++++ .../fabro-types/src/sandbox_provider.rs | 13 ++++ lib/crates/fabro-types/src/settings/server.rs | 39 ++++++++++ .../fabro-workflow/src/operations/start.rs | 7 +- .../src/.openapi-generator/FILES | 3 + .../fabro-api-client/src/models/index.ts | 3 + .../src/models/server-namespace.ts | 4 + .../server-sandbox-provider-settings.ts | 19 +++++ .../server-sandbox-providers-settings.ts | 24 ++++++ .../src/models/server-sandbox-settings.ts | 22 ++++++ 27 files changed, 593 insertions(+), 26 deletions(-) create mode 100644 lib/packages/fabro-api-client/src/models/server-sandbox-provider-settings.ts create mode 100644 lib/packages/fabro-api-client/src/models/server-sandbox-providers-settings.ts create mode 100644 lib/packages/fabro-api-client/src/models/server-sandbox-settings.ts diff --git a/docs/public/administration/sandboxing.mdx b/docs/public/administration/sandboxing.mdx index 749eedeed..e74dad1cc 100644 --- a/docs/public/administration/sandboxing.mdx +++ b/docs/public/administration/sandboxing.mdx @@ -7,6 +7,11 @@ Sandboxes isolate agent execution from the host machine. When an agent runs a sh Fabro supports three sandbox providers: `local` (no isolation), `docker` (container-level), and `daytona` (cloud VM). See [Environments](/execution/environments) for full provider-specific configuration. +Operators can enable or disable which providers the server may launch with +`[server.sandbox.providers.]` in `settings.toml`. Missing entries default to +`enabled = true`; setting `enabled = false` rejects new runs whose effective provider is disabled. +Dry-run Docker/Daytona runs execute locally, so they are governed by the `local` provider policy. + ## Network access control For cloud sandboxes (Daytona), you can control outbound network access with `[environments..network]`. Three modes are available: `"allow_all"` (default), `"block"`, and `"cidr_allow_list"` with an `allow = ["..."]` CIDR list. diff --git a/docs/public/administration/server-configuration.mdx b/docs/public/administration/server-configuration.mdx index 7a78d75c9..8d8e6ee2b 100644 --- a/docs/public/administration/server-configuration.mdx +++ b/docs/public/administration/server-configuration.mdx @@ -17,7 +17,7 @@ Fabro only reads `settings.toml`. Older `server.toml`, `user.toml`, and `cli.tom | Scope | Examples | |---|---| -| Server-owned (runtime-only from local `settings.toml`) | `[server.listen]`, `[server.api]`, `[server.web]`, `[server.auth]`, `[server.storage]`, `[server.artifacts]`, `[server.slatedb]`, `[server.scheduler]`, `[server.logging]`, `[server.integrations]` | +| Server-owned (runtime-only from local `settings.toml`) | `[server.listen]`, `[server.api]`, `[server.web]`, `[server.auth]`, `[server.sandbox]`, `[server.storage]`, `[server.artifacts]`, `[server.slatedb]`, `[server.scheduler]`, `[server.logging]`, `[server.integrations]` | | Shared run defaults (layered through `.fabro/project.toml`/`workflow.toml`) | `[run.model]`, `[run.prepare]`, `[run.environment]`, `[environments.]`, `[run.checkpoint]`, `[run.inputs]`, `[run.pull_request]`, `[run.git]`, `[run.hooks]`, `[run.agent]` | The CLI-only `[cli.*]` sections (including `[cli.target]`) belong in the client machine's `settings.toml`. They tell CLI commands how to reach a server. The server process does not read `[cli.*]` for its own binding or routing. @@ -46,6 +46,15 @@ methods = ["dev-token", "github"] [server.auth.github] allowed_usernames = ["alice", "bob"] +[server.sandbox.providers.local] +enabled = true + +[server.sandbox.providers.docker] +enabled = true + +[server.sandbox.providers.daytona] +enabled = true + [server.integrations.github] app_id = "123456" client_id = "Iv1.abc123" @@ -165,6 +174,24 @@ GitHub-specific auth policy. The GitHub OAuth client ID still lives under `[server.integrations.github].client_id`. +### `[server.sandbox.providers]` section + +Controls which sandbox providers the server may launch. Missing provider entries default to +`enabled = true` for backward compatibility. Disabling a provider rejects new runs whose effective +provider is disabled; dry-run Docker/Daytona runs use the local provider and are governed by +`server.sandbox.providers.local.enabled`. + +```toml title="settings.toml" +[server.sandbox.providers.local] +enabled = true + +[server.sandbox.providers.docker] +enabled = true + +[server.sandbox.providers.daytona] +enabled = true +``` + ### `[server.slatedb]` section Configure the embedded SlateDB key-value store used for run event storage. diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml index e85eb2a2e..5a2f9b751 100644 --- a/docs/public/api-reference/fabro-api.yaml +++ b/docs/public/api-reference/fabro-api.yaml @@ -11326,6 +11326,7 @@ components: - web - auth - ip_allowlist + - sandbox - storage - artifacts - slatedb @@ -11343,6 +11344,8 @@ components: $ref: "#/components/schemas/ServerAuthSettings" ip_allowlist: $ref: "#/components/schemas/ServerIpAllowlistSettings" + sandbox: + $ref: "#/components/schemas/ServerSandboxSettings" storage: $ref: "#/components/schemas/ServerStorageSettings" artifacts: @@ -11459,6 +11462,31 @@ components: type: string enum: [GitHubMetaHooks] + ServerSandboxSettings: + type: object + required: [providers] + properties: + providers: + $ref: "#/components/schemas/ServerSandboxProvidersSettings" + + ServerSandboxProvidersSettings: + type: object + required: [local, docker, daytona] + properties: + local: + $ref: "#/components/schemas/ServerSandboxProviderSettings" + docker: + $ref: "#/components/schemas/ServerSandboxProviderSettings" + daytona: + $ref: "#/components/schemas/ServerSandboxProviderSettings" + + ServerSandboxProviderSettings: + type: object + required: [enabled] + properties: + enabled: + type: boolean + ServerStorageSettings: type: object required: [root] diff --git a/lib/crates/fabro-api/build.rs b/lib/crates/fabro-api/build.rs index 2a38da779..14d13d70e 100644 --- a/lib/crates/fabro-api/build.rs +++ b/lib/crates/fabro-api/build.rs @@ -259,6 +259,21 @@ fn main() { "fabro_types::settings::server::IpAllowEntry", &[], ), + ( + "ServerSandboxSettings", + "fabro_types::settings::server::ServerSandboxSettings", + &[], + ), + ( + "ServerSandboxProvidersSettings", + "fabro_types::settings::server::ServerSandboxProvidersSettings", + &[], + ), + ( + "ServerSandboxProviderSettings", + "fabro_types::settings::server::ServerSandboxProviderSettings", + &[], + ), ( "ServerStorageSettings", "fabro_types::settings::server::ServerStorageSettings", diff --git a/lib/crates/fabro-api/src/lib.rs b/lib/crates/fabro-api/src/lib.rs index d7ab6f416..9b40a152c 100644 --- a/lib/crates/fabro-api/src/lib.rs +++ b/lib/crates/fabro-api/src/lib.rs @@ -25,7 +25,8 @@ pub mod types { IpAllowEntry, LogDestination, ObjectStoreSettings, ServerApiSettings, ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings, ServerIntegrationsSettings, ServerIpAllowlistOverrideSettings, ServerIpAllowlistSettings, - ServerListenSettings, ServerLoggingSettings, ServerSchedulerSettings, + ServerListenSettings, ServerLoggingSettings, ServerSandboxProviderSettings, + ServerSandboxProvidersSettings, ServerSandboxSettings, ServerSchedulerSettings, ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings, WebhookStrategy, }; diff --git a/lib/crates/fabro-api/tests/server_settings_round_trip.rs b/lib/crates/fabro-api/tests/server_settings_round_trip.rs index 45d8ef97e..a7421641b 100644 --- a/lib/crates/fabro-api/tests/server_settings_round_trip.rs +++ b/lib/crates/fabro-api/tests/server_settings_round_trip.rs @@ -2,12 +2,18 @@ use std::any::{TypeId, type_name}; use fabro_api::types::{ LogDestination as ApiLogDestination, ObjectStoreSettings as ApiObjectStoreSettings, - ServerNamespace as ApiServerNamespace, ServerSettings as ApiServerSettings, + ServerNamespace as ApiServerNamespace, + ServerSandboxProviderSettings as ApiServerSandboxProviderSettings, + ServerSandboxProvidersSettings as ApiServerSandboxProvidersSettings, + ServerSandboxSettings as ApiServerSandboxSettings, ServerSettings as ApiServerSettings, }; use fabro_config::ServerSettingsBuilder; use fabro_types::ServerSettings; use fabro_types::settings::ServerNamespace; -use fabro_types::settings::server::{LogDestination, ObjectStoreSettings}; +use fabro_types::settings::server::{ + LogDestination, ObjectStoreSettings, ServerSandboxProviderSettings, + ServerSandboxProvidersSettings, ServerSandboxSettings, +}; #[test] fn server_settings_family_reuses_domain_types() { @@ -15,6 +21,9 @@ fn server_settings_family_reuses_domain_types() { assert_same_type::(); assert_same_type::(); assert_same_type::(); + assert_same_type::(); + assert_same_type::(); + assert_same_type::(); } #[test] @@ -40,6 +49,9 @@ methods = ["dev-token", "github"] [server.auth.github] allowed_usernames = ["alice"] +[server.sandbox.providers.daytona] +enabled = false + [server.storage] root = "/srv/fabro" @@ -61,6 +73,18 @@ slug = "fabro-dev" assert_eq!(json["server"]["listen"]["address"], "127.0.0.1:32276"); assert_eq!(json["server"]["storage"]["root"], "/srv/fabro"); assert_eq!(json["server"]["logging"]["destination"], "stdout"); + assert_eq!( + json["server"]["sandbox"]["providers"]["local"]["enabled"], + true + ); + assert_eq!( + json["server"]["sandbox"]["providers"]["docker"]["enabled"], + true + ); + assert_eq!( + json["server"]["sandbox"]["providers"]["daytona"]["enabled"], + false + ); assert!(json.get("features").is_none()); let round_trip: ApiServerSettings = diff --git a/lib/crates/fabro-config/src/layers/mod.rs b/lib/crates/fabro-config/src/layers/mod.rs index bf5f0a38b..6c425c33d 100644 --- a/lib/crates/fabro-config/src/layers/mod.rs +++ b/lib/crates/fabro-config/src/layers/mod.rs @@ -42,8 +42,9 @@ pub use server::{ GithubIntegrationLayer, IntegrationWebhooksLayer, ObjectStoreLocalLayer, ObjectStoreS3Layer, ServerApiLayer, ServerArtifactsLayer, ServerAuthGithubLayer, ServerAuthLayer, ServerIntegrationsLayer, ServerIpAllowlistLayer, ServerIpAllowlistOverrideLayer, ServerLayer, - ServerListenLayer, ServerLoggingLayer, ServerSchedulerLayer, ServerSlateDbLayer, - ServerStorageLayer, ServerWebLayer, SlackIntegrationLayer, + ServerListenLayer, ServerLoggingLayer, ServerSandboxLayer, ServerSandboxProviderLayer, + ServerSandboxProvidersLayer, ServerSchedulerLayer, ServerSlateDbLayer, ServerStorageLayer, + ServerWebLayer, SlackIntegrationLayer, }; pub use settings::SettingsLayer; pub use workflow::WorkflowLayer; diff --git a/lib/crates/fabro-config/src/layers/server.rs b/lib/crates/fabro-config/src/layers/server.rs index bc03a1824..62939a0e3 100644 --- a/lib/crates/fabro-config/src/layers/server.rs +++ b/lib/crates/fabro-config/src/layers/server.rs @@ -23,6 +23,8 @@ pub struct ServerLayer { #[serde(default, skip_serializing_if = "Option::is_none")] pub ip_allowlist: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub sandbox: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub storage: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub artifacts: Option, @@ -109,6 +111,32 @@ pub struct ServerIpAllowlistOverrideLayer { pub trusted_proxy_count: Option, } +/// `[server.sandbox]` — server-owned sandbox provider policy. +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)] +#[serde(deny_unknown_fields)] +pub struct ServerSandboxLayer { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub providers: Option, +} + +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)] +#[serde(deny_unknown_fields)] +pub struct ServerSandboxProvidersLayer { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub local: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub docker: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub daytona: Option, +} + +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)] +#[serde(deny_unknown_fields)] +pub struct ServerSandboxProviderLayer { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, +} + /// `[server.storage]` — single managed local disk root. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)] #[serde(deny_unknown_fields)] diff --git a/lib/crates/fabro-config/src/lib.rs b/lib/crates/fabro-config/src/lib.rs index 7ae698007..0fb321caa 100644 --- a/lib/crates/fabro-config/src/lib.rs +++ b/lib/crates/fabro-config/src/lib.rs @@ -57,6 +57,7 @@ pub use layers::{ RunRunBranchLayer, RunScmLayer, ScmGitHubLayer, ServerApiLayer, ServerArtifactsLayer, ServerAuthGithubLayer, ServerAuthLayer, ServerIntegrationsLayer, ServerIpAllowlistLayer, ServerIpAllowlistOverrideLayer, ServerLayer, ServerListenLayer, ServerLoggingLayer, + ServerSandboxLayer, ServerSandboxProviderLayer, ServerSandboxProvidersLayer, ServerSchedulerLayer, ServerSlateDbLayer, ServerStorageLayer, ServerWebLayer, SettingsLayer, SlackIntegrationLayer, StickyMap, StringOrSplice, WorkflowLayer, }; diff --git a/lib/crates/fabro-config/src/resolve/server.rs b/lib/crates/fabro-config/src/resolve/server.rs index f6c22c699..cfdf08c7a 100644 --- a/lib/crates/fabro-config/src/resolve/server.rs +++ b/lib/crates/fabro-config/src/resolve/server.rs @@ -4,7 +4,8 @@ use fabro_types::settings::server::{ IpAllowEntry, ObjectStoreProvider, ObjectStoreSettings, ServerApiSettings, ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings, ServerIntegrationsSettings, ServerIpAllowlistOverrideSettings, ServerIpAllowlistSettings, - ServerListenSettings, ServerLoggingSettings, ServerNamespace, ServerSchedulerSettings, + ServerListenSettings, ServerLoggingSettings, ServerNamespace, ServerSandboxProviderSettings, + ServerSandboxProvidersSettings, ServerSandboxSettings, ServerSchedulerSettings, ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings, WebhookStrategy, }; @@ -15,8 +16,8 @@ use crate::user::default_storage_dir; use crate::{ IntegrationWebhooksLayer, ObjectStoreLocalLayer, ObjectStoreS3Layer, ServerApiLayer, ServerArtifactsLayer, ServerAuthLayer, ServerIntegrationsLayer, ServerIpAllowlistLayer, - ServerIpAllowlistOverrideLayer, ServerLayer, ServerListenLayer, ServerSlateDbLayer, - ServerStorageLayer, ServerWebLayer, + ServerIpAllowlistOverrideLayer, ServerLayer, ServerListenLayer, ServerSandboxLayer, + ServerSandboxProviderLayer, ServerSlateDbLayer, ServerStorageLayer, ServerWebLayer, }; pub fn resolve_server(layer: &ServerLayer, errors: &mut Vec) -> ServerNamespace { @@ -38,6 +39,7 @@ pub fn resolve_server(layer: &ServerLayer, errors: &mut Vec) -> Se web, auth, ip_allowlist, + sandbox: resolve_sandbox(layer.sandbox.as_ref()), storage: storage.clone(), artifacts: resolve_artifacts(layer.artifacts.as_ref(), &storage.root, errors), slatedb: resolve_slatedb(layer.slatedb.as_ref(), &storage.root, errors), @@ -64,6 +66,31 @@ pub fn resolve_server(layer: &ServerLayer, errors: &mut Vec) -> Se } } +fn resolve_sandbox(layer: Option<&ServerSandboxLayer>) -> ServerSandboxSettings { + let providers = layer.and_then(|sandbox| sandbox.providers.as_ref()); + ServerSandboxSettings { + providers: ServerSandboxProvidersSettings { + local: resolve_sandbox_provider( + providers.and_then(|providers| providers.local.as_ref()), + ), + docker: resolve_sandbox_provider( + providers.and_then(|providers| providers.docker.as_ref()), + ), + daytona: resolve_sandbox_provider( + providers.and_then(|providers| providers.daytona.as_ref()), + ), + }, + } +} + +fn resolve_sandbox_provider( + layer: Option<&ServerSandboxProviderLayer>, +) -> ServerSandboxProviderSettings { + ServerSandboxProviderSettings { + enabled: layer.and_then(|provider| provider.enabled).unwrap_or(true), + } +} + fn resolve_storage(layer: Option<&ServerStorageLayer>) -> ServerStorageSettings { ServerStorageSettings { root: layer diff --git a/lib/crates/fabro-config/src/tests/resolve_server.rs b/lib/crates/fabro-config/src/tests/resolve_server.rs index d3c7899d4..edd419619 100644 --- a/lib/crates/fabro-config/src/tests/resolve_server.rs +++ b/lib/crates/fabro-config/src/tests/resolve_server.rs @@ -135,6 +135,66 @@ fn resolved_server_integrations_are_slack_only_for_chat() { ); } +#[test] +fn server_sandbox_defaults_all_providers_enabled() { + let settings = ServerSettingsBuilder::from_toml( + r#" +_version = 1 + +[server.auth] +methods = ["dev-token"] +"#, + ) + .expect("server settings should resolve"); + + let sandbox = settings.server.sandbox; + assert!(sandbox.providers.local.enabled); + assert!(sandbox.providers.docker.enabled); + assert!(sandbox.providers.daytona.enabled); +} + +#[test] +fn server_sandbox_allows_partial_provider_overrides() { + let settings = ServerSettingsBuilder::from_toml( + r#" +_version = 1 + +[server.auth] +methods = ["dev-token"] + +[server.sandbox.providers.daytona] +enabled = false +"#, + ) + .expect("server settings should resolve"); + + let sandbox = settings.server.sandbox; + assert!(sandbox.providers.local.enabled); + assert!(sandbox.providers.docker.enabled); + assert!(!sandbox.providers.daytona.enabled); +} + +#[test] +fn parsing_rejects_unknown_server_sandbox_provider() { + let err = ServerSettingsBuilder::from_toml( + r#" +_version = 1 + +[server.auth] +methods = ["dev-token"] + +[server.sandbox.providers.exe] +enabled = true +"#, + ) + .expect_err("unknown sandbox provider should be rejected"); + + assert!( + err.to_string().contains("unknown field `exe`"), + "unexpected error: {err}" + ); +} + #[test] fn parsing_rejects_unknown_server_integrations() { let source = r" diff --git a/lib/crates/fabro-install/src/lib.rs b/lib/crates/fabro-install/src/lib.rs index f62de5414..64e191fe0 100644 --- a/lib/crates/fabro-install/src/lib.rs +++ b/lib/crates/fabro-install/src/lib.rs @@ -442,6 +442,21 @@ pub fn write_object_store_settings( } } +fn write_sandbox_provider_policy(server: &mut toml::Table) -> Result<()> { + use fabro_types::SandboxProvider; + let sandbox = ensure_table(server, "sandbox")?; + let providers = ensure_table(sandbox, "providers")?; + for provider in [ + SandboxProvider::Local, + SandboxProvider::Docker, + SandboxProvider::Daytona, + ] { + let entry = ensure_table(providers, &provider.to_string())?; + entry.insert("enabled".to_string(), toml::Value::Boolean(true)); + } + Ok(()) +} + pub fn write_sandbox_settings( doc: &mut toml::Value, selection: InstallSandboxSelection, @@ -461,6 +476,8 @@ pub fn write_sandbox_settings( "provider".to_string(), toml::Value::String(provider.to_string()), ); + let server = ensure_table(root, "server")?; + write_sandbox_provider_policy(server)?; Ok(()) } @@ -1407,6 +1424,9 @@ stale = "remove-me" .and_then(toml::Value::as_str), Some("docker") ); + assert_eq!(sandbox_provider_enabled(&doc, "local"), Some(true)); + assert_eq!(sandbox_provider_enabled(&doc, "docker"), Some(true)); + assert_eq!(sandbox_provider_enabled(&doc, "daytona"), Some(true)); } #[test] @@ -1433,6 +1453,22 @@ stale = "remove-me" .and_then(toml::Value::as_str), Some("daytona") ); + assert_eq!(sandbox_provider_enabled(&doc, "local"), Some(true)); + assert_eq!(sandbox_provider_enabled(&doc, "docker"), Some(true)); + assert_eq!(sandbox_provider_enabled(&doc, "daytona"), Some(true)); + } + + fn sandbox_provider_enabled(doc: &toml::Value, provider: &str) -> Option { + doc.get("server") + .and_then(toml::Value::as_table) + .and_then(|server| server.get("sandbox")) + .and_then(toml::Value::as_table) + .and_then(|sandbox| sandbox.get("providers")) + .and_then(toml::Value::as_table) + .and_then(|providers| providers.get(provider)) + .and_then(toml::Value::as_table) + .and_then(|provider| provider.get("enabled")) + .and_then(toml::Value::as_bool) } #[test] diff --git a/lib/crates/fabro-server/src/run_manifest.rs b/lib/crates/fabro-server/src/run_manifest.rs index ecc61e589..0aefe8f78 100644 --- a/lib/crates/fabro-server/src/run_manifest.rs +++ b/lib/crates/fabro-server/src/run_manifest.rs @@ -25,8 +25,8 @@ use fabro_sandbox::{DockerSandboxOptions, Sandbox, SandboxProvider, SandboxSpec} use fabro_static::EnvVars; use fabro_types::settings::cli::OutputVerbosity; use fabro_types::settings::interp::InterpString; -use fabro_types::settings::run::{EnvironmentProvider, RunGoal, RunMode, RunNamespace}; -use fabro_types::{ManifestPath, RunId, WorkflowSettings}; +use fabro_types::settings::run::{EnvironmentProvider, RunGoal, RunNamespace}; +use fabro_types::{ManifestPath, RunId, ServerSettings, WorkflowSettings}; use fabro_util::check_report::{CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus}; use fabro_validate::Severity; use fabro_workflow::Error as WorkflowError; @@ -502,13 +502,26 @@ async fn build_preflight_report( let resolved_run = materialized.run; let server_settings = state.server_settings(); let github_integration = &server_settings.server.integrations.github; - let sandbox_provider = resolve_sandbox_provider(&resolved_run); - let sandbox_provider = - if resolved_run.execution.mode == RunMode::DryRun && !sandbox_provider.is_local() { - SandboxProvider::Local - } else { - sandbox_provider - }; + let sandbox_provider = effective_sandbox_provider(&resolved_run); + if let Some(error) = sandbox_provider_policy_error(&server_settings, sandbox_provider) { + checks.push(CheckResult { + name: "Sandbox Provider Policy".into(), + status: CheckStatus::Error, + summary: error, + details: Vec::new(), + remediation: None, + }); + return Ok(( + CheckReport { + title: "Run Preflight".into(), + sections: vec![CheckSection { + title: String::new(), + checks, + }], + }, + false, + )); + } run_environment_capability_check(&mut checks, &resolved_run); let needs_github_credentials = sandbox_provider.is_clone_based() || resolved_run.integrations.github.is_token_requested(); @@ -617,8 +630,25 @@ fn base_preflight_checks(prepared: &PreparedManifest, graph: &Graph) -> Vec SandboxProvider { - SandboxProvider::from(settings.environment.provider) +pub(crate) fn sandbox_provider_policy_error( + server_settings: &ServerSettings, + provider: SandboxProvider, +) -> Option { + let enabled = server_settings + .server + .sandbox + .providers + .for_provider(provider) + .enabled; + (!enabled).then(|| { + format!( + "sandbox provider \"{provider}\" is disabled by server.sandbox.providers.{provider}.enabled" + ) + }) +} + +pub(crate) fn effective_sandbox_provider(settings: &RunNamespace) -> SandboxProvider { + SandboxProvider::from(settings.environment.provider).effective_for(settings.execution.mode) } fn resolve_daytona_config(settings: &RunNamespace) -> DaytonaConfig { diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs index 031766d16..bd2638729 100644 --- a/lib/crates/fabro-server/src/server.rs +++ b/lib/crates/fabro-server/src/server.rs @@ -2847,6 +2847,23 @@ async fn finish_cancelled_run_before_execution(state: &Arc, run_id: Ru state.scheduler_notify.notify_one(); } +/// Reject the run before execution if its effective sandbox provider is +/// disabled by server policy. Returns `true` when the run was rejected. +async fn reject_run_if_sandbox_provider_disabled( + state: &Arc, + server_settings: &ServerSettings, + run_id: RunId, + settings: &RunNamespace, +) -> bool { + let provider = run_manifest::effective_sandbox_provider(settings); + let Some(error) = run_manifest::sandbox_provider_policy_error(server_settings, provider) else { + return false; + }; + tracing::warn!(run_id = %run_id, error = %error, "Sandbox provider disabled by server policy"); + fail_run_before_execution(state, run_id, FailureReason::LaunchFailed, error).await; + true +} + async fn fail_run_before_execution( state: &Arc, run_id: RunId, @@ -3596,6 +3613,16 @@ async fn execute_run_in_process(state: Arc, run_id: RunId) { finish_cancelled_run_before_execution(&state, run_id).await; return; } + if reject_run_if_sandbox_provider_disabled( + &state, + &server_settings, + run_id, + &persisted.run_spec().settings.run, + ) + .await + { + return; + } let github_app_result = { let run_spec = persisted.run_spec(); let settings = &run_spec.settings.run; @@ -3820,6 +3847,16 @@ async fn execute_run_subprocess(state: Arc, run_id: RunId) { } }; let agent_fabro_tools_enabled = run_state.spec.settings.run.agent.fabro_tools; + if reject_run_if_sandbox_provider_disabled( + &state, + &state.server_settings(), + run_id, + &run_state.spec.settings.run, + ) + .await + { + return; + } let state_for_build = Arc::clone(&state); let run_dir_for_build = run_dir.clone(); diff --git a/lib/crates/fabro-server/src/server/handler/runs.rs b/lib/crates/fabro-server/src/server/handler/runs.rs index db1f74ca4..c5e892379 100644 --- a/lib/crates/fabro-server/src/server/handler/runs.rs +++ b/lib/crates/fabro-server/src/server/handler/runs.rs @@ -606,6 +606,12 @@ async fn create_run( Err(err) => return ApiError::bad_request(err.to_string()).into_response(), }; let run_id = prepared.run_id.unwrap_or_else(RunId::new); + let provider = run_manifest::effective_sandbox_provider(&prepared.settings.run); + if let Some(error) = + run_manifest::sandbox_provider_policy_error(&state.server_settings(), provider) + { + return ApiError::bad_request(error).into_response(); + } if let Some(parent_id) = prepared.parent_id { if parent_id == run_id { return ApiError::bad_request("A run cannot be its own parent.").into_response(); diff --git a/lib/crates/fabro-server/src/server/tests.rs b/lib/crates/fabro-server/src/server/tests.rs index e327cf318..34a991f0f 100644 --- a/lib/crates/fabro-server/src/server/tests.rs +++ b/lib/crates/fabro-server/src/server/tests.rs @@ -959,6 +959,29 @@ id = "missing" ); } +#[test] +fn sandbox_provider_policy_error_reports_disabled_provider() { + let settings = server_settings_from_toml( + r#" +_version = 1 + +[server.auth] +methods = ["dev-token"] + +[server.sandbox.providers.daytona] +enabled = false +"#, + ); + + assert_eq!( + crate::run_manifest::sandbox_provider_policy_error(&settings, SandboxProvider::Daytona) + .as_deref(), + Some( + "sandbox provider \"daytona\" is disabled by server.sandbox.providers.daytona.enabled" + ) + ); +} + #[test] fn clone_sandbox_credentials_are_available_for_clone_based_providers() { use fabro_types::settings::run::EnvironmentProvider; diff --git a/lib/crates/fabro-server/tests/it/api/install.rs b/lib/crates/fabro-server/tests/it/api/install.rs index 8b9bea24e..d5c431fde 100644 --- a/lib/crates/fabro-server/tests/it/api/install.rs +++ b/lib/crates/fabro-server/tests/it/api/install.rs @@ -34,6 +34,22 @@ fn spa_fixture_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/spa") } +fn assert_sandbox_provider_policy_enabled(settings: &str) { + assert!(settings.contains("[server.sandbox.providers.local]")); + assert!(settings.contains("[server.sandbox.providers.docker]")); + assert!(settings.contains("[server.sandbox.providers.daytona]")); + assert!(settings.contains("enabled = true")); + + let resolved = ServerSettingsBuilder::from_toml(settings) + .expect("settings should resolve") + .server + .sandbox + .providers; + assert!(resolved.local.enabled); + assert!(resolved.docker.enabled); + assert!(resolved.daytona.enabled); +} + async fn mock_daytona_auth_probe(server: &MockServer) -> httpmock::Mock<'_> { server .mock_async(|when, then| { @@ -918,6 +934,7 @@ async fn token_install_finish_persists_settings_env_and_vault() { settings.contains("provider = \"docker\""), "settings.toml should record explicit docker sandbox provider" ); + assert_sandbox_provider_policy_enabled(&settings); let resolved = ServerSettingsBuilder::from_toml(&settings) .expect("settings should resolve") .server; @@ -2677,6 +2694,7 @@ async fn daytona_install_finish_writes_settings_and_vault_secret() { settings.contains("provider = \"daytona\""), "settings.toml should record daytona sandbox provider" ); + assert_sandbox_provider_policy_enabled(&settings); let vault = Vault::load(Storage::new(temp_dir.path()).secrets_path()).unwrap(); assert_eq!(vault.get("DAYTONA_API_KEY"), Some(api_key)); diff --git a/lib/crates/fabro-server/tests/it/api/runs.rs b/lib/crates/fabro-server/tests/it/api/runs.rs index 32c205c1e..c6521ee7c 100644 --- a/lib/crates/fabro-server/tests/it/api/runs.rs +++ b/lib/crates/fabro-server/tests/it/api/runs.rs @@ -52,6 +52,84 @@ async fn request_json( .await } +fn daytona_manifest() -> serde_json::Value { + let mut manifest = minimal_manifest_json(MINIMAL_DOT); + manifest["args"] = serde_json::json!({ "environment": "daytona" }); + manifest +} + +fn daytona_disabled_settings() -> crate::helpers::TestAppSettings { + settings_from_toml( + r" +_version = 1 + +[server.sandbox.providers.daytona] +enabled = false +", + ) +} + +#[tokio::test] +async fn create_run_rejects_disabled_sandbox_provider() { + let app = fabro_server::test_support::build_test_router(test_app_state_with_options( + daytona_disabled_settings(), + 5, + )); + + let request = Request::builder() + .method("POST") + .uri(api("/runs")) + .header("content-type", "application/json") + .body(Body::from(daytona_manifest().to_string())) + .expect("create run request should build"); + let body = response_json( + app.clone().oneshot(request).await.unwrap(), + StatusCode::BAD_REQUEST, + "POST /api/v1/runs", + ) + .await; + + assert_eq!( + body["errors"][0]["detail"], + "sandbox provider \"daytona\" is disabled by server.sandbox.providers.daytona.enabled" + ); +} + +#[tokio::test] +async fn preflight_reports_disabled_sandbox_provider() { + let app = fabro_server::test_support::build_test_router(test_app_state_with_options( + daytona_disabled_settings(), + 5, + )); + + let request = Request::builder() + .method("POST") + .uri(api("/preflight")) + .header("content-type", "application/json") + .body(Body::from(daytona_manifest().to_string())) + .expect("preflight request should build"); + let body = response_json( + app.clone().oneshot(request).await.unwrap(), + StatusCode::OK, + "POST /api/v1/preflight", + ) + .await; + + assert_eq!(body["ok"], false); + let checks = body["checks"]["sections"][0]["checks"] + .as_array() + .expect("preflight checks should be an array"); + let policy_check = checks + .iter() + .find(|check| check["name"] == "Sandbox Provider Policy") + .expect("policy check should be present"); + assert_eq!(policy_check["status"], "error"); + assert_eq!( + policy_check["summary"], + "sandbox provider \"daytona\" is disabled by server.sandbox.providers.daytona.enabled" + ); +} + #[tokio::test] async fn run_responses_include_ask_fabro_affordance() { let settings = settings_from_toml( diff --git a/lib/crates/fabro-types/src/sandbox_provider.rs b/lib/crates/fabro-types/src/sandbox_provider.rs index 18cbbdb81..1034fe2b2 100644 --- a/lib/crates/fabro-types/src/sandbox_provider.rs +++ b/lib/crates/fabro-types/src/sandbox_provider.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; use strum::{Display, EnumString}; +use crate::settings::run::RunMode; + /// Sandbox provider for agent tool operations. #[derive( Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, Display, EnumString, @@ -31,6 +33,17 @@ impl SandboxProvider { pub fn is_clone_based(&self) -> bool { matches!(self, Self::Docker | Self::Daytona) } + + /// Coerce non-local providers to `Local` under dry-run; otherwise + /// unchanged. + #[must_use] + pub fn effective_for(self, mode: RunMode) -> Self { + if mode == RunMode::DryRun && !self.is_local() { + Self::Local + } else { + self + } + } } #[cfg(test)] diff --git a/lib/crates/fabro-types/src/settings/server.rs b/lib/crates/fabro-types/src/settings/server.rs index fa92d3f64..c6fe71305 100644 --- a/lib/crates/fabro-types/src/settings/server.rs +++ b/lib/crates/fabro-types/src/settings/server.rs @@ -29,6 +29,7 @@ pub struct ServerNamespace { pub web: ServerWebSettings, pub auth: ServerAuthSettings, pub ip_allowlist: ServerIpAllowlistSettings, + pub sandbox: ServerSandboxSettings, pub storage: ServerStorageSettings, pub artifacts: ServerArtifactsSettings, pub slatedb: ServerSlateDbSettings, @@ -50,6 +51,7 @@ impl ServerNamespace { web: ServerWebSettings::default(), auth: ServerAuthSettings::default(), ip_allowlist: ServerIpAllowlistSettings::default(), + sandbox: ServerSandboxSettings::default(), storage: ServerStorageSettings::default(), artifacts: ServerArtifactsSettings::default(), slatedb: ServerSlateDbSettings::default(), @@ -133,6 +135,43 @@ pub struct ServerIpAllowlistOverrideSettings { pub trusted_proxy_count: Option, } +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct ServerSandboxSettings { + pub providers: ServerSandboxProvidersSettings, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct ServerSandboxProvidersSettings { + pub local: ServerSandboxProviderSettings, + pub docker: ServerSandboxProviderSettings, + pub daytona: ServerSandboxProviderSettings, +} + +impl ServerSandboxProvidersSettings { + /// Per-provider policy entry. + #[must_use] + pub fn for_provider(&self, provider: crate::SandboxProvider) -> &ServerSandboxProviderSettings { + match provider { + crate::SandboxProvider::Local => &self.local, + crate::SandboxProvider::Docker => &self.docker, + crate::SandboxProvider::Daytona => &self.daytona, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct ServerSandboxProviderSettings { + pub enabled: bool, +} + +impl Default for ServerSandboxProviderSettings { + // The resolver defaults each provider to enabled; keep the struct default + // aligned with that so callers that bypass the resolver behave identically. + fn default() -> Self { + Self { enabled: true } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum IpAllowEntry { Literal(IpNet), diff --git a/lib/crates/fabro-workflow/src/operations/start.rs b/lib/crates/fabro-workflow/src/operations/start.rs index d57624e4f..66f1a5fc4 100644 --- a/lib/crates/fabro-workflow/src/operations/start.rs +++ b/lib/crates/fabro-workflow/src/operations/start.rs @@ -336,13 +336,8 @@ impl RunSession { let resolved = &settings.run; - let sandbox_provider = resolve_sandbox_provider(resolved); let sandbox_provider = - if resolved.execution.mode == RunMode::DryRun && !sandbox_provider.is_local() { - SandboxProvider::Local - } else { - sandbox_provider - }; + resolve_sandbox_provider(resolved).effective_for(resolved.execution.mode); let catalog = Arc::clone(&services.catalog); let configured = configured_providers_for_start(services.vault.as_ref(), Arc::clone(&catalog)).await; diff --git a/lib/packages/fabro-api-client/src/.openapi-generator/FILES b/lib/packages/fabro-api-client/src/.openapi-generator/FILES index e9b62257e..5b231abf2 100644 --- a/lib/packages/fabro-api-client/src/.openapi-generator/FILES +++ b/lib/packages/fabro-api-client/src/.openapi-generator/FILES @@ -397,6 +397,9 @@ models/server-listen-tcp-settings.ts models/server-listen-unix-settings.ts models/server-logging-settings.ts models/server-namespace.ts +models/server-sandbox-provider-settings.ts +models/server-sandbox-providers-settings.ts +models/server-sandbox-settings.ts models/server-scheduler-settings.ts models/server-settings.ts models/server-slate-db-settings.ts diff --git a/lib/packages/fabro-api-client/src/models/index.ts b/lib/packages/fabro-api-client/src/models/index.ts index 627dee568..f3b13b76e 100644 --- a/lib/packages/fabro-api-client/src/models/index.ts +++ b/lib/packages/fabro-api-client/src/models/index.ts @@ -373,6 +373,9 @@ export * from './server-listen-tcp-settings'; export * from './server-listen-unix-settings'; export * from './server-logging-settings'; export * from './server-namespace'; +export * from './server-sandbox-provider-settings'; +export * from './server-sandbox-providers-settings'; +export * from './server-sandbox-settings'; export * from './server-scheduler-settings'; export * from './server-settings'; export * from './server-slate-db-settings'; diff --git a/lib/packages/fabro-api-client/src/models/server-namespace.ts b/lib/packages/fabro-api-client/src/models/server-namespace.ts index f162fe6cc..315243e88 100644 --- a/lib/packages/fabro-api-client/src/models/server-namespace.ts +++ b/lib/packages/fabro-api-client/src/models/server-namespace.ts @@ -36,6 +36,9 @@ import type { ServerListenSettings } from './server-listen-settings'; import type { ServerLoggingSettings } from './server-logging-settings'; // May contain unused imports in some cases // @ts-ignore +import type { ServerSandboxSettings } from './server-sandbox-settings'; +// May contain unused imports in some cases +// @ts-ignore import type { ServerSchedulerSettings } from './server-scheduler-settings'; // May contain unused imports in some cases // @ts-ignore @@ -53,6 +56,7 @@ export interface ServerNamespace { 'web': ServerWebSettings; 'auth': ServerAuthSettings; 'ip_allowlist': ServerIpAllowlistSettings; + 'sandbox': ServerSandboxSettings; 'storage': ServerStorageSettings; 'artifacts': ServerArtifactsSettings; 'slatedb': ServerSlateDbSettings; diff --git a/lib/packages/fabro-api-client/src/models/server-sandbox-provider-settings.ts b/lib/packages/fabro-api-client/src/models/server-sandbox-provider-settings.ts new file mode 100644 index 000000000..c50d09f6d --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/server-sandbox-provider-settings.ts @@ -0,0 +1,19 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +export interface ServerSandboxProviderSettings { + 'enabled': boolean; +} diff --git a/lib/packages/fabro-api-client/src/models/server-sandbox-providers-settings.ts b/lib/packages/fabro-api-client/src/models/server-sandbox-providers-settings.ts new file mode 100644 index 000000000..9fa9a35a1 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/server-sandbox-providers-settings.ts @@ -0,0 +1,24 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { ServerSandboxProviderSettings } from './server-sandbox-provider-settings'; + +export interface ServerSandboxProvidersSettings { + 'local': ServerSandboxProviderSettings; + 'docker': ServerSandboxProviderSettings; + 'daytona': ServerSandboxProviderSettings; +} diff --git a/lib/packages/fabro-api-client/src/models/server-sandbox-settings.ts b/lib/packages/fabro-api-client/src/models/server-sandbox-settings.ts new file mode 100644 index 000000000..cb6a3b2d6 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/server-sandbox-settings.ts @@ -0,0 +1,22 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { ServerSandboxProvidersSettings } from './server-sandbox-providers-settings'; + +export interface ServerSandboxSettings { + 'providers': ServerSandboxProvidersSettings; +}