diff --git a/backend/apps/agents/agents.py b/backend/apps/agents/agents.py index 1ae67167..c6e11ab8 100644 --- a/backend/apps/agents/agents.py +++ b/backend/apps/agents/agents.py @@ -8,6 +8,7 @@ from fastapi import HTTPException, Request from typeguard import typechecked from backend.apps.agents.agent_manager import agent_manager +from backend.apps.agents.core.fault_injection import announce as p_announce_armed_faults from backend.apps.agents.core.models import AgentConfig, AgentSession, ApprovalResponse from backend.apps.agents.core.seq_log import seq_log from backend.apps.agents.manager.session.history_compaction import estimate_post_compact_input @@ -24,6 +25,7 @@ p_group_meta_inflight: dict[tuple[str, str], asyncio.Future] = {} @asynccontextmanager async def agents_lifespan(): logger.info("Agents sub-app starting") + p_announce_armed_faults() await agent_manager.reconcile_on_startup() await agent_manager.restore_all_sessions() # Off the critical path: crash-cut turns resume themselves once everything is hydrated. diff --git a/backend/apps/agents/core/fault_injection.py b/backend/apps/agents/core/fault_injection.py index 89abec12..aa2353ff 100644 --- a/backend/apps/agents/core/fault_injection.py +++ b/backend/apps/agents/core/fault_injection.py @@ -12,9 +12,12 @@ the drill reports a pass. OSW_FAULT=policy_block,auth_401 bash run.sh """ +import logging import os from typing import Set +logger = logging.getLogger(__name__) + # Every fault the harness knows. A name outside this set is a typo, not a feature. KNOWN_FAULTS: Set[str] = { "policy_block", # the provider declines the request (ENG-383 failover, ENG-387 doors) @@ -60,3 +63,19 @@ def unknown_faults() -> Set[str]: if not raw: return set() return {p.strip() for p in raw.split(",") if p.strip()} - KNOWN_FAULTS + + +def announce() -> None: + """Say out loud, once at boot, that this process will inject failures, and name any word that + armed nothing. Silence here is the exact row-6 shape this module exists to kill: a typo arms + NOTHING, the drill then exercises the untouched happy path, and the pass is meaningless.""" + raw = os.environ.get("OSW_FAULT", "") + if not raw.strip(): + return + live = sorted({p.strip() for p in raw.split(",") if p.strip()} & KNOWN_FAULTS) + bogus = sorted(unknown_faults()) + logger.warning( + "OSW_FAULT is set: this process will DELIBERATELY INJECT failures. " + f"armed={live or 'NOTHING'}" + + (f"; not a known fault, armed nothing: {bogus}" if bogus else "") + ) diff --git a/backend/tests/test_fault_injection.py b/backend/tests/test_fault_injection.py index b61f84a4..861bf366 100644 --- a/backend/tests/test_fault_injection.py +++ b/backend/tests/test_fault_injection.py @@ -12,7 +12,7 @@ import pytest import builtins from backend.apps.agents.core.fault_injection import ( - KNOWN_FAULTS, armed, armed_once, reset_fired, unknown_faults, + KNOWN_FAULTS, announce, armed, armed_once, reset_fired, unknown_faults, ) from backend.apps.agents.core.error_classify import ( has_auth_status, is_connection_lost, is_content_policy_block, @@ -107,3 +107,23 @@ def test_every_known_fault_is_wired_somewhere(): "a fault this build knows but wires nowhere is a guard that can never be drilled" for kind in WIRED_IN: assert p_block(kind).strip(), f"{kind} has an empty branch" + + +def test_arming_is_announced_and_a_typo_is_named(monkeypatch, caplog): + # The harness built to kill row-6 silence had it: unknown_faults() existed and NOTHING called it, + # so a mistyped name armed nothing while the drill exercised the untouched happy path. + monkeypatch.setenv("OSW_FAULT", "policy_block,plicy_blok") + with caplog.at_level("WARNING"): + announce() + assert "policy_block" in caplog.text and "plicy_blok" in caplog.text + caplog.clear() + monkeypatch.delenv("OSW_FAULT") + with caplog.at_level("WARNING"): + announce() + assert caplog.text == "", "a shipped build must say nothing at all" + + +def test_the_boot_path_actually_announces(): + src = open("backend/apps/agents/agents.py").read() + assert "p_announce_armed_faults()" in src, \ + "an announcement nothing calls is the silence it was written to prevent"