core: add community policy engine seam

This commit is contained in:
github-ops
2026-05-14 17:14:29 +00:00
parent 1f2ef70813
commit c6db8d9c99
3 changed files with 145 additions and 0 deletions
+1
View File
@@ -144,5 +144,6 @@ Progress:
- done:
- Phase 0 / task `0.1`: создан `crank-core::ext` module skeleton
- Phase 0 / task `0.2`: `MachineCredentialVerifier` и связанные типы вынесены из `apps/mcp-server` в public seam `crank_core::ext::auth`
- Phase 0 / task `0.5`: введены `PolicyEngine`, `SessionActor`, `PolicyAction`, `PolicyScope`, `PolicyDecision` и default `OwnerOnlyPolicyEngine` в public seam `crank_core::ext::access`
- Phase 0 / task `0.7`: введены `CapabilityProfile` и `CommunityCapabilityProfile`, а `admin-api` capability payload теперь собирается через public seam вместо локального literal
- backward-compatible alias `CommunityMachineCredentialVerifier` сохранен, поведение Community не изменено
+141
View File
@@ -1 +1,142 @@
use crate::{MembershipRole, UserId, WorkspaceId};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SessionActor {
pub user_id: UserId,
pub workspace_id: WorkspaceId,
pub role: MembershipRole,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[allow(clippy::enum_variant_names)]
pub enum PolicyAction {
ReadWorkspace,
WriteWorkspace,
ReadWorkspaceAccess,
WriteWorkspaceAccess,
ReadOperation,
WriteOperation,
ReadAgent,
WriteAgent,
ReadPlatformApiKey,
WritePlatformApiKey,
ReadSecret,
WriteSecret,
ReadAuthProfile,
WriteAuthProfile,
ReadObservability,
ReadCapability,
}
impl PolicyAction {
pub fn is_read(self) -> bool {
matches!(
self,
Self::ReadWorkspace
| Self::ReadWorkspaceAccess
| Self::ReadOperation
| Self::ReadAgent
| Self::ReadPlatformApiKey
| Self::ReadSecret
| Self::ReadAuthProfile
| Self::ReadObservability
| Self::ReadCapability
)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PolicyScope {
Workspace(WorkspaceId),
Global,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PolicyDecision {
Allow,
Deny { reason: &'static str },
}
pub trait PolicyEngine: Send + Sync {
fn check(
&self,
actor: &SessionActor,
action: PolicyAction,
scope: PolicyScope,
) -> PolicyDecision;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct OwnerOnlyPolicyEngine;
impl PolicyEngine for OwnerOnlyPolicyEngine {
fn check(
&self,
actor: &SessionActor,
action: PolicyAction,
_scope: PolicyScope,
) -> PolicyDecision {
if matches!(actor.role, MembershipRole::Owner) || action.is_read() {
PolicyDecision::Allow
} else {
PolicyDecision::Deny {
reason: "write actions require owner role in community",
}
}
}
}
#[cfg(test)]
mod tests {
use super::{
OwnerOnlyPolicyEngine, PolicyAction, PolicyDecision, PolicyEngine, PolicyScope,
SessionActor,
};
use crate::{MembershipRole, UserId, WorkspaceId};
fn actor(role: MembershipRole) -> SessionActor {
SessionActor {
user_id: UserId::new("user_01"),
workspace_id: WorkspaceId::new("ws_01"),
role,
}
}
#[test]
fn owner_is_allowed_for_write_actions() {
let decision = OwnerOnlyPolicyEngine.check(
&actor(MembershipRole::Owner),
PolicyAction::WriteWorkspace,
PolicyScope::Workspace(WorkspaceId::new("ws_01")),
);
assert_eq!(decision, PolicyDecision::Allow);
}
#[test]
fn non_owner_is_allowed_for_read_actions() {
let decision = OwnerOnlyPolicyEngine.check(
&actor(MembershipRole::Viewer),
PolicyAction::ReadWorkspace,
PolicyScope::Workspace(WorkspaceId::new("ws_01")),
);
assert_eq!(decision, PolicyDecision::Allow);
}
#[test]
fn non_owner_is_denied_for_write_actions() {
let decision = OwnerOnlyPolicyEngine.check(
&actor(MembershipRole::Admin),
PolicyAction::WriteWorkspace,
PolicyScope::Workspace(WorkspaceId::new("ws_01")),
);
assert_eq!(
decision,
PolicyDecision::Deny {
reason: "write actions require owner role in community",
}
);
}
}
+3
View File
@@ -32,6 +32,9 @@ pub use cache::{
pub use edition::{
EditionCapabilities, EditionLimits, MachineAccessMode, OperationSecurityLevel, ProductEdition,
};
pub use ext::access::{
OwnerOnlyPolicyEngine, PolicyAction, PolicyDecision, PolicyEngine, PolicyScope, SessionActor,
};
pub use ext::auth::{
MachineCredentialVerifier, MachineCredentialVerifierError, SharedMachineCredentialVerifier,
VerifiedMachineCredential,