fix(workspace): remove import-time WORKSPACE_ID guard — lazy validation (mc#1180)

a2a_client.py raised RuntimeError at MODULE IMPORT if WORKSPACE_ID was
unset. This blocked smoke tests, type checkers, IDE autocompletion, and
any script that imports the module without the full runtime env
(e.g. hermes#20 publish-image import smoke).

Fix: move the guard out of module-level into _require_workspace_id(),
a lazy helper called at the first use site. Module imports are now
side-effect-free; first API call raises the same clear error if
WORKSPACE_ID is missing.

Affected call sites updated to use _require_workspace_id():
  - enrich_peer_metadata()
  - list_peers (3 locations)
  - get_workspace_info()

Test coverage: 4 new cases in TestLazyWorkspaceId
  - import succeeds without WORKSPACE_ID set
  - _require_workspace_id() raises when unset
  - _require_workspace_id() returns value when set
  - enrich_peer_metadata raises without WORKSPACE_ID

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 08:35:55 +00:00
parent eac4fda48a
commit 6e7ce2fc43
2 changed files with 101 additions and 9 deletions
+21 -9
View File
@@ -22,10 +22,22 @@ from platform_auth import auth_headers, self_source_headers
logger = logging.getLogger(__name__)
_WORKSPACE_ID_raw = os.environ.get("WORKSPACE_ID")
if not _WORKSPACE_ID_raw:
raise RuntimeError("WORKSPACE_ID environment variable is required but not set")
WORKSPACE_ID = _WORKSPACE_ID_raw
WORKSPACE_ID = os.environ.get("WORKSPACE_ID", "")
def _require_workspace_id() -> str:
"""Raise RuntimeError if WORKSPACE_ID is unset.
Call this at the start of any function that makes a platform API call
that requires a source workspace ID. The check is lazy so that:
1. ``import a2a_client`` succeeds without WORKSPACE_ID set (smoke
tests, type checkers, IDE autocompletion, module introspection).
2. Actual runtime usage (inside a workspace container) raises a clear
error at the first failing call rather than at import time.
"""
if not WORKSPACE_ID:
raise RuntimeError("WORKSPACE_ID environment variable is required but not set")
return WORKSPACE_ID
# Platform URL: always host.docker.internal inside containers. The platform API
# is only reachable via the Docker network mesh from inside a workspace
# container regardless of the runtime environment (Docker/host).
@@ -306,7 +318,7 @@ def enrich_peer_metadata(
# the same as a registry miss, which is the desired UX.
return record
src = (source_workspace_id or "").strip() or WORKSPACE_ID
src = (source_workspace_id or "").strip() or _require_workspace_id()
url = f"{PLATFORM_URL}/registry/discover/{canon}"
try:
with httpx.Client(timeout=2.0) as client:
@@ -427,7 +439,7 @@ async def discover_peer(target_id: str, source_workspace_id: str | None = None)
safe_id = _validate_peer_id(target_id)
if safe_id is None:
return None
src = (source_workspace_id or "").strip() or WORKSPACE_ID
src = (source_workspace_id or "").strip() or _require_workspace_id()
async with httpx.AsyncClient(timeout=10.0) as client:
try:
resp = await client.get(
@@ -551,7 +563,7 @@ async def send_a2a_message(peer_id: str, message: str, source_workspace_id: str
safe_id = _validate_peer_id(peer_id)
if safe_id is None:
return f"{_A2A_ERROR_PREFIX}invalid peer_id (expected UUID): {peer_id!r}"
src = (source_workspace_id or "").strip() or WORKSPACE_ID
src = (source_workspace_id or "").strip() or _require_workspace_id()
target_url = f"{PLATFORM_URL}/workspaces/{safe_id}/a2a"
# Fix F (Cycle 5 / H2 — flagged 5 consecutive audits): timeout=None allowed
@@ -708,7 +720,7 @@ async def get_peers_with_diagnostic(source_workspace_id: str | None = None) -> t
The legacy get_peers() shim below preserves the bare-list contract for
non-tool callers.
"""
src = (source_workspace_id or "").strip() or WORKSPACE_ID
src = (source_workspace_id or "").strip() or _require_workspace_id()
url = f"{PLATFORM_URL}/registry/{src}/peers"
async with httpx.AsyncClient(timeout=10.0) as client:
try:
@@ -768,7 +780,7 @@ async def get_workspace_info(source_workspace_id: str | None = None) -> dict:
- 404 / other → workspace never existed (or transient)
- exception → network / auth failure
"""
src = source_workspace_id or WORKSPACE_ID
src = source_workspace_id or _require_workspace_id()
async with httpx.AsyncClient(timeout=10.0) as client:
try:
resp = await client.get(
+80
View File
@@ -1490,3 +1490,83 @@ class TestWaitForEnrichmentInFlight:
a2a_client._peer_metadata.clear()
a2a_client._peer_names.clear()
a2a_client._peer_in_flight_clear_for_testing()
# ---------------------------------------------------------------------------
# Lazy WORKSPACE_ID validation (KI fix: import-time guard removed)
# ---------------------------------------------------------------------------
class TestLazyWorkspaceId:
"""Regression: module import must NOT raise when WORKSPACE_ID is unset.
Before the fix, ``import a2a_client`` raised RuntimeError at module level
if WORKSPACE_ID was not set, blocking smoke tests, type checkers, IDE
autocompletion, and any script that imports the module without the full
runtime env. The guard was moved to lazy first-use so imports are
side-effect-free while first API call still fails fast with a clear error.
"""
def test_import_succeeds_without_workspace_id(self):
"""import a2a_client must not raise RuntimeError when WORKSPACE_ID is unset."""
import sys
# Simulate a subprocess-like environment: a fresh interpreter
# that has never imported this module and has no WORKSPACE_ID.
# We use importlib.util to load the module with WORKSPACE_ID removed.
env_backup = os.environ.pop("WORKSPACE_ID", None)
try:
# Remove any cached import so we get a fresh load.
mods_to_remove = [k for k in sys.modules if k.startswith("a2a_client")]
for mod in mods_to_remove:
del sys.modules[mod]
import a2a_client as ac
# Import must succeed; WORKSPACE_ID should be empty string.
assert ac.WORKSPACE_ID == ""
finally:
# Restore env so other tests are unaffected.
if env_backup is not None:
os.environ["WORKSPACE_ID"] = env_backup
# Re-import with original env restored.
import importlib
import a2a_client as _restored
importlib.reload(_restored)
def test_require_workspace_id_raises_without_it(self):
"""_require_workspace_id() must raise RuntimeError when WORKSPACE_ID is empty."""
import a2a_client
original = a2a_client.WORKSPACE_ID
a2a_client.WORKSPACE_ID = ""
try:
with pytest.raises(RuntimeError, match="WORKSPACE_ID"):
a2a_client._require_workspace_id()
finally:
a2a_client.WORKSPACE_ID = original
def test_require_workspace_id_returns_value_when_set(self):
"""_require_workspace_id() must return WORKSPACE_ID when it is set."""
import a2a_client
original = a2a_client.WORKSPACE_ID
a2a_client.WORKSPACE_ID = "test-workspace-123"
try:
result = a2a_client._require_workspace_id()
assert result == "test-workspace-123"
finally:
a2a_client.WORKSPACE_ID = original
def test_enrich_peer_metadata_raises_without_workspace_id(self):
"""enrich_peer_metadata must raise RuntimeError when WORKSPACE_ID is unset."""
import a2a_client
original = a2a_client.WORKSPACE_ID
a2a_client.WORKSPACE_ID = ""
# Must use a valid UUID so _validate_peer_id doesn't return None early.
try:
with pytest.raises(RuntimeError, match="WORKSPACE_ID"):
a2a_client.enrich_peer_metadata(
"00000000-0000-0000-0000-000000000001"
)
finally:
a2a_client.WORKSPACE_ID = original