update
This commit is contained in:
@@ -388,85 +388,88 @@ class _FakeEmb:
|
||||
return arr @ arr.T
|
||||
|
||||
|
||||
async def _cross_env(db, tmp_path):
|
||||
async def _cross_env(db, tmp_path, finalisiert=True):
|
||||
"""Zwei finalisierte Karten in der End-Barriere; die Sub-Rows liegen in der DB
|
||||
(post-finalize ist die DB die Wahrheit, nicht mehr das Karten-Payload)."""
|
||||
flow = Flow(TOPIC, work_dir=tmp_path)
|
||||
cards = []
|
||||
for bnorm, subs in (("alpha", ["Gleiche Aussage", "Nur in Alpha"]),
|
||||
("beta", ["Gleiche Aussage", "Nur in Beta"])):
|
||||
payload = {"title": bnorm.title(),
|
||||
"raw": {bnorm.title(): list(subs)},
|
||||
"sidecar": {bnorm.title(): [{"title": s, "level": "beginner"} for s in subs]},
|
||||
"facts": {bnorm.title(): {blocks._norm_title(s): {"key_points": [f"kp {s}"]} for s in subs}}}
|
||||
payload = {"title": bnorm.title()}
|
||||
if finalisiert:
|
||||
payload.update(pattern={}, artefacts={})
|
||||
await db.kanban_upsert_card(TOPIC, "artefacts", bnorm, "ablock", "konsolidierung", payload)
|
||||
await _seed_block(db, bnorm, subs)
|
||||
cards.append({"card_id": bnorm, "payload": payload})
|
||||
return flow, cards
|
||||
files = {k: tmp_path / f"{k}.json" for k in
|
||||
("sub_roh", "facts", "sidecar", "question_pattern", "artefakte")}
|
||||
return flow, cards, files
|
||||
|
||||
|
||||
async def test_crossblock_folds_loser(testdb, tmp_path, monkeypatch):
|
||||
"""Einstimmig „a" → Beta verliert die geteilte Aussage, Karten wandern zu levels."""
|
||||
"""Einstimmig „a" → Betas geteilte Aussage wird variant, ihre Frage wandert zum
|
||||
Gewinner (falte_sub), Karten gehen auf DONE."""
|
||||
db = testdb
|
||||
flow, cards = await _cross_env(db, tmp_path)
|
||||
flow, cards, files = await _cross_env(db, tmp_path)
|
||||
sn = blocks._norm_title("Gleiche Aussage")
|
||||
await db.upsert_question_pattern(TOPIC, "beta", sn, "Beta", "Gleiche Aussage", "F?")
|
||||
monkeypatch.setattr(ba, "embedding", _FakeEmb)
|
||||
fake = _fake_slot({"j1": {"pairs": {"1": "a"}}, "j2": {"pairs": {"1": "a"}}})
|
||||
monkeypatch.setattr(ba, "run_single_slot", fake)
|
||||
await ba._proc_konsolidierung(_ctx(), flow, {}, "", cards)
|
||||
await ba._proc_konsolidierung(_ctx(), flow, files, "", cards)
|
||||
assert "Gleiche Aussage" in fake.calls[0]["prompt"]
|
||||
beta = await db.kanban_get_card(TOPIC, "artefacts", "beta")
|
||||
assert beta["stage"] == "question_pattern"
|
||||
assert beta["payload"]["raw"]["Beta"] == ["Nur in Beta"]
|
||||
assert blocks._norm_title("Gleiche Aussage") not in beta["payload"]["facts"]["Beta"]
|
||||
# Barriere liegt jetzt hinter levels/relevance → auch die sidecar muss den Fold tragen
|
||||
assert [e["title"] for e in beta["payload"]["sidecar"]["Beta"]] == ["Nur in Beta"]
|
||||
alpha = await db.kanban_get_card(TOPIC, "artefacts", "alpha")
|
||||
assert alpha["stage"] == "question_pattern"
|
||||
assert alpha["payload"]["raw"]["Alpha"] == ["Gleiche Aussage", "Nur in Alpha"]
|
||||
rows = {r["sub_norm"]: r["status"] for r in await db.list_subblocks(TOPIC, "beta")}
|
||||
assert rows[blocks._norm_title("Gleiche Aussage")] == "variant"
|
||||
for cid in ("alpha", "beta"):
|
||||
assert (await db.kanban_get_card(TOPIC, "artefacts", cid))["stage"] == ba.DONE
|
||||
beta_rows = {r["sub_norm"]: r["status"] for r in await db.list_subblocks(TOPIC, "beta")}
|
||||
assert beta_rows[sn] == "variant"
|
||||
alpha_rows = {r["sub_norm"]: r["status"] for r in await db.list_subblocks(TOPIC, "alpha")}
|
||||
assert alpha_rows[sn] == "consensus"
|
||||
fragen = await db.list_question_pattern(TOPIC)
|
||||
assert {(r["block_norm"], r["sub_norm"]) for r in fragen} == {("alpha", sn)} # umgehängt
|
||||
|
||||
|
||||
async def test_crossblock_tiebreaker_folds(testdb, tmp_path, monkeypatch):
|
||||
"""j1/j2 uneinig → j3 entscheidet mit Mehrheit; hier „a" → Beta verliert."""
|
||||
db = testdb
|
||||
flow, cards = await _cross_env(db, tmp_path)
|
||||
flow, cards, files = await _cross_env(db, tmp_path)
|
||||
monkeypatch.setattr(ba, "embedding", _FakeEmb)
|
||||
fake = _fake_slot({"j1": {"pairs": {"1": "a"}}, "j2": {"pairs": {"1": "nein"}},
|
||||
"j3": {"pairs": {"1": "a"}}})
|
||||
monkeypatch.setattr(ba, "run_single_slot", fake)
|
||||
await ba._proc_konsolidierung(_ctx(), flow, {}, "", cards)
|
||||
await ba._proc_konsolidierung(_ctx(), flow, files, "", cards)
|
||||
assert len(fake.calls) == 3
|
||||
beta = await db.kanban_get_card(TOPIC, "artefacts", "beta")
|
||||
assert beta["payload"]["raw"]["Beta"] == ["Nur in Beta"]
|
||||
rows = {r["sub_norm"]: r["status"] for r in await db.list_subblocks(TOPIC, "beta")}
|
||||
assert rows[blocks._norm_title("Gleiche Aussage")] == "variant"
|
||||
|
||||
|
||||
async def test_crossblock_dissent_without_tiebreaker_keeps_both(testdb, tmp_path, monkeypatch):
|
||||
"""j3 liefert nichts (FAILED) → fail-open, Paar bleibt."""
|
||||
db = testdb
|
||||
flow, cards = await _cross_env(db, tmp_path)
|
||||
flow, cards, files = await _cross_env(db, tmp_path)
|
||||
monkeypatch.setattr(ba, "embedding", _FakeEmb)
|
||||
fake = _fake_slot({"j1": {"pairs": {"1": "a"}}, "j2": {"pairs": {"1": "b"}}}) # j3 fehlt → FAILED
|
||||
monkeypatch.setattr(ba, "run_single_slot", fake)
|
||||
await ba._proc_konsolidierung(_ctx(), flow, {}, "", cards)
|
||||
beta = await db.kanban_get_card(TOPIC, "artefacts", "beta")
|
||||
assert beta["stage"] == "question_pattern"
|
||||
assert beta["payload"]["raw"]["Beta"] == ["Gleiche Aussage", "Nur in Beta"]
|
||||
await ba._proc_konsolidierung(_ctx(), flow, files, "", cards)
|
||||
assert (await db.kanban_get_card(TOPIC, "artefacts", "beta"))["stage"] == ba.DONE
|
||||
rows = {r["sub_norm"]: r["status"] for r in await db.list_subblocks(TOPIC, "beta")}
|
||||
assert rows[blocks._norm_title("Gleiche Aussage")] == "consensus"
|
||||
|
||||
|
||||
async def test_crossblock_ersatzrichter(testdb, tmp_path, monkeypatch):
|
||||
"""Nur ein Richter liefert → Ersatz jE als zweite Stimme; Einstimmigkeit faltet."""
|
||||
db = testdb
|
||||
flow, cards = await _cross_env(db, tmp_path)
|
||||
flow, cards, files = await _cross_env(db, tmp_path)
|
||||
monkeypatch.setattr(ba, "embedding", _FakeEmb)
|
||||
fake = _fake_slot({"j1": {"pairs": {"1": "a"}}, "jE": {"pairs": {"1": "a"}}}) # j2 → FAILED
|
||||
monkeypatch.setattr(ba, "run_single_slot", fake)
|
||||
await ba._proc_konsolidierung(_ctx(), flow, {}, "", cards)
|
||||
beta = await db.kanban_get_card(TOPIC, "artefacts", "beta")
|
||||
assert beta["payload"]["raw"]["Beta"] == ["Nur in Beta"]
|
||||
await ba._proc_konsolidierung(_ctx(), flow, files, "", cards)
|
||||
rows = {r["sub_norm"]: r["status"] for r in await db.list_subblocks(TOPIC, "beta")}
|
||||
assert rows[blocks._norm_title("Gleiche Aussage")] == "variant"
|
||||
|
||||
|
||||
async def test_crossblock_without_embedding_advances(testdb, tmp_path, monkeypatch):
|
||||
db = testdb
|
||||
flow, cards = await _cross_env(db, tmp_path)
|
||||
flow, cards, files = await _cross_env(db, tmp_path)
|
||||
|
||||
class _Aus:
|
||||
@staticmethod
|
||||
@@ -478,33 +481,27 @@ async def test_crossblock_without_embedding_advances(testdb, tmp_path, monkeypat
|
||||
|
||||
monkeypatch.setattr(ba, "embedding", _Aus)
|
||||
monkeypatch.setattr(ba, "run_single_slot", kein_agent)
|
||||
await ba._proc_konsolidierung(_ctx(), flow, {}, "", cards)
|
||||
await ba._proc_konsolidierung(_ctx(), flow, files, "", cards)
|
||||
for cid in ("alpha", "beta"):
|
||||
assert (await db.kanban_get_card(TOPIC, "artefacts", cid))["stage"] == ba.DONE
|
||||
|
||||
|
||||
async def test_crossblock_nachzuegler_zurueck_zu_fragen(testdb, tmp_path, monkeypatch):
|
||||
"""Resume-Karte aus der alten Stage-Position (kein pattern im Payload) → zurück nach
|
||||
question_pattern, KEIN Dedup — finalize würde den Fold sonst re-spiegeln."""
|
||||
db = testdb
|
||||
flow, cards, files = await _cross_env(db, tmp_path, finalisiert=False)
|
||||
|
||||
async def kein_agent(*a, **kw):
|
||||
raise AssertionError("Nachzügler dürfen keinen Dedup auslösen")
|
||||
|
||||
monkeypatch.setattr(ba, "embedding", _FakeEmb)
|
||||
monkeypatch.setattr(ba, "run_single_slot", kein_agent)
|
||||
await ba._proc_konsolidierung(_ctx(), flow, files, "", cards)
|
||||
for cid in ("alpha", "beta"):
|
||||
assert (await db.kanban_get_card(TOPIC, "artefacts", cid))["stage"] == "question_pattern"
|
||||
|
||||
|
||||
async def test_crossblock_context_wins(testdb, tmp_path, monkeypatch):
|
||||
"""Kontext-Sub (Block schon hinter der Barrier) gewinnt auch bei Verdict „b" —
|
||||
die Paket-Seite fällt, der Kontext bleibt unangetastet."""
|
||||
db = testdb
|
||||
flow = Flow(TOPIC, work_dir=tmp_path)
|
||||
payload = {"title": "Alpha", "raw": {"Alpha": ["Gleiche Aussage"]}, "facts": {"Alpha": {}}}
|
||||
await db.kanban_upsert_card(TOPIC, "artefacts", "alpha", "ablock", "konsolidierung", payload)
|
||||
await _seed_block(db, "alpha", ["Gleiche Aussage"])
|
||||
cards = [{"card_id": "alpha", "payload": payload}]
|
||||
# Kontext-Block "gamma" ist bereits weiter (Stage levels) und hält dieselbe Aussage
|
||||
await db.kanban_upsert_card(TOPIC, "artefacts", "gamma", "ablock", "levels",
|
||||
{"title": "Gamma", "raw": {"Gamma": ["Gleiche Aussage"]}, "facts": {}})
|
||||
await _seed_block(db, "gamma", ["Gleiche Aussage"])
|
||||
monkeypatch.setattr(ba, "embedding", _FakeEmb)
|
||||
# Verdict „a": das Paket (A) soll behalten — Kontext faltet trotzdem nie
|
||||
fake = _fake_slot({"j1": {"pairs": {"1": "a"}}, "j2": {"pairs": {"1": "a"}}})
|
||||
monkeypatch.setattr(ba, "run_single_slot", fake)
|
||||
await ba._proc_konsolidierung(_ctx(), flow, {}, "", cards)
|
||||
alpha = await db.kanban_get_card(TOPIC, "artefacts", "alpha")
|
||||
assert alpha["payload"]["raw"].get("Alpha", []) == [] # Paket-Seite gefaltet
|
||||
gamma_rows = {r["sub_norm"]: r["status"] for r in await db.list_subblocks(TOPIC, "gamma")}
|
||||
assert gamma_rows[blocks._norm_title("Gleiche Aussage")] == "consensus" # Kontext unberührt
|
||||
rows = {r["sub_norm"]: r["status"] for r in await db.list_subblocks(TOPIC, "beta")}
|
||||
assert rows[blocks._norm_title("Gleiche Aussage")] == "consensus"
|
||||
|
||||
|
||||
async def test_finalize_defaultet_level_nachzuegler(testdb, tmp_path):
|
||||
@@ -547,21 +544,20 @@ async def test_crossblock_chunking_faltet_global(testdb, tmp_path, monkeypatch):
|
||||
cards = []
|
||||
for bnorm, subs in (("alpha", ["Gleiche Aussage", "Zweite gleiche Aussage", "Nur in Alpha"]),
|
||||
("beta", ["Gleiche Aussage", "Zweite gleiche Aussage"])):
|
||||
payload = {"title": bnorm.title(),
|
||||
"raw": {bnorm.title(): list(subs)},
|
||||
"sidecar": {bnorm.title(): [{"title": s, "level": "beginner"} for s in subs]},
|
||||
"facts": {bnorm.title(): {blocks._norm_title(s): {"key_points": []} for s in subs}}}
|
||||
payload = {"title": bnorm.title(), "pattern": {}, "artefacts": {}}
|
||||
await db.kanban_upsert_card(TOPIC, "artefacts", bnorm, "ablock", "konsolidierung", payload)
|
||||
await _seed_block(db, bnorm, subs)
|
||||
cards.append({"card_id": bnorm, "payload": payload})
|
||||
files = {k: tmp_path / f"{k}.json" for k in
|
||||
("sub_roh", "facts", "sidecar", "question_pattern", "artefakte")}
|
||||
monkeypatch.setattr(ba, "embedding", _FakeEmb)
|
||||
monkeypatch.setattr(ba, "CROSS_CHUNK_PAARE", 1) # 2 Paare → 2 Chunks
|
||||
fake = _fake_slot({"j1": {"pairs": {"1": "a"}}, "j2": {"pairs": {"1": "a"}}})
|
||||
monkeypatch.setattr(ba, "run_single_slot", fake)
|
||||
await ba._proc_konsolidierung(_ctx(), flow, {}, "", cards)
|
||||
await ba._proc_konsolidierung(_ctx(), flow, files, "", cards)
|
||||
assert len(fake.calls) == 4 # 2 Chunks × j1/j2
|
||||
beta = await db.kanban_get_card(TOPIC, "artefacts", "beta")
|
||||
assert beta["payload"]["raw"].get("Beta", []) == [] # beide Dubletten global gefaltet
|
||||
rows = {r["sub_norm"]: r["status"] for r in await db.list_subblocks(TOPIC, "beta")}
|
||||
assert set(rows.values()) == {"variant"} # beide Dubletten global gefaltet
|
||||
|
||||
|
||||
async def test_facts_nachfass_holt_nur_fehlende(testdb, tmp_path, monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user