Skip to content

Commit bb95992

Browse files
fix: remove trailing whitespace in agent-os (#739)
ruff --fix --unsafe-fixes for W293 across 7 files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e97a9b4 commit bb95992

File tree

17 files changed

+1663
-95
lines changed

17 files changed

+1663
-95
lines changed

packages/agent-marketplace/src/agent_marketplace/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,17 @@
2020
ComplianceResult,
2121
MCPServerPolicy,
2222
MarketplacePolicy,
23+
OrgMarketplacePolicy,
2324
evaluate_plugin_compliance,
2425
load_marketplace_policy,
2526
)
27+
from agent_marketplace.quality_scoring import (
28+
PluginQualityProfile,
29+
QualityBadge,
30+
QualityDimension,
31+
QualityScore,
32+
QualityStore,
33+
)
2634
from agent_marketplace.registry import PluginRegistry
2735
from agent_marketplace.schema_adapters import (
2836
ClaudePluginManifest,
@@ -53,13 +61,19 @@
5361
"MCPServerPolicy",
5462
"MarketplaceError",
5563
"MarketplacePolicy",
64+
"OrgMarketplacePolicy",
5665
"PluginInstaller",
5766
"PluginManifest",
67+
"PluginQualityProfile",
5868
"PluginRegistry",
5969
"PluginSigner",
6070
"PluginTrustConfig",
6171
"PluginTrustStore",
6272
"PluginType",
73+
"QualityBadge",
74+
"QualityDimension",
75+
"QualityScore",
76+
"QualityStore",
6377
"TRUST_TIERS",
6478
"adapt_to_canonical",
6579
"compute_initial_score",

packages/agent-marketplace/src/agent_marketplace/manifest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ class PluginManifest(BaseModel):
6262
None, description="Minimum AgentMesh version required"
6363
)
6464
signature: Optional[str] = Field(None, description="Base64-encoded Ed25519 signature")
65+
organization: Optional[str] = Field(
66+
None,
67+
description="Owning organization (None = global/shared plugin)",
68+
)
6569

6670
@field_validator("name")
6771
@classmethod

