Skip to content

feat(sandbox): validate configuration before workload activation - #3259

Open
johntmyers wants to merge 2 commits into
mainfrom
feat/3145-policy-activation/johntmyers
Open

feat(sandbox): validate configuration before workload activation#3259
johntmyers wants to merge 2 commits into
mainfrom
feat/3145-policy-activation/johntmyers

Conversation

@johntmyers

@johntmyers johntmyers commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

🏗️ build-from-issue-agent

Summary

An image policy can be valid alone but become invalid after credentialed-provider composition. Keep the workload stopped until the supervisor validates and accepts the exact effective policy and matching provider environment. Expose a repairable configuration error instead of starting the workload and discovering the problem on its first connection.

Related Issue

Closes #3145

Changes

  • Add fenced, generation-specific admission reports and readiness conditions that survive compute-driver observations. Preserve rejected desired configuration for management and allow full policy replacement before first acceptance.
  • Reconcile startup until a valid configuration is available, capture its provider environment, and defer live polling until the main process starts. Stage live policy/provider changes together and invalidate old policy guards before publishing credentials.
  • Carry workload-image policy discovery through authenticated sidecar bootstrap. Deliver accepted policy and child environment together, with ordered live updates.
  • Expose admission diagnostics through CLI JSON/table output and the Go SDK; regenerate protocol bindings and update mock services.
  • Reconcile main's public/durable schema inventory and verify an old stored sandbox decodes without fabricating admission acceptance.
  • Document the activation gate, repair workflow, restrictive no-policy behavior, live failure posture, and contributor/operator guidance.

Scope and compatibility

Gateway-managed supervisors require the admission contract; missing admission from older gateways is fail-closed. Explicit user/global policy precedence remains unchanged. Image/host setup failures such as unresolved OCI users retain their existing startup error behavior. Sidecar disconnects retain existing fail-closed behavior and do not add transparent independent restart recovery.

Testing

  • mise run pre-commit
  • mise run ci (full Rust, Python, TypeScript, and Go checks/tests)
  • OPENSHELL_E2E_DOCKER_TEST=policy_activation mise run e2e:docker: conformance smoke and activation/repair/restart scenario passed
  • OPENSHELL_E2E_KUBE_EXTRA_VALUES=deploy/helm/openshell/ci/values-sidecar.yaml OPENSHELL_E2E_KUBERNETES_FEATURES='' mise run e2e:kubernetes: sidecar conformance smoke passed

Full CI used a command-local tag.gpgSign=false override because the workstation's global signing setting interferes with temporary Git fixture repositories. The existing SBOM timing test passed in the completed run; no SBOM code was changed.

For Kubernetes, imported the base image explicitly with containerd's --platform linux/arm64 before sandbox creation: k3d's default multi-platform archive import reported a missing digest. The completed smoke run required no pod intervention. The dev/CI sidecar overlay uses its existing processBinaryAwareNetworkPolicy: false setting.

E2E results are attested against implementation commit 43095b382e62f489a50849b0c043599bb0b86006. After merging main 0357daee, regenerated the conflicting Go bindings, reconciled the new schema inventory, added a legacy-storage fixture, and reran full CI successfully. E2E lanes were not rerun after that merge.

Added coverage includes rejected image/provider composition and atomic repair, stale supervisor/generation reports, readiness across restart, pending/repaired sidecar bootstrap, provider-policy publication, CLI/Go admission output, and a Docker workload-marker scenario proving no launch before repair followed by successful activation and restart.

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture, user documentation, and related skills updated

Closes #3145

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
@johntmyers

Copy link
Copy Markdown
Collaborator Author

🏗️ build-from-issue-agent

E2E Test Attestation

Local E2E checks passed for commit 43095b382e62f489a50849b0c043599bb0b86006. The Docker run used the matching runtime worktree before committing; the Kubernetes run built the committed revision (0.0.117-dev.106+g43095b382).

Command Mode Result
OPENSHELL_E2E_DOCKER_TEST=policy_activation mise run e2e:docker Docker combined supervisor Conformance smoke passed; activation test: 1 passed, 0 failed, 0 ignored
OPENSHELL_E2E_KUBE_EXTRA_VALUES=deploy/helm/openshell/ci/values-sidecar.yaml OPENSHELL_E2E_KUBERNETES_FEATURES='' mise run e2e:kubernetes Kubernetes sidecar Conformance smoke passed

Tests executed

  • Docker smoke: status JSON, status, sandbox create, get-ready, list-visible, exec, delete, list-empty — all passed.
  • Docker policy_activation::invalid_image_provider_bundle_waits_for_repair_before_launch — passed. Checks the same image policy alone, rejected credentialed-provider composition, absent workload marker while blocked, no supervisor restart, full replacement repair and one launch, invalid live replacement rejection, and stop/start.
  • Kubernetes sidecar smoke: status JSON, status, sandbox create, get-ready, list-visible, exec, delete, list-empty — all passed.

