Skip to content

[Bug] Supervisor does not acknowledge newer sandbox policy revisions with an unchanged policy hash #2518

Description

@NaveCohenMonday

Agent Diagnostic

  • Skills loaded: create-github-issue repository workflow
  • OpenShell version tested: v0.0.91
  • Latest release checked: v0.0.92; the settings poll loop still gates policy status on runtime hash or middleware changes
  • Known fixes reviewed: fix(sandbox): acknowledge initial policy load and expose SDK labels #2159 / fix(sandbox): acknowledge initial policy revision; expose SDK labels/selectors #2170 fixed initial policy acknowledgement, but not later same-hash revisions
  • Possible duplicates reviewed: searched issues and pull requests for policy acknowledgement, same hash, pending revision, and supervisor polling terms; no matching report found
  • Findings: a newer sandbox-scoped revision can be consumed for provider/config changes while remaining permanently unacknowledged when its effective policy hash is unchanged
  • Remaining reason for filing: desired and current policy revisions can remain different even though the effective policy is already loaded

Description

Actual behavior: after the supervisor has loaded sandbox policy revision 1 with hash H, settings polling can return sandbox policy revision 2 with the same hash H. This occurs when revision metadata or provider attachment state changes without changing effective policy content. The supervisor may refresh provider/config state, but policy_runtime_changed is false, so it never enqueues PolicyStatusUpdate::loaded(2). The gateway continues to report desired revision 2 while current revision remains 1.

Expected behavior: a newer sandbox-scoped revision with the already-loaded effective policy hash should be acknowledged as loaded without rebuilding or reloading the identical OPA policy. If the revision also requires policy-runtime or middleware reconciliation, acknowledgement must wait for that reconciliation to succeed. Provider-environment refresh has its own revision/readiness lifecycle and should not incorrectly keep an already-loaded policy revision pending.

Reproduction Steps

  1. Start the supervisor with sandbox policy revision 1 and effective hash H.
  2. Persist sandbox policy revision 2 with the same effective hash H and a newer provider/config revision.
  3. Wake the settings poller (see related watcher issue [Bug] UpdateConfig does not wake sandbox watchers after an atomic policy revision commit #2517).
  4. Observe that provider/config reconciliation runs.
  5. Observe that no LOADED status is reported for policy revision 2 because policy_runtime_changed is false.
  6. Query policy status and observe desired revision 2, current revision 1, and non-converged state.

A focused unit test can construct a SettingsPollResult with source Sandbox, version 2, and the current hash, then assert that revision 2 is selected for acknowledgement while equal/older revisions, different hashes, local overrides, and non-reloading origins are rejected.

Environment

  • OS: Linux arm64
  • Runtime: Kubernetes sidecar topology
  • OpenShell: v0.0.91
  • Latest release checked: yes, v0.0.92
  • Possible duplicates checked: yes

Suspected Cause

The poll loop tracks current_policy_hash but not the version associated with that loaded hash. Its work gate and policy-status emission are driven by policy_runtime_changed, which intentionally remains false for identical hashes. Version convergence is therefore accidentally coupled to policy reload.

Proposed Fix

Track the currently acknowledged policy version separately from the loaded policy hash. When a polling result:

  • comes from PolicySource::Sandbox;
  • has a version newer than the acknowledged version;
  • has the same non-empty hash as the loaded runtime policy; and
  • belongs to a policy origin that reloads gateway policy,

select it for acknowledgement without an OPA reload. Enqueue PolicyStatusUpdate::loaded(version) after any required policy-runtime or middleware reconciliation succeeds, update the tracked version, and emit the normal configuration-state success event. Keep provider-environment refresh and its readiness reporting independent from policy acknowledgement.

Do not apply this shortcut to local policy overrides, different hashes, equal/older revisions, or failed reconciliation.

Logs

loaded policy: version=1 hash=H
polled policy: version=2 hash=H source=Sandbox
provider environment: refreshed
reported loaded policy version: 1

Activity

  1. added
    area:policyPolicy engine and policy lifecycle work
    and removed
    state:triage-neededOpened without agent diagnostics and needs triage
    on Jul 28, 2026
  2. added theissue type on Jul 28, 2026
  3. drew commented on Jul 28, 2026

    @drew
    Collaborator

    📋 triage-agent

    Triage Assessment

    Classification: bug-confirmed

    Summary

    This is a confirmed policy-lifecycle defect, not user error. A newer sandbox-scoped revision can legitimately retain the loaded effective-policy hash, but the supervisor couples LOADED status emission to runtime policy changes, leaving the newer revision pending indefinitely.

    Investigation

    The current supervisor poll loop tracks current_policy_hash but not the last acknowledged sandbox policy version. gateway_policy_runtime_needs_reconciliation is intentionally false when the hash and middleware registry are unchanged, and the early work gate then skips the poll result. Even when middleware reconciliation causes the loop to proceed, PolicyStatusUpdate::loaded(result.version) remains nested under if policy_changed.

    This matches the current code in crates/openshell-sandbox/src/lib.rs and the acknowledgement branch at lines 2821–2912. Existing gateway coverage also demonstrates that a provenance-only update can create a newer revision with unchanged policy content/hash, so this state is valid server behavior.

    Searches found no duplicate. #2159 / #2170 fixed initial-load acknowledgement only; #2517 is complementary watcher behavior. The defect remains after v0.0.92 in the inspected main revision.

    Recommendation

    Track the acknowledged sandbox policy version separately from the loaded runtime hash. Acknowledge a newer same-hash sandbox revision only for gateway-reloadable policy origins and only after any required policy-runtime or middleware reconciliation succeeds. Seed and update that tracker on initial acknowledgement and successful hash-changing loads; exclude global policy, local overrides, empty hashes, and equal/older versions. Add focused supervisor coverage for same-hash newer revisions and preserve ordered status reporting.

    Recommended labels: area:supervisor, area:policy. Remove state:triage-needed and assign issue type Bug. Do not apply state:agent-ready; that remains a human decision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:policyPolicy engine and policy lifecycle workarea:supervisorProxy and routing-path work

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions