feat: check-assets ↔ KI gekoppelt — byte-getauschte Bilder neu bewerten

Da bekannte Bilder aus Effizienzgründen nicht jeden Scan neu geladen werden
(url_hashes), prüfte die KI einen späteren Byte-Tausch eines bekannten Bildes
nicht von selbst erneut. Lücke geschlossen: Erkennt die Datei-Prüfung
(check-assets) eine geänderte Bilddatei, wird deren URL über
BaselineManager.invalidate_ai_url_hashes aus der Hash-Erinnerung entfernt →
nächster Scan lädt das Bild neu, neue Bytes = neuer Hash = Cache-Miss = die KI
bewertet den Inhalt neu.

Einhängung in cmd_scan (vor run_ai_analysis → Neubewertung noch im selben Scan)
und cmd_check_assets. Integritäts- und Inhaltsprüfung greifen so ineinander.

Tests: 260 grün (+2: invalidate entfernt nur die genannten URLs; geändertes
Bild wird nach Invalidierung neu geladen und beanstandet).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-13 04:22:38 +02:00
commit 78dc79ceab
4 changed files with 80 additions and 2 deletions

View file

@ -689,6 +689,17 @@ def _run_assets_core(cfg: dict, bm: BaselineManager, snap: dict) -> tuple[dict,
return asset_diff, score_asset_diff(asset_diff, cfg), bool(baseline_hashes)
def _invalidate_ai_for_changed_assets(bm: BaselineManager, asset_diff: dict) -> None:
"""Geänderte Bild-Assets (byte-getauscht) aus der KI-url_hashes-Erinnerung entfernen,
damit die KI sie neu lädt UND inhaltlich neu bewertet. Nicht-Bild-URLs sind nicht in
url_hashes werden ignoriert."""
changed_urls = [c["url"] for c in (asset_diff or {}).get("changed", []) if c.get("url")]
n = bm.invalidate_ai_url_hashes(changed_urls)
if n:
logging.getLogger("scanner.assets").info(
"KI-Cache für %d geänderte Bild(er) invalidiert → inhaltliche Neubewertung.", n)
def cmd_check(args: argparse.Namespace, cfg: dict) -> int:
logger = logging.getLogger("scanner.check")
bm = BaselineManager(cfg["data_dir"])
@ -762,6 +773,9 @@ def cmd_scan(args: argparse.Namespace, cfg: dict) -> int:
levels.append(asset_assess["level"])
score_total += asset_assess.get("score", 0)
bm.mark_check_run("assets")
# Geänderte Bilder → KI-Cache invalidieren, damit die KI sie inhaltlich neu
# bewertet (läuft hier vor run_ai_analysis → Neubewertung noch im selben Scan).
_invalidate_ai_for_changed_assets(bm, asset_diff)
if pc.get("ext_links", True) and bm.is_check_due("ext_links", interval):
print(" Wöchentliche Prüfung externer Links läuft mit ...")
ext_links = _run_ext_links_core(cfg, snap) # informativ, hebt Level nicht an
@ -1148,6 +1162,7 @@ def cmd_check_assets(args: argparse.Namespace, cfg: dict) -> int:
print("Prüfe die Dateien Ihrer Website (Bilder/Skripte/Stylesheets) ...")
asset_diff, assessment, has_baseline = _run_assets_core(cfg, bm, snap)
bm.mark_check_run("assets")
_invalidate_ai_for_changed_assets(bm, asset_diff)
report = {
"generated_at": datetime.now(timezone.utc).isoformat(),

View file

@ -292,6 +292,22 @@ class BaselineManager:
json.dumps(state, indent=2, ensure_ascii=False), encoding="utf-8"
)
def invalidate_ai_url_hashes(self, urls: list[str]) -> int:
"""Entfernt URLs aus der url_hashes-Erinnerung. Folge: Die KI lädt diese Bilder
beim nächsten Lauf erneut und bewertet sie inhaltlich neu (geänderte Bytes
neuer Hash Cache-Miss Analyse). Wird von der Datei-Prüfung (check-assets)
für geänderte Bild-Assets aufgerufen. Returns Anzahl entfernter Einträge."""
if not urls:
return 0
ledger = self.load_ai_ledger()
uh = ledger.get("url_hashes", {})
removed = [u for u in urls if u in uh]
for u in removed:
del uh[u]
if removed:
self.save_ai_ledger(ledger)
return len(removed)
def dismiss_ai_entries(self, fingerprints: list[str] | None = None) -> int:
"""
Mark ledger entries as dismissed (false-positive acknowledgement).