No selected scenarios were skipped. The Kubernetes command intentionally selects standalone conformance only, with no additional Rust E2E features. Its existing dev/CI overlay disables binary-aware network policy; this does not attest the strict cross-container /proc identity path.

The first Kubernetes attempt was blocked by k3d's multi-platform image import missing a digest. Before the successful fresh run, imported the cached base image into containerd explicitly with --platform linux/arm64. No pod intervention was needed in the successful run. Both ephemeral gateways/clusters were cleaned up.

Full mise run ci and mise run pre-commit also passed. Local E2E results are recorded here because these lanes are not part of the regular branch CI checks.

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

@johntmyers johntmyers added the gator:in-review Gator is reviewing or awaiting PR review feedback label Sep 10, 2026

@johntmyers johntmyers left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

PR Review Status

The accepted issue and updated architecture, Fern docs, skills, and tests make this cross-cutting admission change project-valid. The initial review found two blocking startup-lifecycle correctness issues.

Action required: address GATOR-bde03eff-01 and GATOR-bde03eff-02, then push an updated head for a focused follow-up review.

Blocking findings:

  • GATOR-bde03eff-01: non-repairable startup and sidecar failures can wait forever.
  • GATOR-bde03eff-02: restart admission can reopen static-policy replacement after a workload has already activated.

Carried findings:

  • None
Gator metadata
  • Validation: Implements accepted issue #3145 with explicit architecture and user documentation.
  • Docs: Fern docs updated for admission status and repair workflow.
  • Checks: Current-head branch, Helm, Trivy, and DCO gates are green; required E2E dispatch is deferred while blocking review findings remain.
  • E2E: test:e2e required for sandbox lifecycle, gateway/supervisor, policy, and provider-flow changes; not yet applied.
  • Head SHA: bde03eff0530e92a1aaa43f50df4c8a64dfb0474
  • Base SHA: 0357daee316f32a4d5c312174d68672cb0f4d389
  • Merge base SHA: 0357daee316f32a4d5c312174d68672cb0f4d389
  • Patch ID: 7d3bc99d684bba94391baaf2eb70dc73e59b1103
  • Gator payload: 8
  • Review mode: initial
  • Previous reviewed SHA: none
  • Review budget exhausted: no
  • Maintainer decision required: no
  • Next state: gator:in-review

let registration_snapshot = loop {
match gateway.snapshot(id).await {
Ok(snapshot) => break snapshot,
Err(_) => tokio::time::sleep(Duration::from_secs(2)).await,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

Warning — GATOR-bde03eff-01 · Non-repairable startup failures wait forever

Summary: An operator with an incompatible or unauthorized gateway, a fenced supervisor, or a missing process sidecar can leave a sandbox in Provisioning forever. These new loops discard all gateway errors, while sidecar discovery has no timeout, so failures that policy/provider repair cannot fix neither surface an actionable error nor let the supervisor restart.

Fix: Retry only classified transient failures with bounded backoff; return non-transient or exhausted failures, terminate rejected registrations, and bound sidecar discovery with the existing ready timeout. Keep indefinite waiting only after the gateway explicitly reports a repairable configuration rejection.

Verify: Make StartupGateway::snapshot return permission-denied or not-found and assert startup returns that error; start a pending sidecar server without a client and assert it fails after the ready timeout.

Agent context
  • Location: crates/openshell-sandbox/src/lib.rs:2581
  • Sibling sites: registration at lines 2584–2598, later snapshots at 2602–2605, and sidecar discovery at 174–180.

admission.state
!= i32::from(openshell_core::proto::ConfigurationAdmissionState::Accepted)
});
let should_backfill_policy = if startup_blocked && !sandbox_caller {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

Warning — GATOR-bde03eff-02 · Restart reopens immutable static policy

Summary: An authorized operator can stop and restart a sandbox that already ran, hold restart admission in Pending or Rejected, and replace filesystem, Landlock, or process fields. Restart resets admission to Pending, but this predicate interprets any non-Accepted state as proof that no child has consumed static restrictions, breaking the documented immutable-after-activation contract.

Fix: Gate static replacement on durable evidence that the sandbox has never activated, not the current admission state. Use the retained process-instance identity only if its lifecycle guarantees this distinction; otherwise persist an explicit first-activation marker. Apply the predicate to every projection path.

Verify: Activate, stop, and restart a sandbox into Pending/Rejected admission, then submit a policy with changed static fields; the update must still be rejected.

Agent context
  • Location: crates/openshell-server/src/grpc/policy.rs:3778
  • Sibling sites: admission reset in compute/mod.rs:1487 and projection paths in policy.rs:2324 and policy_store.rs:64.

@johntmyers johntmyers added gator:blocked Gator is blocked by process or repository gates gator:in-review Gator is reviewing or awaiting PR review feedback and removed gator:in-review Gator is reviewing or awaiting PR review feedback gator:blocked Gator is blocked by process or repository gates labels Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gator:blocked Gator is blocked by process or repository gates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(sandbox): validate effective policy before workload activation

1 participant