194 lines
9.1 KiB
Python
194 lines
9.1 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from .models import FailureReport
|
|
|
|
_SECRET_PATTERNS = (
|
|
(
|
|
re.compile(r"(?i)\b(?:mysql|mariadb|postgres(?:ql)?|sqlserver)://[^\s]+"),
|
|
"[DATABASE_URL]",
|
|
),
|
|
(re.compile(r"(?i)(password|passwd|secret|token|api[_ -]?key)\s*[:=]\s*\S+"), r"\1=[REDACTED]"),
|
|
(re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b"), "[EMAIL]"),
|
|
(re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b"), "[IP]"),
|
|
(re.compile(r"(?i)bearer\s+[A-Za-z0-9._~+/=-]+"), "Bearer [REDACTED]"),
|
|
(re.compile(r"\bAKIA[0-9A-Z]{16}\b"), "[AWS_KEY]"),
|
|
(re.compile(r"-----BEGIN [A-Z ]+KEY-----[\s\S]+?-----END [A-Z ]+KEY-----"), "[PRIVATE_KEY]"),
|
|
(re.compile(r"\b[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b"), "[JWT]"),
|
|
)
|
|
|
|
_REPOSITORY_PLAYBOOK = """
|
|
Repository ownership map:
|
|
- app/Services/SQLGeneratorService.php assembles live schema and business context for SQL generation.
|
|
- config/erp_semantics.php owns ERP joins, metrics, formulas, filters, and reference examples.
|
|
- config/cockpit_semantics.php owns device and customer-database semantics.
|
|
- app/Services/AIService.php owns provider calls, pseudonymization boundaries, and response parsing.
|
|
- app/Services/TenantDatabaseService.php owns ERP/config/customer source selection and tenant metadata.
|
|
- Cross-database workflows are orchestrated in ChatController/TenantDatabaseService; one SQL query
|
|
cannot join tables exposed through different Laravel connections unless the tracked architecture
|
|
explicitly supports qualified access.
|
|
- app/Services/SQLValidatorService.php owns read-only SQL safety; do not weaken it to accept a fix.
|
|
- app/Http/Controllers/ChatController.php orchestrates routing, execution, answers, and diagnostics.
|
|
- app/Services/FailureTrackingService.php and FailureAutomationService.php own failure handoff.
|
|
- tests/Unit is preferred for deterministic service/context rules; tests/Feature is for HTTP,
|
|
persistence, authorization, or real isolated-database behavior.
|
|
|
|
Repair workflow:
|
|
1. Inspect the owning implementation, its configuration, and the nearest existing test first.
|
|
2. State one root-cause hypothesis and make the smallest change that can prove it.
|
|
3. Put reusable business knowledge in tracked semantics configuration, not question-specific code.
|
|
4. A prompt/context fix must test the real generated context. A deterministic SQL builder must test
|
|
its exact SQL and execute it against synthetic fixtures when useful.
|
|
5. Tests must contain assertions that execute; reject empty loops and broad unrelated fallbacks.
|
|
6. Do not expose protected production methods publicly for tests. Use a test-only subclass.
|
|
7. Do not duplicate one rule in service code and semantics config, or add explanatory comments for
|
|
self-evident test setup.
|
|
8. Inspect the final diff for malformed edits, unrelated files, hardcoded report values, and tests
|
|
that pass without exercising the changed behavior. Before retrying an edit, reread the current
|
|
file and never leave conflict markers or partial methods behind.
|
|
9. PHP config files are executable code. Preserve brackets, commas, quotes, and array nesting; run
|
|
`php -l` on every changed PHP file before broader tests.
|
|
"""
|
|
|
|
|
|
def redact(value: str | None) -> str:
|
|
result = value or ""
|
|
for pattern, replacement in _SECRET_PATTERNS:
|
|
result = pattern.sub(replacement, result)
|
|
return result[:12_000]
|
|
|
|
|
|
def repair_files(report: FailureReport) -> tuple[str, ...]:
|
|
expected = f"{report.user_feedback or ''}\n{report.expected_behavior or ''}".lower()
|
|
if "cockpit_config" in expected and ("fk_" in expected or "erp" in expected):
|
|
return (
|
|
"app/Http/Controllers/ChatController.php",
|
|
"app/Services/TenantDatabaseService.php",
|
|
"tests/Unit/ChatControllerSourceResolutionTest.php",
|
|
)
|
|
if '__LAI_' in (report.response or ''):
|
|
return (
|
|
"app/Services/AIService.php",
|
|
"app/Http/Controllers/ChatController.php",
|
|
"tests/Feature/AIPrivacyBoundaryTest.php",
|
|
)
|
|
return ()
|
|
|
|
|
|
def build_task(report: FailureReport) -> str:
|
|
prompt = redact(report.prompt)
|
|
feedback = redact(report.user_feedback)
|
|
expected_behavior = redact(report.expected_behavior)
|
|
response = redact(report.response)
|
|
error = redact(report.error_message)
|
|
sql = redact(report.sql_query)
|
|
tenant_scope = ''
|
|
if report.context.get('database_type') == 'customer':
|
|
tenant_scope = """
|
|
Tenant routing fact:
|
|
The customer has already selected one tenant database. SQL inside that database must not
|
|
invent an additional customer discriminator column. Use only columns present in tracked
|
|
schema/semantics, and fix SQL-generation guidance rather than rewriting one unknown column
|
|
to another guessed column. TenantDatabaseService records the explicit source type in
|
|
database.connections.<connection>.tenant_type; use that metadata instead of database-name
|
|
patterns or heuristics.
|
|
"""
|
|
sql_semantics = ''
|
|
if report.category in {'incorrect_answer', 'sql_execution'}:
|
|
sql_semantics = """
|
|
SQL repair guidance:
|
|
When the SQL is syntactically valid but uses the wrong business calculation, prefer adding or
|
|
correcting tracked domain knowledge in config/*_semantics.php so the schema prompt teaches the
|
|
model the right joins, filters, and formula. Do not hardcode one user's question or article ID
|
|
in SQLGeneratorService, and do not add post-generation replacement of guessed SQL.
|
|
|
|
For regression coverage of prompt guidance, expose the relevant protected schema/business-
|
|
context builder through a test subclass and assert that its real output contains the required
|
|
formula and rules. Do not mock AIService to return the known-bad SQL and then expect prompt
|
|
guidance to rewrite that mocked response. Keep database fixture tests only when production code
|
|
itself deterministically builds and executes the SQL under test.
|
|
|
|
For an unknown-column failure, compare every alias.column in the observed SQL with the table
|
|
schema and identify the owning table before editing. Teach the exact ownership, join, grouping,
|
|
and real column names in semantics config, then add a reference SQL example for the reported
|
|
question shape. Never replace one missing column with an unverified synonym.
|
|
|
|
When expected behavior names tables from multiple databases or connections, first inspect source
|
|
routing and existing staged-query workflows. Do not put an impossible cross-connection join only
|
|
in ERP semantics. Implement deterministic orchestration that resolves identifiers in one source
|
|
and passes only the required identifier into the next source, preserving tenant isolation.
|
|
"""
|
|
placeholder_guidance = ''
|
|
if '__LAI_' in response:
|
|
placeholder_guidance = """
|
|
Privacy placeholder repair guidance:
|
|
Trace the actual answer path through AIService::generateDatabaseResponse, issued token
|
|
restoration, and ChatController's local fallback. A token can only be restored when it exists
|
|
in the exact token map returned by protect(); never test a made-up token against an unrelated
|
|
map. Cover model-invented or malformed __LAI_...__ output with an HTTP-backed AIService test
|
|
and verify the user receives a real local database-result fallback, not a raw placeholder.
|
|
"""
|
|
|
|
return f"""You are fixing failure report #{report.id} in the Laravel AI Chat repository.
|
|
|
|
{_REPOSITORY_PLAYBOOK}
|
|
|
|
Rules:
|
|
- First add a regression test that reproduces the reported behavior.
|
|
- The regression test must execute the relevant production behavior; never mock or stub the behavior being tested.
|
|
- Make the smallest production change that fixes the root cause.
|
|
- Preserve read-only SQL validation and tenant isolation.
|
|
- Never read or modify .env files, credentials, deployment secrets, or production data.
|
|
- Do not weaken tests, authentication, authorization, SQL validation, or safety checks.
|
|
- Do not commit or push; the orchestrator owns Git publication.
|
|
|
|
Failure category: {report.category}
|
|
Feedback type: {report.feedback_type or 'not specified'}
|
|
Source: {report.source}
|
|
Model: {redact(str(report.context.get('model', 'unknown')))}
|
|
Database type: {redact(str(report.context.get('database_type', 'unknown')))}
|
|
|
|
User request:
|
|
{prompt or '[not available]'}
|
|
|
|
User feedback:
|
|
{feedback or '[not available]'}
|
|
|
|
Expected behavior:
|
|
{expected_behavior or '[not available]'}
|
|
|
|
Observed answer:
|
|
{response or '[not available]'}
|
|
|
|
Observed error:
|
|
{error or '[not available]'}
|
|
|
|
Generated SQL, if relevant:
|
|
{sql or '[not available]'}
|
|
{tenant_scope}
|
|
{sql_semantics}
|
|
{placeholder_guidance}
|
|
|
|
Run focused tests while working. Leave the repository with the fix and regression test applied.
|
|
"""
|
|
|
|
|
|
def build_retry_task(report: FailureReport, exception: Exception) -> str:
|
|
failure = redact(str(exception))
|
|
|
|
return build_task(report) + f"""
|
|
|
|
The previous implementation failed isolated validation.
|
|
|
|
Correct the existing changes using this test output:
|
|
|
|
{failure}
|
|
|
|
Keep the regression test meaningful. Do not remove or weaken tests, safety checks,
|
|
authentication, authorization, SQL validation, or tenant isolation. If a new test calls an
|
|
unrelated specialized code path that correctly returns no result, repair the test to exercise
|
|
the owning production behavior; never broaden unrelated production routing merely to make the
|
|
test pass. Do not commit or push.
|
|
"""
|