packages/agent-marketplace/src/agent_marketplace/marketplace_policy.py

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
Defines marketplace-level policies for MCP server allowlist/blocklist
77
enforcement, plugin type restrictions, and signature requirements.
88
Operators can declare which MCP servers are permitted for plugins.
9+
Supports organization-scoped policy overrides (Issues #733, #737).
910
"""
1011

1112
from __future__ import annotations
1213

1314
import logging
15+
from dataclasses import dataclass, field
1416
from pathlib import Path
1517
from typing import Optional
1618

@@ -58,6 +60,16 @@ def validate_mode(cls, v: str) -> str:
5860
return v
5961

6062

63+
@dataclass
64+
class OrgMarketplacePolicy:
65+
"""Organization-scoped marketplace policy that inherits from enterprise base."""
66+
67+
organization: str
68+
additional_allowed_plugin_types: list[str] = field(default_factory=list)
69+
additional_blocked_plugins: list[str] = field(default_factory=list)
70+
mcp_server_overrides: MCPServerPolicy | None = None
71+
72+
6173
class MarketplacePolicy(BaseModel):
6274
"""Top-level marketplace policy controlling plugin admission."""
6375

@@ -73,6 +85,73 @@ class MarketplacePolicy(BaseModel):
7385
False,
7486
description="Require Ed25519 signatures on all plugins",
7587
)
88+
org_policies: dict[str, OrgMarketplacePolicy] = Field(
89+
default_factory=dict,
90+
description="Organization-scoped policy overrides keyed by org name",
91+
)
92+
org_mcp_policies: dict[str, MCPServerPolicy] = Field(
93+
default_factory=dict,
94+
description="Per-organization MCP server policy overrides",
95+
)
96+
97+
def get_effective_policy(self, organization: str | None = None) -> MarketplacePolicy:
98+
"""Resolve effective policy for an organization (base + org overrides).
99+
100+
When *organization* is ``None`` or the org has no overrides, the base
101+
enterprise policy is returned unchanged. Otherwise the base policy is
102+
merged with the org-specific additions.
103+
"""
104+
if organization is None or organization not in self.org_policies:
105+
return self
106+
107+
org = self.org_policies[organization]
108+
109+
# Merge allowed plugin types: base + org additions
110+
merged_types = self.allowed_plugin_types
111+
if merged_types is not None and org.additional_allowed_plugin_types:
112+
merged_types = list(set(merged_types + org.additional_allowed_plugin_types))
113+
114+
# Merge MCP server policy if org has overrides
115+
merged_mcp = org.mcp_server_overrides if org.mcp_server_overrides else self.mcp_servers
116+
117+
return MarketplacePolicy(
118+
mcp_servers=merged_mcp,
119+
allowed_plugin_types=merged_types,
120+
require_signature=self.require_signature,
121+
)
122+
123+
def get_effective_mcp_policy(self, organization: str | None = None) -> MCPServerPolicy:
124+
"""Get effective MCP server policy for an organization.
125+
126+
Org policies inherit from the base (enterprise) MCP policy.
127+
Org can add to allowlist but cannot remove base restrictions.
128+
"""
129+
base = self.mcp_servers
130+
if organization is None or organization not in self.org_mcp_policies:
131+
return base
132+
org = self.org_mcp_policies[organization]
133+
# Merge: org inherits base restrictions, can add more allowed servers
134+
if base.mode == "blocklist":
135+
# Org cannot un-block servers blocked at enterprise level
136+
merged_blocked = list(set(base.blocked + org.blocked))
137+
merged_allowed = [s for s in org.allowed if s not in base.blocked]
138+
return MCPServerPolicy(
139+
mode=base.mode,
140+
blocked=merged_blocked,
141+
allowed=merged_allowed,
142+
require_declaration=base.require_declaration or org.require_declaration,
143+
)
144+
else: # allowlist
145+
# Org can only allow servers already in the enterprise allowlist
146+
merged_allowed = (
147+
[s for s in org.allowed if s in base.allowed] if base.allowed else org.allowed
148+
)
149+
return MCPServerPolicy(
150+
mode=base.mode,
151+
allowed=merged_allowed or base.allowed,
152+
blocked=list(set(base.blocked + org.blocked)),
153+
require_declaration=base.require_declaration or org.require_declaration,
154+
)
76155

77156

78157
class ComplianceResult(BaseModel):
@@ -125,6 +204,7 @@ def evaluate_plugin_compliance(
125204
manifest: PluginManifest,
126205
policy: MarketplacePolicy,
127206
mcp_servers: list[str] | None = None,
207+
organization: str | None = None,
128208
) -> ComplianceResult:
129209
"""Check whether a plugin manifest complies with a marketplace policy.
130210
@@ -134,6 +214,9 @@ def evaluate_plugin_compliance(
134214
mcp_servers: Optional list of MCP server names declared by the plugin.
135215
When ``None``, MCP declaration checks that require a server list
136216
will flag a violation if ``require_declaration`` is enabled.
217+
organization: Optional organization name. When provided the
218+
effective MCP policy is resolved via
219+
:meth:`MarketplacePolicy.get_effective_mcp_policy`.
137220
138221
Returns:
139222
A :class:`ComplianceResult` indicating compliance status and any
@@ -155,8 +238,8 @@ def evaluate_plugin_compliance(
155238
f"(allowed: {', '.join(policy.allowed_plugin_types)})"
156239
)
157240

158-
# -- MCP server policy ----------------------------------------------------
159-
mcp_policy = policy.mcp_servers
241+
# -- MCP server policy (org-aware) ----------------------------------------
242+
mcp_policy = policy.get_effective_mcp_policy(organization)
160243

161244
if mcp_policy.require_declaration and mcp_servers is None:
162245
violations.append(
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
"""Plugin quality scoring model — separate from trust/compliance scoring.
4+
5+
Trust tiers answer 'is this plugin safe?'
6+
Quality scoring answers 'is this plugin good?'
7+
"""
8+
9+
from __future__ import annotations
10+
11+
from dataclasses import dataclass, field
12+
from enum import Enum
13+
14+
15+
class QualityDimension(str, Enum):
16+
"""Dimensions along which plugin quality is assessed."""
17+
18+
DOCUMENTATION = "documentation"
19+
TEST_COVERAGE = "test_coverage"
20+
OUTPUT_ACCURACY = "output_accuracy"
21+
RELIABILITY = "reliability"
22+
PERFORMANCE = "performance"
23+
USER_SATISFACTION = "user_satisfaction"
24+
25+
26+
class QualityBadge(str, Enum):
27+
"""Badge tiers awarded based on overall quality score."""
28+
29+
UNRATED = "unrated"
30+
BRONZE = "bronze"
31+
SILVER = "silver"
32+
GOLD = "gold"
33+
PLATINUM = "platinum"
34+
35+
36+
@dataclass
37+
class QualityScore:
38+
"""Quality assessment for a single dimension."""
39+
40+
dimension: QualityDimension
41+
score: float # 0.0 to 1.0
42+
evidence: str = ""
43+
assessed_at: str = ""
44+
45+
46+
@dataclass
47+
class PluginQualityProfile:
48+
"""Aggregated quality profile for a plugin."""
49+
50+
plugin_name: str
51+
plugin_version: str
52+
scores: list[QualityScore] = field(default_factory=list)
53+
54+
@property
55+
def overall_score(self) -> float:
56+
"""Return the mean score across all assessed dimensions."""
57+
if not self.scores:
58+
return 0.0
59+
return sum(s.score for s in self.scores) / len(self.scores)
60+
61+
@property
62+
def badge(self) -> QualityBadge:
63+
"""Derive a badge from the overall score."""
64+
score = self.overall_score
65+
if score >= 0.9:
66+
return QualityBadge.PLATINUM
67+
elif score >= 0.75:
68+
return QualityBadge.GOLD
69+
elif score >= 0.6:
70+
return QualityBadge.SILVER
71+
elif score >= 0.4:
72+
return QualityBadge.BRONZE
73+
return QualityBadge.UNRATED
74+
75+
def get_score(self, dimension: QualityDimension) -> float | None:
76+
"""Return the score for a specific dimension, or ``None`` if unrated."""
77+
for s in self.scores:
78+
if s.dimension == dimension:
79+
return s.score
80+
return None
81+
82+
83+
@dataclass
84+
class QualityStore:
85+
"""In-memory store for plugin quality profiles."""
86+
87+
_profiles: dict[str, PluginQualityProfile] = field(default_factory=dict)
88+
89+
def _key(self, name: str, version: str) -> str:
90+
return f"{name}@{version}"
91+
92+
def record_score(
93+
self,
94+
plugin_name: str,
95+
plugin_version: str,
96+
score: QualityScore,
97+
) -> None:
98+
"""Record (or update) a quality score for a plugin dimension."""
99+
key = self._key(plugin_name, plugin_version)
100+
if key not in self._profiles:
101+
self._profiles[key] = PluginQualityProfile(
102+
plugin_name=plugin_name, plugin_version=plugin_version
103+
)
104+
profile = self._profiles[key]
105+
# Update existing dimension or add new
106+
profile.scores = [s for s in profile.scores if s.dimension != score.dimension]
107+
profile.scores.append(score)
108+
109+
def get_profile(
110+
self, plugin_name: str, plugin_version: str
111+
) -> PluginQualityProfile | None:
112+
"""Retrieve the quality profile for a specific plugin version."""
113+
return self._profiles.get(self._key(plugin_name, plugin_version))
114+
115+
def get_badge(self, plugin_name: str, plugin_version: str) -> QualityBadge:
116+
"""Return the quality badge for a plugin, defaulting to UNRATED."""
117+
profile = self.get_profile(plugin_name, plugin_version)
118+
return profile.badge if profile else QualityBadge.UNRATED

packages/agent-marketplace/src/agent_marketplace/registry.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from agent_marketplace.manifest import MarketplaceError, PluginManifest, PluginType
1818
from agent_marketplace.marketplace_policy import (
1919
MarketplacePolicy,
20+
OrgMarketplacePolicy,
2021
evaluate_plugin_compliance,
2122
)
2223

@@ -57,20 +58,23 @@ def register(
5758
self,
5859
manifest: PluginManifest,
5960
mcp_servers: list[str] | None = None,
61+
organization: str | None = None,
6062
) -> None:
6163
"""Register a plugin manifest.
6264
6365
Args:
6466
manifest: The manifest to register.
6567
mcp_servers: Optional list of MCP server names declared by the plugin.
68+
organization: Optional organization context for org-scoped policy checks.
6669
6770
Raises:
6871
MarketplaceError: If the exact name+version already exists or the
6972
plugin does not comply with the marketplace policy.
7073
"""
7174
if self._marketplace_policy is not None:
7275
result = evaluate_plugin_compliance(
73-
manifest, self._marketplace_policy, mcp_servers
76+
manifest, self._marketplace_policy, mcp_servers,
77+
organization=organization,
7478
)
7579
if not result.compliant:
7680
raise MarketplaceError(
@@ -173,6 +177,26 @@ def list_plugins(self, type_filter: Optional[PluginType] = None) -> list[PluginM
173177
results.append(latest)
174178
return results
175179

180+
def list_for_organization(self, organization: str) -> list[PluginManifest]:
181+
"""List plugins visible to an organization (org-specific + global).
182+
183+
A plugin is visible to an organization when its ``organization``
184+
field is ``None`` (global) or matches the given *organization*.
185+
186+
Args:
187+
organization: Organization name to filter by.
188+
189+
Returns:
190+
List of matching manifests (all versions).
191+
"""
192+
results: list[PluginManifest] = []
193+
for name in self._plugins:
194+
for version in self._plugins[name]:
195+
manifest = self._plugins[name][version]
196+
if manifest.organization is None or manifest.organization == organization:
197+
results.append(manifest)
198+
return results
199+
176200
# ------------------------------------------------------------------
177201
# Persistence
178202
# ------------------------------------------------------------------

0 commit comments

Comments
 (0)