update
This commit is contained in:
@@ -25,6 +25,9 @@ def _fake_single_slot(tmp_path):
|
||||
if "-pair-" in key:
|
||||
pairs = prompt.count("\nA: ")
|
||||
out = {"pairs": {str(i + 1): "ja" for i in range(pairs)}}
|
||||
elif "-dedup-" in key:
|
||||
pairs = prompt.count("\nA: ")
|
||||
out = {"pairs": {str(i + 1): "nein" for i in range(pairs)}}
|
||||
elif "-clarify-" in key:
|
||||
keep = [line[2:].split(" — ")[0] for line in prompt.splitlines()
|
||||
if line.startswith("- ")]
|
||||
@@ -57,11 +60,11 @@ async def board_env(testdb, tmp_path, monkeypatch):
|
||||
return False
|
||||
monkeypatch.setattr(bi, "_emb_ok", no_emb)
|
||||
|
||||
async def fake_subblocks(ctx, set_p, files, entries, instructions, wipe=True, ns="", seeds=None, lbl=""):
|
||||
async def fake_subblocks(ctx, set_p, files, entries, instructions, wipe=True, ns="", seeds=None, lbl="", sources=None):
|
||||
title = list(entries.values())[0].split(" — ")[0]
|
||||
return {title: ["Sub Eins", "Sub Zwei"]}
|
||||
|
||||
async def fake_facts(ctx, set_p, files, raw, q, folder, instructions, ns="", lbl=""):
|
||||
async def fake_facts(ctx, set_p, files, raw, q, folder, instructions, ns="", lbl="", sources=None):
|
||||
facts = {t: {_norm_title(s): {"key_points": [f"Fakt zu {s}"], "cited_facts": []}
|
||||
for s in subs} for t, subs in raw.items()}
|
||||
return facts, {}
|
||||
@@ -134,6 +137,7 @@ async def test_board1_full_flow(board_env):
|
||||
# reader union survived the pipeline (consensus evidence on the block card)
|
||||
alpha = next(c for c in done if c["payload"]["title"] == "Alpha")
|
||||
assert set(alpha["payload"]["readers"]) == {"r1", "r2"}
|
||||
assert alpha["payload"]["n_size"] == 2 # LPT estimate travels with the card
|
||||
# board 2: one artefact card per block ran through to done_artefact (+ outline singleton)
|
||||
art_done = await db.kanban_cards(TOPIC, board="artefacts", stage="done_artefact")
|
||||
assert len(art_done) == 5 # 4 blocks + outline card
|
||||
@@ -185,7 +189,7 @@ async def test_empty_subblocks_completes_without_deadletter(board_env, monkeypat
|
||||
import blocks as blx
|
||||
db, ctx, files = board_env
|
||||
|
||||
async def empty_subs(ctx, set_p, files, entries, instructions, wipe=True, ns="", seeds=None, lbl=""):
|
||||
async def empty_subs(ctx, set_p, files, entries, instructions, wipe=True, ns="", seeds=None, lbl="", sources=None):
|
||||
return {}
|
||||
monkeypatch.setattr(ba, "_subblocks_block", empty_subs)
|
||||
await db.kanban_upsert_card(TOPIC, "artefacts", "leer", "ablock", "subblocks",
|
||||
@@ -264,7 +268,7 @@ async def test_panel_confirms_demote(board_env, tmp_path, monkeypatch):
|
||||
assert c1["stage"] == "rejected"
|
||||
assert c1["payload"]["reason"] == "fragment"
|
||||
assert c1["payload"]["parent_norm"] == "codeblock"
|
||||
assert (await db.kanban_get_card(TOPIC, B, "b-2"))["stage"] == "grouping"
|
||||
assert (await db.kanban_get_card(TOPIC, B, "b-2"))["stage"] == "dedup"
|
||||
|
||||
|
||||
async def test_panel_overrules_single_vote(board_env, tmp_path, monkeypatch):
|
||||
@@ -278,7 +282,7 @@ 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"}),
|
||||
])
|
||||
assert (await db.kanban_get_card(TOPIC, B, "b-1"))["stage"] == "grouping"
|
||||
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"]
|
||||
assert journal["degradiert"] == 0
|
||||
@@ -329,7 +333,7 @@ async def test_floor_vetoes_structureless_demote(board_env, tmp_path, monkeypatc
|
||||
("b-3", {"title": "Aufgabenlisten (Task Lists)", "description": "Checkboxen"}),
|
||||
("b-4", {"title": "Aufgabenlisten", "description": "GFM-Listen mit Checkbox"}),
|
||||
])
|
||||
assert (await db.kanban_get_card(TOPIC, B, "b-1"))["stage"] == "grouping" # floor veto
|
||||
assert (await db.kanban_get_card(TOPIC, B, "b-1"))["stage"] == "dedup" # floor veto
|
||||
assert (await db.kanban_get_card(TOPIC, B, "b-3"))["stage"] == "rejected" # containment holds
|
||||
journal = json.loads(next(tmp_path.glob("inventar-filter-*.json")).read_text(encoding="utf-8"))
|
||||
assert journal["floor_veto"] == ["Blockzitat"]
|
||||
@@ -388,19 +392,268 @@ async def test_supplement_reopens_dead_lineage(board_env, tmp_path, monkeypatch)
|
||||
assert fresh and fresh["stage"] == "ingest" and fresh["payload"]["supplement"] is True
|
||||
|
||||
|
||||
# ── Dedup-Stage: globaler Paar-Abgleich nach dem Naming ─────────────────────────────
|
||||
|
||||
def _angle_vecs(mapping):
|
||||
"""Vector fake with controllable cosine: mapped substring → angle (degrees) in a
|
||||
shared 2D plane; unmapped texts get their own orthogonal axis (cos 0 to everything)."""
|
||||
import math as m
|
||||
import numpy as np
|
||||
|
||||
async def fake(flow, texts):
|
||||
dim = 2 + len(texts)
|
||||
rows = []
|
||||
for i, t in enumerate(texts):
|
||||
v = np.zeros(dim)
|
||||
for key, deg in mapping.items():
|
||||
if key in t:
|
||||
v[0], v[1] = m.cos(m.radians(deg)), m.sin(m.radians(deg))
|
||||
break
|
||||
else:
|
||||
v[2 + i] = 1.0
|
||||
rows.append(v)
|
||||
return np.vstack(rows)
|
||||
return fake
|
||||
|
||||
|
||||
async def _run_dedup(db, ctx, tmp_path, cards):
|
||||
"""Seed block cards into dedup and run ONE barrier pass over them."""
|
||||
for cid, p in cards:
|
||||
await db.kanban_upsert_card(TOPIC, B, cid, "block", "dedup", p)
|
||||
rows = [{"card_id": cid, "payload": dict(p)} for cid, p in cards]
|
||||
await bi._proc_dedup(ctx, _mk_flow(tmp_path), rows)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def emb_on(monkeypatch):
|
||||
async def yes(flow):
|
||||
return True
|
||||
monkeypatch.setattr(bi, "_emb_ok", yes)
|
||||
|
||||
|
||||
async def test_dedup_merges_confirmed_pair(board_env, tmp_path, monkeypatch, emb_on):
|
||||
"""Judge-„ja" merged: Verlierer → grouped (reason/merged_into), Champion sammelt reader."""
|
||||
# Titel-Cos ~0.77 (unter Auto-0.95) → Kandidat, der Judge entscheidet
|
||||
monkeypatch.setattr(bi, "_vec_rows", _angle_vecs({"satisfiability": 0, "sat": 40}))
|
||||
counter = {}
|
||||
monkeypatch.setattr(bi, "run_single_slot", _slot_router([
|
||||
("-dedup-", {"pairs": {"1": "ja"}}),
|
||||
], counter))
|
||||
await _run_dedup(db_ := board_env[0], ctx := board_env[1], tmp_path, [
|
||||
("b-1", {"title": "SAT", "description": "kurz", "readers": ["r1"]}),
|
||||
("b-2", {"title": "SAT (Satisfiability Problem)",
|
||||
"description": "Erfüllbarkeit Boolescher Ausdrücke", "readers": ["r2"]}),
|
||||
])
|
||||
loser = await db_.kanban_get_card(TOPIC, B, "b-1")
|
||||
champ = await db_.kanban_get_card(TOPIC, B, "b-2")
|
||||
assert loser["stage"] == "grouped"
|
||||
assert loser["payload"]["reason"] == "merged"
|
||||
assert loser["payload"]["merged_into"] == "SAT (Satisfiability Problem)"
|
||||
assert champ["stage"] == "grouping"
|
||||
assert set(champ["payload"]["readers"]) == {"r1", "r2"}
|
||||
journal = json.loads(next(tmp_path.glob("inventar-dedup-*.json")).read_text(encoding="utf-8"))
|
||||
assert journal["merged"] == [{"dublette": "SAT", "in": "SAT (Satisfiability Problem)"}]
|
||||
assert journal["paare_detail"][0]["verdict"] == "ja"
|
||||
assert counter["-dedup-"] == 2 # Zwei-Judge-Panel
|
||||
|
||||
|
||||
async def test_dedup_judge_nein_keeps_both(board_env, tmp_path, monkeypatch, emb_on):
|
||||
monkeypatch.setattr(bi, "_vec_rows", _angle_vecs({"modifizierter": 0, "greedy": 40}))
|
||||
monkeypatch.setattr(bi, "run_single_slot", _slot_router([
|
||||
("-dedup-", {"pairs": {"1": "nein"}}),
|
||||
]))
|
||||
await _run_dedup(board_env[0], board_env[1], tmp_path, [
|
||||
("b-1", {"title": "Greedy-Algorithmus", "description": "Basisverfahren"}),
|
||||
("b-2", {"title": "Modifizierter Greedy-Algorithmus", "description": "Variante"}),
|
||||
])
|
||||
assert (await board_env[0].kanban_get_card(TOPIC, B, "b-1"))["stage"] == "grouping"
|
||||
assert (await board_env[0].kanban_get_card(TOPIC, B, "b-2"))["stage"] == "grouping"
|
||||
|
||||
|
||||
async def test_dedup_relation_guard_blocks_identical_tokens(board_env, tmp_path, monkeypatch, emb_on):
|
||||
"""Gleiche Tokens, andere Richtung: Judge sagt „ja", Titel-Cos 1.0 (Auto-Kante) —
|
||||
der Relation-Guard blockt beides, beide Karten überleben."""
|
||||
monkeypatch.setattr(bi, "_vec_rows", _angle_vecs({"hamiltonian": 0}))
|
||||
monkeypatch.setattr(bi, "run_single_slot", _slot_router([
|
||||
("-dedup-", lambda key: {"pairs": {"1": "ja"}}),
|
||||
]))
|
||||
await _run_dedup(board_env[0], board_env[1], tmp_path, [
|
||||
("b-1", {"title": "Hamiltonian Cycle ≤ Hamiltonian Path", "description": "Reduktion"}),
|
||||
("b-2", {"title": "Hamiltonian Path ≤ Hamiltonian Cycle", "description": "Reduktion"}),
|
||||
])
|
||||
assert (await board_env[0].kanban_get_card(TOPIC, B, "b-1"))["stage"] == "grouping"
|
||||
assert (await board_env[0].kanban_get_card(TOPIC, B, "b-2"))["stage"] == "grouping"
|
||||
journal = json.loads(next(tmp_path.glob("inventar-dedup-*.json")).read_text(encoding="utf-8"))
|
||||
assert journal["paare_detail"][0]["verdict"] == "guard_veto"
|
||||
|
||||
|
||||
async def test_dedup_panel_disagreement_keeps_both(board_env, tmp_path, monkeypatch, emb_on):
|
||||
"""Merge braucht Einstimmigkeit: j1 ja + j2 nein → beide überleben (Journal: uneinig)."""
|
||||
monkeypatch.setattr(bi, "_vec_rows", _angle_vecs({"satisfiability": 0, "sat": 40}))
|
||||
monkeypatch.setattr(bi, "run_single_slot", _slot_router([
|
||||
("-dedup-", lambda key: {"pairs": {"1": "ja" if "-j1" in key else "nein"}}),
|
||||
]))
|
||||
await _run_dedup(board_env[0], board_env[1], tmp_path, [
|
||||
("b-1", {"title": "SAT", "description": "d1"}),
|
||||
("b-2", {"title": "SAT (Satisfiability Problem)", "description": "d2"}),
|
||||
])
|
||||
assert (await board_env[0].kanban_get_card(TOPIC, B, "b-1"))["stage"] == "grouping"
|
||||
assert (await board_env[0].kanban_get_card(TOPIC, B, "b-2"))["stage"] == "grouping"
|
||||
journal = json.loads(next(tmp_path.glob("inventar-dedup-*.json")).read_text(encoding="utf-8"))
|
||||
assert journal["paare_detail"][0]["verdict"] == "uneinig"
|
||||
|
||||
|
||||
async def test_dedup_title_only_candidate(board_env, tmp_path, monkeypatch, emb_on):
|
||||
"""Titel-Cos über dem Floor reicht als Kandidat — auch wenn der Mittelwert
|
||||
(verschiedene Beschreibungs-Facetten) darunter liegt."""
|
||||
# Beschreibungen fast orthogonal (0° vs 80°), Titel ähnlich (0° vs 40°)
|
||||
monkeypatch.setattr(bi, "_vec_rows", _angle_vecs(
|
||||
{"völlig": 0, "ganz": 80, "alpha kern": 0, "alpha zentrum": 40}))
|
||||
monkeypatch.setattr(bi, "run_single_slot", _slot_router([
|
||||
("-dedup-", {"pairs": {"1": "ja"}}),
|
||||
]))
|
||||
await _run_dedup(board_env[0], board_env[1], tmp_path, [
|
||||
("b-1", {"title": "Alpha Kern", "description": "völlig anderes"}),
|
||||
("b-2", {"title": "Alpha Zentrum", "description": "ganz anders zwei"}),
|
||||
])
|
||||
stages = sorted([(await board_env[0].kanban_get_card(TOPIC, B, c))["stage"]
|
||||
for c in ("b-1", "b-2")])
|
||||
assert stages == ["grouped", "grouping"] # Paar wurde gejudged und merged
|
||||
|
||||
|
||||
def test_canonical_key_camel_and_catalogue():
|
||||
"""CamelCase-Split + Katalog-Phrasen-Strip; Varianten-Ziffern bleiben erhalten."""
|
||||
from blocks import _canonical_key as k
|
||||
assert k("Set Cover") == k("SetCover-Problem") != ""
|
||||
assert k("Definition 6.19 (NP)") == "np"
|
||||
assert k("SAT") != k("3-SAT") # Varianten-Ziffer ist Signal, keine Katalognummer
|
||||
assert k("ModifiedGreedy") == k("Modified Greedy")
|
||||
# englische Katalog-Phrasen gleichwertig (Quellen sind nicht immer deutsch)
|
||||
assert k("Corollary 3.2 (VC)") == "vc"
|
||||
assert k("Chapter 7: Vertex Cover") == k("Vertex Cover")
|
||||
assert k("Section 2.1 Matching") == k("Matching")
|
||||
|
||||
|
||||
def test_relation_guard_ignores_trailing_scaffolding():
|
||||
"""Trailing „Reduktion/Transformation" ist kein Operand — sonst blockt der Guard
|
||||
den korrekten Merge; Richtungs-Konflikte bleiben erkannt."""
|
||||
from blocks import _relation_conflict as c
|
||||
assert not c("SetCover ≤ HittingSet", "SetCover ≤ HittingSet Reduktion")
|
||||
assert not c("A → B", "A → B Transformation")
|
||||
assert c("Hamiltonian Cycle ≤ Hamiltonian Path", "Hamiltonian Path ≤ Hamiltonian Cycle")
|
||||
|
||||
|
||||
def test_relation_guard_english_and_operator_suffix():
|
||||
"""Englisches „Reduction" ist Scaffolding wie „Reduktion"; ein angehängtes
|
||||
p/m am Operator („≤p") gehört zum Operator, nicht zum Operanden.
|
||||
Beides waren Fehl-Vetos im aak-Lauf. Varianten-Konflikte bleiben."""
|
||||
from blocks import _relation_conflict as c
|
||||
assert not c("3-Exact Cover ≤ SubSet Sum", "Reduction 3-EXACT COVER ≤ SUBSET SUM")
|
||||
assert not c("k-CLIQUE ≤ k-INDEPENDENT SET", "Reduction k-CLIQUE ≤ k-INDEPENDENT SET")
|
||||
assert not c("3-SAT ≤ 3-Färbung", "3-SAT ≤p 3-Färbung")
|
||||
assert c("SAT ≤ Clique", "3-SAT ≤ Clique") # Variante als Operand bleibt Konflikt
|
||||
|
||||
|
||||
async def test_dedup_casefold_title_candidate(board_env, tmp_path, monkeypatch, emb_on):
|
||||
"""GROSSSCHREIBUNG darf den Titel-Kanal nicht brechen: Titel werden casefolded
|
||||
eingebettet („VERTEX COVER" vs. „Vertex Cover (VC)" lag real bei Cos 0.55)."""
|
||||
monkeypatch.setattr(bi, "_vec_rows", _angle_vecs({"vertex cover": 0}))
|
||||
monkeypatch.setattr(bi, "run_single_slot", _slot_router([
|
||||
("-dedup-", {"pairs": {"1": "ja"}}),
|
||||
]))
|
||||
await _run_dedup(board_env[0], board_env[1], tmp_path, [
|
||||
("b-1", {"title": "VERTEX COVER", "description": "knapp"}),
|
||||
("b-2", {"title": "Vertex Cover (VC)", "description": "Knotenüberdeckung ausführlich"}),
|
||||
])
|
||||
stages = sorted([(await board_env[0].kanban_get_card(TOPIC, B, c))["stage"]
|
||||
for c in ("b-1", "b-2")])
|
||||
assert stages == ["grouped", "grouping"]
|
||||
|
||||
|
||||
async def test_dedup_no_embedding_passes_through(board_env, tmp_path, monkeypatch):
|
||||
"""Ohne Embedding-Modell winkt die Stage durch — 0 Agent-Calls (kein n²-Fallback)."""
|
||||
counter = {}
|
||||
monkeypatch.setattr(bi, "run_single_slot", _slot_router([("-dedup-", {"pairs": {}})], counter))
|
||||
await _run_dedup(board_env[0], board_env[1], tmp_path, [
|
||||
("b-1", {"title": "SAT", "description": "d"}),
|
||||
("b-2", {"title": "SAT (Satisfiability Problem)", "description": "d"}),
|
||||
])
|
||||
assert (await board_env[0].kanban_get_card(TOPIC, B, "b-1"))["stage"] == "grouping"
|
||||
assert (await board_env[0].kanban_get_card(TOPIC, B, "b-2"))["stage"] == "grouping"
|
||||
assert counter == {}
|
||||
|
||||
|
||||
async def test_dedup_second_wave_merges_into_context(board_env, tmp_path, monkeypatch, emb_on):
|
||||
"""Supplement-Welle: Neuling merged in den bestätigten Block; der bleibt unberührt."""
|
||||
db, ctx, files = board_env
|
||||
monkeypatch.setattr(bi, "_vec_rows", _angle_vecs({"satisfiability": 0, "sat": 40}))
|
||||
monkeypatch.setattr(bi, "run_single_slot", _slot_router([
|
||||
("-dedup-", {"pairs": {"1": "ja"}}),
|
||||
]))
|
||||
await db.kanban_upsert_card(TOPIC, B, "b-old", "block", "done_block",
|
||||
{"title": "SAT", "description": "Erfüllbarkeitsproblem",
|
||||
"readers": ["r1"], "mirrored_norm": "sat"})
|
||||
await db.upsert_block(TOPIC, "sat", "SAT", "Erfüllbarkeitsproblem", [])
|
||||
await _run_dedup(db, ctx, tmp_path, [
|
||||
("b-new", {"title": "SAT (Satisfiability Problem)", "description": "kurz", "readers": ["r9"]}),
|
||||
])
|
||||
new = await db.kanban_get_card(TOPIC, B, "b-new")
|
||||
old = await db.kanban_get_card(TOPIC, B, "b-old")
|
||||
assert new["stage"] == "grouped"
|
||||
assert new["payload"]["merged_into"] == "SAT"
|
||||
assert old["stage"] == "done_block" # context never demoted
|
||||
assert set(old["payload"]["readers"]) == {"r1", "r9"}
|
||||
|
||||
|
||||
async def test_dedup_resume_no_new_calls(board_env, tmp_path, monkeypatch, emb_on):
|
||||
monkeypatch.setattr(bi, "_vec_rows", _angle_vecs({"satisfiability": 0, "sat": 40}))
|
||||
counter = {}
|
||||
monkeypatch.setattr(bi, "run_single_slot", _slot_router([
|
||||
("-dedup-", {"pairs": {"1": "nein"}}),
|
||||
], counter))
|
||||
cards = [("b-1", {"title": "SAT", "description": "d1"}),
|
||||
("b-2", {"title": "SAT (Satisfiability Problem)", "description": "d2"})]
|
||||
await _run_dedup(board_env[0], board_env[1], tmp_path, cards)
|
||||
assert counter["-dedup-"] == 2 # zwei Panel-Judges
|
||||
await _run_dedup(board_env[0], board_env[1], tmp_path, cards)
|
||||
assert counter["-dedup-"] == 2 # judge files reused
|
||||
|
||||
|
||||
async def test_dedup_complete_link_no_chaining(board_env, tmp_path, monkeypatch, emb_on):
|
||||
"""A≈B ja, B≈C ja, A–C kein Kandidat → complete-link merged nur ein Paar."""
|
||||
# Winkel 0/30/60: A-B und B-C sind Kandidaten (cos .87), A-C nicht (cos .5 < Floor)
|
||||
monkeypatch.setattr(bi, "_vec_rows", _angle_vecs({"alpha": 0, "beta": 30, "gamma": 60}))
|
||||
monkeypatch.setattr(bi, "run_single_slot", _slot_router([
|
||||
("-dedup-", {"pairs": {"1": "ja", "2": "ja"}}),
|
||||
]))
|
||||
await _run_dedup(board_env[0], board_env[1], tmp_path, [
|
||||
("b-1", {"title": "Konzept Alpha", "description": "a"}),
|
||||
("b-2", {"title": "Konzept Beta", "description": "bb"}),
|
||||
("b-3", {"title": "Konzept Gamma", "description": "c"}),
|
||||
])
|
||||
db = board_env[0]
|
||||
stages = {cid: (await db.kanban_get_card(TOPIC, B, cid))["stage"]
|
||||
for cid in ("b-1", "b-2", "b-3")}
|
||||
assert sorted(stages.values()) == ["grouped", "grouping", "grouping"]
|
||||
|
||||
|
||||
# ── Makespan: Slot-Priorität, vorgezogene Gliederung ────────────────────────────────
|
||||
|
||||
def test_agent_priority_order():
|
||||
"""Board 1 zuerst; in Board 2 gewinnen späte Stages (Restarbeit vor Nachschub)."""
|
||||
from agents import _agent_priority as p
|
||||
t = "blocks-Markdown"
|
||||
assert p(f"{t}-research-1") < p(f"{t}-filter-abc-c0") < p(f"{t}-supplement")
|
||||
assert p(f"{t}-research-1") < p(f"{t}-filter-abc-c0") < p(f"{t}-dedup-abc-c0") < p(f"{t}-supplement")
|
||||
# Gruppierung-Keys heißen "gruppierung": ohne eigenen Eintrag fielen sie ans Ende
|
||||
# und verhungerten hinter Board 2 (aak: 211 min Slot-Wartezeit)
|
||||
assert p(f"{t}-dedup-abc-c0") < p(f"{t}-gruppierung-xyz-cTOP") < p(f"{t}-supplement")
|
||||
assert p(f"{t}-gruppierung-xyz-cTOP") < p(f"{t}-ns-subblock-c1-r2-1")
|
||||
assert (p(f"{t}-outline-judge") < p(f"{t}-ns-artifact-example-c0")
|
||||
< p(f"{t}-ns-question-pattern-c0") < p(f"{t}-ns-relevance-final-c0")
|
||||
< p(f"{t}-ns-level-final-c0") < p(f"{t}-ns-facts-erg-c0")
|
||||
< p(f"{t}-ns-subblock-c1-r2-1"))
|
||||
assert p(f"{t}-supplement") < p(f"{t}-outline-1")
|
||||
assert p("guide-t-writer-k1") == 16 # unmatched → after everything
|
||||
assert p("guide-t-writer-k1") == 18 # unmatched → after everything
|
||||
|
||||
|
||||
async def test_outline_runs_before_artefacts_finish(board_env, monkeypatch):
|
||||
@@ -509,3 +762,5 @@ def test_per_block_functions_accept_wrapper_kwargs():
|
||||
params = inspect.signature(getattr(blx, fn)).parameters
|
||||
assert "ns" in params and "lbl" in params, fn
|
||||
assert "seeds" in inspect.signature(blx._subblocks_block).parameters
|
||||
for fn in ("_subblocks_block", "_facts_block"): # Board 2 reicht die Block-Quellen durch
|
||||
assert "sources" in inspect.signature(getattr(blx, fn)).parameters, fn
|
||||
|
||||
Reference in New Issue
Block a user