fix(integration): F41+F37 (Astra S2) — Agenten-Stundenlimit und SMTP-Env-Namen
Check Cross-Plugin Imports / check (push) Waiting to run

F41 — Stundliches Agentenlimit zaehlte ab jetzt() statt letzte Stunde:
one_hour_ago = datetime.now(UTC) zog die Stunde nie ab — die Abfrage
zaehlte nur Eintraege ab dem aktuellen Zeitpunkt (wirksam null), das
konfigurierte Limit schuetzte nicht vor wiederholten Starts. Fix:
timedelta(hours=1) + Import.

F37 — SMTP-Variablen hiessen in Compose anders als in Settings:
Settings erwarten smtp_username/smtp_from_email/smtp_use_tls, Compose
setzte SMTP_USER/SMTP_FROM/SMTP_TLS — Benutzername, Absender und TLS
kamen nie an (Systemmails: Reset, Einladungen, geplante Alarmierung).
Fix: Compose (app+worker) und .env-Beispiele durchgaengig auf die
Settings-Namen (SMTP_USERNAME/SMTP_FROM_EMAIL/SMTP_USE_TLS) umgestellt.
Prod-Check: SMTP dort aktuell unkonfiguriert (leere Werte verifiziert) —
umbenennen risikofrei; sobald SMTP gesetzt wird, greift die Kette.

Test-Anpassung (F11-Folge, Vertragsaenderung): 3 veraltete Approval-
Unit-Tests in test_phase_f_agents (MagicMock-Ketten gegen VOR-F11-
Semantik) durch dokumentierte Skip-Verweise auf die 6 echten
F11-Tests in test_s1_security_guards ersetzt — reale DB, deterministisch.

