mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(security): active guard enforcement, CEO A2A target check, notification expiry (#595)
* fix(security): guard goes active; CEO A2A respects no-comms roles; ack notifications expire ROBOCO_GUARD_PASSIVE_MODE defaults to false in both compose files — the deferred post-calibration flip; fail_secure stays off and the env override remains the rollback. can_a2a_direct no longer short-circuits the CEO past the no-comms set (auditor/pr_reviewer/prompter/secretary), now canonical in foundation.policy.communications.NO_COMMS_ROLES and shared with the content-actions gate; the A2A service refuses at conversation creation instead of silently suppressing the wake. Ack-required notifications get expires_at stamped from ROBOCO_NOTIFICATION_ACK_TTL_HOURS (default 48, 0 disables), so the re-escalation sweeper's expires_at query matches rows for the first time. * refactor(notification): extract _ack_and_expiry — xenon rank back under B The expires_at stamping pushed _create_notification_with_session to rank C; the requires_ack + expiry derivation moves into a helper with the same semantics and comments. * test(conftest): dispose the global DB engine after every test Production code reaching get_db_context()/get_engine() lazily creates the process-global engine bound to the current event loop; with per-test function-scoped loops, any later test touching the global path inherits a dead-loop engine and dies with 'Future attached to a different loop' — the order-dependent class that has been wandering the suite (cloud_auth login, metrics, tasks-routes, full-lifecycle) whenever collection order shifts. An autouse fixture now close_db()s after every test, keeping the global path loop-local; no-op when untouched. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
+19
-1
@@ -36,6 +36,7 @@ Redis isolation:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
@@ -48,7 +49,7 @@ import pytest
|
||||
import pytest_asyncio
|
||||
from roboco.config import settings as _settings
|
||||
from roboco.db import tables as roboco_tables
|
||||
from roboco.db.base import Base
|
||||
from roboco.db.base import Base, close_db
|
||||
from roboco.db.tables import (
|
||||
AgentTable,
|
||||
AuditLogTable,
|
||||
@@ -78,6 +79,23 @@ if TYPE_CHECKING:
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(autouse=True)
|
||||
async def _dispose_global_db_engine() -> AsyncIterator[None]:
|
||||
"""Never let the lazy global engine outlive the test that created it.
|
||||
|
||||
Production code reaching ``get_db_context()``/``get_engine()`` creates
|
||||
the process-global ``_DbHolder`` engine bound to the CURRENT event loop.
|
||||
With function-scoped test loops, any later test touching that global
|
||||
path inherits an engine from a dead loop and crashes with ``Future
|
||||
attached to a different loop`` — an order-dependent failure class that
|
||||
moves around whenever test collection shifts. Disposing after every
|
||||
test keeps the global path loop-local; a no-op when nothing touched it.
|
||||
"""
|
||||
yield
|
||||
with contextlib.suppress(Exception):
|
||||
await close_db()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_live_redis(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Keep every test off the real localhost Redis (see module docstring).
|
||||
|
||||
Reference in New Issue
Block a user