This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user