Verifikation: phase_f_agents 39 passed/3 skipped, ruff clean,
agent_runner Syntax OK, Compose-Namen durchgaengig verifiziert.
This commit is contained in:
Agent Zero
2026-09-18 11:15:45 +02:00
parent 8c5682f669
commit 5d6fe6b1f6
5 changed files with 20 additions and 95 deletions
+3 -3
View File
@@ -44,10 +44,10 @@ STORAGE_PATH=/data/storage
# --- SMTP (for password reset emails) -----------------------------------------
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=noreply@example.com
SMTP_USERNAME=noreply@example.com
SMTP_PASSWORD=YOUR_SMTP_PASSWORD
SMTP_FROM=noreply@example.com
SMTP_TLS=true
SMTP_FROM_EMAIL=noreply@example.com
SMTP_USE_TLS=true
# --- bcrypt tuning ----------------------------------------------------------
BCRYPT_ROUNDS=12
+3 -3
View File
@@ -90,10 +90,10 @@ S3_SECURE=true
# === SMTP / EMAIL ===
SMTP_HOST=localhost
SMTP_PORT=587
SMTP_USER=
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_FROM=no-reply@localhost
SMTP_TLS=true
SMTP_FROM_EMAIL=no-reply@localhost
SMTP_USE_TLS=true
# === RATE LIMITING ===
RATE_LIMIT_LOGIN_MAX=5
@@ -12,7 +12,7 @@ from __future__ import annotations
import logging
import uuid
from datetime import UTC, datetime
from datetime import UTC, datetime, timedelta
from typing import Any
from sqlalchemy import func, select
@@ -65,7 +65,7 @@ async def run_agent(
# ── Safety Check 1: Rate Limit ──
if agent.max_executions_per_hour:
async with factory() as db:
one_hour_ago = datetime.now(UTC)
one_hour_ago = datetime.now(UTC) - timedelta(hours=1)
count_result = await db.execute(
select(func.count())
.select_from(AgentRun)
+6 -6
View File
@@ -86,10 +86,10 @@ services:
STORAGE_PATH: ${STORAGE_PATH:-/data/storage}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER:-}
SMTP_USERNAME: ${SMTP_USERNAME:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_FROM: ${SMTP_FROM:-no-reply@localhost}
SMTP_TLS: ${SMTP_TLS:-true}
SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-no-reply@localhost}
SMTP_USE_TLS: ${SMTP_USE_TLS:-true}
BCRYPT_ROUNDS: ${BCRYPT_ROUNDS:-12}
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@media-on.de}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:?ADMIN_PASSWORD must be set — no default credentials (Astra F30)}
@@ -131,10 +131,10 @@ services:
STORAGE_PATH: ${STORAGE_PATH:-/data/storage}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER:-}
SMTP_USERNAME: ${SMTP_USERNAME:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_FROM: ${SMTP_FROM:-no-reply@localhost}
SMTP_TLS: ${SMTP_TLS:-true}
SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-no-reply@localhost}
SMTP_USE_TLS: ${SMTP_USE_TLS:-true}
MAIL_ENCRYPTION_KEY: ${MAIL_ENCRYPTION_KEY:?MAIL_ENCRYPTION_KEY is required}
volumes:
- storage:/data/storage
+6 -81
View File
@@ -767,66 +767,13 @@ class TestApproval:
@pytest.mark.asyncio
async def test_approve_approval_request(self, tenant_id, mock_db):
"""resolve_approval_request approves a pending request."""
request_id = uuid.uuid4()
approver_id = uuid.uuid4()
req = ApprovalRequest(
id=request_id,
tenant_id=tenant_id,
entity_type="agent_definition",
entity_id=uuid.uuid4(),
action="execute",
requested_by=uuid.uuid4(),
status="pending",
)
result = MagicMock()
result.scalar_one_or_none.return_value = req
mock_db.execute.return_value = result
updated = await resolve_approval_request(
mock_db,
tenant_id,
request_id,
decision="approved",
approver_id=approver_id,
comment="OK",
)
assert updated is req
assert req.status == "approved"
assert req.approver_id == approver_id
assert req.comment == "OK"
assert req.resolved_at is not None
"""Superseded by TestF11ApprovalBinding (real DB, deterministic)."""
pytest.skip("F11 contract covered by tests/test_s1_security_guards.py::TestF11ApprovalBinding with a real database")
@pytest.mark.asyncio
async def test_reject_approval_request(self, tenant_id, mock_db):
"""resolve_approval_request rejects a pending request."""
request_id = uuid.uuid4()
req = ApprovalRequest(
id=request_id,
tenant_id=tenant_id,
entity_type="agent_definition",
entity_id=uuid.uuid4(),
action="execute",
requested_by=uuid.uuid4(),
status="pending",
)
result = MagicMock()
result.scalar_one_or_none.return_value = req
mock_db.execute.return_value = result
updated = await resolve_approval_request(
mock_db,
tenant_id,
request_id,
decision="rejected",
approver_id=uuid.uuid4(),
comment="No",
)
assert updated is req
assert req.status == "rejected"
"""Superseded by TestF11ApprovalBinding (real DB, deterministic)."""
pytest.skip("F11 contract covered by tests/test_s1_security_guards.py::TestF11ApprovalBinding with a real database")
@pytest.mark.asyncio
async def test_expire_approval_request(self, tenant_id, mock_db):
@@ -852,30 +799,8 @@ class TestApproval:
@pytest.mark.asyncio
async def test_resolve_non_pending_returns_none(self, tenant_id, mock_db):
"""Resolving a non-pending request returns None."""
request_id = uuid.uuid4()
req = ApprovalRequest(
id=request_id,
tenant_id=tenant_id,
entity_type="agent_definition",
entity_id=uuid.uuid4(),
action="execute",
requested_by=uuid.uuid4(),
status="approved",
)
result = MagicMock()
result.scalar_one_or_none.return_value = req
mock_db.execute.return_value = result
updated = await resolve_approval_request(
mock_db,
tenant_id,
request_id,
decision="rejected",
approver_id=uuid.uuid4(),
)
assert updated is None
"""Superseded: F11 changed the contract to ApprovalDecisionError(409)."""
pytest.skip("F11 contract covered by tests/test_s1_security_guards.py::TestF11ApprovalBinding (not_pending 409) with a real database")
@pytest.mark.asyncio
async def test_list_approvals_with_filters(self, tenant_id, mock_db):