Skip to content

feat(gateway/sandbox): support arbitrary gateway-to-sandbox runtime settings over SandboxConfig #405

Closed

Description

@johntmyers

Problem Statement

Issue #393 currently combines two concerns:

  1. OCSF logging integration in openshell-sandbox (the primary deliverable)
  2. A broader gateway -> sandbox operational settings channel via SandboxConfig

The OCSF work only requires a narrow config shape (config_revision + logging.ocsf_enabled) to support hot-reload. Expanding #393 to define and implement a general arbitrary-settings channel increases implementation risk, review scope, and delivery time for the logging migration.

We need a dedicated follow-up issue for designing and implementing a robust gateway -> sandbox arbitrary settings mechanism on top of the existing policy poll response channel.

Proposed Design

Implement a generalized settings channel in the SandboxConfig transport path, separate from #393:

  • Keep transport path: GetSandboxPolicyResponse.sandbox_config
  • Keep revision semantics: gateway maintains a monotonic config_revision; sandbox applies only newer revisions
  • Add a dedicated settings payload for arbitrary runtime settings, using a namespaced key/value model with typed values
  • Define sandbox-side apply semantics:
    • unknown keys are ignored safely
    • invalid values are rejected with structured log output
    • partial apply does not roll back unrelated valid keys
  • Keep backward compatibility:
    • older gateway (no arbitrary settings payload) remains no-op
    • older sandbox ignores unknown payload fields from newer gateway

Delivery should include:

  • Proto schema update for arbitrary settings payload
  • Gateway config source plumbing and response population
  • Sandbox apply loop integration and revision tracking
  • Unit tests for backward compatibility and apply semantics
  • E2E test covering runtime update without sandbox restart
  • Documentation update in architecture/ and user-facing docs where relevant

Alternatives Considered

  1. Keep arbitrary-settings work inside feat(sandbox): integrate openshell-ocsf crate with dual-file output, OCSF toggle config, and log site migration #393

    • Rejected because it couples a large schema/control-plane change to the OCSF migration, increasing risk and delaying primary logging outcomes.
  2. Add only more typed fields immediately (no generic settings payload)

    • Rejected because each new runtime setting would require another proto/control-plane roundtrip and repeated plumbing work.
  3. Send untyped JSON blobs only

    • Rejected because it weakens validation, compatibility guarantees, and observability of invalid config.

Agent Investigation

  • Reviewed #393 and confirmed it already introduces SandboxConfig on GetSandboxPolicyResponse with revision-driven hot-reload.
  • Confirmed #393 currently frames SandboxConfig as a general-purpose operational config channel while also carrying the OCSF migration and 93-site logging refactor.
  • This split keeps #393 focused on OCSF deliverables and moves generalized settings-channel design into an independent issue.

