From 860877c4d0c8dade40989e237e1dc1aae1451a17 Mon Sep 17 00:00:00 2001 From: James Wiesebron Date: Sat, 25 Apr 2026 14:00:06 -0700 Subject: [PATCH 1/2] Fix #2066: collapse duplicate AgentRole into single source of truth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shared/egg_restrictions/patterns.py defined its own AgentRole class that mirrored egg_contracts.agent_roles.AgentRole, with a comment asking readers to "keep values in sync" — enforced only by unit tests. PR #2061 surfaced the failure mode: it added REVIEWER_SECURITY and REVIEWER_CONCURRENCY to the canonical enum but not the gateway- side mirror, breaking CI. Replace the duplicate with a re-export of the canonical StrEnum so new roles propagate automatically. Trim the now-tautological cross- sync tests to a single identity assertion as a tripwire against any future re-introduction of a parallel enum. Co-Authored-By: Claude Opus 4.7 (1M context) --- shared/egg_restrictions/patterns.py | 33 +++------------- tests/gateway/test_agent_restrictions.py | 38 +++++-------------- .../shared/egg_contracts/test_agent_roles.py | 24 +++++------- 3 files changed, 25 insertions(+), 70 deletions(-) diff --git a/shared/egg_restrictions/patterns.py b/shared/egg_restrictions/patterns.py index ea94292d8e..ab404cc711 100644 --- a/shared/egg_restrictions/patterns.py +++ b/shared/egg_restrictions/patterns.py @@ -12,34 +12,13 @@ import posixpath from dataclasses import dataclass, field +# Re-export the canonical AgentRole StrEnum from egg_contracts so the +# gateway sees a single source of truth — adding a role in egg_contracts +# is now automatically visible here, removing the silent-drift failure +# mode from #2066. +from egg_contracts.agent_roles import AgentRole -class AgentRole: - """Agent role identifiers. - - Note: Mirrors egg_contracts.agent_roles.AgentRole to avoid import - complexity in the gateway module. Values must be kept in sync. - """ - - CODER = "coder" - TESTER = "tester" - DOCUMENTER = "documenter" - # Analysis roles - ARCHITECT = "architect" - TASK_PLANNER = "task_planner" - RISK_ANALYST = "risk_analyst" - REFINER = "refiner" - # Review roles - REVIEWER_CODE = "reviewer_code" - REVIEWER_CONTRACT = "reviewer_contract" - REVIEWER_AGENT_DESIGN = "reviewer_agent_design" - REVIEWER_REFINE = "reviewer_refine" - REVIEWER_PLAN = "reviewer_plan" - # Utility roles - AUTOFIXER = "autofixer" - CONFLICT_RESOLVER = "conflict_resolver" - # Interface roles - OVERSEER = "overseer" - INSPECTOR = "inspector" +__all__ = ["AGENT_PATTERNS", "AgentFilePattern", "AgentRole"] @dataclass diff --git a/tests/gateway/test_agent_restrictions.py b/tests/gateway/test_agent_restrictions.py index 58552fed21..781a263d31 100644 --- a/tests/gateway/test_agent_restrictions.py +++ b/tests/gateway/test_agent_restrictions.py @@ -25,39 +25,19 @@ class TestAgentRoleConsistency: - """Verify gateway AgentRole matches shared library AgentRole.""" + """Tripwire: gateway AgentRole must be the canonical egg_contracts enum. - def test_role_values_match_shared_library(self): - """Gateway AgentRole values must match egg_contracts.agent_roles.AgentRole. - - This test prevents subtle bugs from enum value drift between the two modules. - """ - from egg_contracts.agent_roles import AgentRole as SharedAgentRole + Issue #2066: replaced the per-package AgentRole duplicate with a + re-export so a new role added in egg_contracts is automatically + visible to the gateway. The identity assertion below catches anyone + re-introducing a parallel class or enum in egg_restrictions or the + gateway. + """ - # Verify all shared library roles exist in gateway with same values - for shared_role in SharedAgentRole: - assert hasattr(AgentRole, shared_role.name), ( - f"Gateway AgentRole missing role: {shared_role.name}" - ) - gateway_value = getattr(AgentRole, shared_role.name) - assert gateway_value == shared_role.value, ( - f"Role value mismatch for {shared_role.name}: " - f"gateway={gateway_value}, shared={shared_role.value}" - ) - - def test_all_gateway_roles_in_shared_library(self): - """All gateway roles must exist in the shared library.""" + def test_gateway_agent_role_is_canonical(self): from egg_contracts.agent_roles import AgentRole as SharedAgentRole - gateway_roles = { - AgentRole.CODER, - AgentRole.TESTER, - AgentRole.DOCUMENTER, - } - shared_values = {r.value for r in SharedAgentRole} - - for role in gateway_roles: - assert role in shared_values, f"Gateway role '{role}' not found in shared library" + assert AgentRole is SharedAgentRole class TestPathTraversalPrevention: diff --git a/tests/shared/egg_contracts/test_agent_roles.py b/tests/shared/egg_contracts/test_agent_roles.py index cfa30545c6..4cba43dffb 100644 --- a/tests/shared/egg_contracts/test_agent_roles.py +++ b/tests/shared/egg_contracts/test_agent_roles.py @@ -739,23 +739,19 @@ def test_all_canonical_roles_in_types(self): class TestRoleSyncWithGateway: - """Verify gateway/agent_restrictions.py AgentRole stays in sync.""" + """Tripwire: the gateway must use the canonical AgentRole enum. - def test_all_canonical_roles_in_gateway(self): - """Every canonical AgentRole should have a constant in gateway AgentRole.""" + Issue #2066: gateway/agent_restrictions.py and + egg_restrictions.patterns now re-export this enum rather than + redefining it, eliminating the silent-drift failure mode that + PR #2061 surfaced. The identity check fails if anyone re-adds + a parallel class or enum in either module. + """ + + def test_gateway_agent_role_is_canonical(self): from agent_restrictions import AgentRole as GatewayAgentRole - canonical_values = {r.value for r in AgentRole} - # Gateway AgentRole is a plain class with string constants - gateway_values = { - v - for k, v in vars(GatewayAgentRole).items() - if not k.startswith("_") and isinstance(v, str) - } - missing = canonical_values - gateway_values - assert not missing, ( - f"gateway/agent_restrictions.py AgentRole is missing constants: {missing}" - ) + assert GatewayAgentRole is AgentRole # --------------------------------------------------------------------------- From 101f39edae547e194d10791757a3593fc14c7165 Mon Sep 17 00:00:00 2001 From: "egg-reviewer[bot]" <261018737+egg-reviewer[bot]@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:32:07 +0000 Subject: [PATCH 2/2] Declare egg-contracts dep in egg-shared distribution egg_restrictions.patterns now imports AgentRole from egg_contracts. The egg-shared distribution didn't declare the dependency, so a standalone 'pip install egg-shared' would ImportError on first use of egg_restrictions.patterns. Monorepo install is unaffected (root pyproject.toml installs both packages together), so this is hygiene rather than a live regression. Addresses non-blocking observation #1 from PR #2082 review. --- shared/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/pyproject.toml b/shared/pyproject.toml index ad4ca6b3fc..57d360d075 100644 --- a/shared/pyproject.toml +++ b/shared/pyproject.toml @@ -3,7 +3,7 @@ name = "egg-shared" version = "0.1.0" description = "Shared modules for egg (enrichment, logging, notifications, etc.)" requires-python = ">=3.11" -dependencies = ["pyyaml>=6.0", "anthropic>=0.50,<1.0", "httpx>=0.25.0", "markdownify>=0.13.1"] +dependencies = ["pyyaml>=6.0", "anthropic>=0.50,<1.0", "httpx>=0.25.0", "markdownify>=0.13.1", "egg-contracts"] [build-system] requires = ["setuptools>=61.0"]