This specification outlines the implementation of a new sandbox type called "pint" in the CodeSandbox platform. The feature enables passing a feature flag from the CodeSandbox SDK through the server to the pitcher-manager during sandbox creation and VM startup processes.
- Add support for a new sandbox type identifier: "pint"
- Implement feature flag propagation from SDK to server to pitcher-manager
- Ensure backward compatibility with existing sandbox creation flows
- Store feature flag information in sandbox records for persistence
- Feature flag checking should not add significant latency to sandbox creation
- Flag should be efficiently passed through the entire request flow
- No impact on existing sandbox types' performance
- Feature flag should only be accessible to authorized users/teams
- No sensitive information exposure through feature flag
- Standard authorization flows must be maintained
CodeSandbox SDK → CodeSandbox Server → Pitcher Manager
↓ ↓ ↓
Feature Flag Store Flag Use Flag for
Detection in Database Container Setup
- SDK Layer: Accept optional feature flag parameter in sandbox creation requests
- Server Layer: Validate, store, and forward feature flag to pitcher-manager
- Pitcher Manager: Receive flag and apply pint-specific container configuration
Add new column to store the sandbox type:
ALTER TABLE sandboxes ADD COLUMN sandbox_type VARCHAR(50) DEFAULT 'standard';
CREATE INDEX idx_sandboxes_sandbox_type ON sandboxes(sandbox_type);
If more granular control is needed:
CREA
TE TABLE sandbox_feature_flags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sandbox_id UUID NOT NULL REFERENCES sandboxes(id),
flag_name VARCHAR(255) NOT NULL,
flag_value BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_sandbox_feature_flags_sandbox_id ON sandbox_feature_flags(sandbox_id);
CREATE INDEX idx_sandbox_feature_flags_flag_name ON sandbox_feature_flags(flag_name);
Add feature flag support to sandbox creation options:
export interface CreateSandboxOpts extends CreateSandboxBaseOpts {
id?: string;
/**
* Feature flags for experimental sandbox types
*/
featureFlags?: {
sandboxType?: 'standard' | 'pint';
[key: string]: any;
};
}
export interface StartSandboxOpts {
// ... existing properties
/**
* Feature flags for experimental features
*/
featureFlags?: {
sandboxType?: 'standard' | 'pint';
[key: string]: any;
};
}
Update the create method to handle feature flags:
private async createTemplateSandbox(
opts?: CreateSandboxOpts & StartSandboxOpts
) {
// ... existing code
const startResponse = await this.api.startVm(
sandbox.id,
getStartOptions(opts, {
featureFlags: opts?.featureFlags
})
);
return new Sandbox(sandbox.id, this.api, startResponse, this.tracer);
}
Update getStartOptions to include feature flags:
export function getStartOptions(opts?: StartSandboxOpts, additional?: any): any {
return {
tier: opts?.vmTier?.name,
hibernation_timeout_seconds: opts?.hibernationTimeoutSeconds,
ipcountry: opts?.ipcountry,
automatic_wakeup_config: opts?.automaticWakeupConfig,
feature_flags: {
...opts?.featureFlags,
...additional?.featureFlags
}
};
}
Add sandbox_type field to schema:
schema "sandboxes" do
# ... existing fields
field :sandbox_type, :string, default: "standard"
# ... rest of schema
end
def changeset(sandbox, attrs) do
sandbox
|> cast(attrs, [..., :sandbox_type])
|> validate_inclusion(:sandbox_type, ["standard", "pint"])
# ... rest of changeset
end
Update start action to handle feature flags:
def start(conn, _params) do
team = current_team(conn)
scopes = current_scopes(conn)
params = conn.body_params
with {:sandbox, %Sandbox{v2: true} = sandbox} <-
{:sandbox, Sandboxes.get_by_shortid(conn.path_params["id"], preload: [:team])},
{:authorization, authorization} when authorization in [:read, :write_code] <-
{:authorization, Sandboxes.get_team_authorization(sandbox, team)},
pitcher_authorization <- translate_authorization(authorization, scopes),
{:ok, sandbox, response} <-
Operations.start_vm(
sandbox,
team,
%{
authorization: pitcher_authorization,
authorization_method: :team,
tier: params[:tier],
hibernation_timeout_seconds: params[:hibernation_timeout_seconds],
cloudflare_ipcountry: params[:ipcountry] || get_req_header(conn, "cf-ipcountry"),
forked_from_sandbox: get_forked_from_sandbox(sandbox),
pitcher_manager_url: get_req_header(conn, "x-pitcher-manager-url") |> List.first(),
automatic_wakeup_config: params[:automatic_wakeup_config],
feature_flags: params[:feature_flags] || %{}
}
) do
conn
|> put_view(VMController.StartResponse)
|> put_status(200)
|> render(:json, pitcher_response: response, id: sandbox.shortid)
else
# ... error handling
end
end
Update start_vm function to handle feature flags:
@type start_vm_opts :: %{
required(:authorization) => String.t(),
required(:authorization_method) => :team,
optional(:automatic_wakeup_config) => %{http: boolean(), websocket: boolean()} | nil,
optional(:cloudflare_ipcountry) => String.t(),
optional(:ip) => String.t(),
optional(:forked_from_sandbox) => Sandbox.t() | nil,
optional(:hibernation_timeout_seconds) => pos_integer(),
optional(:is_new_sandbox) => boolean(),
optional(:pitcher_manager_url) => String.t() | nil,
optional(:tier) => String.t(),
optional(:feature_flags) => map()
}
def start_vm(sandbox, team, start_vm_opts) do
with {:ok, %Resources{} = resources} <- get_vm_resources(sandbox, team, start_vm_opts[:tier]),
{:ok, resources} <-
validate_hibernation_timeout(resources, start_vm_opts[:hibernation_timeout_seconds]),
{:ok, updated_sandbox} <- maybe_update_sandbox_type(sandbox, start_vm_opts[:feature_flags]),
{:ok, response} <-
External.Pitcher.start_sandbox_instance(
nil,
updated_sandbox,
start_vm_opts.authorization,
authorization_method: start_vm_opts.authorization_method,
resources: resources,
ip: start_vm_opts[:ip],
cloudflare_ipcountry: start_vm_opts[:cloudflare_ipcountry],
forked_from_sandbox: start_vm_opts[:forked_from_sandbox],
pitcher_manager_url: start_vm_opts[:pitcher_manager_url],
is_new_sandbox: start_vm_opts[:is_new_sandbox],
automatic_wakeup_config: start_vm_opts[:automatic_wakeup_config],
feature_flags: start_vm_opts[:feature_flags]
) do
{:ok, updated_sandbox, response}
end
end
defp maybe_update_sandbox_type(sandbox, feature_flags) do
case get_in(feature_flags, ["sandboxType"]) do
"pint" ->
sandbox
|> Ecto.Changeset.change(sandbox_type: "pint")
|> Repo.update()
_ ->
{:ok, sandbox}
end
end
Update start_sandbox_instance to pass feature flags:
def start_sandbox_instance(user, sandbox, authorization, opts \\ []) do
# ... existing code for building body
feature_flags = Keyword.get(opts, :feature_flags, %{})
body =
%{
# ... existing body fields
featureFlags: feature_flags,
annotations: %{
# ... existing annotations
"sandboxType" => sandbox.sandbox_type || "standard",
"pintEnabled" => to_string(get_in(feature_flags, ["sandboxType"]) == "pint")
}
}
# ... rest of body building
# ... rest of function
end
Add feature flags to the VM start request schema:
components:
schemas:
VMStartRequest:
type: object
properties:
# ... existing properties
feature_flags:
type: object
properties:
sandboxType:
type: string
enum: ["standard", "pint"]
description: "The sandbox type to create"
additionalProperties: true
description: "Feature flags for experimental functionality"
Create migration file:
defmodule CodeSandbox.Repo.Migrations.AddSandboxTypeToSandboxes do
use Ecto.Migration
def change do
alter table(:sandboxes) do
add :sandbox_type, :string, default: "standard"
end
create index(:sandboxes, [:sandbox_type])
end
end
-
SDK Tests
- Test feature flag passing in sandbox creation
- Test backward compatibility with existing API
- Test validation of feature flag values
-
Server Tests
- Test VM controller handles feature flags correctly
- Test sandbox type sto rage in database
- Test pitcher integration with feature flags
- Test authorization remains intact
-
Integration Tests
- End-to-end sandbox creation with pint feature flag
- Test feature flag propagation through entire stack
- Test VM startup with pint configuration
# Example test case
test "creates sandbox with pint feature flag", %{conn: conn, team: team} do
sandbox = insert(:sandbox, team: team, v2: true)
params = %{
"feature_flags" => %{
"sandboxType" => "pint"
}
}
conn = post(conn, "/api/v1/vm/#{sandbox.shortid}/start", params)
assert %{"id" => _} = json_response(conn, 200)
updated_sandbox = Repo.get!(Sandbox, sandbox.id)
assert updated_sandbox.sandbox_type == "pint"
end
- Test performance impact of feature flag processing
- Ensure no degradation in sandbox creation latency
- Test concurrent sandbox creation with mixed feature flags
- Elixir/Phoenix: Server-side feature flag handling
- TypeScript: SDK type definitions and client-side logic
- PostgreSQL: Database schema changes
- Pitcher Manager: Must support receiving and processing feature flags
- All existing sandbox creation flows must continue working
- Default sandbox type remains "standard"
- Feature flag parameter is optional
- No breaking changes to existing APIs
- Consider using a feature flag service (LaunchDarkly, etc.) for runtime control
- Implement gradual rollout capabilities
- Add monitoring and metrics for pint sandbox usage
- Add metrics for pint sandbox creation rates
- Monitor feature flag usage patterns
- Track error rates for pint vs standard sandboxes
- Add logging for feature flag propagation
- Validate feature flag inputs to prevent injection attacks
- Ensure feature flags don't expose sensitive information
- Maintain proper authorization checks regardless of sandbox type
- Audit feature flag changes for compliance
- Feature flag processing should be minimal overhead
- Database queries should use indexes for sandbox_type
- Consider caching feature flag configurations
- Monitor resource usage of pint vs standard sandboxes
- Implement database schema changes
- Add feature flag support to SDK and server
- Deploy with feature disabled by default
- Enable for internal testing teams
- Validate feature flag propagation
- Monitor for issues and performance impacts
- Enable for select beta users
- Monitor usage patterns and feedback
- Scale based on results
- Enable feature for all eligible users
- Document in API documentation
- Provide migration guides if needed
- Feature flag successfully propagates from SDK to pitcher-manager
- Pint sandbox type can be created and started via public APIs
- No performance degradation for existing sandbox types
- Feature can be enabled/disabled without code deployments
- All existing functionality remains intact
- Comprehensive test coverage (>90%) for new functionality
-
Risk: Breaking existing flows Mitigation: Extensive backward compatibility testing and gradual rollout
-
Risk: Performance degradation Mitigation: Load testing and performance monitoring
-
Risk: Feature flag propagation failures Mitigation: Comprehensive integration testing and error handling
-
Risk: Security vulnerabilities Mitigation: Security review of feature flag handling and validation