Activity

  1. johntmyers commented on Mar 17, 2026

    @johntmyers
    CollaboratorAuthor

    🏗️ build-plan

    Implementation Plan

    Issue type: feat
    Complexity: High
    Confidence: Medium-High — core design decisions are now fixed; remaining work is implementation detail and migration mechanics.

    Review Decisions (Locked 2026-03-17)

    • Global policy semantics: when a global policy is set, all sandboxes consume that policy in its entirety (no merge with sandbox policy).
    • CLI direction: extend existing workflows with --global (avoid separate scope syntax).
    • Migration strategy: perform a direct in-place refactor (no long-lived dual API path) if implementation remains non-user-facing and low-risk.

    Summary

    Refactor sandbox policy polling into a broader settings channel so the sandbox pulls a single settings envelope (policy + keyed settings) from the gateway. Add durable global gateway settings with per-key precedence over sandbox settings, enforce mutual exclusivity for a key at gateway vs sandbox scope, and expose CLI commands for get/set/delete flows with one-setting-per-request semantics. Global policy is treated as a reserved setting and, when present globally, fully overrides sandbox-level policy.

    State Machine (Settings Resolution)

    flowchart TD
        A[Sandbox check-in poll] --> B[Load gateway global settings singleton]
        B --> C[Load sandbox-scoped settings object]
        C --> D[Per-key resolve: global key wins]
        D --> E[Resolve policy: global policy key present?]
        E -->|yes| F[Use global policy payload entirely]
        E -->|no| G[Use sandbox policy payload]
        F --> H[Compose effective response: policy + settings]
        G --> H
        H --> I[Return settings response]
    
        J[CLI: set gateway setting key K] --> K[Prompt: this disables sandbox-level K]
        K --> L[Persist global K + bump global revision]
        L --> M[Sandboxes receive K from global on next poll]
    
        N[CLI: set sandbox setting key K] --> O{Is global K set?}
        O -->|yes| P[Reject with FailedPrecondition]
        O -->|no| Q[Persist sandbox K + bump sandbox revision]
    
        R[CLI: delete gateway setting key K] --> S[Remove global K + bump global revision]
        S --> T[Sandbox-level K becomes effective again]
    
    Loading

    Scope

    • proto/sandbox.proto: Replace policy-only poll payload with settings envelope carrying effective policy + effective settings metadata.
    • proto/openshell.proto: Add/extend setting management RPCs for gateway/global and sandbox scopes (one setting per request).
    • crates/openshell-server/src/grpc.rs: Implement effective settings resolution, global-precedence enforcement, and per-key lock error semantics.
    • crates/openshell-server/src/persistence/mod.rs + crates/openshell-server/src/persistence/{sqlite,postgres}.rs: Add durable models/helpers for global singleton settings and sandbox-scoped settings.
    • crates/openshell-server/src/sandbox/mod.rs (or new settings module): Define settings domain model, merge logic, reserved policy key handling, and revision tracking.
    • crates/openshell-sandbox/src/grpc_client.rs: Refactor polling client to consume settings envelope.
    • crates/openshell-sandbox/src/lib.rs: Replace policy-only poll/apply loop with settings poll/apply loop while preserving LKG behavior for policy failures.
    • crates/openshell-cli/src/main.rs + crates/openshell-cli/src/run.rs: Extend commands with --global for global operations and enforce one-setting-per-request UX.
    • e2e/python/test_sandbox_policy.py (and/or new e2e file): Add end-to-end coverage for precedence, lock behavior, and global-policy propagation.
    • architecture/ + docs/: Update architecture and CLI documentation.

    Implementation Steps

    1. Define settings wire model.

      • Add typed SettingValue envelope and reserved policy key payload.
      • Include key source metadata (GLOBAL or SANDBOX) in effective responses.
      • Keep one-setting-per-mutation requests and deterministic success/failure responses.
    2. Add durable global + sandbox settings persistence.

      • Persist one global singleton settings object (e.g., object name global).
      • Persist sandbox-scoped settings objects keyed by sandbox ID.
      • Track revisions by scope and compute an effective revision for sandbox polling.
    3. Direct in-place poll channel refactor.

      • Convert the existing sandbox poll channel from policy-only to settings envelope semantics in one cutover.
      • Update server and sandbox client/loop together so no user-facing workflow changes.
    4. Implement precedence, locking, and unlock rules.

      • Global key present => sandbox mutation for that key returns FailedPrecondition.
      • Global delete of key unlocks sandbox control for that key.
      • Apply this uniformly to arbitrary settings and the reserved policy key.
    5. Implement global policy-as-setting semantics.

      • Global policy key present => all sandboxes use that global policy payload entirely.
      • Sandbox policy writes rejected while global policy key is present.
      • Deleting global policy key restores sandbox policy management.
      • Reuse existing policy validation/safety checks before persistence.
    6. CLI UX with --global.

      • Extend policy commands with --global (example: openshell policy set --global --policy <file>).
      • Add/extend settings commands using the same --global pattern for key/value operations.
      • Prompt on global set: "This disables sandbox-level management for . Continue?".
      • Add non-interactive bypass (--yes) for automation.
    7. Validation and verification.

      • Server unit/integration tests for precedence, lock rejection, delete-unlock, reserved policy key behavior.
      • CLI parsing and prompt behavior tests for --global and --yes.
      • Sandbox tests for settings poll/apply and policy reload safety.
      • E2E tests for full lifecycle with global takeover and release.

    Test Plan

    • Unit tests:
      • Merge algorithm: global overrides sandbox per key.
      • Conflict guard: sandbox write rejected when global key exists.
      • Delete unlock: removing global key restores sandbox key effectiveness.
      • Reserved policy key: validation and serialization/deserialization.
    • Integration tests:
      • gRPC set/get/delete across both scopes with one-setting-at-a-time success/failure semantics.
      • Policy status behavior remains correct when policy is delivered via settings envelope.
    • E2E tests:
      • Create sandbox with sandbox-level key, then set same key globally; verify effective key source is global and sandbox set returns error.
      • Delete global key and verify sandbox-level value is effective again.
      • Set global policy and verify all active sandboxes consume the global policy on poll.

    Risks & Open Questions

    • Typed setting value encoding (oneof primitives vs structured payload) and long-term extensibility.
    • Exact command naming for non-policy key/value operations (settings vs extending another existing namespace) while preserving --global UX.

    Documentation Impact

    • architecture/: settings control-plane model, precedence rules, and poll-state machine.
    • docs/: --global usage for policy/settings, lockout behavior, and unlock via global delete.

    Revision 2 — locked global policy replacement semantics, --global CLI direction, and direct in-place refactor strategy from stakeholder feedback
    Revision 1 — initial plan based on expanded UX and scope split from #393

  2. self-assigned this
    on Mar 17, 2026
  3. added a commit that references this issue on Mar 17, 2026
    ca8ea6b
  4. added 3 commits that reference this issue on Mar 18, 2026
    0f93e18
    3f6aabf
    6435850
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions