From 3fc2ea99cff34a6184e5fb60b15297376a5f334c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 29 Jun 2026 02:33:51 +0000 Subject: [PATCH 1/7] fix: escape JSON braces in format_record_json to prevent loguru format_map KeyError Loguru calls str.format_map() on the string returned by a format callable, which caused KeyError when the JSON output contained {-delimited keys. Escaping { and } before returning lets format_map pass through safely. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01RResBJyUf3NWWSY7VBCR7z --- netengine/logging/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netengine/logging/core.py b/netengine/logging/core.py index 1b830e3..c686686 100644 --- a/netengine/logging/core.py +++ b/netengine/logging/core.py @@ -130,7 +130,8 @@ def format_record_json(record: "Record") -> str: "parent_span_id": record["extra"].get("parent_span_id"), } - return json.dumps(log_entry) + "\n" + # Escape braces so loguru's format_map pass doesn't treat JSON keys as placeholders. + return json.dumps(log_entry).replace("{", "{{").replace("}", "}}") + "\n" # ============================================================================ From b063c2d40e0242dab223666793aa02b0d0e21efb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 29 Jun 2026 02:45:44 +0000 Subject: [PATCH 2/7] fix: resolve pre-existing CI failures across linting, type-checking, and tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - run_migrations.py: rewrite to remove three overlapping corrupt fragments; now a clean wrapper over netengine.db.migrations.run_migrations - type: ignore[import] → [import-untyped] in migration_service, db/migrations, core/migrations; remove now-unused ignore in docker_factory - diagnostic/preflight: add generic type args to _compose_config return - spec/loader: suppress attr-defined for private PydanticUndefined import; wrap != comparison in bool() to satisfy no-any-return - pki_cert_rotation_worker: use IssuedCertificateMetadata in callback type to match what state.issued_certificates actually yields - init_wizard: disable reverse_dns in business/datacenter AND profiles (unsupported in alpha) - test_api_auth: rewrite corrupt test file; add missing `patch` import and fix _Session mock to properly capture ssl kwarg and return introspection data; supply platform_client_secret in _phase4_state helper - test_dns_add_zone_record_callers: replace stale DockerHandler patch with docker_client assignment (phase_pki no longer imports DockerHandler directly) - test_orchestrator_m3 / test_e2e_bootstrap: set pki_output so _discard_completion_flags_without_outputs does not strip phase_completed["3"] - black formatting on all affected files Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01RResBJyUf3NWWSY7VBCR7z --- netengine/cli/init_wizard.py | 4 +- netengine/core/docker_factory.py | 2 +- netengine/core/migrations.py | 34 ++++--- netengine/core/state.py | 4 +- netengine/db/migrations.py | 4 +- netengine/diagnostic/preflight.py | 2 +- netengine/spec/loader.py | 4 +- netengine/utils/migration_service.py | 18 ++-- netengine/utils/run_migrations.py | 83 ---------------- netengine/workers/pki_cert_rotation_worker.py | 4 +- .../test_dns_add_zone_record_callers.py | 6 +- tests/integration/test_e2e_bootstrap.py | 1 + tests/integration/test_m8_operator_api.py | 5 +- tests/integration/test_orchestrator_m3.py | 1 + tests/test_api_auth.py | 95 +++++++++---------- tests/test_migration_queues.py | 2 +- 16 files changed, 95 insertions(+), 174 deletions(-) diff --git a/netengine/cli/init_wizard.py b/netengine/cli/init_wizard.py index d23f391..051b43d 100644 --- a/netengine/cli/init_wizard.py +++ b/netengine/cli/init_wizard.py @@ -414,7 +414,7 @@ def _core_host(host: int, subnet: str) -> str: "nat": False, "dynamic_ip": False, "inbound": "allowed", - "reverse_dns": True, + "reverse_dns": False, }, "residential": { "dhcp": True, @@ -428,7 +428,7 @@ def _core_host(host: int, subnet: str) -> str: "nat": False, "dynamic_ip": False, "inbound": "allowed", - "reverse_dns": True, + "reverse_dns": False, }, "airgapped": { "dhcp": True, diff --git a/netengine/core/docker_factory.py b/netengine/core/docker_factory.py index ed687d1..124560f 100644 --- a/netengine/core/docker_factory.py +++ b/netengine/core/docker_factory.py @@ -25,7 +25,7 @@ def create_docker_client() -> tuple[Optional[Any], bool]: try: from netengine.handlers.docker_handler import DockerHandler - client: Any = DockerHandler() # type: ignore[no-untyped-call] + client: Any = DockerHandler() return client, False except Exception as exc: logger.warning(f"Docker unavailable, falling back to mock mode: {exc}") diff --git a/netengine/core/migrations.py b/netengine/core/migrations.py index 36d4c2f..54f6866 100644 --- a/netengine/core/migrations.py +++ b/netengine/core/migrations.py @@ -70,12 +70,14 @@ def discover(self) -> list[MigrationFile]: version, _, rest = path.stem.partition("_") name = rest or path.stem checksum = hashlib.sha256(path.read_bytes()).hexdigest() - migrations.append(MigrationFile(version=version, name=name, path=path, checksum=checksum)) + migrations.append( + MigrationFile(version=version, name=name, path=path, checksum=checksum) + ) return migrations async def apply_pending(self) -> list[MigrationRecord]: """Apply pending migrations and return records applied in this invocation.""" - import asyncpg # type: ignore[import] + import asyncpg # type: ignore[import-untyped] conn = await asyncpg.connect(self.db_url) try: @@ -111,7 +113,7 @@ async def apply_pending(self) -> list[MigrationRecord]: await conn.close() async def status(self) -> MigrationStatus: - import asyncpg # type: ignore[import] + import asyncpg # type: ignore[import-untyped] conn = await asyncpg.connect(self.db_url) try: @@ -132,7 +134,9 @@ async def status(self) -> MigrationStatus: pgmq_available = await self._pgmq_available(conn) pgmq_installed = await self._pgmq_installed(conn) - missing_queues = await self._missing_queues(conn) if pgmq_installed else [q.value for q in Queue] + missing_queues = ( + await self._missing_queues(conn) if pgmq_installed else [q.value for q in Queue] + ) return MigrationStatus( applied=[record for record in records if record.status == "applied"], @@ -147,8 +151,7 @@ async def status(self) -> MigrationStatus: await conn.close() async def _ensure_table(self, conn: Any) -> None: - await conn.execute( - """ + await conn.execute(""" CREATE TABLE IF NOT EXISTS netengine_schema_migrations ( version text PRIMARY KEY, name text NOT NULL, @@ -157,17 +160,14 @@ async def _ensure_table(self, conn: Any) -> None: applied_at timestamptz, error text ) - """ - ) + """) async def _records(self, conn: Any) -> list[MigrationRecord]: - rows = await conn.fetch( - """ + rows = await conn.fetch(""" SELECT version, name, checksum, status, applied_at, error FROM netengine_schema_migrations ORDER BY version - """ - ) + """) return [ MigrationRecord( version=row["version"], @@ -203,10 +203,16 @@ async def _upsert_record( ) async def _pgmq_available(self, conn: Any) -> bool: - return bool(await conn.fetchval("SELECT EXISTS (SELECT 1 FROM pg_available_extensions WHERE name = 'pgmq')")) + return bool( + await conn.fetchval( + "SELECT EXISTS (SELECT 1 FROM pg_available_extensions WHERE name = 'pgmq')" + ) + ) async def _pgmq_installed(self, conn: Any) -> bool: - return bool(await conn.fetchval("SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgmq')")) + return bool( + await conn.fetchval("SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgmq')") + ) async def _missing_queues(self, conn: Any) -> list[str]: rows = await conn.fetch( diff --git a/netengine/core/state.py b/netengine/core/state.py index e210102..e892be1 100644 --- a/netengine/core/state.py +++ b/netengine/core/state.py @@ -312,7 +312,9 @@ def _discard_completion_flags_without_outputs(self) -> None: "9": (self.org_apps_output,), } for phase, outputs in phase_outputs.items(): - if self.phase_completed.get(phase) is True and any(output is None for output in outputs): + if self.phase_completed.get(phase) is True and any( + output is None for output in outputs + ): self.phase_completed.pop(phase, None) if self.pki_output is None and self.phase_completed.get("3") is True: diff --git a/netengine/db/migrations.py b/netengine/db/migrations.py index 1b0b3a7..a13a7d1 100644 --- a/netengine/db/migrations.py +++ b/netengine/db/migrations.py @@ -131,7 +131,7 @@ async def run_migrations( migrations_dir: Path | str = MIGRATIONS_DIR, ) -> MigrationRunResult: """Apply all pending SQL migrations and return structured outcomes.""" - import asyncpg # type: ignore[import] + import asyncpg # type: ignore[import-untyped] resolved_dir = Path(migrations_dir) migration_files = discover_migrations(resolved_dir) @@ -233,7 +233,7 @@ async def migration_status( migrations_dir: Path | str = MIGRATIONS_DIR, ) -> MigrationStatusReport: """Inspect migration state without applying pending migration files.""" - import asyncpg # type: ignore[import] + import asyncpg # type: ignore[import-untyped] resolved_dir = Path(migrations_dir) migration_files = discover_migrations(resolved_dir) diff --git a/netengine/diagnostic/preflight.py b/netengine/diagnostic/preflight.py index 751cf1e..1dbfabb 100644 --- a/netengine/diagnostic/preflight.py +++ b/netengine/diagnostic/preflight.py @@ -72,7 +72,7 @@ def _run(command: list[str], *, timeout: float = 8.0) -> subprocess.CompletedPro return subprocess.run(command, capture_output=True, text=True, timeout=timeout, check=False) -def _compose_config(project_root: Path | None = None) -> dict: +def _compose_config(project_root: Path | None = None) -> dict[str, object]: compose_file = (project_root or _repo_root()) / "docker-compose.yml" try: return yaml.safe_load(compose_file.read_text()) or {} diff --git a/netengine/spec/loader.py b/netengine/spec/loader.py index 5f10a2e..46f15d7 100644 --- a/netengine/spec/loader.py +++ b/netengine/spec/loader.py @@ -25,7 +25,7 @@ def _resolve_feature_state_paths(spec: NetEngineSpec) -> Iterator[tuple[Any, str """Yield feature-state entries with concrete paths, values, and defaults.""" from collections.abc import Mapping - from pydantic.fields import PydanticUndefined + from pydantic.fields import PydanticUndefined # type: ignore[attr-defined] from netengine.spec.feature_state import FEATURE_STATE_REGISTRY @@ -87,7 +87,7 @@ def _is_active_feature_value(value: Any, default_value: Any) -> bool: return comparable_value is True and comparable_value != comparable_default if comparable_value in (None, "", [], {}, (), set()): return False - return comparable_value != comparable_default + return bool(comparable_value != comparable_default) def _validate_feature_states(spec: NetEngineSpec) -> None: diff --git a/netengine/utils/migration_service.py b/netengine/utils/migration_service.py index 83d47e0..24d7d1f 100644 --- a/netengine/utils/migration_service.py +++ b/netengine/utils/migration_service.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any, Callable, Iterable if TYPE_CHECKING: - import asyncpg # type: ignore[import] + import asyncpg # type: ignore[import-untyped] MIGRATION_TABLE = "netengine_schema_migrations" @@ -146,7 +146,9 @@ def split_sql_statements(sql: str) -> list[str]: class MigrationService: - def __init__(self, db_url: str, migrations_dir: Path, logger: Callable[[str], None] | None = None): + def __init__( + self, db_url: str, migrations_dir: Path, logger: Callable[[str], None] | None = None + ): self.db_url = db_url self.migrations_dir = migrations_dir self.logger = logger or (lambda message: None) @@ -214,7 +216,9 @@ async def _apply_one(self, conn: "asyncpg.Connection", path: Path, checksum: str context = _statement_context(statement) await conn.execute(statement) else: - self.logger(f"Migration {path.name}: pending (non-transactional operations detected)") + self.logger( + f"Migration {path.name}: pending (non-transactional operations detected)" + ) for statement in statements: context = _statement_context(statement) await conn.execute(statement) @@ -245,8 +249,7 @@ async def _apply_one(self, conn: "asyncpg.Connection", path: Path, checksum: str raise MigrationApplyError(path.name, context, exc) from exc async def _ensure_table(self, conn: "asyncpg.Connection") -> None: - await conn.execute( - f""" + await conn.execute(f""" CREATE TABLE IF NOT EXISTS {MIGRATION_TABLE} ( filename TEXT PRIMARY KEY, checksum TEXT NOT NULL, @@ -254,8 +257,7 @@ async def _ensure_table(self, conn: "asyncpg.Connection") -> None: error TEXT, applied_at TIMESTAMPTZ NOT NULL DEFAULT now() ) - """ - ) + """) def _migration_files(self) -> Iterable[Path]: return sorted(self.migrations_dir.glob("*.sql")) @@ -268,7 +270,7 @@ def _statement_context(statement: str, max_length: int = 240) -> str: def _asyncpg() -> Any: try: - import asyncpg # type: ignore[import] + import asyncpg # type: ignore[import-untyped] except ModuleNotFoundError as exc: raise RuntimeError("asyncpg is required to run database migrations") from exc return asyncpg diff --git a/netengine/utils/run_migrations.py b/netengine/utils/run_migrations.py index 77d8852..a49985e 100644 --- a/netengine/utils/run_migrations.py +++ b/netengine/utils/run_migrations.py @@ -1,88 +1,5 @@ """Compatibility wrapper for applying NetEngine database migrations.""" -import os -from pathlib import Path -from urllib.parse import quote - -from netengine.utils.migration_service import MigrationService - - -def _db_url_from_environment() -> str: - db_url = os.environ.get("NETENGINE_DB_URL") - if db_url: - return db_url - - db_host = os.environ.get("SUPABASE_DB_HOST", "localhost") - db_port = os.environ.get("SUPABASE_DB_PORT", "5432") - db_user = os.environ.get("SUPABASE_DB_USER", "postgres") - db_password = os.environ.get("SUPABASE_DB_PASSWORD", "") - db_name = os.environ.get("SUPABASE_DB_NAME", "postgres") - auth = quote(db_user, safe="") - if db_password: - auth = f"{auth}:{quote(db_password, safe='')}" - return f"postgresql://{auth}@{db_host}:{db_port}/{db_name}" - - -async def apply_migrations() -> None: - """Apply SQL migrations to Postgres with explicit partial-failure semantics. - - Reads NETENGINE_DB_URL (e.g. postgresql://user:pass@host:5432/db). - Falls back to SUPABASE_DB_* variables for backward compatibility with cloud setups. - """ - migrations_dir = Path(__file__).parent.parent.parent / "migrations" - service = MigrationService(_db_url_from_environment(), migrations_dir, print) - await service.apply() - - -if __name__ == "__main__": - asyncio.run(apply_migrations()) - -from netengine.core.migrations import MigrationService, apply_migration_files - - -async def apply_migrations() -> None: - """Apply pending SQL migrations using the shared migration service. -import os -from pathlib import Path - -from netengine.utils.migrations import apply_migration_files - - -from __future__ import annotations - - Reads NETENGINE_DB_URL first, then DATABASE_URL for consistency with the - CLI's startup and migration commands. - """ - db_url = os.environ.get("NETENGINE_DB_URL") or os.environ.get("DATABASE_URL") - if not db_url: - raise RuntimeError("Database URL is required: set NETENGINE_DB_URL or DATABASE_URL.") - - migrations_dir = Path(__file__).parent.parent.parent / "migrations" - await MigrationService(db_url, migrations_dir).apply_pending() - import asyncpg # type: ignore[import] - - db_url = os.environ.get("NETENGINE_DB_URL") - migrations_dir = Path(__file__).parent.parent.parent / "migrations" - migration_files = sorted(migrations_dir.glob("*.sql")) - if not migration_files: - raise FileNotFoundError(f"No migration files found in: {migrations_dir}") - - if db_url: - conn = await asyncpg.connect(db_url) - else: - parsed_port = int(os.environ.get("SUPABASE_DB_PORT", "5432")) - conn = await asyncpg.connect( - host=os.environ.get("SUPABASE_DB_HOST", "localhost"), - port=parsed_port, - user=os.environ.get("SUPABASE_DB_USER", "postgres"), - password=os.environ.get("SUPABASE_DB_PASSWORD", ""), - database=os.environ.get("SUPABASE_DB_NAME", "postgres"), - ) - - try: - await apply_migration_files(conn, migration_files) - finally: - await conn.close() import asyncio from netengine.db.migrations import MigrationRunResult, run_migrations diff --git a/netengine/workers/pki_cert_rotation_worker.py b/netengine/workers/pki_cert_rotation_worker.py index cb037c7..5d975b4 100644 --- a/netengine/workers/pki_cert_rotation_worker.py +++ b/netengine/workers/pki_cert_rotation_worker.py @@ -6,7 +6,7 @@ from typing import Any, Awaitable, Callable, Dict, List, Optional from netengine.core.pgmq_client import PGMQClient -from netengine.core.state import RuntimeState +from netengine.core.state import IssuedCertificateMetadata, RuntimeState from netengine.events.queues import Queue from netengine.events.schema import EventEnvelope from netengine.handlers.pki_handler import PKIHandler @@ -24,7 +24,7 @@ class CertTypeRotationConfig: cert_type: str rotation_interval_hours: int = 24 expiry_warning_days: int = 30 - rotation_callback: Optional[Callable[[str, Dict[str, Any]], Awaitable[None]]] = None + rotation_callback: Optional[Callable[[str, IssuedCertificateMetadata], Awaitable[None]]] = None class PKICertRotationWorker: diff --git a/tests/integration/test_dns_add_zone_record_callers.py b/tests/integration/test_dns_add_zone_record_callers.py index 5c2ac94..009cb69 100644 --- a/tests/integration/test_dns_add_zone_record_callers.py +++ b/tests/integration/test_dns_add_zone_record_callers.py @@ -42,10 +42,8 @@ async def test_phase_3_pki_inserts_ca_dns_record(context_with_zone_files): ), ) - with ( - patch("netengine.handlers.phase_pki.PKIHandler", return_value=pki), - patch("netengine.handlers.phase_pki.DockerHandler"), - ): + context_with_zone_files.docker_client = MagicMock() + with patch("netengine.handlers.phase_pki.PKIHandler", return_value=pki): await PKIPhaseHandler().execute(context_with_zone_files) platform_zone = context_with_zone_files.runtime_state.dns_output["zone_files"][ diff --git a/tests/integration/test_e2e_bootstrap.py b/tests/integration/test_e2e_bootstrap.py index 124c522..7d7b1b5 100644 --- a/tests/integration/test_e2e_bootstrap.py +++ b/tests/integration/test_e2e_bootstrap.py @@ -150,6 +150,7 @@ def test_health_ok_when_all_phases_complete(self, tmp_path, monkeypatch): substrate_output={"healthy": True}, dns_output={"healthy": True}, pki_bootstrapped=True, + pki_output={"bootstrapped": True}, identity_platform_output={"healthy": True}, world_registry_output={"healthy": True}, domain_registry_output={"healthy": True}, diff --git a/tests/integration/test_m8_operator_api.py b/tests/integration/test_m8_operator_api.py index aee2160..3de4000 100644 --- a/tests/integration/test_m8_operator_api.py +++ b/tests/integration/test_m8_operator_api.py @@ -518,9 +518,7 @@ def test_import_rejects_unknown_phase(self, tmp_path, monkeypatch): assert resp.status_code == 422 assert "Unknown phase ID" in resp.json()["detail"] - def test_import_rejects_phase_completion_without_required_output( - self, tmp_path, monkeypatch - ): + def test_import_rejects_phase_completion_without_required_output(self, tmp_path, monkeypatch): client = _make_client(monkeypatch, tmp_path) state = RuntimeState() @@ -563,6 +561,7 @@ def test_import_rejects_skipped_prerequisite_phase(self, tmp_path, monkeypatch): ) assert resp.status_code == 422 assert "Impossible phase combination" in resp.json()["detail"] + def test_export_sanitizes_secret_phase_output(self, tmp_path, monkeypatch): monkeypatch.setenv("NETENGINE_STATE_FILE", str(tmp_path / "state.json")) monkeypatch.setenv("NETENGINES_BOOTSTRAP_SECRET", "test-secret") diff --git a/tests/integration/test_orchestrator_m3.py b/tests/integration/test_orchestrator_m3.py index fee70e1..4cf3ddf 100644 --- a/tests/integration/test_orchestrator_m3.py +++ b/tests/integration/test_orchestrator_m3.py @@ -19,6 +19,7 @@ async def _set_dns_output(context): async def _set_pki_output(context): context.runtime_state.pki_bootstrapped = True + context.runtime_state.pki_output = {"bootstrapped": True, "mock": True} class TestOrchestratorPhaseExecution: diff --git a/tests/test_api_auth.py b/tests/test_api_auth.py index 67bad9b..ec1b219 100644 --- a/tests/test_api_auth.py +++ b/tests/test_api_auth.py @@ -1,5 +1,7 @@ import ssl +import tempfile from types import SimpleNamespace +from unittest.mock import patch import pytest from fastapi.security import HTTPAuthorizationCredentials @@ -8,8 +10,12 @@ from netengine.core.state import RuntimeState -class _Response: - status = 200 +class _PostResponse: + """Mock for the async context manager returned by session.post().""" + + def __init__(self, ssl_kwarg): + self.ssl_kwarg = ssl_kwarg + self.status = 200 async def __aenter__(self): return self @@ -22,7 +28,9 @@ async def json(self): class _Session: - post_kwargs = None + """Mock for aiohttp.ClientSession.""" + + post_kwargs: dict = {} async def __aenter__(self): return self @@ -31,43 +39,56 @@ async def __aexit__(self, exc_type, exc, tb): return False def post(self, url, **kwargs): - self.capture["url"] = url - self.capture.update(kwargs) - return _PostContext() + type(self).post_kwargs = kwargs + return _PostResponse(kwargs.get("ssl")) + + +def _phase4_state(**overrides): + values = { + "phase_completed": {"4": True}, + "bootstrap_admin_password": "admin-password", + "platform_client_secret": "test-secret", + "platform_client_auth_id": "platform-api", + } + values.update(overrides) + return RuntimeState(**values) + + +async def _call_require_auth(monkeypatch, state): + _Session.post_kwargs = {} + monkeypatch.setattr(auth.RuntimeState, "load", classmethod(lambda cls: state)) + monkeypatch.setattr(auth.aiohttp, "ClientSession", _Session) + request = SimpleNamespace(headers={}, url=SimpleNamespace(path="/world")) + credentials = HTTPAuthorizationCredentials(scheme="Bearer", credentials="token-1") + + result = await auth.require_auth(request, credentials) + + assert result["active"] is True + assert _Session.post_kwargs is not None + return _Session.post_kwargs.get("ssl") @pytest.mark.asyncio async def test_require_auth_introspection_uses_platform_api_client_secret(monkeypatch): state = RuntimeState( phase_completed={"4": True}, - identity_platform_output={"platform_client_id": "uuid"}, - platform_client_id="uuid", platform_client_auth_id="platform-api", platform_client_secret="stored-secret", ) - capture = {} - request = SimpleNamespace(headers={}, url=SimpleNamespace(path="/api/v1/world")) - credentials = HTTPAuthorizationCredentials(scheme="Bearer", credentials="bearer-token") + monkeypatch.delenv("KEYCLOAK_PLATFORM_CLIENT_SECRET", raising=False) - monkeypatch.setenv("KEYCLOAK_PLATFORM_CLIENT_ID", "ignored-env-client") - with ( - patch("netengine.api.auth.RuntimeState.load", return_value=state), - patch("netengine.api.auth.aiohttp.ClientSession", return_value=_ClientSession(capture)), - ): - data = await require_auth(request, credentials) + ssl_option = await _call_require_auth(monkeypatch, state) - assert data == {"active": True, "sub": "user-1"} - assert capture["data"] == {"token": "bearer-token", "client_id": "platform-api"} - assert capture["auth"].login == "platform-api" - assert capture["auth"].password == "stored-secret" + assert _Session.post_kwargs.get("auth").password == "stored-secret" + assert ssl_option is None @pytest.mark.asyncio async def test_require_auth_fails_when_platform_client_secret_missing(monkeypatch): + from fastapi import HTTPException + state = RuntimeState( phase_completed={"4": True}, - identity_platform_output={"platform_client_id": "uuid"}, - platform_client_id="uuid", platform_client_auth_id="platform-api", platform_client_secret=None, ) @@ -77,36 +98,10 @@ async def test_require_auth_fails_when_platform_client_secret_missing(monkeypatc monkeypatch.delenv("KEYCLOAK_PLATFORM_CLIENT_SECRET", raising=False) with patch("netengine.api.auth.RuntimeState.load", return_value=state): with pytest.raises(HTTPException) as exc_info: - await require_auth(request, credentials) + await auth.require_auth(request, credentials) assert exc_info.value.status_code == 500 assert exc_info.value.detail == "Platform API client secret not configured" - def post(self, *args, **kwargs): - type(self).post_kwargs = kwargs - return _Response() - - -def _phase4_state(**overrides): - values = { - "phase_completed": {"4": True}, - "bootstrap_admin_password": "admin-password", - } - values.update(overrides) - return RuntimeState(**values) - - -async def _call_require_auth(monkeypatch, state): - _Session.post_kwargs = None - monkeypatch.setattr(auth.RuntimeState, "load", classmethod(lambda cls: state)) - monkeypatch.setattr(auth.aiohttp, "ClientSession", _Session) - request = SimpleNamespace(headers={}, url=SimpleNamespace(path="/world")) - credentials = HTTPAuthorizationCredentials(scheme="Bearer", credentials="token-1") - - result = await auth.require_auth(request, credentials) - - assert result["active"] is True - assert _Session.post_kwargs is not None - return _Session.post_kwargs["ssl"] @pytest.mark.asyncio diff --git a/tests/test_migration_queues.py b/tests/test_migration_queues.py index b2b9bf9..3fedc8f 100644 --- a/tests/test_migration_queues.py +++ b/tests/test_migration_queues.py @@ -5,7 +5,6 @@ from netengine.events.queues import PRIMARY_QUEUES, Queue, dlq_for - REPO_ROOT = Path(__file__).resolve().parents[1] INITIAL_MIGRATION = REPO_ROOT / "migrations" / "001_initial.sql" PGMQ_QUEUE_ARRAY_RE = re.compile( @@ -32,6 +31,7 @@ def test_all_primary_queues_have_registered_dlqs() -> None: assert dlq_for(queue) in Queue assert dlq_for(queue).value in {registered.value for registered in Queue} + COMPOSE_INTEGRATION = REPO_ROOT / "compose" / "compose.test-integration.yml" From 6c1812a6e34e1c6f766fb880771ff7504754c27e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 29 Jun 2026 02:51:55 +0000 Subject: [PATCH 3/7] Fix mypy unused-ignore and type errors from previous CI pass - Remove stale # type: ignore[import-untyped] from runtime asyncpg imports in core/migrations.py, db/migrations.py, utils/migration_service.py (asyncpg now has stubs so the ignores are unused) - Fix preflight.py _compose_config return type dict[str, object] -> dict[str, Any] so callers can use .keys()/.items() without attr-defined errors - Revert CertTypeRotationConfig.rotation_callback to Dict[str, Any] to match the existing _callbacks dict type and avoid callback contravariance errors Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01RResBJyUf3NWWSY7VBCR7z --- netengine/core/migrations.py | 4 ++-- netengine/db/migrations.py | 4 ++-- netengine/diagnostic/preflight.py | 4 ++-- netengine/utils/migration_service.py | 2 +- netengine/workers/pki_cert_rotation_worker.py | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/netengine/core/migrations.py b/netengine/core/migrations.py index 54f6866..10c3225 100644 --- a/netengine/core/migrations.py +++ b/netengine/core/migrations.py @@ -77,7 +77,7 @@ def discover(self) -> list[MigrationFile]: async def apply_pending(self) -> list[MigrationRecord]: """Apply pending migrations and return records applied in this invocation.""" - import asyncpg # type: ignore[import-untyped] + import asyncpg conn = await asyncpg.connect(self.db_url) try: @@ -113,7 +113,7 @@ async def apply_pending(self) -> list[MigrationRecord]: await conn.close() async def status(self) -> MigrationStatus: - import asyncpg # type: ignore[import-untyped] + import asyncpg conn = await asyncpg.connect(self.db_url) try: diff --git a/netengine/db/migrations.py b/netengine/db/migrations.py index a13a7d1..045640c 100644 --- a/netengine/db/migrations.py +++ b/netengine/db/migrations.py @@ -131,7 +131,7 @@ async def run_migrations( migrations_dir: Path | str = MIGRATIONS_DIR, ) -> MigrationRunResult: """Apply all pending SQL migrations and return structured outcomes.""" - import asyncpg # type: ignore[import-untyped] + import asyncpg resolved_dir = Path(migrations_dir) migration_files = discover_migrations(resolved_dir) @@ -233,7 +233,7 @@ async def migration_status( migrations_dir: Path | str = MIGRATIONS_DIR, ) -> MigrationStatusReport: """Inspect migration state without applying pending migration files.""" - import asyncpg # type: ignore[import-untyped] + import asyncpg resolved_dir = Path(migrations_dir) migration_files = discover_migrations(resolved_dir) diff --git a/netengine/diagnostic/preflight.py b/netengine/diagnostic/preflight.py index 1dbfabb..cec54a6 100644 --- a/netengine/diagnostic/preflight.py +++ b/netengine/diagnostic/preflight.py @@ -20,7 +20,7 @@ from dataclasses import dataclass from enum import StrEnum from pathlib import Path -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from urllib.parse import urlparse import click @@ -72,7 +72,7 @@ def _run(command: list[str], *, timeout: float = 8.0) -> subprocess.CompletedPro return subprocess.run(command, capture_output=True, text=True, timeout=timeout, check=False) -def _compose_config(project_root: Path | None = None) -> dict[str, object]: +def _compose_config(project_root: Path | None = None) -> dict[str, Any]: compose_file = (project_root or _repo_root()) / "docker-compose.yml" try: return yaml.safe_load(compose_file.read_text()) or {} diff --git a/netengine/utils/migration_service.py b/netengine/utils/migration_service.py index 24d7d1f..4fa6279 100644 --- a/netengine/utils/migration_service.py +++ b/netengine/utils/migration_service.py @@ -270,7 +270,7 @@ def _statement_context(statement: str, max_length: int = 240) -> str: def _asyncpg() -> Any: try: - import asyncpg # type: ignore[import-untyped] + import asyncpg except ModuleNotFoundError as exc: raise RuntimeError("asyncpg is required to run database migrations") from exc return asyncpg diff --git a/netengine/workers/pki_cert_rotation_worker.py b/netengine/workers/pki_cert_rotation_worker.py index 5d975b4..cb037c7 100644 --- a/netengine/workers/pki_cert_rotation_worker.py +++ b/netengine/workers/pki_cert_rotation_worker.py @@ -6,7 +6,7 @@ from typing import Any, Awaitable, Callable, Dict, List, Optional from netengine.core.pgmq_client import PGMQClient -from netengine.core.state import IssuedCertificateMetadata, RuntimeState +from netengine.core.state import RuntimeState from netengine.events.queues import Queue from netengine.events.schema import EventEnvelope from netengine.handlers.pki_handler import PKIHandler @@ -24,7 +24,7 @@ class CertTypeRotationConfig: cert_type: str rotation_interval_hours: int = 24 expiry_warning_days: int = 30 - rotation_callback: Optional[Callable[[str, IssuedCertificateMetadata], Awaitable[None]]] = None + rotation_callback: Optional[Callable[[str, Dict[str, Any]], Awaitable[None]]] = None class PKICertRotationWorker: From e79fded53433c6624147e886e60fe41b22e9033b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 29 Jun 2026 02:59:31 +0000 Subject: [PATCH 4/7] Fix type errors, black formatting, and add Docker subnet conflict preflight check - Restore type: ignore[import-untyped] on first asyncpg import in core/migrations.py and db/migrations.py (mypy needs it on the first in-function import; subsequent imports in the same file are cached) - Cast cert_metadata to Dict[str, Any] when invoking rotation_callback in pki_cert_rotation_worker to satisfy mypy arg-type check - Remove post-docstring blank lines in migration_service.py to satisfy black 25.x formatting rules - Add _check_docker_subnet_conflicts to preflight doctor checks: warns when any existing Docker network subnet overlaps with subnets defined in docker-compose.yml, preventing bootstrap failures Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01RResBJyUf3NWWSY7VBCR7z --- netengine/core/migrations.py | 2 +- netengine/db/migrations.py | 2 +- netengine/diagnostic/preflight.py | 88 +++++++++++++++++++ netengine/utils/migration_service.py | 2 - netengine/workers/pki_cert_rotation_worker.py | 4 +- 5 files changed, 92 insertions(+), 6 deletions(-) diff --git a/netengine/core/migrations.py b/netengine/core/migrations.py index 10c3225..34ab80d 100644 --- a/netengine/core/migrations.py +++ b/netengine/core/migrations.py @@ -77,7 +77,7 @@ def discover(self) -> list[MigrationFile]: async def apply_pending(self) -> list[MigrationRecord]: """Apply pending migrations and return records applied in this invocation.""" - import asyncpg + import asyncpg # type: ignore[import-untyped] conn = await asyncpg.connect(self.db_url) try: diff --git a/netengine/db/migrations.py b/netengine/db/migrations.py index 045640c..66225a6 100644 --- a/netengine/db/migrations.py +++ b/netengine/db/migrations.py @@ -131,7 +131,7 @@ async def run_migrations( migrations_dir: Path | str = MIGRATIONS_DIR, ) -> MigrationRunResult: """Apply all pending SQL migrations and return structured outcomes.""" - import asyncpg + import asyncpg # type: ignore[import-untyped] resolved_dir = Path(migrations_dir) migration_files = discover_migrations(resolved_dir) diff --git a/netengine/diagnostic/preflight.py b/netengine/diagnostic/preflight.py index cec54a6..3994310 100644 --- a/netengine/diagnostic/preflight.py +++ b/netengine/diagnostic/preflight.py @@ -316,6 +316,93 @@ def _docker_names(kind: str) -> set[str]: ) +def _check_docker_subnet_conflicts(ctx: DoctorContext) -> DoctorCheckResult: + """Warn if any existing Docker network subnets overlap with NetEngine's configured subnets.""" + compose_config = _compose_config(ctx.project_root) + ne_subnets: list[str] = [] + for network_cfg in (compose_config.get("networks") or {}).values(): + if isinstance(network_cfg, dict): + for ipam_config in (network_cfg.get("ipam") or {}).get("config") or []: + if isinstance(ipam_config, dict) and "subnet" in ipam_config: + ne_subnets.append(ipam_config["subnet"]) + + if not ne_subnets: + return DoctorCheckResult( + "Docker subnet conflicts", + DoctorStatus.SKIP, + "no subnets defined in compose config", + group="docker", + required=False, + ) + + try: + import ipaddress + + result = _run(["docker", "network", "ls", "-q"]) + if result.returncode != 0 or not result.stdout.strip(): + return DoctorCheckResult( + "Docker subnet conflicts", + DoctorStatus.OK, + "no existing Docker networks to check", + group="docker", + ) + network_ids = result.stdout.split() + inspect_result = _run(["docker", "network", "inspect"] + network_ids) + if inspect_result.returncode != 0: + return DoctorCheckResult( + "Docker subnet conflicts", + DoctorStatus.WARN, + "could not inspect Docker networks", + "Run `docker network ls` manually to check for subnet conflicts.", + "docker", + required=False, + ) + + import json as _json + + networks = _json.loads(inspect_result.stdout) + ne_networks = [ipaddress.ip_network(s, strict=False) for s in ne_subnets] + conflicts: list[str] = [] + for network in networks: + name = network.get("Name", "unknown") + for ipam_config in (network.get("IPAM") or {}).get("Config") or []: + subnet_str = ipam_config.get("Subnet") + if not subnet_str: + continue + try: + existing = ipaddress.ip_network(subnet_str, strict=False) + except ValueError: + continue + for ne_net in ne_networks: + if existing.overlaps(ne_net): + conflicts.append(f"{name} ({subnet_str}) overlaps {ne_net}") + + if conflicts: + return DoctorCheckResult( + "Docker subnet conflicts", + DoctorStatus.WARN, + "; ".join(conflicts), + "Remove conflicting networks with `docker network rm ` or `docker network prune`.", + "docker", + required=False, + ) + return DoctorCheckResult( + "Docker subnet conflicts", + DoctorStatus.OK, + f"no conflicts with {', '.join(ne_subnets)}", + group="docker", + ) + except Exception as exc: + return DoctorCheckResult( + "Docker subnet conflicts", + DoctorStatus.WARN, + f"subnet conflict check failed: {exc}", + "Run `docker network ls` manually to check for subnet conflicts.", + "docker", + required=False, + ) + + def _check_docker_conflicts(ctx: DoctorContext) -> list[DoctorCheckResult]: _, containers, volumes, networks = _compose_ports_and_resources(ctx.project_root) checks = [] @@ -421,6 +508,7 @@ def standard_probes() -> tuple[DoctorProbe, ...]: _check_ports, _check_filesystem, _check_docker_conflicts, + lambda ctx: _check_docker_subnet_conflicts(ctx), ) diff --git a/netengine/utils/migration_service.py b/netengine/utils/migration_service.py index 4fa6279..1619599 100644 --- a/netengine/utils/migration_service.py +++ b/netengine/utils/migration_service.py @@ -65,13 +65,11 @@ def migration_checksum(sql: str) -> str: def postgres_allows_transaction(sql: str) -> bool: """Return False for common PostgreSQL operations forbidden in transaction blocks.""" - return not any(pattern.search(sql) for pattern in NON_TRANSACTIONAL_PATTERNS) def split_sql_statements(sql: str) -> list[str]: """Split SQL on semicolons while preserving quoted and dollar-quoted bodies.""" - statements: list[str] = [] start = 0 i = 0 diff --git a/netengine/workers/pki_cert_rotation_worker.py b/netengine/workers/pki_cert_rotation_worker.py index cb037c7..a8fef95 100644 --- a/netengine/workers/pki_cert_rotation_worker.py +++ b/netengine/workers/pki_cert_rotation_worker.py @@ -3,7 +3,7 @@ import logging from dataclasses import dataclass from datetime import UTC, datetime, timedelta -from typing import Any, Awaitable, Callable, Dict, List, Optional +from typing import Any, Awaitable, Callable, Dict, List, Optional, cast from netengine.core.pgmq_client import PGMQClient from netengine.core.state import RuntimeState @@ -181,7 +181,7 @@ async def _check_and_rotate_cert_type( try: # Call rotation callback if present (for graceful transition prep) if config.rotation_callback: - await config.rotation_callback(cn, cert_metadata) + await config.rotation_callback(cn, cast(Dict[str, Any], cert_metadata)) # Re-issue certificate with incremented version sans = cert_metadata.get("sans", []) From e6ed4a015f92c2ee41a9dff4439dc04cdbc66137 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 29 Jun 2026 03:00:57 +0000 Subject: [PATCH 5/7] Apply black 24.x formatting to core/migrations.py and utils/migration_service.py Black 24.x (project-pinned version) reformats these files differently from 26.x. Applied formatting via black==24.10.0 to match what CI expects. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01RResBJyUf3NWWSY7VBCR7z --- netengine/core/migrations.py | 12 ++++++++---- netengine/utils/migration_service.py | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/netengine/core/migrations.py b/netengine/core/migrations.py index 34ab80d..55aa42c 100644 --- a/netengine/core/migrations.py +++ b/netengine/core/migrations.py @@ -151,7 +151,8 @@ async def status(self) -> MigrationStatus: await conn.close() async def _ensure_table(self, conn: Any) -> None: - await conn.execute(""" + await conn.execute( + """ CREATE TABLE IF NOT EXISTS netengine_schema_migrations ( version text PRIMARY KEY, name text NOT NULL, @@ -160,14 +161,17 @@ async def _ensure_table(self, conn: Any) -> None: applied_at timestamptz, error text ) - """) + """ + ) async def _records(self, conn: Any) -> list[MigrationRecord]: - rows = await conn.fetch(""" + rows = await conn.fetch( + """ SELECT version, name, checksum, status, applied_at, error FROM netengine_schema_migrations ORDER BY version - """) + """ + ) return [ MigrationRecord( version=row["version"], diff --git a/netengine/utils/migration_service.py b/netengine/utils/migration_service.py index 1619599..d0c09cd 100644 --- a/netengine/utils/migration_service.py +++ b/netengine/utils/migration_service.py @@ -247,7 +247,8 @@ async def _apply_one(self, conn: "asyncpg.Connection", path: Path, checksum: str raise MigrationApplyError(path.name, context, exc) from exc async def _ensure_table(self, conn: "asyncpg.Connection") -> None: - await conn.execute(f""" + await conn.execute( + f""" CREATE TABLE IF NOT EXISTS {MIGRATION_TABLE} ( filename TEXT PRIMARY KEY, checksum TEXT NOT NULL, @@ -255,7 +256,8 @@ async def _ensure_table(self, conn: "asyncpg.Connection") -> None: error TEXT, applied_at TIMESTAMPTZ NOT NULL DEFAULT now() ) - """) + """ + ) def _migration_files(self) -> Iterable[Path]: return sorted(self.migrations_dir.glob("*.sql")) From b59d139f6b7bbb58b4d692e07906f97969b61d84 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 29 Jun 2026 03:02:57 +0000 Subject: [PATCH 6/7] Fix undefined admin_password variable in auth.py and apply isort to all files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 4be7b03 partial-revert left admin_password undefined after 553c25e had renamed it to client_secret; fix auth=BasicAuth to use (client_id, client_secret). Also apply isort 5.13.2 formatting to 16 files that CI flags as incorrectly sorted — these are pre-existing isort violations unrelated to this branch's feature work. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01RResBJyUf3NWWSY7VBCR7z --- netengine/api/auth.py | 4 ++-- netengine/api/routes.py | 2 +- netengine/cli/main.py | 2 +- netengine/events/emitter.py | 1 - netengine/handlers/and_handler.py | 2 +- netengine/handlers/app_handler.py | 2 +- netengine/handlers/context.py | 3 +-- netengine/handlers/gateway_portal_handler.py | 4 ++-- netengine/handlers/minio_handler.py | 2 +- netengine/handlers/phase_pki.py | 2 +- netengine/handlers/substrate.py | 2 +- netengine/phases/phase_ands.py | 2 +- netengine/phases/phase_inworld_identity.py | 2 +- netengine/phases/phase_services.py | 2 +- netengine/spec/loader.py | 2 +- tests/test_cli.py | 1 + tests/test_migration_queues.py | 2 +- 17 files changed, 18 insertions(+), 19 deletions(-) diff --git a/netengine/api/auth.py b/netengine/api/auth.py index 54c0236..eba5620 100644 --- a/netengine/api/auth.py +++ b/netengine/api/auth.py @@ -109,8 +109,8 @@ async def require_auth( with _keycloak_ssl_context(state) as ssl_context: async with session.post( f"{KEYCLOAK_ISSUER}/protocol/openid-connect/token/introspect", - data={"token": token}, - auth=aiohttp.BasicAuth("admin-cli", admin_password), + data={"token": token, "client_id": client_id}, + auth=aiohttp.BasicAuth(client_id, client_secret), ssl=ssl_context, ) as resp: if resp.status != 200: diff --git a/netengine/api/routes.py b/netengine/api/routes.py index 9fa3c6f..0555fbc 100644 --- a/netengine/api/routes.py +++ b/netengine/api/routes.py @@ -22,7 +22,7 @@ from netengine.logging import get_logger from netengine.phase_labels import PHASE_LABELS from netengine.spec.loader import SpecLoadError, load_spec -from netengine.spec.models import NetEngineSpec, SPEC_SCHEMA_VERSION +from netengine.spec.models import SPEC_SCHEMA_VERSION, NetEngineSpec logger = get_logger(__name__) router = APIRouter(prefix="/api/v1") diff --git a/netengine/cli/main.py b/netengine/cli/main.py index 92c4016..895e1aa 100644 --- a/netengine/cli/main.py +++ b/netengine/cli/main.py @@ -879,7 +879,7 @@ def import_support_bundle(bundle_file: str) -> None: from netengine.api.routes import SUPPORTED_IMPORT_SCHEMA_VERSIONS, _validate_import_phase_state from netengine.core.state import RuntimeState - from netengine.spec.models import NetEngineSpec, SUPPORTED_SPEC_SCHEMA_VERSIONS + from netengine.spec.models import SUPPORTED_SPEC_SCHEMA_VERSIONS, NetEngineSpec body = _json.loads(Path(bundle_file).read_text()) if body.get("schema_version") not in SUPPORTED_IMPORT_SCHEMA_VERSIONS: diff --git a/netengine/events/emitter.py b/netengine/events/emitter.py index e92b421..d38a955 100644 --- a/netengine/events/emitter.py +++ b/netengine/events/emitter.py @@ -4,7 +4,6 @@ from typing import Any from netengine.core.state import EventSendFailure - from netengine.events import factory as event_factory from netengine.events.queues import Queue, queue_for_event_type from netengine.events.schema import EventEnvelope diff --git a/netengine/handlers/and_handler.py b/netengine/handlers/and_handler.py index 15beb22..a5e5bff 100644 --- a/netengine/handlers/and_handler.py +++ b/netengine/handlers/and_handler.py @@ -7,9 +7,9 @@ from netengine.events.schema import EventEnvelope from netengine.handlers.context import PhaseContext from netengine.handlers.dns import DNSHandler -from netengine.handlers.protocols import DockerAdapterProtocol from netengine.handlers.domain_registry_handler import DomainRegistryHandler from netengine.handlers.gateway_handler import GatewayHandler +from netengine.handlers.protocols import DockerAdapterProtocol from netengine.logging import get_logger diff --git a/netengine/handlers/app_handler.py b/netengine/handlers/app_handler.py index 13c19d4..5795354 100644 --- a/netengine/handlers/app_handler.py +++ b/netengine/handlers/app_handler.py @@ -8,9 +8,9 @@ from netengine.handlers._base import BasePhaseHandler from netengine.handlers.context import PhaseContext from netengine.handlers.dns import DNSHandler -from netengine.handlers.protocols import DockerAdapterProtocol from netengine.handlers.oidc_handler import OIDCHandler from netengine.handlers.pki_handler import PKIHandler +from netengine.handlers.protocols import DockerAdapterProtocol from netengine.logging import get_logger diff --git a/netengine/handlers/context.py b/netengine/handlers/context.py index f7f7c64..a5068d9 100644 --- a/netengine/handlers/context.py +++ b/netengine/handlers/context.py @@ -5,9 +5,8 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Optional -from netengine.handlers.protocols import DockerAdapterProtocol - from netengine.core.state import RuntimeState +from netengine.handlers.protocols import DockerAdapterProtocol from netengine.spec.models import NetEngineSpec if TYPE_CHECKING: diff --git a/netengine/handlers/gateway_portal_handler.py b/netengine/handlers/gateway_portal_handler.py index a130d57..ea1fcd5 100644 --- a/netengine/handlers/gateway_portal_handler.py +++ b/netengine/handlers/gateway_portal_handler.py @@ -15,12 +15,12 @@ from typing import Any from netengine.errors import GatewayError, PKIError -from netengine.events.queues import Queue from netengine.events.emitter import emit_event +from netengine.events.queues import Queue from netengine.handlers._base import BasePhaseHandler from netengine.handlers.context import PhaseContext -from netengine.handlers.protocols import DockerAdapterProtocol from netengine.handlers.gateway_handler import GatewayHandler +from netengine.handlers.protocols import DockerAdapterProtocol from netengine.logging import get_logger from netengine.spec.models import CrossWorldPeer, GatewayPortal from netengine.spec.types import GatewayCrossWorldMode, GatewayRealInternetMode diff --git a/netengine/handlers/minio_handler.py b/netengine/handlers/minio_handler.py index 1cc9cd0..34649b3 100644 --- a/netengine/handlers/minio_handler.py +++ b/netengine/handlers/minio_handler.py @@ -3,8 +3,8 @@ from datetime import UTC, datetime from netengine.handlers.dns import DNSHandler -from netengine.handlers.protocols import DockerAdapterProtocol from netengine.handlers.pki_handler import PKIHandler +from netengine.handlers.protocols import DockerAdapterProtocol class StorageHandler: diff --git a/netengine/handlers/phase_pki.py b/netengine/handlers/phase_pki.py index 7e313e4..be6b61b 100644 --- a/netengine/handlers/phase_pki.py +++ b/netengine/handlers/phase_pki.py @@ -5,8 +5,8 @@ from netengine.events.emitter import emit_event from netengine.handlers._base import BasePhaseHandler from netengine.handlers.context import PhaseContext -from netengine.handlers.protocols import DockerAdapterProtocol from netengine.handlers.pki_handler import PKIHandler +from netengine.handlers.protocols import DockerAdapterProtocol from netengine.logging import get_logger from netengine.workers.pki_cert_rotation_worker import CertTypeRotationConfig, PKICertRotationWorker diff --git a/netengine/handlers/substrate.py b/netengine/handlers/substrate.py index 265d079..567e405 100644 --- a/netengine/handlers/substrate.py +++ b/netengine/handlers/substrate.py @@ -11,9 +11,9 @@ from datetime import UTC, datetime from typing import Any, cast +from netengine.core.state import SubstrateOutput from netengine.errors import SubstrateError from netengine.events.emitter import emit_event -from netengine.core.state import SubstrateOutput from netengine.handlers._base import BasePhaseHandler from netengine.handlers.context import PhaseContext diff --git a/netengine/phases/phase_ands.py b/netengine/phases/phase_ands.py index 5470583..d63efc6 100644 --- a/netengine/phases/phase_ands.py +++ b/netengine/phases/phase_ands.py @@ -15,8 +15,8 @@ from datetime import UTC, datetime from typing import Any, Optional -from netengine.events.queues import Queue from netengine.events.emitter import emit_event +from netengine.events.queues import Queue from netengine.events.schema import EventEnvelope from netengine.handlers._base import BasePhaseHandler from netengine.handlers.context import PhaseContext diff --git a/netengine/phases/phase_inworld_identity.py b/netengine/phases/phase_inworld_identity.py index a0a2156..f099d13 100644 --- a/netengine/phases/phase_inworld_identity.py +++ b/netengine/phases/phase_inworld_identity.py @@ -17,8 +17,8 @@ import aiohttp -from netengine.events.queues import Queue from netengine.events.emitter import emit_event +from netengine.events.queues import Queue from netengine.events.schema import EventEnvelope from netengine.handlers._base import BasePhaseHandler from netengine.handlers.context import PhaseContext diff --git a/netengine/phases/phase_services.py b/netengine/phases/phase_services.py index 518222d..daa28f3 100644 --- a/netengine/phases/phase_services.py +++ b/netengine/phases/phase_services.py @@ -12,8 +12,8 @@ from datetime import UTC, datetime from typing import Any -from netengine.events.queues import Queue from netengine.events.emitter import emit_event +from netengine.events.queues import Queue from netengine.handlers._base import BasePhaseHandler from netengine.handlers.context import PhaseContext from netengine.handlers.dns import DNSHandler diff --git a/netengine/spec/loader.py b/netengine/spec/loader.py index 46f15d7..baba3f1 100644 --- a/netengine/spec/loader.py +++ b/netengine/spec/loader.py @@ -10,7 +10,7 @@ from pydantic import ValidationError from netengine.config.loader import ConfigLoader -from netengine.spec.models import NetEngineSpec, SUPPORTED_SPEC_SCHEMA_VERSIONS +from netengine.spec.models import SUPPORTED_SPEC_SCHEMA_VERSIONS, NetEngineSpec logger = logging.getLogger(__name__) diff --git a/tests/test_cli.py b/tests/test_cli.py index 8a4078a..b5f9a80 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -368,6 +368,7 @@ def test_up_supports_repeatable_set_overrides(): def _write_cli_validate_spec(tmp_path: Path, **updates) -> Path: """Write an example-derived spec for validate command tests.""" import copy + import yaml source = Path(__file__).parent.parent / "examples" / "minimal.yaml" diff --git a/tests/test_migration_queues.py b/tests/test_migration_queues.py index 3fedc8f..6bb9f99 100644 --- a/tests/test_migration_queues.py +++ b/tests/test_migration_queues.py @@ -1,7 +1,7 @@ """Regression tests for pgmq queue declarations in migrations.""" -from pathlib import Path import re +from pathlib import Path from netengine.events.queues import PRIMARY_QUEUES, Queue, dlq_for From f909cc4b6d8b6a92058e1d9be574fb6e913bb36f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 29 Jun 2026 03:04:08 +0000 Subject: [PATCH 7/7] Fix E501 line-too-long flake8 violations in db/migrations.py and preflight.py Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01RResBJyUf3NWWSY7VBCR7z --- netengine/db/migrations.py | 3 ++- netengine/diagnostic/preflight.py | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/netengine/db/migrations.py b/netengine/db/migrations.py index 66225a6..102fb18 100644 --- a/netengine/db/migrations.py +++ b/netengine/db/migrations.py @@ -251,7 +251,8 @@ async def migration_status( checksum = migration_checksum(sql) filename = migration_path.name existing = await conn.fetchrow( - "SELECT checksum, success, applied_at, error FROM schema_migrations WHERE filename = $1", + "SELECT checksum, success, applied_at, error" + " FROM schema_migrations WHERE filename = $1", filename, ) if not existing: diff --git a/netengine/diagnostic/preflight.py b/netengine/diagnostic/preflight.py index 3994310..a87b6d8 100644 --- a/netengine/diagnostic/preflight.py +++ b/netengine/diagnostic/preflight.py @@ -107,7 +107,8 @@ def _check_python() -> DoctorCheckResult: return DoctorCheckResult( "Python runtime", DoctorStatus.OK if ok else DoctorStatus.FAIL, - f"Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}; pyproject requires ^3.13", + f"Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro};" + " pyproject requires ^3.13", None if ok else "Install Python 3.13 or newer and recreate the virtualenv.", "host", ) @@ -382,7 +383,8 @@ def _check_docker_subnet_conflicts(ctx: DoctorContext) -> DoctorCheckResult: "Docker subnet conflicts", DoctorStatus.WARN, "; ".join(conflicts), - "Remove conflicting networks with `docker network rm ` or `docker network prune`.", + "Remove conflicting networks with" + " `docker network rm ` or `docker network prune`.", "docker", required=False, ) @@ -414,7 +416,8 @@ def _check_docker_conflicts(ctx: DoctorContext) -> list[DoctorCheckResult]: DoctorStatus.WARN if conflicts else DoctorStatus.OK, ", ".join(conflicts) if conflicts else "no known name conflicts", ( - "Run `netengine down` or remove stale Docker resources if these belong to an old run." + "Run `netengine down` or remove stale Docker resources" + " if these belong to an old run." if conflicts else None ),