pub struct PolicyStore { /* private fields */ }Expand description
Handle bound to a Repository + path prefix. Mirrors the
agentstategraph-tasks TaskStore pattern.
Implementations§
Source§impl PolicyStore
impl PolicyStore
pub fn new( repo: Arc<Repository>, prefix: impl Into<String>, agent_id: impl Into<String>, ) -> Self
Sourcepub fn with_verifier(self, verifier: Arc<dyn SignatureVerifier>) -> Self
pub fn with_verifier(self, verifier: Arc<dyn SignatureVerifier>) -> Self
Install a signature verifier. Policies whose signature fails
verification are treated as not-currently-active by
Self::active and therefore ignored by evaluate /
evaluate_change. Pass-through if never called.
Sourcepub fn with_require_signed(self, require_signed: bool) -> Self
pub fn with_require_signed(self, require_signed: bool) -> Self
When true (and a verifier is registered) unsigned policies
are filtered out of Self::active. Defaults to false.
No-op when no verifier is registered — unsigned policies stay live in pass-through mode.
Sourcepub fn with_external_evaluators(
self,
registry: Arc<ExternalEvaluatorRegistry>,
) -> Self
pub fn with_external_evaluators( self, registry: Arc<ExternalEvaluatorRegistry>, ) -> Self
Install an external-evaluator registry (0.7.5 §4a). Policies
whose external_evaluator.kind matches a registered runner are
dispatched through it inside evaluate_scoped /
evaluate_change_scoped; policies with an unregistered kind
are silently skipped (treated as not-matching). Pass-through
when never called — every policy goes through the local
evaluator.
pub fn prefix(&self) -> &str
pub fn agent_id(&self) -> &str
Sourcepub fn propose(
&self,
ref_name: &str,
policy: Policy,
) -> Result<String, PolicyError>
pub fn propose( &self, ref_name: &str, policy: Policy, ) -> Result<String, PolicyError>
Write a proposed policy (not yet ratified). Fails if an entry already lives at the same path — supersede instead.
The path, version, proposed_by, and proposed_at fields
are normalized/overridden on write: callers pass a Policy with
path set to the intended logical path (slashes, with or without
leading /), and the store writes version = 1, proposed_by = agent_id, proposed_at = now, ratified_by = None.
Sourcepub fn ratify(
&self,
ref_name: &str,
path: &str,
ratifier: &str,
reasoning: &str,
) -> Result<(), PolicyError>
pub fn ratify( &self, ref_name: &str, path: &str, ratifier: &str, reasoning: &str, ) -> Result<(), PolicyError>
Ratify an unratified proposal at path. Fails if the policy
does not exist, is already ratified, or the ratifier / reasoning
are empty.
Sourcepub fn supersede(
&self,
ref_name: &str,
path: &str,
new_policy: Policy,
) -> Result<String, PolicyError>
pub fn supersede( &self, ref_name: &str, path: &str, new_policy: Policy, ) -> Result<String, PolicyError>
Replace the active policy at path with new_policy. The old
active version is moved to history/<version> and the new one
is written at the active path with version = old + 1 and
supersedes = "<path>@<old_version>".
The new policy is written as already ratified iff its
ratified_by is Some(_) — callers that want propose-then-
ratify semantics on a supersede can leave it None and call
ratify separately.
Sourcepub fn set_signature(
&self,
ref_name: &str,
path: &str,
signature: PolicySignature,
) -> Result<(), PolicyError>
pub fn set_signature( &self, ref_name: &str, path: &str, signature: PolicySignature, ) -> Result<(), PolicyError>
Attach (or replace) a signature on the active policy at path.
Called by the MCP policy_sign tool (§2c of the 0.7.5 plan) after
the registered PolicySigner has produced the signature bytes
over the canonical form. The existing active policy is loaded,
policy.signature is overwritten, and the result is written back
under IntentCategory::PolicySign so the commit log stays filterable.
This is the only writer that may mutate Policy::signature
post-ratification without bumping version — signatures are
detached metadata over the canonical body and the canonical body
excludes signature itself.
Sourcepub fn get(
&self,
ref_name: &str,
path: &str,
version: Option<u64>,
) -> Result<Policy, PolicyError>
pub fn get( &self, ref_name: &str, path: &str, version: Option<u64>, ) -> Result<Policy, PolicyError>
Fetch the active (ratified or proposed) policy at path, or a
pinned historical version when version is Some(_).
Sourcepub fn list(
&self,
ref_name: &str,
prefix_filter: Option<&str>,
) -> Result<Vec<Policy>, PolicyError>
pub fn list( &self, ref_name: &str, prefix_filter: Option<&str>, ) -> Result<Vec<Policy>, PolicyError>
List every policy (active versions only) whose path starts with
prefix_filter (path form without leading slash). None lists
all policies under the store prefix.
Sourcepub fn list_scoped(
&self,
ref_name: &str,
prefix_filter: Option<&str>,
tenant_filter: Option<&str>,
) -> Result<Vec<Policy>, PolicyError>
pub fn list_scoped( &self, ref_name: &str, prefix_filter: Option<&str>, tenant_filter: Option<&str>, ) -> Result<Vec<Policy>, PolicyError>
Tenant-scoped variant of Self::list (0.7.5 §3b).
tenant_filter semantics:
None→ no filter; every policy (globals + all tenants) is visible. This is the back-compat path used bySelf::list.Some(tid)→ only policies withtenant_id == Some(tid)ortenant_id == None(globals always apply) are returned.
Sourcepub fn active(
&self,
ref_name: &str,
prefix_filter: Option<&str>,
) -> Result<Vec<Policy>, PolicyError>
pub fn active( &self, ref_name: &str, prefix_filter: Option<&str>, ) -> Result<Vec<Policy>, PolicyError>
List currently-active policies: ratified AND active_from <= now
AND not expired.
A ratified policy with a future active_from is skipped the
same way an unratified proposal is — it’s scheduled but not
yet live. When a signature verifier has been installed via
Self::with_verifier, policies whose signature fails
verification — or whose signature is missing while
require_signed is true — are also filtered out with the
same not-currently-active semantics. No verifier → pass
through (back-compat).
Sourcepub fn active_scoped(
&self,
ref_name: &str,
prefix_filter: Option<&str>,
tenant_filter: Option<&str>,
) -> Result<Vec<Policy>, PolicyError>
pub fn active_scoped( &self, ref_name: &str, prefix_filter: Option<&str>, tenant_filter: Option<&str>, ) -> Result<Vec<Policy>, PolicyError>
Tenant-scoped variant of Self::active (0.7.5 §3b).
Applies the tenant filter after the signature filter so
signature-rejected policies are never surfaced regardless of
the tenant. tenant_filter semantics match Self::list_scoped:
None is no filter; Some(tid) keeps policies with
tenant_id == Some(tid) OR tenant_id == None (globals always
apply). policies_for_situation_scoped, evaluate_scoped, and
evaluate_change_scoped all route through this method, so they
inherit the filter for free.
Sourcepub fn history(
&self,
ref_name: &str,
path: &str,
) -> Result<Vec<Policy>, PolicyError>
pub fn history( &self, ref_name: &str, path: &str, ) -> Result<Vec<Policy>, PolicyError>
Walk the supersedes chain. Returned oldest-first (version 1 → current).
Sourcepub fn policies_for_situation(
&self,
ref_name: &str,
situation: &Situation,
) -> Result<Vec<Policy>, PolicyError>
pub fn policies_for_situation( &self, ref_name: &str, situation: &Situation, ) -> Result<Vec<Policy>, PolicyError>
Every ratified policy whose situation_selector matches situation.
Sourcepub fn policies_for_situation_scoped(
&self,
ref_name: &str,
situation: &Situation,
tenant_filter: Option<&str>,
) -> Result<Vec<Policy>, PolicyError>
pub fn policies_for_situation_scoped( &self, ref_name: &str, situation: &Situation, tenant_filter: Option<&str>, ) -> Result<Vec<Policy>, PolicyError>
Tenant-scoped variant of Self::policies_for_situation
(0.7.5 §3b). Delegates to Self::active_scoped so the tenant
filter is inherited.
Sourcepub fn evaluate(
&self,
ref_name: &str,
situation: &Situation,
action: &str,
agent_id: &str,
) -> Result<Decision, PolicyError>
pub fn evaluate( &self, ref_name: &str, situation: &Situation, action: &str, agent_id: &str, ) -> Result<Decision, PolicyError>
Authorization evaluation — POLICY_V1.md §5.
Sourcepub fn evaluate_scoped(
&self,
ref_name: &str,
situation: &Situation,
action: &str,
agent_id: &str,
tenant_filter: Option<&str>,
) -> Result<Decision, PolicyError>
pub fn evaluate_scoped( &self, ref_name: &str, situation: &Situation, action: &str, agent_id: &str, tenant_filter: Option<&str>, ) -> Result<Decision, PolicyError>
Tenant-scoped variant of Self::evaluate (0.7.5 §3b). Routes
through Self::policies_for_situation_scoped so only policies
with tenant_id == Some(tid) or tenant_id == None contribute
to the decision.
External-evaluator dispatch (0.7.5 §4a): matched policies carrying
an external_evaluator whose kind is registered in the store’s
registry are routed to the external runner; their decisions are
combined with the local evaluator’s verdict under the existing
deny > require_approval > allow precedence. Policies whose
external kind is not registered are skipped.
Sourcepub fn evaluate_change(
&self,
ref_name: &str,
proposal: &ChangeProposal,
) -> Result<Decision, PolicyError>
pub fn evaluate_change( &self, ref_name: &str, proposal: &ChangeProposal, ) -> Result<Decision, PolicyError>
Change-proposal evaluation — POLICY_V1.md §22.2.
Sourcepub fn evaluate_change_scoped(
&self,
ref_name: &str,
proposal: &ChangeProposal,
tenant_filter: Option<&str>,
) -> Result<Decision, PolicyError>
pub fn evaluate_change_scoped( &self, ref_name: &str, proposal: &ChangeProposal, tenant_filter: Option<&str>, ) -> Result<Decision, PolicyError>
Tenant-scoped variant of Self::evaluate_change (0.7.5 §3b).
Routes through Self::active_scoped.
External-evaluator dispatch (0.7.5 §4a): matched policies carrying
an external_evaluator whose kind is registered in the store’s
registry are routed to the external runner for their portion of
the decision; their decisions are combined with the local
change-evaluator’s verdict under deny > require_approval > allow
precedence. Policies whose external kind is not registered are
skipped. External runners see proposal.action / proposal.agent_id
and an empty Situation — the change-proposal flow does not
carry a situation context of its own.