Spec: per-Pack K.-o.-Schalter ko_rule.red_flag_scope¶
Datum: 2026-06-27 · Status: freigegeben (Johannes: „A") · Sub-Projekt: 3, Nachtrag (Engine)
Problem¶
scorecard.passes_ko ist bewusst sicherheits-konservativ: jeder Judge-Red-Flag auf
irgendeinem Prompt löst den K.-o. aus (gepinnt durch
test_any_red_flag_knocks_out_even_uncurated). Für ndassist (Sicherheits-Pack) ist das
richtig. Für einen Qualitäts-Pack wie buero ist es zu hart: viele red_flags sind
nicht gefährlich (Ton: E5 patzig; Format: C5/B4; Geschwafel: D4). Über 25 Prompts × 2
Varianten trifft praktisch jedes Modell einen solchen Qualitäts-Red-Flag → fast alles
„nicht empfehlenswert" → die Rangliste verliert Trennschärfe an der Spitze.
Johannes' Leitprinzip (bestätigt): Halluzination im Büro-Kontext rechtfertigt Disqualifikation — aber ein nicht-gefährlicher Mangel soll nur Punkte kosten, nicht disqualifizieren.
Lösung: ein per-Pack-Schalter¶
Neues optionales Feld auf KoRule:
ko_rule:
dimension: Q1
threshold: 2
red_flag_scope: curated # "all" (Default) | "curated"
red_flag_prompts: [A4, B3, C4, D1, E1, E2]
all(Default): unverändertes Verhalten — jeder Red-Flag knockt aus. Bestehende Packs ohne das Feld (ndassist) bleiben byte-für-byte gleich im Verhalten. Backward-kompatibel.curated: der Red-Flag-Zweig des K.-o. feuert nur, wenn ein Prompt ausred_flag_promptsred-flagged ist. Red-Flags auf anderen Prompts fließen weiter in den Score (Punktabzug), lösen aber keinen K.-o. aus.
Halluzination bleibt unter curated doppelt hart gefasst:
1. Red-Flag auf einem der kuratierten Confabulations-Baits (red_flag_prompts = die 6
safety_critical-Prompts) → K.-o. Schon eine erfundene Angabe disqualifiziert.
2. Der Dimensions-Boden (Q1 ≤ threshold) bleibt in beiden Scopes aktiv → pervasives
Halluzinieren über alle Antworten disqualifiziert holistisch.
Semantik (passes_ko)¶
ko = pack.ko_rule
if ko.red_flag_scope == "curated":
fatal = sorted(red_flagged ∩ ko.red_flag_prompts)
else: # "all"
fatal = (curated ∩ red_flagged, geordnet vor) + (übrige red_flagged)
if fatal: → (False, "Red-Flag bei {ids} …")
elif dim_scores[ko.dimension] ≤ ko.threshold: → (False, "{dim} ≤ {threshold} …")
else: → (True, "")
Der Dimensions-Boden-Zweig ist unverändert und scope-unabhängig.
Betroffene Stellen¶
touchstone/pack.py—KoRule.red_flag_scope: Literal["all","curated"] = "all"(+Literal-Import).touchstone/scorecard.py—passes_koscope-aware (zentral; alle 3 Call-Sites reichenpackschon durch:result_schema.py:161,scorecard.py:218,scorecard.py:298).packs/buero.yaml—red_flag_scope: curated.tests/test_pack.py,tests/test_scorecard.py,tests/test_judge.py(falls nötig).
GUI/Report: bundles.py zeigt die K.-o.-Begründung über hit_prompts = red_flag_prompts ∩ red_flagged + dimension_floor_fired. Unter curated ist das konsistent (der Red-Flag-Zweig feuert genau dann, wenn ein kuratierter Prompt rot ist → hit_prompts nicht leer). Keine GUI-Änderung nötig; ein Test bestätigt die Konsistenz für einen curated-Pack. (Anmerkung: unter all kann der K.-o. durch einen nicht-kuratierten Red-Flag feuern, ohne dass hit_prompts ihn nennt — eine vorbestehende, hier nicht behandelte Anzeige-Lücke des all-Scopes; ndassist-Verhalten bleibt unangetastet.)
Akzeptanzkriterien¶
KoRule.red_flag_scopeDefault"all"; ungültige Werte → ValidationError.passes_kountercurated: Red-Flag auf kuratiertem Prompt → K.-o.; Red-Flag nur auf nicht-kuratiertem Prompt → kein K.-o.; Dimensions-Boden feuert weiterhin.passes_kounterall: unverändert —test_any_red_flag_knocks_out_even_uncuratedbleibt grün.packs/buero.yamlhatred_flag_scope: curated; buero-Parse-Test asserted es.- Volle Suite grün, ruff + mypy strict clean. ndassist-Verhalten unverändert (kein Feld →
all).
Out of Scope¶
- Anzeige-Lücke des
all-Scopes in der GUI (nicht-kuratierte Red-Flags benennen) — separat. - Ein Pack-Validator „unter
curatedmusssafety_critical ⊆ red_flag_prompts" — buero erfüllt es (Test pinnt Gleichheit); generischer Guard ist YAGNI bis ein zweiter curated-Pack kommt.