update
This commit is contained in:
@@ -324,8 +324,9 @@ async def test_panel_confirms_demote(board_env, tmp_path, monkeypatch):
|
||||
assert (await db.kanban_get_card(TOPIC, B, "b-2"))["stage"] == "dedup"
|
||||
|
||||
|
||||
async def test_panel_overrules_single_vote(board_env, tmp_path, monkeypatch):
|
||||
"""Nur 1 von 3 Panel-Stimmen bestätigt den Judge-Demote → Karte überlebt (Journal)."""
|
||||
async def test_single_recheck_vote_confirms_proposal(board_env, tmp_path, monkeypatch):
|
||||
"""M3.2: ein pass-1-Vorschlag wird schon von EINER Recheck-Stimme bestätigt → rejected.
|
||||
(15/17 mehrheitlich geretteten Karten waren später QA-Dubletten — Overturn nur einstimmig.)"""
|
||||
db, ctx, files = board_env
|
||||
monkeypatch.setattr(bi, "run_single_slot", _slot_router([
|
||||
("-filter-recheck-", _confirm_votes({"1"}, {"fragments": {"1": 2}, "drop": []})),
|
||||
@@ -335,6 +336,23 @@ async def test_panel_overrules_single_vote(board_env, tmp_path, monkeypatch):
|
||||
("b-1", {"title": "Blockzitat", "description": "Zitat mit >"}),
|
||||
("b-2", {"title": "Codeblock", "description": "Code mit Einrückung"}),
|
||||
])
|
||||
c1 = await db.kanban_get_card(TOPIC, B, "b-1")
|
||||
assert c1["stage"] == "rejected"
|
||||
assert c1["payload"]["reason"] == "fragment"
|
||||
assert c1["payload"]["parent_norm"] == "codeblock"
|
||||
|
||||
|
||||
async def test_unanimous_recheck_keep_overrules_proposal(board_env, tmp_path, monkeypatch):
|
||||
"""M3.2: nur ein EINSTIMMIGES Recheck-Panel (0 Demote-Stimmen) hebt den pass-1-Vorschlag auf."""
|
||||
db, ctx, files = board_env
|
||||
monkeypatch.setattr(bi, "run_single_slot", _slot_router([
|
||||
("-filter-recheck-", _confirm_votes(set(), {"fragments": {"1": 2}, "drop": []})),
|
||||
("-filter-", {"fragments": {"1": 2}, "drop": []}),
|
||||
]))
|
||||
await _run_filter(db, ctx, tmp_path, [
|
||||
("b-1", {"title": "Blockzitat", "description": "Zitat mit >"}),
|
||||
("b-2", {"title": "Codeblock", "description": "Code mit Einrückung"}),
|
||||
])
|
||||
assert (await db.kanban_get_card(TOPIC, B, "b-1"))["stage"] == "dedup"
|
||||
journal = json.loads(next(tmp_path.glob("inventar-filter-*.json")).read_text(encoding="utf-8"))
|
||||
assert journal["ueberstimmt"] == ["Blockzitat"]
|
||||
@@ -587,6 +605,45 @@ def test_canonical_key_camel_and_catalogue():
|
||||
assert k("Section 2.1 Matching") == k("Matching")
|
||||
|
||||
|
||||
def test_canonical_key_glued_problem_suffix():
|
||||
"""Verklebtes Kompositum-Suffix „…problem" wird abgetrennt, damit „Cliquenproblem" und
|
||||
„Clique" denselben Blocking-Key teilen (aak: sonst nie Dedup-Kandidat). Fugen-n/-s + Plural
|
||||
inklusive; die generische Suffix-Regel darf keine echten Varianten über-mergen."""
|
||||
from blocks import _canonical_key as k
|
||||
assert k("Clique") == k("Cliquenproblem") == k("Cliquenprobleme") != ""
|
||||
assert k("Set Cover") == k("SetCover-Problem") # bestehender Hyphen-Pfad bleibt
|
||||
assert k("SAT") != k("3-SAT") # Varianten-Ziffer bleibt Signal
|
||||
assert k("Problem") != k("Clique") # bloßes „Problem" wird nicht zum Stamm
|
||||
|
||||
|
||||
def test_reference_strip_and_is_reference():
|
||||
"""Katalog-Nummern werden gestrippt, der Konzeptname bleibt; reine Nummern → leer + Referenz.
|
||||
Reale aak-Schadensfälle (Satz-/Bemerkungs-Titel liefen wörtlich bis done)."""
|
||||
from blocks import _reference_strip as strip, _is_reference as isref
|
||||
assert strip("Satz 7.13 (Christofides)") == "Christofides"
|
||||
assert strip("Satz 7.6: Kriterium für Eulerschen Kreis") == "Kriterium für Eulerschen Kreis"
|
||||
assert strip("N P via nicht-deterministische Turingmaschine (Definition 6.19)") \
|
||||
== "N P via nicht-deterministische Turingmaschine"
|
||||
assert strip("Bemerkung 7.22") == ""
|
||||
assert strip("Vertex Cover") == "Vertex Cover" # kein Katalog-Gerüst → unverändert
|
||||
assert isref("Bemerkung 7.22") and isref("Satz 7.18") and isref("Korollar 6.18")
|
||||
assert isref("Bedingung (**)")
|
||||
assert not isref("Satz 7.13 (Christofides)") # hat Konzept → keine Referenz
|
||||
assert not isref("Satz 7.6: Kriterium für Eulerschen Kreis")
|
||||
assert not isref("P⊆NP") and not isref("Σ*") # kurze Symbole bleiben echt
|
||||
|
||||
|
||||
def test_is_named_statement_construction_suffix():
|
||||
"""Reduktion mit Konstruktions-Suffix (: / = nach dem Ziel) ist ein Fragment, kein Statement —
|
||||
darf nicht mehr vor Demotion geschützt sein (aak: „3-SAT ≤ K-COLOR: G=(V,E) Konstruktion")."""
|
||||
from blocks import _is_named_statement as named
|
||||
assert not named("3-SAT ≤ K-COLOR: G=(V,E) Konstruktion")
|
||||
assert named("3-SAT ≤ Clique") # saubere Reduktion bleibt geschützt
|
||||
assert named("Clique → Vertex Cover")
|
||||
assert not named("X ist NP-vollständig") # unäre Aussage bleibt demotable
|
||||
assert named("Satz von Cook/Levin: SAT ist NP-vollständig ⇔ …") # benanntes Ergebnis bleibt
|
||||
|
||||
|
||||
def test_relation_guard_ignores_trailing_scaffolding():
|
||||
"""Trailing „Reduktion/Transformation" ist kein Operand — sonst blockt der Guard
|
||||
den korrekten Merge; Richtungs-Konflikte bleiben erkannt."""
|
||||
@@ -1213,6 +1270,49 @@ async def test_namecheck_ok_behaelt_titel(testdb, tmp_path, monkeypatch):
|
||||
assert block["payload"]["description"] == "Eigene Beschreibung"
|
||||
|
||||
|
||||
async def test_namecheck_strips_source_numbering(testdb, tmp_path, monkeypatch):
|
||||
"""M1.2: wählt der Check-Judge einen numerierten Titel, wird die Katalog-Nummer deterministisch
|
||||
zum Konzept gestrippt ('Satz 7.13 (Christofides)' → 'Christofides')."""
|
||||
db = testdb
|
||||
|
||||
async def fake_members(topic, cid):
|
||||
return [{"norm": "satz 7.13 (christofides)", "title": "Satz 7.13 (Christofides)",
|
||||
"description": "3/2-Approximation für metrisches TSP", "readers": ["r1"], "sources": []},
|
||||
{"norm": "b", "title": "B", "description": "db", "readers": ["r2"], "sources": []}]
|
||||
|
||||
async def fake_slot(ctx, label, *, key, prompt, role, capabilities, payload, timeout):
|
||||
return "ok", payload((0, '{"best": 1}', "")) # Judge wählt den numerierten Member-Titel
|
||||
|
||||
monkeypatch.setattr(bi, "_member_rows", fake_members)
|
||||
monkeypatch.setattr(bi, "run_single_slot", fake_slot)
|
||||
payload = {"title": "Satz 7.13 (Christofides)", "description": "3/2-Approximation",
|
||||
"main_norm": "satz 7.13 (christofides)"}
|
||||
await db.kanban_upsert_card(TOPIC, B, "c7", "cluster", "naming_check", payload)
|
||||
ctx = GenContext(topic=TOPIC, provider="claude", is_cancelled=lambda: False)
|
||||
await bi._namecheck_one(ctx, _mk_flow(tmp_path), {"card_id": "c7", "payload": payload})
|
||||
block = await db.kanban_get_card(TOPIC, B, "b-c7")
|
||||
assert block["payload"]["title"] == "Christofides"
|
||||
|
||||
|
||||
async def test_consensus_gate_single_reader_majority_quorum(testdb, tmp_path, monkeypatch):
|
||||
"""M5.1: Einzel-Reader-Fund → clarify mit Mehrheits-Quorum statt Einstimmigkeit."""
|
||||
db = testdb
|
||||
monkeypatch.setattr(bi, "source_folder", lambda t: None) # thema: kein Korpus
|
||||
|
||||
async def fake_members(topic, cid):
|
||||
return [{"title": "Seltenes Konzept", "description": "einmal erwähnt",
|
||||
"readers": ["r1"], "supplement": False}]
|
||||
|
||||
monkeypatch.setattr(bi, "_member_rows", fake_members)
|
||||
monkeypatch.setattr(bi, "_rep", lambda rows: rows[0])
|
||||
await db.kanban_upsert_card(TOPIC, B, "cs", "cluster", "consensus_gate", {})
|
||||
ctx = GenContext(topic=TOPIC, provider="claude", is_cancelled=lambda: False)
|
||||
await bi._proc_consensus_gate(ctx, _mk_flow(tmp_path), [{"card_id": "cs", "payload": {}}])
|
||||
card = await db.kanban_get_card(TOPIC, B, "cs")
|
||||
assert card["stage"] == "clarify"
|
||||
assert card["payload"]["quorum"] == "majority"
|
||||
|
||||
|
||||
# ── Sanierung: Titel auf Korpus-Form, Beschreibungspflicht (QA: fremd/hygiene) ──────
|
||||
|
||||
def test_sanierung_schema_varianten():
|
||||
@@ -1229,6 +1329,11 @@ def test_sanierung_noetig():
|
||||
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
|
||||
# M1.3: Referenz-Titel / unbalancierte Klammer / >80 Zeichen triggern auch ohne Korpus (Singletons)
|
||||
assert bi._sanierung_noetig({"title": "Bemerkung 7.22", "description": "d"}, None)
|
||||
assert bi._sanierung_noetig({"title": "N P (Definition 6.19", "description": "d"}, None)
|
||||
assert bi._sanierung_noetig({"title": "A" * 81, "description": "d"}, None)
|
||||
assert not bi._sanierung_noetig({"title": "Clique", "description": "d"}, None)
|
||||
|
||||
|
||||
def _sanierung_env(tmp_path, monkeypatch, antwort):
|
||||
|
||||
Reference in New Issue
Block a user