Skip to content

feat(policy): pluggable PolicyProvider subsystem with mutation gate#1668

Closed
dvavili wants to merge 2 commits into
NVIDIA:mainfrom
dvavili:dvavili/policy-provider-subsystem
Closed

feat(policy): pluggable PolicyProvider subsystem with mutation gate#1668
dvavili wants to merge 2 commits into
NVIDIA:mainfrom
dvavili:dvavili/policy-provider-subsystem

Conversation

@dvavili
Copy link
Copy Markdown

@dvavili dvavili commented Jun 1, 2026

Summary

Promote policy delivery and mutation acceptance from inline Store calls in the gRPC layer to a PolicyProvider trait. Lets an alternate provider refuse policy mutations while still serving an authoritative effective policy at admission time.

  • PolicyProvider trait: id(), get_effective_policy(), permits_mutation(), and three mutators (set/update/delete_policy) — mutators have default Unsupported impls.
  • LocalPolicyProvider wraps the existing Store; current behavior unchanged.
  • [openshell.policy] type = "local" | "attested" config selector, following the ProviderPlugin-style type convention. attested is reserved (returns a clear startup error until its impl lands in a follow-up).
  • permits_mutation() is the coarse gate threaded through every mutation entry point in grpc/policy.rs: the three RPC mutators plus the seven chunk-approval handlers introduced by feat(policy): add agentic approval loop #1528 (submit_policy_analysis, approve/reject/approve_all/edit/undo/clear_draft_chunks). A refusing provider yields Status::unimplemented without touching the store.
  • PolicyError::Unsupported maps to tonic::Status::unimplemented at the gRPC edge — the openshell policy {set,update,delete} rejection falls out of the trait default.

Standalone-useful, and the seam an attested-policy follow-up plugs into.

Test plan

  • cargo test -p openshell-server --lib -- --test-threads=1 — 734 passing
  • New: trait-default Unsupported, LocalPolicyProvider semantics, refusing-provider integration through every gated handler with before/after store-snapshot checks
  • No CLI surface change; openshell policy {set,update,delete} continues to work via LocalPolicyProvider

dvavili added 2 commits June 1, 2026 11:17
Promote policy delivery to a pluggable subsystem in the gateway. The
gRPC handlers route sandbox-scoped set, merge-ops, and global delete
through a registered PolicyProvider; the existing in-tree store is
exposed as LocalPolicyProvider, preserving today's behavior.

- PolicyProvider trait: id, get_effective_policy, plus three mutators
  (set_policy, update_policy, delete_policy) with default impls
  returning PolicyError::Unsupported { policy_type, operation }.
- LocalPolicyProvider wraps Store. set_policy fully delegates;
  update_policy and delete_policy gate the operation through the
  trait while the handler retains the merge-with-retry loop and the
  global-settings-map mutation respectively (asymmetry documented in
  the trait module doc).
- [openshell.policy] type = "local" | "attested" added to the config
  file, aligning with the existing `type`-selector convention used by
  ProviderPlugin. "attested" is accepted as a known-but-not-yet-
  available value (clear startup error rather than panic or unknown-
  type error).
- PolicyError::Unsupported maps to tonic::Status::unimplemented, so
  any provider that does not override the three mutators (e.g. the
  forthcoming AttestedPolicyProvider) refuses set/update/delete
  without per-handler conditionals.

Covered by 12 new unit tests (policy_provider trait defaults + Local
flows over a fresh sqlite store) and 4 new handler-level integration
tests (Local set succeeds; refusing provider yields unimplemented on
set, merge-ops, and global delete).
The agentic approval loop introduced in NVIDIA#1528 added seven chunk-mutation
RPCs (submit_policy_analysis, approve/reject/approve_all/edit/undo/clear
_draft_chunk) that mutate policy state through the draft-chunk store
without going through the three set/update/delete mutators. The
PolicyProvider trait gated only the latter, leaving the chunk surface
as a bypass for any read-only driver.

Add `permits_mutation()` to the trait with a default `Unsupported`
return; override in LocalPolicyProvider to `Ok(())`. Thread the gate
through every mutation entry point in grpc/policy.rs as the first
post-authz action, so a refusing provider fails fast without
touching the store:

- handle_submit_policy_analysis
- handle_approve_draft_chunk
- handle_reject_draft_chunk
- handle_approve_all_draft_chunks
- handle_edit_draft_chunk
- handle_undo_draft_chunk
- handle_clear_draft_chunks
- handle_update_config sandbox-set, sandbox-merge, global-delete arms
- handle_update_config global-set inline arm (separate from the
  per-op trait path)

The per-op set_policy/update_policy/delete_policy provider calls
remain in place as the natural seam for a future driver that wants
to override one mutation without refusing all of them; permits_mutation
is the coarse gate that runs first.

Tests: 7 new chunk-handler refusing_provider integration tests (each
asserts the draft-chunk store is unchanged after the refused call),
plus permits_mutation_is_ok_for_local_provider and
stub_provider_permits_mutation_returns_unsupported. 3 pre-existing
refusing_provider tests updated to expect `mutation` instead of the
per-op operation name.

handle_report_policy_status is intentionally not gated — it is the
sandbox-side load-result pong, not a control-plane mutation; gating
would break legitimate sandbox status reports under attested mode.
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented Jun 1, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Thank you for your interest in contributing to OpenShell, @dvavili.

This project uses a vouch system for first-time contributors. Before submitting a pull request, you need to be vouched by a maintainer.

To get vouched:

  1. Open a Vouch Request discussion.
  2. Describe what you want to change and why.
  3. Write in your own words — do not have an AI generate the request.
  4. A maintainer will comment /vouch if approved.
  5. Once vouched, open a new PR (preferred) or reopen this one after a few minutes.

See CONTRIBUTING.md for details.

@github-actions github-actions Bot closed this Jun 1, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Thank you for your submission! We ask that you sign our Developer Certificate of Origin before we can accept your contribution. You can sign the DCO by adding a comment below using this text:


I have read the DCO document and I hereby sign the DCO.


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the DCO Assistant Lite bot.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant