update
This commit is contained in:
@@ -1181,3 +1181,144 @@ async def test_namecheck_ok_behaelt_titel(testdb, tmp_path, monkeypatch):
|
||||
block = await db.kanban_get_card(TOPIC, B, "b-c9")
|
||||
assert block["payload"]["title"] == "Eigener Titel"
|
||||
assert block["payload"]["description"] == "Eigene Beschreibung"
|
||||
|
||||
|
||||
# ── Sanierung: Titel auf Korpus-Form, Beschreibungspflicht (QA: fremd/hygiene) ──────
|
||||
|
||||
def test_sanierung_schema_varianten():
|
||||
assert bi._sanierung_schema({"title": " k-Color ", "description": "d"}) == ("k-Color", "d")
|
||||
assert bi._sanierung_schema({"title": "", "description": "nur Beschreibung"}) == ("", "nur Beschreibung")
|
||||
assert bi._sanierung_schema({"title": "x" * 90, "description": "d"}) == ("", "d") # zu lang
|
||||
assert bi._sanierung_schema({"title": "", "description": ""}) is None
|
||||
assert bi._sanierung_schema("quatsch") is None
|
||||
|
||||
|
||||
def test_sanierung_noetig():
|
||||
ctoks = {"color", "graph"}
|
||||
assert bi._sanierung_noetig({"title": "k-Color", "description": ""}, ctoks) # leere Beschreibung
|
||||
assert bi._sanierung_noetig({"title": "k-Coloring", "description": "d"}, ctoks) # kein Korpus-Anker
|
||||
assert not bi._sanierung_noetig({"title": "k-Color", "description": "d"}, ctoks)
|
||||
assert not bi._sanierung_noetig({"title": "k-Coloring", "description": "d"}, None) # thema: kein Korpus
|
||||
|
||||
|
||||
def _sanierung_env(tmp_path, monkeypatch, antwort):
|
||||
"""Korpus mit 'k-Color'-Oberflächenform; Judge antwortet mit `antwort`."""
|
||||
(tmp_path / "korpus.txt").write_text(
|
||||
"Das k-Color Problem: Kann der Graph mit k Farben gefärbt werden? NP-vollständig.",
|
||||
encoding="utf-8")
|
||||
monkeypatch.setattr(bi, "source_folder", lambda t: tmp_path)
|
||||
seen = {}
|
||||
|
||||
async def fake_slot(ctx, label, *, key, prompt, role, capabilities, payload, timeout):
|
||||
seen[key] = prompt
|
||||
return "ok", payload((0, json.dumps(antwort["val"]), ""))
|
||||
|
||||
monkeypatch.setattr(bi, "run_single_slot", fake_slot)
|
||||
return GenContext(topic=TOPIC, provider="claude", is_cancelled=lambda: False), seen
|
||||
|
||||
|
||||
async def test_singleton_unverankert_wird_saniert(testdb, tmp_path, monkeypatch):
|
||||
"""Singleton-Cluster mit Reader-Titel ohne Korpus-Anker: Naming wird nicht mehr
|
||||
übersprungen — der Titel wird auf die Korpus-Oberflächenform umgeschrieben
|
||||
(gemessener aak-Fall 'k-Coloring' statt 'k-Color')."""
|
||||
db = testdb
|
||||
antwort = {"val": {"title": "k-Color", "description": "Kann der Graph mit k Farben gefärbt werden?"}}
|
||||
ctx, seen = _sanierung_env(tmp_path, monkeypatch, antwort)
|
||||
|
||||
async def fake_members(topic, cid):
|
||||
return [{"norm": "k-coloring", "title": "k-Coloring",
|
||||
"description": "Kann der Graph mit k Farben gefärbt werden?",
|
||||
"readers": ["r1", "r2"], "sources": []}]
|
||||
|
||||
monkeypatch.setattr(bi, "_member_rows", fake_members)
|
||||
payload = {"title": "k-Coloring", "description": "Kann der Graph mit k Farben gefärbt werden?"}
|
||||
await db.kanban_upsert_card(TOPIC, B, "c1", "cluster", "naming", payload)
|
||||
await bi._name_one(ctx, _mk_flow(tmp_path), {"card_id": "c1", "payload": payload})
|
||||
card = await db.kanban_get_card(TOPIC, B, "c1")
|
||||
assert card["stage"] == "naming_check"
|
||||
assert card["payload"]["title"] == "k-Color"
|
||||
assert any("-sanierung-" in k for k in seen)
|
||||
|
||||
|
||||
async def test_leere_beschreibung_wird_gefuellt(testdb, tmp_path, monkeypatch):
|
||||
"""Verankerter Titel, leere Beschreibung (gemessener aak-Fall der SetCover-Fragmente):
|
||||
Beschreibung wird belegt nachgefasst, Titel bleibt."""
|
||||
db = testdb
|
||||
antwort = {"val": {"title": "k-Color", "description": "Färbbarkeit mit k Farben."}}
|
||||
ctx, _seen = _sanierung_env(tmp_path, monkeypatch, antwort)
|
||||
|
||||
async def fake_members(topic, cid):
|
||||
return [{"norm": "k-color", "title": "k-Color", "description": "",
|
||||
"readers": ["r1"], "sources": []}]
|
||||
|
||||
monkeypatch.setattr(bi, "_member_rows", fake_members)
|
||||
payload = {"title": "k-Color", "description": ""}
|
||||
await db.kanban_upsert_card(TOPIC, B, "c5", "cluster", "naming", payload)
|
||||
await bi._name_one(ctx, _mk_flow(tmp_path), {"card_id": "c5", "payload": payload})
|
||||
card = await db.kanban_get_card(TOPIC, B, "c5")
|
||||
assert card["payload"]["title"] == "k-Color"
|
||||
assert card["payload"]["description"] == "Färbbarkeit mit k Farben."
|
||||
|
||||
|
||||
async def test_sanierung_unverankerter_vorschlag_verfaellt(testdb, tmp_path, monkeypatch):
|
||||
"""Judge-Vorschlag ohne Korpus-Anker wird verworfen (dieselbe Messlatte wie QA-fremd);
|
||||
die Beschreibung wird trotzdem übernommen."""
|
||||
db = testdb
|
||||
antwort = {"val": {"title": "Graphfärbungsproblem", "description": "Färbbarkeit mit k Farben."}}
|
||||
ctx, _seen = _sanierung_env(tmp_path, monkeypatch, antwort)
|
||||
|
||||
async def fake_members(topic, cid):
|
||||
return [{"norm": "k-coloring", "title": "k-Coloring",
|
||||
"description": "Kann der Graph mit k Farben gefärbt werden?",
|
||||
"readers": ["r1"], "sources": []}]
|
||||
|
||||
monkeypatch.setattr(bi, "_member_rows", fake_members)
|
||||
payload = {"title": "k-Coloring", "description": "Kann der Graph mit k Farben gefärbt werden?"}
|
||||
await db.kanban_upsert_card(TOPIC, B, "c2", "cluster", "naming", payload)
|
||||
await bi._name_one(ctx, _mk_flow(tmp_path), {"card_id": "c2", "payload": payload})
|
||||
card = await db.kanban_get_card(TOPIC, B, "c2")
|
||||
assert card["payload"]["title"] == "k-Coloring" # unverankert → verfällt
|
||||
assert card["stage"] == "naming_check"
|
||||
|
||||
|
||||
async def test_sanierung_fail_open(testdb, tmp_path, monkeypatch):
|
||||
"""Judge-Ausfall → Karte läuft unverändert weiter (kein Deadletter am Naming)."""
|
||||
db = testdb
|
||||
(tmp_path / "korpus.txt").write_text("Der Graph ist endlich.", encoding="utf-8")
|
||||
monkeypatch.setattr(bi, "source_folder", lambda t: tmp_path)
|
||||
|
||||
async def broken_slot(*a, **kw):
|
||||
return "failed", None
|
||||
|
||||
async def fake_members(topic, cid):
|
||||
return [{"norm": "x", "title": "Graph", "description": "", "readers": ["r1"], "sources": []}]
|
||||
|
||||
monkeypatch.setattr(bi, "run_single_slot", broken_slot)
|
||||
monkeypatch.setattr(bi, "_member_rows", fake_members)
|
||||
payload = {"title": "Graph", "description": ""}
|
||||
await db.kanban_upsert_card(TOPIC, B, "c3", "cluster", "naming", payload)
|
||||
ctx = GenContext(topic=TOPIC, provider="claude", is_cancelled=lambda: False)
|
||||
await bi._name_one(ctx, _mk_flow(tmp_path), {"card_id": "c3", "payload": payload})
|
||||
card = await db.kanban_get_card(TOPIC, B, "c3")
|
||||
assert card["stage"] == "naming_check" and card["payload"]["title"] == "Graph"
|
||||
|
||||
|
||||
async def test_sanierung_anker_und_beschreibung_ok_kein_judge(testdb, tmp_path, monkeypatch):
|
||||
"""Verankerter Titel + Beschreibung vorhanden → kein Sanierungs-Call."""
|
||||
db = testdb
|
||||
(tmp_path / "korpus.txt").write_text("Das k-Color Problem im Graph.", encoding="utf-8")
|
||||
monkeypatch.setattr(bi, "source_folder", lambda t: tmp_path)
|
||||
|
||||
async def never_slot(*a, **kw):
|
||||
raise AssertionError("Sanierung darf ohne Befund-Form nicht laufen")
|
||||
|
||||
async def fake_members(topic, cid):
|
||||
return [{"norm": "k-color", "title": "k-Color", "description": "d", "readers": ["r1"], "sources": []}]
|
||||
|
||||
monkeypatch.setattr(bi, "run_single_slot", never_slot)
|
||||
monkeypatch.setattr(bi, "_member_rows", fake_members)
|
||||
payload = {"title": "k-Color", "description": "d"}
|
||||
await db.kanban_upsert_card(TOPIC, B, "c4", "cluster", "naming", payload)
|
||||
ctx = GenContext(topic=TOPIC, provider="claude", is_cancelled=lambda: False)
|
||||
await bi._name_one(ctx, _mk_flow(tmp_path), {"card_id": "c4", "payload": payload})
|
||||
assert (await db.kanban_get_card(TOPIC, B, "c4"))["stage"] == "naming_check"
|
||||
|
||||
@@ -75,11 +75,9 @@ def _mk_race(finder_by_agent):
|
||||
if "-r1-" in key:
|
||||
agent = int(key.rsplit("-", 1)[1])
|
||||
subs = finder_by_agent.get(agent) or []
|
||||
if subs and (m := _MD_PATH.search(prompt)):
|
||||
if subs: # Finder antworten als TEXT (Marker-Format), kein out_path mehr
|
||||
text = "<!-- block: Alpha -->\n" + "\n".join(f"- {s}" for s in subs)
|
||||
with open(m.group(1), "w", encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
outs.append(slot["payload"](None))
|
||||
outs.append(slot["payload"]((0, text, "")))
|
||||
outs = [o for o in outs if o]
|
||||
return outs or None
|
||||
fake_race.slots_seen = []
|
||||
@@ -177,11 +175,8 @@ async def test_catchup_adds_and_stops(sub_env, monkeypatch, tmp_path):
|
||||
if any("-subblock-x" in s["key"] for s in slots):
|
||||
hit["n"] += 1
|
||||
if hit["n"] == 1: # first catch-up round: both agents agree on one new sub
|
||||
for slot in slots[:2]:
|
||||
m = _MD_PATH.search(slot["prompt"])
|
||||
with open(m.group(1), "w", encoding="utf-8") as f:
|
||||
f.write("<!-- block: Alpha -->\n- Vertiefung der Konzepte")
|
||||
return [slot["payload"](None) for slot in slots[:2]]
|
||||
text = "<!-- block: Alpha -->\n- Vertiefung der Konzepte"
|
||||
return [slot["payload"]((0, text, "")) for slot in slots[:2]]
|
||||
return None
|
||||
return await base(topic, label, slots, *a, **k)
|
||||
|
||||
@@ -251,13 +246,8 @@ async def test_paraphrase_saturation_stops_early(sub_env, monkeypatch):
|
||||
|
||||
async def with_r2(topic, label, slots, *a, **k):
|
||||
if any("-r2-" in s["key"] for s in slots):
|
||||
outs = []
|
||||
for slot in slots[:2]:
|
||||
m = _MD_PATH.search(slot["prompt"])
|
||||
with open(m.group(1), "w", encoding="utf-8") as f:
|
||||
f.write("<!-- block: Alpha -->\n- Umbruch erfordert explizite Marker!")
|
||||
outs.append(slot["payload"](None))
|
||||
return outs
|
||||
text = "<!-- block: Alpha -->\n- Umbruch erfordert explizite Marker!"
|
||||
return [slot["payload"]((0, text, "")) for slot in slots[:2]]
|
||||
return await base_fake(topic, label, slots, *a, **k)
|
||||
|
||||
monkeypatch.setattr(blx, "_race", with_r2)
|
||||
@@ -282,11 +272,8 @@ async def test_round_cap_stops_endless_finders(sub_env, monkeypatch):
|
||||
rn = _re.search(r"-r(\d+)-", slots[0]["key"])
|
||||
n = rn.group(1) if rn else "x"
|
||||
for slot in slots[:2]:
|
||||
m = _MD_PATH.search(slot["prompt"])
|
||||
with open(m.group(1), "w", encoding="utf-8") as f:
|
||||
f.write(f"<!-- block: Alpha -->\n- Konzept{n} ist eigenständig")
|
||||
prompts.append((slot["key"], slot["prompt"]))
|
||||
outs.append(slot["payload"](None))
|
||||
outs.append(slot["payload"]((0, f"<!-- block: Alpha -->\n- Konzept{n} ist eigenständig", "")))
|
||||
return outs
|
||||
|
||||
monkeypatch.setattr(blx, "_race", endless)
|
||||
@@ -362,8 +349,9 @@ def test_sink_json_writes_only_valid(tmp_path):
|
||||
|
||||
|
||||
async def test_clarify_inline_evidence_no_tools(sub_env, monkeypatch, tmp_path):
|
||||
"""Mit Korpus: Judges bekommen Auszüge inline und laufen ohne Tools (Text-Antwort);
|
||||
die j-Datei schreibt die Engine. Finder bleiben unverändert bei capabilities=files."""
|
||||
"""Mit Korpus: Judges UND Finder bekommen Auszüge inline und laufen ohne Tools
|
||||
(Text-Antwort); die Dateien schreibt die Engine. Der Finder verlor vorher 3–13
|
||||
Tool-Runden pro Call mit der Material-Suche via glob/grep/bash."""
|
||||
db, ctx, files = sub_env
|
||||
d = _corpus(tmp_path)
|
||||
monkeypatch.setattr(blx, "source_folder", lambda t: d)
|
||||
@@ -378,7 +366,9 @@ async def test_clarify_inline_evidence_no_tools(sub_env, monkeypatch, tmp_path):
|
||||
assert judges and all(s["capabilities"] == "none" for s in judges)
|
||||
assert "── Skript.txt" in judges[0]["prompt"]
|
||||
assert "ls/find" not in judges[0]["prompt"] # keine Selbst-Recherche-Anweisung mehr
|
||||
assert finders and all(s["capabilities"] == "files" for s in finders)
|
||||
assert finders and all(s["capabilities"] == "none" for s in finders)
|
||||
assert "── Skript.txt" in finders[0]["prompt"] # Auszüge inline statt Dateisystem-Suche
|
||||
assert "web search" not in finders[0]["prompt"]
|
||||
assert list(tmp_path.glob("subblock-final-*-j*.md")) # Engine persistiert die Judge-Antwort
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user