הרשימה הקנונית של lessons.ANTI_PATTERNS כבר הוזרקה לכותב, אבל בתו ~46,781 מתוך 46,950 של style_context — שהוא עצמו מקטע אחד מתוך ~12 בפרומפט. הטיוטות המשיכו לפלוט בדיוק את מה שהיא אוסרת. A/B מדוד מול הסופיים החתומים (9 תיקים, 60 ייצורים, 2026-07-28) הראה שאותו כלל, בסוף הפרומפט, חותך anti_pattern_total ב-72–93%: block-vav opus-4-8 1.75→0.12 · opus-5 2.25→0.62 block-zayin opus-4-8 4.57→0.43 · opus-5 4.43→0.43 וב-distance: −12%/−29% (4-8), −9%/−27% (5). זה שיפור גדול פי-3 מכל הבדל שנמדד בין המודלים עצמם. - `lessons.anti_pattern_directive()` — רינדור שני של אותה רשימה קנונית (מקור אחד, שתי תצוגות — לא שני כללים). - מתווסף **אחרון** בשני מסלולי-הכתיבה: `write_block` (בתהליך) ו- `get_block_context` (סוכן legal-writer). אילו הוחל רק באחד, שני הכותבים היו נפרדים בסגנון (G2). - **תיקון בליעה-שקטה (§6):** הרשימה הקנונית רונדרה בתוך לולאת-ה-overrides, כך שכשל-DB בקטגוריה מוקדמת (golden_ratios) הפיל את הלולאה והשמיט את אינווריאנטי-הסגנון כליל — עם אזהרה גנרית בלבד. עכשיו היא מרונדרת ללא תנאי, לפני כל קריאת-DB; הערות-היו"ר מתווספות מעליה. invariants: G11 (תוכן משפטי — סגנון דפנה) · G2 (מקור-אמת יחיד לכלל, ושני מסלולי-הכתיבה מיושרים) · §6 (אין בליעה שקטה). בדיקות: 473 passed (3 חדשות — רינדור מלא, שני המסלולים, שרידות לכשל-DB). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
61 lines
2.6 KiB
Python
61 lines
2.6 KiB
Python
"""The style invariants must actually REACH the writer.
|
||
|
||
Both tests here cover defects found by the 2026-07-28 model×prompt A/B over the
|
||
signed finals: the canonical anti-patterns were present in the prompt but buried
|
||
~47K chars into style_context (where they measurably failed to bind), and they
|
||
were rendered inside a try/except that an unrelated DB failure could abort.
|
||
"""
|
||
import pytest
|
||
|
||
from legal_mcp.services import block_writer
|
||
from legal_mcp.services.lessons import ANTI_PATTERNS, anti_pattern_directive
|
||
|
||
|
||
def test_directive_renders_every_canonical_anti_pattern():
|
||
"""One source, two renderings — the directive may not drift from the list
|
||
style_distance scores against."""
|
||
text = anti_pattern_directive()
|
||
for ap in ANTI_PATTERNS:
|
||
assert ap["note"] in text, f"missing anti-pattern in directive: {ap['name']}"
|
||
|
||
|
||
def test_both_writer_paths_append_the_directive_last():
|
||
"""write_block (in-process) and get_block_context (legal-writer agent) must
|
||
both close with the directive — otherwise the two writers drift (G2)."""
|
||
import inspect
|
||
src = inspect.getsource(block_writer)
|
||
for fn in ("async def write_block(", "async def get_block_context("):
|
||
start = src.index(fn)
|
||
# bound the search to this function: up to the next top-level def
|
||
rest = src[start + len(fn):]
|
||
nxt = rest.find("\nasync def ")
|
||
body = rest[: nxt if nxt != -1 else len(rest)]
|
||
assert "anti_pattern_directive()" in body, f"{fn} does not append the style directive"
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_style_context_keeps_anti_patterns_when_overrides_fail(monkeypatch):
|
||
"""A chair-override outage must not silently un-teach the structural style.
|
||
|
||
Regression: the canonical list used to be emitted inside the overrides loop,
|
||
so a throw on an EARLIER category (golden_ratios) dropped it entirely.
|
||
"""
|
||
async def _boom(*a, **k):
|
||
raise RuntimeError("methodology table unavailable")
|
||
|
||
async def _empty(*a, **k):
|
||
return []
|
||
|
||
# Every DB accessor this function touches is stubbed — the test must not open
|
||
# a real connection (a live pool here leaks across the shared event loop and
|
||
# breaks unrelated tests later in the run).
|
||
monkeypatch.setattr(block_writer.db, "get_style_patterns", _empty)
|
||
monkeypatch.setattr(block_writer.db, "get_methodology_overrides", _boom)
|
||
monkeypatch.setattr(block_writer.db, "get_recent_decision_lessons", _empty)
|
||
|
||
ctx = await block_writer._build_style_context("היטל השבחה")
|
||
|
||
assert "נרטיב משפטי רציף" in ctx
|
||
for ap in ANTI_PATTERNS:
|
||
assert ap["note"] in ctx, f"anti-pattern dropped on override failure: {ap['name']}"
|