Isolate cross-database repair validation
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-08-28 13:26:43 +02:00
parent aa979f3ca9
commit 75894f8b52
4 changed files with 116 additions and 4 deletions

View File

@@ -5,6 +5,7 @@ from pathlib import Path, PurePosixPath
from .config import Settings
from .git import ChangeSummary
from .models import FailureReport
_ALWAYS_DENIED = (
".env",
@@ -66,3 +67,16 @@ def evaluate_changes(
errors.append(f"Direct-main denied path changed: {filename}")
return PolicyResult(not errors, tuple(errors))
def evaluate_report_scope(report: FailureReport, summary: ChangeSummary) -> PolicyResult:
expected = f"{report.user_feedback or ''}\n{report.expected_behavior or ''}".lower()
requires_cross_database_orchestration = "cockpit_config" in expected and "fk_" in expected
has_application_change = any(filename.startswith("app/") for filename in summary.files)
errors = []
if requires_cross_database_orchestration and not has_application_change:
errors.append(
"Cross-database report changed no application code; inspect routing and implement staged orchestration"
)
return PolicyResult(not errors, tuple(errors))

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import logging
import re
import signal
import threading
import time
@@ -8,13 +9,15 @@ from collections.abc import Iterator
from contextlib import contextmanager
from dataclasses import asdict
import pymysql
from .agent import AiderAgent
from .config import Settings
from .database import FailureRepository
from .git import ChangeSummary, GitWorkspace
from .gitea import GiteaClient
from .models import FailureReport
from .policy import evaluate_changes
from .policy import evaluate_changes, evaluate_report_scope
from .prompt import build_retry_task, build_task
LOG = logging.getLogger(__name__)
@@ -77,10 +80,13 @@ class AutomationWorker:
",".join(summary.files) or "none",
)
policy = evaluate_changes(summary, self.settings, workspace.path)
scope_policy = evaluate_report_scope(report, summary)
if policy.allowed and not scope_policy.allowed:
policy = scope_policy
if not policy.allowed:
retryable = not summary.files or all(
filename.startswith("tests/") for filename in summary.files
)
) or not scope_policy.allowed
if retryable and attempt < self.settings.max_agent_attempts:
LOG.warning(
"Policy rejected retryable changes for report %s on attempt %s: %s",
@@ -224,6 +230,7 @@ class AutomationWorker:
attempt: int | None = None,
) -> list[dict]:
results = []
self._prepare_test_schema()
test_environment = {
"AGENT_TEST_DB_ENABLED": "true",
"APP_ENV": "testing",
@@ -279,6 +286,47 @@ class AutomationWorker:
raise RuntimeError(f"Test command failed: {command}\n{output}")
return results
def _prepare_test_schema(self) -> None:
database = self.settings.test_customer_database
if not re.fullmatch(r"[A-Za-z0-9_]+", database):
raise ValueError("TEST_CUSTOMER_DB contains invalid characters")
connection = pymysql.connect(
host=self.settings.test_database_host,
port=self.settings.test_database_port,
user=self.settings.test_database_user,
password=self.settings.test_database_password,
database=database,
autocommit=True,
connect_timeout=10,
)
try:
with connection.cursor() as cursor:
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS FK_Nummerkreis (
Kennung INT PRIMARY KEY,
Bezeichnung VARCHAR(100) NOT NULL,
Bezeichnung_Kurz VARCHAR(20) NOT NULL
)
"""
)
cursor.executemany(
"""
INSERT INTO FK_Nummerkreis (Kennung, Bezeichnung, Bezeichnung_Kurz)
VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE
Bezeichnung = VALUES(Bezeichnung),
Bezeichnung_Kurz = VALUES(Bezeichnung_Kurz)
""",
(
(3, "Rechnung", "RG"),
(4, "Lieferschein", "LS"),
(8, "Gutschrift", "GS"),
),
)
finally:
connection.close()
@contextmanager
def _progress(
self,