fix(sandbox): acknowledge initial policy revision; expose SDK labels/selectors#2170
Conversation
|
All contributors have signed the DCO ✍️ ✅ |
b33c83f to
1819886
Compare
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
cb3efa5 to
eacda4d
Compare
Local validation (macOS 26 / Apple Silicon, Docker Desktop)Docker E2E regression — branch-built gateway + supervisor. The harness The test creates a sandbox from the network-only Unit / lint (macOS):
Downstream integration (AI-Q). Concurrent per-job sandboxes each received a Not covered here: Linux |
|
Root cause: Fix: Report the exact loaded revision once after |
The supervisor loaded and enforced a sandbox-scoped policy but never told the gateway which revision it loaded. The policy poll loop seeded itself with the initial revision's hash on its first poll, so `policy_changed` was never true for that revision and `ReportPolicyStatus(LOADED)` — which only ran in the hot-reload branch — was never called. The revision stayed `Pending` and `current_policy_version` stayed 0 even though the sandbox was `Ready` and the policy was effective. This was most visible with sparse policies that get baseline-enriched into a new revision during startup. After the OPA engine is constructed, report the exact sandbox revision the supervisor loaded as LOADED, and seed the poll loop from that revision so it is not re-reported. Report FAILED with the original construction error if engine construction or conversion fails. Only sandbox-sourced revisions (version > 0) whose canonical content matches the loaded policy are acknowledged; global and local-file policies are untouched. Delivery uses the shared bounded retry, is non-fatal on transient failure, and a pending initial acknowledgement is delivered before any newer revision so policy history is never reordered. Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
The gateway protobuf and CLI already support request-level sandbox labels (`CreateSandboxRequest.name`/`labels`) and selector-based listing (`ListSandboxesRequest.label_selector`), but the public Python SDK dropped them, so Python-created sandboxes could not be found via `openshell sandbox list --selector ...`. Add optional, source-compatible `name`/`labels` to `SandboxClient.create`, `create_session`, and the high-level `Sandbox`, and `label_selector` to `list`/`list_ids`. `SandboxRef` now carries the gateway labels as an immutable mapping (default empty, so `SandboxRef(id, name, status)` still works). Caller-provided label mappings are copied. Attaching the high-level `Sandbox` to an existing sandbox rejects `name`/`labels` since creation metadata cannot change on attach. Template labels remain a separate concept. No protobuf changes are required. Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Excluding the new immutable `labels` field from SandboxRef equality/hash (`compare=False`) preserves the original (id, name, status) identity and keeps the frozen dataclass hashable — a MappingProxyType field would otherwise make `hash(SandboxRef(...))` raise. Also defensively copy caller-provided labels in the high-level `Sandbox` so later caller mutation cannot change what is sent. Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
The poll loop retried a pending initial acknowledgement before processing any newer revision, but retried unconditionally forever. A permanently undeliverable ack (e.g. the revision was superseded before it could be reported) would then stall all later policy hot-reloads and provider-env refreshes. Cap the retries; after the bound, give up and resume normal polling so the loop cannot livelock on a stuck acknowledgement. Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Regression for NVIDIA#2159: create a sandbox with the network-only policy-advisor fixture, which the supervisor enriches with baseline filesystem paths during startup (creating revision 2, superseding revision 1). Assert the effective policy reaches revision 2 and no revision remains Pending once the supervisor acknowledges the load. Adds SandboxGuard::create_keep_with_args to create a kept sandbox with an initial --policy. Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
eacda4d to
e7c3790
Compare
|
recheck |
|
@KyleZheng1284 please address the failing tests and DCO statement. you need to post the actual DCO text. |
|
I have read the DCO document and I hereby sign the DCO. |
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
johntmyers
left a comment
There was a problem hiding this comment.
Principal-engineer code review found three actionable issues. Each inline comment includes a concrete sequence showing how the problem would surface in practice. Tests and CI were not run as part of this code-only review.
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
|
Label |
Summary
Running into two lifecycle/API gaps that prevent consumers from
reliably managing policy-bound sandboxes.
First, a sandbox can reach
Readywith an effective policy while the exactinitial sandbox-scoped policy revision remains
Pendingandcurrent_policy_versionremains0. This occurs when startup enriches thepolicy before constructing the OPA engine: the policy is enforced successfully,
but the initial revision is never acknowledged through
ReportPolicyStatus. This PR reports that revision asLOADEDafter successfulconstruction, or
FAILEDwith the original error when construction fails.Second, the gateway already supports request-level sandbox labels and
selector-based listing, but the public Python SDK does not expose them. This PR
adds creation-time
nameandlabels, selector-basedlistandlist_ids,and immutable gateway labels on
SandboxRef.These changes let clients verify that the gateway’s declared policy state
matches the policy OpenShell actually enforces and reliably discover sandboxes
by workload metadata. They do not weaken attestation or introduce a new policy
state model; they make the existing state authoritative and accessible through
the Python SDK.
Related Issue
Closes #2159
Changes
fix(sandbox): acknowledge initial policy revision(+ retry-bound follow-up)OpaEngine::from_proto()succeeds, acknowledge the exact sandbox-scopedrevision the supervisor loaded (
ReportPolicyStatus(LOADED)), and seed the pollloop from that revision so it is not re-reported.
FAILEDwith the original construction error if engine construction orpolicy conversion fails.
PolicySource::Sandbox, version > 0) whosecanonical content matches the loaded policy are acknowledged; global and
local-file policies are untouched.
failure, and a pending initial acknowledgement is delivered before any newer
revision so policy history is never reordered. The pending retry is itself
bounded so a permanently-undeliverable ack cannot stall later hot-reloads.
Existing hot-reload behavior is unchanged.
feat(python): expose sandbox labels and selectors(+ hashability follow-up)name/labelsonSandboxClient.create,create_session, and thehigh-level
Sandbox;label_selectoronlist/list_ids.SandboxRefcarries gateway labels as an immutable mapping (default empty, soSandboxRef(id, name, status)still works); labels are excluded fromequality/hash to preserve the original identity and keep the frozen dataclass
hashable.
Sandboxto an existing sandbox rejectsname/labels.Caller label mappings are copied. Template labels remain a separate concept.
No protobuf, database, or configuration changes.
Testing
Validated locally on macOS 26 / Apple Silicon with Docker Desktop. Branch
Checks pass on Linux amd64 and arm64. Linux
hard_requirementacceptance hasnot been validated.
mise run test:python, 72 passed) — label/selectorforwarding, immutable-labels, attach-rejection, SandboxRef-hashability.
cargo test -p openshell-sandbox, 72 passed) for theinitial-ack candidate / structural-match helpers.
cargo clippy -p openshell-sandbox --all-targets— 0 warnings;cargo fmtand
ruff check/ruff format --checkclean.openshell/supervisor:dev):initial_sparse_policy_is_acknowledged_as_loaded— sparse policy enriches torevision 2 with no revision left
Pending. (See validation comment below.)Known limitations — remaining before merge
mise run e2e:rustsuite not gated here — the fix(sandbox): acknowledge initial policy load and expose SDK labels #2159 regression abovepasses in isolation; the full Docker suite has broader environment needs.
match (provider rule names stripped, version zeroed) rather than binding to
the exact canonical revision object. Wants review for provider-composed policies.
hard_requirementacceptance not run.Checklist
architecture/sandbox.md,docs/sandboxes/manage-sandboxes.mdx)