diff --git a/gateway/app/core/blob.py b/gateway/app/core/blob.py index 3c4adf6d..f1d11af5 100644 --- a/gateway/app/core/blob.py +++ b/gateway/app/core/blob.py @@ -162,7 +162,7 @@ async def url(self, key: str) -> str: ) -@lru_cache() +@lru_cache def get_blob_backend() -> BlobBackend: """Resolve the configured blob backend. Cached per-process.""" backend_name = os.environ.get("BLOB_BACKEND", "local").strip().lower() diff --git a/gateway/app/core/config.py b/gateway/app/core/config.py index 21646045..304fdbc4 100644 --- a/gateway/app/core/config.py +++ b/gateway/app/core/config.py @@ -410,7 +410,7 @@ def _validate_exchange_rate(cls, value: Decimal) -> Decimal: ) -@lru_cache() +@lru_cache def get_settings() -> Settings: return Settings() diff --git a/gateway/app/core/dependencies.py b/gateway/app/core/dependencies.py index 7fcaf497..2f1724c7 100644 --- a/gateway/app/core/dependencies.py +++ b/gateway/app/core/dependencies.py @@ -22,7 +22,7 @@ from ..services.oracle_broadcast import OracleBroadcastEngine -@lru_cache() +@lru_cache def get_iot_bridge() -> ProtocolBridge: """Singleton IoT Protocol Bridge with ACL engine and MQTT translator.""" # Settings are read per call, not from a module-level singleton: tests and @@ -37,7 +37,7 @@ def get_iot_bridge() -> ProtocolBridge: return bridge -@lru_cache() +@lru_cache def get_autonomous_pm() -> AutonomousPM: """Singleton Autonomous Product Manager with event store and anomaly detector.""" settings = get_settings() @@ -48,67 +48,67 @@ def get_autonomous_pm() -> AutonomousPM: ) -@lru_cache() +@lru_cache def get_media_engine() -> MediaEngine: """Singleton Programmatic Media Engine with full pipeline.""" return MediaEngine() -@lru_cache() +@lru_cache def get_agent_comms() -> AgentComms: """Singleton Agent Communications with registry and message router.""" return AgentComms() -@lru_cache() +@lru_cache def get_content_factory() -> ContentFactory: """Singleton Content Factory with format adapters and algorithmic scheduler.""" return ContentFactory() -@lru_cache() +@lru_cache def get_red_team_swarm() -> RedTeamSwarm: """Singleton Red Team Security Swarm with attack engine and scan store.""" return RedTeamSwarm() -@lru_cache() +@lru_cache def get_agent_oracle() -> AgentOracle: """Singleton Agent Oracle with crawler, indexer, and registration engine.""" return AgentOracle() -@lru_cache() +@lru_cache def get_agent_money() -> AgentMoney: """Singleton Agent Money engine with wallets, metering, and arbitrage.""" return AgentMoney() -@lru_cache() +@lru_cache def get_protocol_engine() -> ProtocolEngine: """Singleton Protocol Generation Engine — code-to-discovery pipeline.""" return ProtocolEngine() -@lru_cache() +@lru_cache def get_rtaas_engine() -> RTaaSEngine: """Singleton Red-Team-as-a-Service engine.""" return RTaaSEngine() -@lru_cache() +@lru_cache def get_sandbox_engine() -> SandboxEngine: """Singleton Interactive Testing Sandbox engine.""" return SandboxEngine() -@lru_cache() +@lru_cache def get_telemetry_scope() -> TelemetryScope: """Singleton Telemetry Scoping engine — multi-tenant autonomous PM.""" return TelemetryScope() -@lru_cache() +@lru_cache def get_broadcast_engine() -> OracleBroadcastEngine: """Singleton Oracle Broadcast — pushes APIs into agent directories.""" return OracleBroadcastEngine(oracle_service=get_agent_oracle()) diff --git a/gateway/app/core/durable_state.py b/gateway/app/core/durable_state.py index ec2b5e6e..7ef1b8f8 100644 --- a/gateway/app/core/durable_state.py +++ b/gateway/app/core/durable_state.py @@ -610,7 +610,7 @@ async def close(self) -> None: self._backend = "memory" -@lru_cache() +@lru_cache def get_durable_state() -> DurableStateStore: return DurableStateStore() diff --git a/gateway/app/core/health.py b/gateway/app/core/health.py index 3c2dbba3..6067acc8 100644 --- a/gateway/app/core/health.py +++ b/gateway/app/core/health.py @@ -70,7 +70,7 @@ async def _run_check( start = time.monotonic() try: result = await asyncio.wait_for(check(), timeout=CHECK_TIMEOUT_SECONDS) - except asyncio.TimeoutError: + except TimeoutError: result = { "status": "down", "error": f"timeout after {CHECK_TIMEOUT_SECONDS}s", diff --git a/gateway/app/db/database.py b/gateway/app/db/database.py index a9598198..5b30430d 100644 --- a/gateway/app/db/database.py +++ b/gateway/app/db/database.py @@ -208,7 +208,7 @@ def _normalize_bind_parameters(parameters: Any) -> Any: to_naive_utc(value) if isinstance(value, datetime) else value for value in parameters ] - if all(a is b for a, b in zip(rewritten_seq, parameters)): + if all(a is b for a, b in zip(rewritten_seq, parameters, strict=True)): return parameters return tuple(rewritten_seq) if isinstance(parameters, tuple) else rewritten_seq return parameters @@ -243,7 +243,7 @@ def _coerce_naive_utc( # noqa: ANN001, ANN202 ): if executemany and isinstance(parameters, (list, tuple)): normalized = [_normalize_bind_parameters(p) for p in parameters] - if all(a is b for a, b in zip(normalized, parameters)): + if all(a is b for a, b in zip(normalized, parameters, strict=True)): return statement, parameters return statement, normalized return statement, _normalize_bind_parameters(parameters) diff --git a/gateway/app/optimizer/planner.py b/gateway/app/optimizer/planner.py index 83547479..49c17793 100644 --- a/gateway/app/optimizer/planner.py +++ b/gateway/app/optimizer/planner.py @@ -97,17 +97,17 @@ def _greedy_heuristic( total_risk = 0.0 for action in scored: c = action.get("credit_cost", 0.0) - l = action.get("latency_ms", 0.0) + latency_ms = action.get("latency_ms", 0.0) r = action.get("risk_score", 0.0) if total_cost + c > state.remaining_budget: continue - if total_latency + l > state.slo_window_seconds * 1000: + if total_latency + latency_ms > state.slo_window_seconds * 1000: continue if total_risk + r > risk_budget: continue selected.append(action) total_cost += c - total_latency += l + total_latency += latency_ms total_risk += r if len(selected) >= max_actions: break diff --git a/gateway/app/routers/mcp.py b/gateway/app/routers/mcp.py index 33eb294e..ba742f77 100644 --- a/gateway/app/routers/mcp.py +++ b/gateway/app/routers/mcp.py @@ -446,8 +446,9 @@ class ToolCallResponse(BaseModel): """MCP tool call response.""" content: list[dict[str, Any]] - isError: bool = False - structuredContent: dict[str, Any] | None = None + # MCP tool-result field names are camelCase on the wire. + isError: bool = False # noqa: N815 + structuredContent: dict[str, Any] | None = None # noqa: N815 receipt: dict[str, Any] | None = None diff --git a/gateway/app/routers/preflight.py b/gateway/app/routers/preflight.py index 9575a765..7c1f767b 100644 --- a/gateway/app/routers/preflight.py +++ b/gateway/app/routers/preflight.py @@ -12,7 +12,7 @@ wallet-scoped tenant key is refused with 403 admin_access_denied. """ -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Body, Depends from pydantic import BaseModel, Field from datetime import datetime @@ -91,7 +91,7 @@ class PreflightResponse(BaseModel): ), ) async def run_preflight( - request: PreflightRequest = PreflightRequest(), + request: PreflightRequest = Body(default_factory=PreflightRequest), auth: AuthContext = Depends(get_auth_context), ): # The report is operator configuration, not tenant data: a wallet-scoped diff --git a/gateway/app/schemas/optimizer.py b/gateway/app/schemas/optimizer.py index 395acaa2..1ef84c6e 100644 --- a/gateway/app/schemas/optimizer.py +++ b/gateway/app/schemas/optimizer.py @@ -1,7 +1,7 @@ from __future__ import annotations import math -from typing import Annotated, Any, Dict, List, Literal, Optional +from typing import Annotated, Any, Literal, Optional from pydantic import BaseModel, Field, field_validator @@ -37,16 +37,16 @@ class OptimizerState(BaseModel): daily_spend_used: float = Field(..., ge=0, allow_inf_nan=False) daily_limit: float = Field(..., ge=0, allow_inf_nan=False) rate_limit_headroom: float = Field(..., ge=0, le=1, allow_inf_nan=False) - service_health: Dict[str, Literal["healthy", "degraded", "down"]] - simulation_flags: Dict[str, bool] - auth_scope: List[str] - task_context: Dict + service_health: dict[str, Literal["healthy", "degraded", "down"]] + simulation_flags: dict[str, bool] + auth_scope: list[str] + task_context: dict remaining_budget: float = Field(..., ge=0, allow_inf_nan=False) slo_window_seconds: int = Field(30, ge=1) @field_validator("task_context") @classmethod - def _task_context_is_finite(cls, task_context: Dict) -> Dict: + def _task_context_is_finite(cls, task_context: dict) -> dict: # task_context is untyped, but its candidate_actions carry the # credit_cost / latency_ms / risk_score / expected_value / reliability # the planner scores and budgets with. A NaN cost passes every budget @@ -60,7 +60,7 @@ def _task_context_is_finite(cls, task_context: Dict) -> Dict: class OptimizerRequest(BaseModel): state: OptimizerState - objective_overrides: Optional[Dict[str, FiniteFloat]] = None + objective_overrides: Optional[dict[str, FiniteFloat]] = None max_actions: Optional[int] = Field(default=5, ge=1) require_real_effects: bool = False @@ -70,10 +70,10 @@ class OptimizerResponse(BaseModel): # solver was never a declared dependency). Selection is the deterministic # greedy heuristic, so these are the two statuses the planner can emit. status: Literal["HeuristicFallback", "Infeasible"] - selected_actions: List[Dict] - rejected_actions: List[Dict] - policy_reasons: Dict[str, str] + selected_actions: list[dict] + rejected_actions: list[dict] + policy_reasons: dict[str, str] expected_utility: float - totals: Dict[str, float] - constraint_margins: Dict[str, float] - governance: Optional[Dict] = None + totals: dict[str, float] + constraint_margins: dict[str, float] + governance: Optional[dict] = None diff --git a/gateway/app/schemas/red_team.py b/gateway/app/schemas/red_team.py index 75e4eb6c..86ae2dd5 100644 --- a/gateway/app/schemas/red_team.py +++ b/gateway/app/schemas/red_team.py @@ -103,7 +103,7 @@ class ScanRequest(BaseModel): description="Which service pillars to attack. Default: all.", ) attack_categories: list[AttackCategory] = Field( - default=[c for c in AttackCategory], + default=list(AttackCategory), description="Which attack vectors to deploy. Default: all.", ) intensity: str = Field( diff --git a/gateway/app/services/awi_rag_engine.py b/gateway/app/services/awi_rag_engine.py index ad00fbf3..1aa489f7 100644 --- a/gateway/app/services/awi_rag_engine.py +++ b/gateway/app/services/awi_rag_engine.py @@ -744,7 +744,7 @@ def _extract_entities( if isinstance(entity, str) ) - return sorted(list(entities))[:100] + return sorted(entities)[:100] def _infer_session_type( self, @@ -886,7 +886,7 @@ def _cosine_similarity(self, a: list[float], b: list[float]) -> float: if len(a) != len(b): return 0.0 - dot_product = sum(x * y for x, y in zip(a, b)) + dot_product = sum(x * y for x, y in zip(a, b, strict=True)) norm_a = math.sqrt(sum(x * x for x in a)) norm_b = math.sqrt(sum(x * x for x in b)) @@ -937,7 +937,7 @@ def _suggest_actions( suggested = [] - for action, score in sorted_actions[:5]: + for action, _score in sorted_actions[:5]: if action not in suggested: suggested.append(action) diff --git a/gateway/app/services/behavioral_sandbox.py b/gateway/app/services/behavioral_sandbox.py index 9d362ee2..2b2f6b5f 100644 --- a/gateway/app/services/behavioral_sandbox.py +++ b/gateway/app/services/behavioral_sandbox.py @@ -284,7 +284,7 @@ async def execute_tool( if request.dry_run: execution.cost_estimate = result.get("cost_estimate", 0.0) - except asyncio.TimeoutError: + except TimeoutError: execution.status = ExecutionStatus.TIMEOUT execution.error = f"Execution timed out after {env.timeout_seconds}s" except Exception as e: @@ -472,7 +472,7 @@ async def _execute_python_docker( stdout, stderr = await asyncio.wait_for( proc.communicate(), timeout=timeout_seconds ) - except asyncio.TimeoutError: + except TimeoutError: proc.kill() await proc.wait() return { @@ -534,7 +534,7 @@ async def _execute_python_host( stdout, stderr = await asyncio.wait_for( proc.communicate(), timeout=timeout_seconds ) - except asyncio.TimeoutError: + except TimeoutError: proc.kill() await proc.wait() return { diff --git a/gateway/app/services/content_factory.py b/gateway/app/services/content_factory.py index b8856563..35f39af6 100644 --- a/gateway/app/services/content_factory.py +++ b/gateway/app/services/content_factory.py @@ -1170,7 +1170,7 @@ async def launch_campaign( ) schedule_summary = { "total_scheduled": len(recommendations), - "platforms": list(set(r.platform for r in recommendations)), + "platforms": list({r.platform for r in recommendations}), "date_range": ( f"{recommendations[0].recommended_time.strftime('%Y-%m-%d')} to " f"{recommendations[-1].recommended_time.strftime('%Y-%m-%d')}" diff --git a/gateway/app/services/mcp_dispatch_attempts.py b/gateway/app/services/mcp_dispatch_attempts.py index 3531b15c..fd3780b4 100644 --- a/gateway/app/services/mcp_dispatch_attempts.py +++ b/gateway/app/services/mcp_dispatch_attempts.py @@ -131,7 +131,7 @@ async def count_duplicate_denial_receipts() -> tuple[int | None, str | None]: _query_duplicate_denial_receipts(), timeout=DUPLICATE_DENIAL_COUNT_TIMEOUT_SECONDS, ) - except (RuntimeError, SQLAlchemyError, asyncio.TimeoutError) as exc: + except (TimeoutError, RuntimeError, SQLAlchemyError) as exc: logger.warning("duplicate_denial_count_unavailable: %s", type(exc).__name__) return None, type(exc).__name__ return count, None diff --git a/gateway/app/services/media_engine.py b/gateway/app/services/media_engine.py index 359ca27b..6d8202c9 100644 --- a/gateway/app/services/media_engine.py +++ b/gateway/app/services/media_engine.py @@ -161,7 +161,7 @@ async def _generate_synthetic_hooks(self, video: StoredVideo) -> list[ViralHook] ("retention_signal", "Strong opening hook with pattern interrupt"), ] - for i in range(num_hooks): + for _ in range(num_hooks): start = random.uniform(0, max(0, duration - self._max_hook_duration)) hook_dur = random.uniform(self._min_hook_duration, self._max_hook_duration) trigger = random.choice(trigger_types) diff --git a/gateway/app/services/pods.py b/gateway/app/services/pods.py index e748828a..f8398361 100644 --- a/gateway/app/services/pods.py +++ b/gateway/app/services/pods.py @@ -200,7 +200,9 @@ async def create_pod( ) provisioned: list[ProvisionedMember] = [] - for member, member_budget in zip(members, member_budgets): + for member, member_budget in zip( + members, member_budgets, strict=True + ): agent_id_in_flight = ( member.agent_id or f"pod-member-{len(provisioned)}" ) diff --git a/gateway/app/services/receipts.py b/gateway/app/services/receipts.py index 6305a5b5..dd6cfd40 100644 --- a/gateway/app/services/receipts.py +++ b/gateway/app/services/receipts.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json import re import uuid from decimal import Decimal @@ -65,9 +66,6 @@ class ReceiptWriteContendedError(RuntimeError): reason = "receipt_write_contended" -import json - - def _loads_dict(value: str | None) -> dict[str, Any]: try: decoded = json.loads(value or "{}") diff --git a/gateway/app/services/sandbox.py b/gateway/app/services/sandbox.py index 4c97b676..68ea087c 100644 --- a/gateway/app/services/sandbox.py +++ b/gateway/app/services/sandbox.py @@ -413,7 +413,7 @@ def _evaluate_action( if action_type == "submit_transform": # Check if agent guessed the transformation rule guess = str(action_value).lower() - for rule in env.hidden_rules: + for _rule in env.hidden_rules: if any( keyword in guess for keyword in [ diff --git a/gateway/app/services/telemetry_pm.py b/gateway/app/services/telemetry_pm.py index 471dd8aa..25f36cb1 100644 --- a/gateway/app/services/telemetry_pm.py +++ b/gateway/app/services/telemetry_pm.py @@ -561,7 +561,7 @@ async def _detect_error_spike(self, owner: str | None) -> AnomalyCandidate | Non ) if baseline_rate > 0 and recent_rate > baseline_rate * 3: - sources = set(se.event.source for se in recent_errors) + sources = {se.event.source for se in recent_errors} return AnomalyCandidate( category="error_spike", severity=Severity.HIGH, diff --git a/gateway/app/services/upstream_mcp.py b/gateway/app/services/upstream_mcp.py index 9e26f980..f503ce58 100644 --- a/gateway/app/services/upstream_mcp.py +++ b/gateway/app/services/upstream_mcp.py @@ -76,7 +76,7 @@ "calls_total": 0, "latency_seconds_total": 0.0, "latency_seconds_max": 0.0, - "outcomes": {outcome: 0 for outcome in _CALL_OUTCOMES}, + "outcomes": dict.fromkeys(_CALL_OUTCOMES, 0), } diff --git a/gateway/app/trust/receipts.py b/gateway/app/trust/receipts.py index 43a6a113..9d6c3bf5 100644 --- a/gateway/app/trust/receipts.py +++ b/gateway/app/trust/receipts.py @@ -123,7 +123,7 @@ async def _drain(self) -> None: break try: batch.append(await asyncio.wait_for(self._queue.get(), timeout)) - except asyncio.TimeoutError: + except TimeoutError: break for kwargs, future in batch: try: diff --git a/gateway/failure_lab/claims.py b/gateway/failure_lab/claims.py index 4e2c3892..7b1d520a 100644 --- a/gateway/failure_lab/claims.py +++ b/gateway/failure_lab/claims.py @@ -37,7 +37,9 @@ from typing import Any from failure_lab import TEST_DEFINITION_VERSION -from failure_lab import __version__ as FAILURE_LAB_VERSION +from failure_lab import __version__ + +FAILURE_LAB_VERSION = __version__ CLAIMS_SCHEMA_VERSION = "failure-lab-claims/1" diff --git a/gateway/failure_lab/cli.py b/gateway/failure_lab/cli.py index 5af9b841..61cc0633 100644 --- a/gateway/failure_lab/cli.py +++ b/gateway/failure_lab/cli.py @@ -49,7 +49,7 @@ from typing import Any from failure_lab import TEST_DEFINITION_VERSION -from failure_lab import __version__ as LAB_VERSION +from failure_lab import __version__ from failure_lab.evidence import SecretLeakError from failure_lab.runner import ( BUNDLE_DIRECTORY_NAME, @@ -61,6 +61,8 @@ ) from failure_lab.telemetry import TrafficSource +LAB_VERSION = __version__ + PROGRAM = "python -m failure_lab" #: Exit code for a CLI-level problem -- a name that does not resolve, a diff --git a/gateway/failure_lab/evidence.py b/gateway/failure_lab/evidence.py index 643b0c53..9101a83a 100644 --- a/gateway/failure_lab/evidence.py +++ b/gateway/failure_lab/evidence.py @@ -66,7 +66,9 @@ from typing import TYPE_CHECKING, Any from failure_lab import TEST_DEFINITION_VERSION -from failure_lab import __version__ as FAILURE_LAB_VERSION +from failure_lab import __version__ + +FAILURE_LAB_VERSION = __version__ if TYPE_CHECKING: # pragma: no cover - typing only, keeps the import graph light from failure_lab.scenarios.base import ScenarioResult diff --git a/gateway/failure_lab/refund_tool.py b/gateway/failure_lab/refund_tool.py index 19e1cae1..5804ea77 100644 --- a/gateway/failure_lab/refund_tool.py +++ b/gateway/failure_lab/refund_tool.py @@ -190,7 +190,7 @@ class _BearerAuth: def __init__(self, app: Any, token: str) -> None: self._app = app - self._expected = f"Bearer {token}".encode("utf-8") + self._expected = f"Bearer {token}".encode() async def __call__(self, scope: Any, receive: Any, send: Any) -> None: if scope.get("type") == "http": diff --git a/gateway/failure_lab/runner.py b/gateway/failure_lab/runner.py index e7292d9d..71215161 100644 --- a/gateway/failure_lab/runner.py +++ b/gateway/failure_lab/runner.py @@ -70,7 +70,7 @@ from typing import Any from failure_lab import TEST_DEFINITION_VERSION -from failure_lab import __version__ as LAB_VERSION +from failure_lab import __version__ from failure_lab.claims import ( ClaimsManifest, build_claims_manifest, @@ -112,6 +112,8 @@ pseudonymous_subject, ) +LAB_VERSION = __version__ + #: The run agreed with every documented expectation and nothing errored. EXIT_OK = 0 #: At least one observed verdict differs from what the suite documents -- diff --git a/gateway/failure_lab/telemetry.py b/gateway/failure_lab/telemetry.py index 3a69b9d2..10d3b2cc 100644 --- a/gateway/failure_lab/telemetry.py +++ b/gateway/failure_lab/telemetry.py @@ -258,7 +258,7 @@ def pseudonymous_subject(raw: str, *, namespace: str = "failure-lab") -> str: sees it. Hashing an email address would still be an identifier for a person, so do not: pass the session, not the human. """ - digest = hashlib.sha256(f"{namespace}\x1f{raw}".encode("utf-8")).hexdigest() + digest = hashlib.sha256(f"{namespace}\x1f{raw}".encode()).hexdigest() return f"sub_{digest[:24]}" diff --git a/gateway/ruff.toml b/gateway/ruff.toml index 3730ce95..af02f36b 100644 --- a/gateway/ruff.toml +++ b/gateway/ruff.toml @@ -1,7 +1,46 @@ target-version = "py311" [lint] -# Pyflakes: unused imports/variables, undefined names, redefinitions. -# Previously this file selected only E501 and then immediately ignored it, -# so `ruff check` passed with zero rules actually enforced. -select = ["F"] +# Match sdk/python (pyproject.toml [tool.ruff.lint]): pycodestyle, pyflakes, +# warnings, isort, pep8-naming, pyupgrade, bugbear, and comprehensions. +# CI runs `ruff check` (.github/workflows/ci.yml). Selecting only F let bug +# classes such as B023, B905, and E402 pass with the rest of this set. +select = ["E", "F", "W", "I", "N", "UP", "B", "C4"] +ignore = [ + # Same as the SDK. `ruff format --check` is the formatting gate. + "E501", + # Existing import-sort backlog across the tree. A mechanical isort rewrite + # is a separate change, so I001 does not gate new code until that lands. + "I001", + # Pyupgrade backlog. UP042 (StrEnum) and UP035 (moved imports) can change + # runtime behavior; UP007/UP017/UP045 are annotation and datetime rewrites + # large enough to belong in their own change. + "UP007", + "UP017", + "UP035", + "UP042", + "UP045", + # Exception-chaining backlog: `raise` inside `except` without `from`. + # That changes traceback context only. Fixing it is a separate sweep, + # so B904 does not gate new code until that lands. + "B904", + # `dict()` / `list()` call style. Some rewrites share a mutable value. + "C408", + # Adding an `Error` suffix renames public exception types. + "N818", +] + +[lint.flake8-bugbear] +# FastAPI requires these calls in parameter defaults. B008 still flags every +# other function call used as a default (the shared mutable-default bug). +extend-immutable-calls = [ + "fastapi.Depends", + "fastapi.Query", + "fastapi.Path", + "fastapi.Header", + "fastapi.Body", + "fastapi.File", + "fastapi.Form", + "fastapi.Security", + "fastapi.Cookie", +] diff --git a/gateway/scripts/agent_self_credential_proof.py b/gateway/scripts/agent_self_credential_proof.py index 14ade530..3ee7638e 100644 --- a/gateway/scripts/agent_self_credential_proof.py +++ b/gateway/scripts/agent_self_credential_proof.py @@ -92,7 +92,7 @@ async def verify_receipts(cl: httpx.AsyncClient, receipt_ids: list[str]) -> None pub = Ed25519PublicKey.from_public_bytes(base64.b64decode(keys[p["kid"]])) sig = base64.b64decode(p["signature"]) - def verifies(message: bytes) -> bool: + def verifies(message: bytes, pub=pub, sig=sig) -> bool: try: pub.verify(sig, message) except InvalidSignature: diff --git a/gateway/scripts/awi_representation_benchmark.py b/gateway/scripts/awi_representation_benchmark.py index 461592ed..b541a2b6 100644 --- a/gateway/scripts/awi_representation_benchmark.py +++ b/gateway/scripts/awi_representation_benchmark.py @@ -21,8 +21,11 @@ if str(_REPO_ROOT) not in sys.path: sys.path.insert(0, str(_REPO_ROOT)) -from app.schemas.awi import AWIRepresentationType -from app.services.awi_representation import ProgressiveRepresentationEngine +# Imports follow the sys.path insert above. +from app.schemas.awi import AWIRepresentationType # noqa: E402 +from app.services.awi_representation import ( # noqa: E402 + ProgressiveRepresentationEngine, +) EXPECTED_TERMS = ( diff --git a/gateway/scripts/check_doc_references.py b/gateway/scripts/check_doc_references.py index 6e382b69..d98f838a 100644 --- a/gateway/scripts/check_doc_references.py +++ b/gateway/scripts/check_doc_references.py @@ -142,7 +142,7 @@ def _names_in_body(body: list[ast.stmt]) -> set[str]: found.add(statement.target.id) return found - for path, source in sources.items(): + for _path, source in sources.items(): try: tree = ast.parse(source) except SyntaxError: diff --git a/gateway/scripts/invariant_attacks/attack1_double_charge.py b/gateway/scripts/invariant_attacks/attack1_double_charge.py index edeb26bc..9cc5d3f4 100644 --- a/gateway/scripts/invariant_attacks/attack1_double_charge.py +++ b/gateway/scripts/invariant_attacks/attack1_double_charge.py @@ -15,7 +15,7 @@ import json import sys -import attacklib as A +import attacklib as A # noqa: N812 N = 10 MARKER = "atk1-parallel" @@ -65,7 +65,7 @@ def one(i): "error_or_reason": A.reason_of(r), } ) - distinct_receipts = sorted(set(x for x in receipt_ids if x)) + distinct_receipts = sorted({x for x in receipt_ids if x}) winners = sum(1 for p in per_response if p["carried_receipt"]) collapsed = sum(1 for p in per_response if not p["carried_receipt"]) diff --git a/gateway/scripts/invariant_attacks/attack2_budget.py b/gateway/scripts/invariant_attacks/attack2_budget.py index 6f693ea2..8b8c217f 100644 --- a/gateway/scripts/invariant_attacks/attack2_budget.py +++ b/gateway/scripts/invariant_attacks/attack2_budget.py @@ -17,7 +17,7 @@ import json import sys -import attacklib as A +import attacklib as A # noqa: N812 def sequential_boundary(): @@ -77,7 +77,7 @@ def sequential_boundary(): } -def concurrency_race(N=10, cap=7): +def concurrency_race(N=10, cap=7): # noqa: N803 cred = A.provision("atk2b-budget-race") permit = A.issue_permit( cred, diff --git a/gateway/scripts/invariant_attacks/attack2_budget_postgres.py b/gateway/scripts/invariant_attacks/attack2_budget_postgres.py index d4a35b36..3ba1fd9e 100644 --- a/gateway/scripts/invariant_attacks/attack2_budget_postgres.py +++ b/gateway/scripts/invariant_attacks/attack2_budget_postgres.py @@ -7,12 +7,12 @@ import json import sys -import attacklib as A +import attacklib as A # noqa: N812 A.API = os.environ.get("API_URL", "http://127.0.0.1:8001") # PG-backed instance -def race(cap, N, label): +def race(cap, N, label): # noqa: N803 cred = A.provision(f"pgrace-{label}") p = A.issue_permit( cred, diff --git a/gateway/scripts/invariant_attacks/attack2_mechanism_sqlite.py b/gateway/scripts/invariant_attacks/attack2_mechanism_sqlite.py index 3c93cb9d..22bef615 100644 --- a/gateway/scripts/invariant_attacks/attack2_mechanism_sqlite.py +++ b/gateway/scripts/invariant_attacks/attack2_mechanism_sqlite.py @@ -12,7 +12,7 @@ import json import sys -import attacklib as A +import attacklib as A # noqa: N812 CAP = 6 # cost 2/call -> cap should allow exactly 3 successful calls N = 8 diff --git a/gateway/scripts/invariant_attacks/attack3_scope.py b/gateway/scripts/invariant_attacks/attack3_scope.py index 16d8ea53..2306c9b3 100644 --- a/gateway/scripts/invariant_attacks/attack3_scope.py +++ b/gateway/scripts/invariant_attacks/attack3_scope.py @@ -14,7 +14,7 @@ import json import sys -import attacklib as A +import attacklib as A # noqa: N812 MARK = "atk3-scope" diff --git a/gateway/scripts/invariant_attacks/attack4_forgery.py b/gateway/scripts/invariant_attacks/attack4_forgery.py index e0cd9a70..ab1f6c27 100644 --- a/gateway/scripts/invariant_attacks/attack4_forgery.py +++ b/gateway/scripts/invariant_attacks/attack4_forgery.py @@ -17,7 +17,7 @@ import json import subprocess import sys -import attacklib as A +import attacklib as A # noqa: N812 import os import tempfile diff --git a/gateway/scripts/invariant_attacks/attack5_crash_sqlite.py b/gateway/scripts/invariant_attacks/attack5_crash_sqlite.py index ca267a8a..e7184c36 100644 --- a/gateway/scripts/invariant_attacks/attack5_crash_sqlite.py +++ b/gateway/scripts/invariant_attacks/attack5_crash_sqlite.py @@ -32,7 +32,7 @@ from concurrent.futures import ThreadPoolExecutor from decimal import Decimal -import attacklib as A +import attacklib as A # noqa: N812 SCRATCH = os.environ.get("TP_WORKDIR", os.getcwd()) PIDFILE = os.environ.get("TP_PIDFILE", f"{SCRATCH}/server.pid") @@ -235,7 +235,7 @@ def worker(): request_tag = attempted[idx] run_worker_request( request_tag, - lambda: A.invoke( + lambda request_tag=request_tag, idx=idx: A.invoke( cred["api_key"], wallet, pid_c, diff --git a/gateway/scripts/invariant_attacks/attack6_key_misuse.py b/gateway/scripts/invariant_attacks/attack6_key_misuse.py index 49c4e5f3..0257826d 100644 --- a/gateway/scripts/invariant_attacks/attack6_key_misuse.py +++ b/gateway/scripts/invariant_attacks/attack6_key_misuse.py @@ -12,7 +12,7 @@ import json import sys -import attacklib as A +import attacklib as A # noqa: N812 MARK = "atk6" @@ -25,18 +25,18 @@ def matches_response(resp, *, status, reason=None): def main(): - A_cred = A.provision("atk6-alice") - B_cred = A.provision("atk6-bob") + A_cred = A.provision("atk6-alice") # noqa: N806 + B_cred = A.provision("atk6-bob") # noqa: N806 # A permit legitimately owned/bound by A - permitA = A.issue_permit( + permitA = A.issue_permit( # noqa: N806 A_cred, allowed_tools=["partner.notes.write"], scopes=["tool:partner.notes.write:invoke", "billing:charge"], max_credits=100, idem="atk6-permitA", )["json"] - pidA = permitA["permit_id"] + pidA = permitA["permit_id"] # noqa: N806 notes0 = A.notes_count_for(MARK) diff --git a/gateway/scripts/invariant_attacks/attack_combined.py b/gateway/scripts/invariant_attacks/attack_combined.py index a1feafe0..1557446e 100644 --- a/gateway/scripts/invariant_attacks/attack_combined.py +++ b/gateway/scripts/invariant_attacks/attack_combined.py @@ -63,7 +63,7 @@ from collections import Counter from concurrent.futures import ThreadPoolExecutor -import attacklib as A +import attacklib as A # noqa: N812 SCRATCH = os.environ.get("TP_WORKDIR", os.getcwd()) PIDFILE = os.environ.get("TP_PIDFILE", f"{SCRATCH}/server.pid") @@ -417,7 +417,7 @@ def record_xtenant(resp): ) ) for k in dedup_keys: - for j in range(DEDUP_FANOUT): + for _ in range(DEDUP_FANOUT): jobs.append( ( "dedup", @@ -571,7 +571,7 @@ def worker(): with q_lock: worker_errors[tag] += 1 - WORKERS = 24 + worker_count = 24 pid_before = current_pid() killed = {"at_debits": None, "pid": pid_before} @@ -599,8 +599,8 @@ def killer(): kt = threading.Thread(target=killer) kt.start() - with ThreadPoolExecutor(max_workers=WORKERS) as ex: - futs = [ex.submit(worker) for _ in range(WORKERS)] + with ThreadPoolExecutor(max_workers=worker_count) as ex: + futs = [ex.submit(worker) for _ in range(worker_count)] for f in futs: try: f.result(timeout=60) @@ -630,14 +630,14 @@ def killer(): # REPLAY every load + budget key once, identical payload replay_hist = Counter() - for i, k in enumerate(load_keys): + for _i, k in enumerate(load_keys): r = A.invoke( victim["api_key"], wallet, p_load, k, f"load {k}", rpc_id=f"replay-{k}" ) replay_hist[ "receipt" if A.receipt_of(r) else (A.reason_of(r) or "conn_err") ] += 1 - for i, k in enumerate(budget_keys): + for _i, k in enumerate(budget_keys): r = A.invoke( victim["api_key"], wallet, p_budget, k, f"budget {k}", rpc_id=f"replay-{k}" ) diff --git a/gateway/scripts/load_battery.py b/gateway/scripts/load_battery.py index 14e2a74f..865a0a05 100644 --- a/gateway/scripts/load_battery.py +++ b/gateway/scripts/load_battery.py @@ -299,7 +299,7 @@ async def fire(i: int) -> tuple[float, dict[str, Any] | None, str]: tasks = [asyncio.create_task(fire(i)) for i in range(total_requests)] outcomes = await asyncio.gather(*tasks) - for elapsed_ms, body, idem_key in outcomes: + for elapsed_ms, body, _idem_key in outcomes: result.latencies_ms.append(elapsed_ms) if body is None: result.error_count += 1 diff --git a/gateway/scripts/test_asyncpg_offset.py b/gateway/scripts/test_asyncpg_offset.py index bb353b10..aeb8aa73 100644 --- a/gateway/scripts/test_asyncpg_offset.py +++ b/gateway/scripts/test_asyncpg_offset.py @@ -27,17 +27,19 @@ os.environ["ENABLE_DOGFOOD_TOOL"] = "true" os.environ["VALID_API_KEYS"] = "pg-test-key" -import httpx +# Settings and the engine are captured at import. Env must be set first. +import httpx # noqa: E402 -from app.core import config as config_module -from app.db import database as database_module +from app.core import config as config_module # noqa: E402 +from app.db import database as database_module # noqa: E402 config_module.get_settings.cache_clear() database_module._engine = None database_module._session_factory = None -import importlib -import app.main as main_module +import importlib # noqa: E402 + +import app.main as main_module # noqa: E402 importlib.reload(main_module) diff --git a/gateway/tests/support/mcp_remote_partner_app.py b/gateway/tests/support/mcp_remote_partner_app.py index 72e482f9..b21a4362 100644 --- a/gateway/tests/support/mcp_remote_partner_app.py +++ b/gateway/tests/support/mcp_remote_partner_app.py @@ -244,7 +244,7 @@ class _BearerAuthMiddleware: def __init__(self, wrapped_app: Any) -> None: self._wrapped_app = wrapped_app - self._expected = f"Bearer {_BEARER_TOKEN}".encode("utf-8") + self._expected = f"Bearer {_BEARER_TOKEN}".encode() async def __call__(self, scope: Any, receive: Any, send: Any) -> None: path = str(scope.get("path", "")) diff --git a/gateway/tests/test_action_multiprocess.py b/gateway/tests/test_action_multiprocess.py index 05caba0e..dc4c7f02 100644 --- a/gateway/tests/test_action_multiprocess.py +++ b/gateway/tests/test_action_multiprocess.py @@ -39,7 +39,9 @@ async def harness(tmp_path_factory): async with get_engine().connect() as conn: await stress._assert_migrated_empty_database(conn) for table in ("permits", "receipts"): - cols = await conn.run_sync(lambda c: inspect(c).get_columns(table)) + cols = await conn.run_sync( + lambda c, table=table: inspect(c).get_columns(table) + ) assert set(FIELDS) <= {c["name"] for c in cols}, "stale 042 schema" async for value in stress.stress_harness.__wrapped__(tmp_path_factory): yield value diff --git a/gateway/tests/test_action_postgres.py b/gateway/tests/test_action_postgres.py index 33db7b8a..0785351a 100644 --- a/gateway/tests/test_action_postgres.py +++ b/gateway/tests/test_action_postgres.py @@ -41,7 +41,9 @@ async def require_fresh_action_schema(): async with get_engine().connect() as connection: await _assert_migrated_empty_database(connection) for table in ("permits", "receipts"): - columns = await connection.run_sync(lambda c: inspect(c).get_columns(table)) + columns = await connection.run_sync( + lambda c, table=table: inspect(c).get_columns(table) + ) assert set(FIELDS) <= {c["name"] for c in columns}, "stale 042 schema" diff --git a/gateway/tests/test_awi_phase9.py b/gateway/tests/test_awi_phase9.py index 7bb8c4a2..b21aa4d5 100644 --- a/gateway/tests/test_awi_phase9.py +++ b/gateway/tests/test_awi_phase9.py @@ -476,7 +476,7 @@ def test_semantic_patterns_exist(self, bridge): def test_semantic_pattern_has_tags(self, bridge): """Test that semantic patterns have tags.""" - for semantic_type, pattern in bridge.SEMANTIC_PATTERNS.items(): + for _semantic_type, pattern in bridge.SEMANTIC_PATTERNS.items(): assert "tags" in pattern assert len(pattern["tags"]) > 0 @@ -865,8 +865,8 @@ def fake_persistent_client(**_kwargs): chromadb_module = ModuleType("chromadb") chromadb_config_module = ModuleType("chromadb.config") - setattr(chromadb_module, "PersistentClient", fake_persistent_client) - setattr(chromadb_config_module, "Settings", lambda **_kwargs: object()) + chromadb_module.PersistentClient = fake_persistent_client + chromadb_config_module.Settings = lambda **_kwargs: object() monkeypatch.setitem(sys.modules, "chromadb", chromadb_module) monkeypatch.setitem(sys.modules, "chromadb.config", chromadb_config_module) diff --git a/gateway/tests/test_billing.py b/gateway/tests/test_billing.py index b2d15dad..6be1134a 100644 --- a/gateway/tests/test_billing.py +++ b/gateway/tests/test_billing.py @@ -1250,7 +1250,6 @@ async def test_rejected_charge_does_not_inflate_velocity_counters( """A charge rejected for insufficient funds must not leave the wallet's hourly/daily spend counters inflated (which would trip false daily-limit rejections and velocity auto-freeze despite zero real spend).""" - from decimal import Decimal as _D from app.db.database import get_session_factory from app.db.models import WalletModel @@ -1282,8 +1281,8 @@ async def test_rejected_charge_does_not_inflate_velocity_counters( async with factory() as session: wallet = await session.get(WalletModel, wallet_id) # All three rejected charges were reversed out of the counters. - assert wallet.daily_spent == _D("0") - assert wallet.hourly_spent == _D("0") + assert wallet.daily_spent == Decimal("0") + assert wallet.hourly_spent == Decimal("0") @pytest.mark.anyio diff --git a/gateway/tests/test_constant_test_loop.py b/gateway/tests/test_constant_test_loop.py index f68c5d54..03758f5d 100644 --- a/gateway/tests/test_constant_test_loop.py +++ b/gateway/tests/test_constant_test_loop.py @@ -301,11 +301,11 @@ def test_partner_api_key_bootstrap_json_output(): """Bootstrap script produces JSON-parseable output and never prints the key.""" import json - BOOTSTRAP = ROOT / "scripts" / "partner_api_key_bootstrap.py" + bootstrap_script = ROOT / "scripts" / "partner_api_key_bootstrap.py" # Run with --json flag result = subprocess.run( - [sys.executable, str(BOOTSTRAP), "--json"], + [sys.executable, str(bootstrap_script), "--json"], input=json.dumps( { "api_url": "http://127.0.0.1:8000", @@ -709,7 +709,7 @@ def test_malformed_tool_args_are_a_configuration_error(monkeypatch): """Exit 2, not 1: a bad payload means misconfiguration, not a broken plane.""" module = _load_loop() resolve = module["_resolve_tool_args"] - ConfigurationError = module["ConfigurationError"] + ConfigurationError = module["ConfigurationError"] # noqa: N806 with pytest.raises(ConfigurationError, match="not valid JSON"): resolve("{not json") diff --git a/gateway/tests/test_discover_kyc_honesty.py b/gateway/tests/test_discover_kyc_honesty.py index c1d77215..1c87a887 100644 --- a/gateway/tests/test_discover_kyc_honesty.py +++ b/gateway/tests/test_discover_kyc_honesty.py @@ -104,7 +104,7 @@ async def test_discover_and_health_stripe_truth_match( """Discover's KYC presence must match health's stripe.status truth.""" import app.routers.discover as discover_mod - for stripe_key, expected_health_status, should_have_kyc in [ + for stripe_key, _expected_health_status, _should_have_kyc in [ ("", "not_configured", False), ("sk_test_fake", "not_configured", True), ]: diff --git a/gateway/tests/test_discover_phase9.py b/gateway/tests/test_discover_phase9.py index 459e532d..1343ebbf 100644 --- a/gateway/tests/test_discover_phase9.py +++ b/gateway/tests/test_discover_phase9.py @@ -201,7 +201,7 @@ class TestLlmTxt: def test_llm_txt_has_trust_plane_quickstart(self): """Verify llm.txt points at permit → invoke → receipt, not proof stubs.""" - with open("static/llm.txt", "r") as f: + with open("static/llm.txt") as f: content = f.read() assert "Agent-first" in content, "Missing agent-first preamble" @@ -217,7 +217,7 @@ def test_llm_txt_stays_wedge_only(self): llm.txt should meet only the governed permit → invoke → receipt loop. The proof-surface catalog lives in docs/PROOF_SURFACES.md. """ - with open("static/llm.txt", "r") as f: + with open("static/llm.txt") as f: content = f.read() assert "/v1/permits" in content diff --git a/gateway/tests/test_duplicate_guard_postgres_concurrency.py b/gateway/tests/test_duplicate_guard_postgres_concurrency.py index 8577cbe7..8009259d 100644 --- a/gateway/tests/test_duplicate_guard_postgres_concurrency.py +++ b/gateway/tests/test_duplicate_guard_postgres_concurrency.py @@ -169,7 +169,7 @@ async def test_parallel_identical_new_keys_enforce_mode_one_dispatch(monkeypatch permit_id = permit_resp.json()["permit_id"] # Launch N parallel calls with DIFFERENT keys but IDENTICAL arguments - N = 5 + N = 5 # noqa: N806 identical_message = "duplicate-me-parallel" async def make_call(idx: int): diff --git a/gateway/tests/test_framework_legacy_tools.py b/gateway/tests/test_framework_legacy_tools.py index 7457f374..e9bfc2d4 100644 --- a/gateway/tests/test_framework_legacy_tools.py +++ b/gateway/tests/test_framework_legacy_tools.py @@ -135,7 +135,7 @@ def from_defaults( name: str | None = None, async_fn: Any = None, **_: Any, - ) -> "_StubFunctionTool": + ) -> _StubFunctionTool: return cls(fn, async_fn, name) async def acall(self, **kwargs: Any) -> Any: diff --git a/gateway/tests/test_gateway_auth_breakit.py b/gateway/tests/test_gateway_auth_breakit.py index 8c344247..f9322799 100644 --- a/gateway/tests/test_gateway_auth_breakit.py +++ b/gateway/tests/test_gateway_auth_breakit.py @@ -14,6 +14,7 @@ import jwt as pyjwt import pytest from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +from fastapi import HTTPException from httpx import ASGITransport, AsyncClient from app.core.auth import get_auth_context @@ -158,7 +159,7 @@ async def test_jwt_signed_by_wrong_key_rejected(signing_key): other, algorithm="EdDSA", ) - with pytest.raises(Exception): + with pytest.raises(HTTPException): await get_auth_context(api_key=None, authorization=f"Bearer {token}") @@ -356,7 +357,7 @@ async def test_expired_jwt_rejected(signing_key): private, algorithm="EdDSA", ) - with pytest.raises(Exception): + with pytest.raises(HTTPException): await get_auth_context(api_key=None, authorization=f"Bearer {token}") diff --git a/gateway/tests/test_health_dependencies.py b/gateway/tests/test_health_dependencies.py index 11eb5c0c..3fc84563 100644 --- a/gateway/tests/test_health_dependencies.py +++ b/gateway/tests/test_health_dependencies.py @@ -128,7 +128,7 @@ async def test_report_default_shape(): "sentinel", } # Every check carries an error slot (None when healthy) and latency_ms. - for name, res in report["dependencies"].items(): + for _name, res in report["dependencies"].items(): assert "status" in res assert "latency_ms" in res assert "error" in res diff --git a/gateway/tests/test_invariant_attack_harness.py b/gateway/tests/test_invariant_attack_harness.py index 2ed5bfca..de9b80cb 100644 --- a/gateway/tests/test_invariant_attack_harness.py +++ b/gateway/tests/test_invariant_attack_harness.py @@ -375,14 +375,14 @@ def test_attack2_mechanism_flags_lost_update_without_overspend( def test_combined_requires_every_storm_variant_to_start() -> None: - started = Counter({tag: 1 for tag in combined.REQUIRED_STORM_VECTOR_TAGS}) + started = Counter(dict.fromkeys(combined.REQUIRED_STORM_VECTOR_TAGS, 1)) assert combined.all_required_storm_vectors_started(started) @pytest.mark.parametrize("missing", combined.REQUIRED_STORM_VECTOR_TAGS) def test_combined_fails_closed_when_storm_variant_did_not_start(missing: str) -> None: - started = Counter({tag: 1 for tag in combined.REQUIRED_STORM_VECTOR_TAGS}) + started = Counter(dict.fromkeys(combined.REQUIRED_STORM_VECTOR_TAGS, 1)) del started[missing] assert not combined.all_required_storm_vectors_started(started) diff --git a/gateway/tests/test_ledger_write_contention.py b/gateway/tests/test_ledger_write_contention.py index fada7ecf..c3f5c16f 100644 --- a/gateway/tests/test_ledger_write_contention.py +++ b/gateway/tests/test_ledger_write_contention.py @@ -122,7 +122,7 @@ def __init__(self, n: int | None, error: OperationalError | None = None) -> None self._error = error or _lock_error() self._real = AsyncSession.flush - def patch(self, monkeypatch: Any) -> "_FlushFault": + def patch(self, monkeypatch: Any) -> _FlushFault: fault = self async def flush(session: AsyncSession, *args: Any, **kwargs: Any) -> Any: diff --git a/gateway/tests/test_mcp_legacy_envelope_validation.py b/gateway/tests/test_mcp_legacy_envelope_validation.py index ac13756d..f6b29294 100644 --- a/gateway/tests/test_mcp_legacy_envelope_validation.py +++ b/gateway/tests/test_mcp_legacy_envelope_validation.py @@ -195,8 +195,9 @@ async def test_non_finite_argument_is_refused_before_execution( params = json.dumps( _params(agent, idempotency_key="nonfinite-arg-1", text="__NAN__") ) + # Concatenated, not str.format: `params` is JSON and its braces are data. raw = ( - '{"jsonrpc":"2.0","id":"x","method":"tools/call","params":%s}' % params + '{"jsonrpc":"2.0","id":"x","method":"tools/call","params":' + params + "}" ).replace('"__NAN__"', "NaN") resp = await client.post( "/mcp/messages", diff --git a/gateway/tests/test_mcp_transport_hardening.py b/gateway/tests/test_mcp_transport_hardening.py index e78dca7e..154004b2 100644 --- a/gateway/tests/test_mcp_transport_hardening.py +++ b/gateway/tests/test_mcp_transport_hardening.py @@ -126,11 +126,18 @@ async def caller(client, standard_mcp_enabled, clean_database, effects): def _standard_raw(raw_id: str, meta_key: str = '"k-1"') -> bytes: + # Concatenated so the JSON braces are not format fields. return ( - '{"jsonrpc":"2.0","id":%s,"method":"tools/call","params":{"name":"%s",' - '"arguments":{"text":"one"},"_meta":{"%s":%s}}}' - % (raw_id, TOOL, META_KEY, meta_key) - ).encode("utf-8") + '{"jsonrpc":"2.0","id":' + + raw_id + + ',"method":"tools/call","params":{"name":"' + + TOOL + + '","arguments":{"text":"one"},"_meta":{"' + + META_KEY + + '":' + + meta_key + + "}}}" + ).encode() def _standard_call(*, meta: Any = None, context: Any = None) -> dict: @@ -254,7 +261,7 @@ async def test_standard_non_utf8_header_key_is_refused_not_aliased(client, calle assert caller["effects"] == [] assert await _debits(client, caller["provisioned"]) == 0 - utf8 = {**caller["headers"], "Idempotency-Key": "é-1".encode("utf-8")} + utf8 = {**caller["headers"], "Idempotency-Key": "é-1".encode()} first = _receipt_id(await client.post("/mcp", json=_standard_call(), headers=utf8)) replay = _receipt_id(await client.post("/mcp", json=_standard_call(), headers=utf8)) assert first == replay diff --git a/gateway/tests/test_postgres_datetime_binding.py b/gateway/tests/test_postgres_datetime_binding.py index 379493fd..57cdc049 100644 --- a/gateway/tests/test_postgres_datetime_binding.py +++ b/gateway/tests/test_postgres_datetime_binding.py @@ -26,6 +26,7 @@ from app.core.time import to_naive_utc, utc_now from app.db.database import _normalize_bind_parameters +from tests.support import db_gate class TestToNaiveUtc: @@ -74,8 +75,6 @@ def test_scalar_and_none_parameters_are_safe(self): assert _normalize_bind_parameters("raw") == "raw" -from tests.support import db_gate - POSTGRES_URL = os.environ.get("TEST_POSTGRES_URL", "").strip() diff --git a/gateway/tests/test_preflight.py b/gateway/tests/test_preflight.py index d11fcdf4..cec01720 100644 --- a/gateway/tests/test_preflight.py +++ b/gateway/tests/test_preflight.py @@ -58,6 +58,14 @@ async def test_preflight_default_is_no_go(client): assert data["critical_failures"] > 0 +@pytest.mark.anyio +async def test_preflight_omitted_body_stays_optional(client): + """No JSON body still runs the empty-override sweep.""" + resp = await client.post("/v1/launch/preflight", headers=HEADERS) + assert resp.status_code == 200 + assert resp.json()["verdict"] == "NO-GO" + + @pytest.mark.anyio async def test_preflight_detects_placeholder_base_url(client): """Preflight should flag yourdomain.com as a placeholder.""" diff --git a/gateway/tests/test_qa_svc_money_gaps.py b/gateway/tests/test_qa_svc_money_gaps.py index c4e83913..12bf254b 100644 --- a/gateway/tests/test_qa_svc_money_gaps.py +++ b/gateway/tests/test_qa_svc_money_gaps.py @@ -193,14 +193,14 @@ async def test_non_finite_transfer_moves_no_money(clean_database): money = get_agent_money() sponsor, agent = await _funded_agent(money) before = (await money.get_wallet(agent.wallet_id)).balance - with pytest.raises(Exception): + with pytest.raises(ValueError): await money.transfer( from_wallet_id=agent.wallet_id, to_wallet_id=sponsor.wallet_id, amount=Decimal("Infinity"), caller=CallerIdentity.for_wallet(agent.wallet_id), ) - with pytest.raises(Exception): + with pytest.raises(ValueError): await money.transfer( from_wallet_id=agent.wallet_id, to_wallet_id=sponsor.wallet_id, diff --git a/gateway/tests/test_quickstart_path.py b/gateway/tests/test_quickstart_path.py index 7e99d3a3..e938dfcb 100644 --- a/gateway/tests/test_quickstart_path.py +++ b/gateway/tests/test_quickstart_path.py @@ -386,17 +386,17 @@ def test_live_loop_proof_ignores_ambient_proxy(quickstart_server, tmp_path): property. """ dead_proxy = f"http://127.0.0.1:{_free_port()}" - proxy_env = { - var: dead_proxy - for var in ( + proxy_env = dict.fromkeys( + ( "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "all_proxy", - ) - } + ), + dead_proxy, + ) # Clear any ambient loopback exclusion so the proxy vars would otherwise bite. proxy_env["NO_PROXY"] = "" proxy_env["no_proxy"] = "" diff --git a/gateway/tests/test_revocation_containment.py b/gateway/tests/test_revocation_containment.py index d63eddfd..71f1b177 100644 --- a/gateway/tests/test_revocation_containment.py +++ b/gateway/tests/test_revocation_containment.py @@ -323,7 +323,7 @@ async def get_then_wait_for_rivals(self, entity, ident, *args, **kwargs): if armed and entity is RefreshTokenModel: try: await asyncio.wait_for(barrier.wait(), timeout=2) - except (asyncio.TimeoutError, asyncio.BrokenBarrierError): + except (TimeoutError, asyncio.BrokenBarrierError): pass return row diff --git a/gateway/tests/test_ruff_gate.py b/gateway/tests/test_ruff_gate.py new file mode 100644 index 00000000..726f2d91 --- /dev/null +++ b/gateway/tests/test_ruff_gate.py @@ -0,0 +1,27 @@ +"""Lock the gateway ruff gate to the Python SDK rule set. + +CI runs ``ruff check`` with ``gateway/ruff.toml``. Selecting only Pyflakes (F) +lets bug classes such as loop-variable closures (B023), ``zip`` without +``strict`` (B905), and late imports (E402) pass. +""" + +from __future__ import annotations + +import tomllib +from pathlib import Path + +_GATEWAY_ROOT = Path(__file__).resolve().parents[1] +_SDK_SELECT = {"E", "F", "W", "I", "N", "UP", "B", "C4"} +# Rules the lint audit called out by name. A per-code ignore would hide them. +_MUST_REPORT = {"B023", "B905", "E402"} + + +def test_ruff_selects_the_sdk_rule_set() -> None: + config = tomllib.loads((_GATEWAY_ROOT / "ruff.toml").read_text(encoding="utf-8")) + lint = config["lint"] + select = set(lint["select"]) + ignore = set(lint.get("ignore", [])) + + assert _SDK_SELECT <= select + assert _MUST_REPORT.isdisjoint(ignore) + assert "F" in select diff --git a/gateway/tests/test_security_store.py b/gateway/tests/test_security_store.py index b8113275..060e915a 100644 --- a/gateway/tests/test_security_store.py +++ b/gateway/tests/test_security_store.py @@ -297,7 +297,7 @@ async def test_rtaas_job_round_trip_preserves_targets_and_vulns(): assert row is not None assert "Bearer x" not in (row.targets_json or "") assert "auth_header" not in (row.targets_json or "") - assert {c for c in got.attack_categories} == { + assert set(got.attack_categories) == { AttackCategory.INJECTION, AttackCategory.RATE_LIMIT_EVASION, } diff --git a/gateway/tests/test_tenant_isolation_hardening.py b/gateway/tests/test_tenant_isolation_hardening.py index bc70f0af..f8fea0e4 100644 --- a/gateway/tests/test_tenant_isolation_hardening.py +++ b/gateway/tests/test_tenant_isolation_hardening.py @@ -361,16 +361,16 @@ async def test_iot_registration_metadata_cannot_assign_ownership( # B registers a device while naming A as owner in client-controlled # metadata. Ownership comes from the authenticated key, never the body. - spoof = { - key: a["agent_wallet_id"] - for key in ( + spoof = dict.fromkeys( + ( "owner_wallet_id", "_owner_wallet_id", "__owner_wallet_id", "wallet_id", "owner", - ) - } + ), + a["agent_wallet_id"], + ) create = await client.post( "/v1/iot/devices", json=_iot_device(device_id, metadata=spoof), diff --git a/gateway/tests/test_x402.py b/gateway/tests/test_x402.py index 9ec30222..8dcbcfc1 100644 --- a/gateway/tests/test_x402.py +++ b/gateway/tests/test_x402.py @@ -1058,7 +1058,7 @@ async def counter() -> object: # Two concurrent-style reservations, one release: the surviving count is # exactly one — the release never clobbers the other reservation. - for idx in range(2): + for _idx in range(2): validation = await permits.authorize_and_reserve( permit_id=permit_id, wallet_id=wallet_id,