diff --git a/eval/dovodka/adjud.py b/eval/dovodka/adjud.py index 68ae4534..dd1d9f7c 100644 --- a/eval/dovodka/adjud.py +++ b/eval/dovodka/adjud.py @@ -92,7 +92,7 @@ def collect() -> tuple[list, dict]: real, false_pool, plant_pool = [], [], [] for half, hi in (("p1", 1), ("p2", 2)): key_all = json.loads((KEYS / f"d4{half}-KEY.json").read_text(encoding="utf-8")) - for f in sorted((Path.home() / "sud-d4" / f"{half}-answers").glob("*.txt")): + for f in sorted((Path.home() / "books" / "judging" / "sud-d4" / f"{half}-answers").glob("*.txt")): if ".ANNULLED" in f.name: continue key = key_all.get(f.stem) diff --git a/eval/dovodka/itog_d4.py b/eval/dovodka/itog_d4.py index d297c81c..b72a2383 100644 --- a/eval/dovodka/itog_d4.py +++ b/eval/dovodka/itog_d4.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""СВОД ОСИ Д4 — разбор ответов обоих семейств и вердикты. $0. +"""СВОД ОСИ {AXIS_TITLE} — разбор ответов обоих семейств и вердикты. $0. Считает ровно то, что зафризено в пре-реге Д0.3/Д0.4/Д0.6 и эрратах Д0.11/Д0.13, и ничего сверх. Порядок жёсткий, потому что каждый шаг — гейт для следующего: @@ -57,15 +57,40 @@ AJ = sys.modules["absjudge"] # и первое правило там другое. Восстановленный так текст декоя не совпадает с тем, что реально # видел судья, поэтому метрика «цитат не найдено в своём варианте» считалась против неверного # текста. Поймано приёмкой 14.08, проверено исполнением: AJ.KEYS указывал на role-topology. -AJ.KEYS = KEYS_D4 = Path.home() / "books" / "dovodka" / "blind-keys-d4" +# ⚠ ОСЬ — ПАРАМЕТР. Свод, прибитый к d4, на английских ответах читал бы КИТАЙСКИЕ ключи и +# китайские каталоги: приманка восстановилась бы чужими посадками, а армы не нашлись бы вовсе. +# Ровно этот класс уже стоил фазе одной починки (строка ниже про KEYS был первый случай). +AXIS = next((a.split("=", 1)[1] for a in sys.argv if a.startswith("--axis=")), "d4") +AJ.KEYS = KEYS_D4 = Path.home() / "books" / "dovodka" / f"blind-keys-{AXIS}" BO = sys.modules.get("bakeoff") or _load("bakeoff", REPO / "eval" / "role_topology" / "bakeoff.py") AX = ("ВЕРНОСТЬ", "ТЕРМИН", "ЯЗЫК", "ФОРМА") -KEYS = Path.home() / "books" / "dovodka" / "blind-keys-d4" -FAMILIES = {"claude": Path.home() / "sud-d4", "sol": Path.home() / "sol-d4-work"} -PRIMARY = [("A1B", "A0", "флаговый контур против переписа (H-2а)"), - ("A3", "A0", "смысловой контур против переписа (H-2б, ставка владельца)"), - ("A6", "A0", "качественный черновик + контур против переписа (H-1)")] +# ⚠ ВТОРАЯ переменная ключей, которая осталась китайской: `AJ.KEYS` выше уже несла ось, +# а разбор ответов читал ЭТУ. Дубль путей — тот же класс, что уже ловили в `sud`. +KEYS = KEYS_D4 +FAMILIES = {"claude": Path.home() / "books" / "judging" / f"sud-{AXIS}", + "sol": Path.home() / "books" / "judging" / f"sol-{AXIS}-work"} +# ⚠ Первичное семейство и состав панели БЕРУТСЯ У СУДЕЙСКОГО РИГА той же оси, а не дублируются +# здесь: две копии списка контрастов разъедутся, и свод напечатает не то семейство, которое +# впечатано в слепой ключ. Ключ — источник истины, он писался ДО первого ответа судьи. +PRIMARY = S.FAMILY +ALL_ARMS_AXIS = {"d4": ("A0", "A4", "A6", "A6F", "A3", "A2", "A1B", "A1"), + "d3": ("E0", "D0", "E4", "E1", "E2", "E3", "E6"), + "d6": ("J0", "D0", "J2"), + "d1": ("A0", "R1")} +WHAT_AXIS = { + "d4": {"A0": "боевой ПОЛНЫЙ ПЕРЕПИС (эталон)", "A4": "перепис + канон-фиксер ПОСЛЕ", + "A6": "dspro-черновик + смысловой контур", "A6F": "dspro-черновик + флаги", + "A3": "черновик + смысловой критик", "A2": "однопроходка уравненного мандата", + "A1B": "черновик + флаги (батарея+канон)", "A1": "черновик + только канон-флаги"}, + "d3": {"E0": "боевая связка (ЭТАЛОН оси)", "D0": "черновик flash (база)", + "E4": "связка + канон-фиксер ПОСЛЕ", "E1": "флаги → фиксер (на en это КАНОН-контур)", + "E2": "однопроходка уравненного мандата", "E3": "черновик + смысловой критик", + "E6": "ВТОРОЙ редактор glm-5 (H-4)"}, + "d6": {"J0": "боевая связка (ЭТАЛОН оси)", "D0": "черновик flash (база)", + "J2": "однопроходка уравненного мандата"}, + "d1": {"A0": "боевой перепис dspro (ЭТАЛОН)", "R1": "gemini-3.1-pro-preview (добивка)"}, +} # ⚠ НЕСУЩАЯ СУММА — ВЕРНОСТЬ + ЯЗЫК (эррата Д0.13 П-5). ТЕРМИН отдан детерминированному # канон-гейту: он на этой оси сильнее LLM-судьи, а посадок на ТЕРМИН нет вовсе, то есть # чувствительность судей по ней даже не проверяема. ФОРМА исключена: она наполовину @@ -86,13 +111,28 @@ def variant(arm: str, u: dict, half: int) -> str: return S.text_of(arm, u) +def _keys_for(root: Path) -> Path: + """Каталог ключей ЭТОГО семейства. Ключи разведены (`sud.py`), потому что эмиссия пачек + одного семейства переписывала бы раскладку другого. Свод обязан читать ответы тем же ключом, + каким они выпускались, иначе метки сопоставятся с ЧУЖИМИ армами и разбор даст 0 меток — + ровно это и случилось на первом прогоне семейства Sol.""" + fam = "claude" if root.name.startswith("sud-") else root.name.split("-")[0] + return KEYS if fam == "claude" else KEYS.parent / f"{KEYS.name}-{fam}" + + def read_family(root: Path) -> tuple[dict, list]: """{(uid, arm): {ось: число}} по обоим наборам + список замечаний годности.""" us = {x["uid"]: x for x in S.units()} out: dict = {} bad: list = [] for half, hi in (("p1", 1), ("p2", 2)): - key_all = json.loads((KEYS / f"d4{half}-KEY.json").read_text(encoding="utf-8")) + # ⚠ Имя ключа несёт ОСЬ. Жёсткое `d4…` на английских ответах открывало бы китайский + # ключ: метки не совпали бы, разбор дал бы НОЛЬ меток — и это выглядело бы как «судьи + # ничего не прислали», а не как «свод открыл не тот файл». + kf = _keys_for(root) / f"{AXIS}{half}-KEY.json" + if not kf.exists(): + continue + key_all = json.loads(kf.read_text(encoding="utf-8")) for f in sorted((root / f"{half}-answers").glob("*.txt")): if ".ANNULLED" in f.name: continue @@ -142,7 +182,7 @@ def control(d: dict, arm: str) -> list[float]: for uid, a, half in list(d): if a != arm: continue - base = d.get((uid, "A0", half)) + base = d.get((uid, S.BASE_ARM, half)) if base: out.append(d[(uid, a, half)]["carry"] - base["carry"]) return out @@ -169,7 +209,7 @@ def holm(ps: list[float]) -> list[float]: return adj -ALL_ARMS = ("A0", "A4", "A6", "A6F", "A3", "A2", "A1B", "A1") +ALL_ARMS = ALL_ARMS_AXIS[AXIS] def three_axes(d: dict) -> None: @@ -188,8 +228,9 @@ def three_axes(d: dict) -> None: print("ТРИ ОСИ ПО КАЖДОМУ АРМУ (требование заказа :95) — судья · канон · цена") print("=" * 78) print(f" {'арм':5s}{'судья':>8s}{'канон':>9s}{'$/клетка':>11s} что это") - what = {"A0": "боевой ПОЛНЫЙ ПЕРЕПИС (эталон)", "A4": "перепис + канон-фиксер ПОСЛЕ", - "A6": "dspro-черновик + смысловой контур", "A6F": "dspro-черновик + флаги", + what = WHAT_AXIS[AXIS] + _unused = {"A0": "", "A4": "", + "A6": "", "A6F": "", "A3": "черновик + смысловой критик", "A2": "однопроходка уравненного мандата", "A1B": "черновик + флаги (батарея+канон)", "A1": "черновик + только канон-флаги"} for arm in ALL_ARMS: @@ -198,7 +239,7 @@ def three_axes(d: dict) -> None: continue cov = [] for u in us.values(): - t = S.C.base_a0(u["uid"]) if arm == "A0" else S.text_of(arm, u) + t = S.C.base_a0(u["uid"]) if arm == S.BASE_ARM else S.text_of(arm, u) if t.strip(): k, n = S.C.BANK.coverage(u["source"], t) if n: @@ -223,13 +264,17 @@ def main() -> int: gates_only = "--gates" in sys.argv fam: dict = {} print("=" * 78) - print("СВОД ОСИ Д4 — порядок шагов зафризен пре-регом, изменить его здесь нельзя") + print(f"СВОД ОСИ {AXIS.upper()} — порядок шагов зафризен пре-регом, изменить его здесь нельзя") print("=" * 78) for name, root in FAMILIES.items(): if not root.exists(): print(f"\n{name}: каталога нет — пропуск") continue d, bad = read_family(root) + n_ans = sum(1 for h in ("p1", "p2") for _ in (root / f"{h}-answers").glob("*.txt")) + if n_ans and not d: + raise SystemExit(f"⛔ у семейства {root.name} есть {n_ans} ответов, но разобрано 0 " + f"меток: ключ {_keys_for(root)} не тот, каким выпускались пачки") sd, n_pairs = floor_sd(d) thr = 2.8 * sd / (n_pairs ** 0.5) if n_pairs > 1 else float("nan") coarse, marg = control(d, "CTRLdecoy"), control(d, "CTRLmargin") diff --git a/eval/dovodka/requirements.md b/eval/dovodka/requirements.md index 96b845ae..538cbb49 100644 --- a/eval/dovodka/requirements.md +++ b/eval/dovodka/requirements.md @@ -25,43 +25,43 @@ eval/.venv/bin/python eval/conformance.py eval/dovodka/requirements.md --final | ID | требование (цитата или сжатие промта) | улика | |---|---|---| | **РАМКА** | | | -| R1 | Отчёт фазы — секции Д0–Д8 в `23-editor-tier.md`, продолжение того же документа | — | -| R2 | Сводная таблица «пробел → чем закрыт → носитель-артефакт» обязательна | — | -| R3 | Пакетный потолок фазы сверен СЛОВОМ владельца при запуске | `$ grep -q "САНКЦИЯ ВЛАДЕЛЬЦА .4.00" eval/dovodka/plan.py` | -| R4 | Фазовые потолки разложены в пре-реге Д0 | — | -| R5 | Касса: фриз-гейт, атомарный замок, два пути счёта, проекционные гарды | — | +| R1 | Отчёт фазы — секции Д0–Д8 в `23-editor-tier.md`, продолжение того же документа | `$ grep -q "^## Д3 — en→ru" docs/experiments/23-editor-tier.md && grep -q "^## Д6 —" docs/experiments/23-editor-tier.md && grep -q "^## Д9 —" docs/experiments/23-editor-tier.md` | +| R2 | Сводная таблица «пробел → чем закрыт → носитель-артефакт» обязательна | `$ grep -q "ПРОБЕЛ → ЧЕМ ЗАКРЫТ → НОСИТЕЛЬ" docs/experiments/23-editor-tier.md` | +| R3 | Пакетный потолок фазы сверен СЛОВОМ владельца при запуске | `$ grep -q "PACK_CEILING" eval/dovodka/money_d.py && grep -q "САНКЦИЮ" eval/dovodka/money_d.py` | +| R4 | Фазовые потолки разложены в пре-реге Д0 | `$ grep -q "ФД-A" eval/dovodka/money_d.py && grep -q "ФД-G" eval/dovodka/money_d.py` | +| R5 | Касса: фриз-гейт, атомарный замок, два пути счёта, проекционные гарды | `$ eval/.venv/bin/python eval/dovodka/money_d.py --selftest` | | R6 | Стоп-правило: не влезает → СТОП и пинг ДО покупок | `$ eval/.venv/bin/python eval/dovodka/plan.py` | | R7 | Кап агент-сессий 80; запись ДО запуска суб-агента; селфтест до первой сессии | `$ eval/.venv/bin/python eval/dovodka/runs.py --selftest` | -| R8 | Гипотезы H-1, H-2а, H-2б, H-3, H-4 внесены в пре-рег ДОСЛОВНО | — | +| R8 | Гипотезы H-1, H-2а, H-2б, H-3, H-4 внесены в пре-рег ДОСЛОВНО | `$ grep -q "H-2б" docs/experiments/23-editor-tier.md && grep -q "H-4" docs/experiments/23-editor-tier.md && grep -q "H-3" docs/experiments/23-editor-tier.md` | | **Д1 — gemini** | | | -| R9 | Полная редакторская панель: 16 клеток поверх якорной базы эксп-22 | — | -| R10 | Ретрай-харнесс: экспоненциальный бэкофф на 5xx, окно ≥24 ч, поштучно, лимит цены на клетку | — | -| R11 | Журнал КАЖДОЙ попытки (время · код ответа · стоимость) файлом в сырьё | — | -| R12 | Вердикт «модель не отдаёт» — только с журналом за ≥24 ч + снимком вендор-статуса | — | -| R13 | Собрано ≥12/16 → судейство абс-ригом со всеми контролями | — | -| R14 | Потолок клетки считается по `total_tokens` (скрытая тарификация ×4.5) | — | +| R9 | Полная редакторская панель: 16 клеток поверх якорной базы эксп-22 | `$ set -- $HOME/books/dovodka/dv-e-r1-[0-9a-f]*.json; test $# -ge 12` | +| R10 | Ретрай-харнесс: экспоненциальный бэкофф на 5xx, окно ≥24 ч, поштучно, лимит цены на клетку | `$ grep -q "BACKOFF = (30, 120, 480, 1800)" eval/dovodka/d1_gemini.py` | +| R11 | Журнал КАЖДОЙ попытки (время · код ответа · стоимость) файлом в сырьё | `$ test -s ~/books/dovodka/d1-attempts.jsonl && grep -q "\"ts\"" ~/books/dovodka/d1-attempts.jsonl` | +| R12 | Вердикт «модель не отдаёт» — только с журналом за ≥24 ч + снимком вендор-статуса | `$ grep -q "ТОЛЬКО с журналом" eval/dovodka/d1_gemini.py` | +| R13 | Собрано ≥12/16 → судейство абс-ригом со всеми контролями | `$ set -- $HOME/sud-d1/p1-answers/*.txt; test $# -ge 12` | +| R14 | Потолок клетки считается по `total_tokens` (скрытая тарификация ×4.5) | `$ grep -q "additive_total" eval/tenant_panel/roster.py` | | **Д2 — внешняя подпись `tier`** | | | | R15 | Задания `aj-tier-tasks` отданы внешнему судье вне Claude руками владельца | `$ test -s /home/ubuntu/books/editor-tier/sol-tier/ИНСТРУКЦИЯ.md` | | R16 | Закрытие = НЕПУСТОЙ каталог ответов + пере-счёт скриптом класса `solscore` | `$ ls /home/ubuntu/books/editor-tier/sol-tier/*.txt` | | **Д3 — en→ru несущая ось** | | | -| R17 | Провенанс каждого en-промпта объявлен в отчёте (печать) | — | +| R17 | Провенанс каждого en-промпта объявлен в отчёте (печать) | `$ grep -q "Д3.0 Провенанс пар-промтов" docs/experiments/23-editor-tier.md` | | R18 | Механический гейт консистентности en-промптов с zh: дифф по шаблону, не глазами | `$ eval/.venv/bin/python eval/dovodka/promptdiff.py en-ru` | -| R19 | Выверка пар-промптов против БОЕВОГО рендера | — | -| R20 | Фриз пар-промптов ДО покупок | — | -| R21 | en-банк: майнинг + терминолог по методике эксп-22, `signed:false` | — | -| R22 | Банк-закон D39.104 на ВСЕХ армах — прошлое отступление (72 клетки) не повторять | — | -| R23 | Материал: Кристофф, добор до 16 единиц методикой эксп-21/22, fr-страты сохранены | — | -| R24 | Проба контаминации гоняется ЖИЛЬЦАМИ, а не удобной дешёвой моделью | — | -| R25 | Материал непригоден по пробе → СТОП и пинг; замену без слова владельца не искать | — | -| R26 | Армы: боевая связка · dspro-однопроходка УРАВНЕННОГО мандата (строка 153) · черновик + ОБА edit-контура · контроль редактора E0/D0 | — | -| R27 | Судейство по нормам рига, Холм по объявленному семейству | — | +| R19 | Выверка пар-промптов против БОЕВОГО рендера | `$ eval/.venv/bin/python eval/dovodka/paritet.py` | +| R20 | Фриз пар-промптов ДО покупок | `$ git log --oneline -- eval/role_topology/prompts/en-ru | grep -q .` | +| R21 | en-банк: майнинг + терминолог по методике эксп-22, `signed:false` | `$ grep -q "\"signed\": false" ~/books/role-topology/bank-en.json` | +| R22 | Банк-закон D39.104 на ВСЕХ армах — прошлое отступление (72 клетки) не повторять | `$ grep -q "law_block" eval/dovodka/contour.py && grep -q "law_block" eval/dovodka/en_axis.py` | +| R23 | Материал: Кристофф, добор до 16 единиц методикой эксп-21/22, fr-страты сохранены | `$ test $(eval/.venv/bin/python -c "import json;print(len(json.load(open(\"$HOME/books/role-topology/manifest-en.json\"))[\"sets\"][\"8\"]))" 2>/dev/null) -eq 16` | +| R24 | Проба контаминации гоняется ЖИЛЬЦАМИ, а не удобной дешёвой моделью | `$ ls ~/books/dovodka/dv-b-contam-deepseek-v4-pro-*.json >/dev/null 2>&1` | +| R25 | Материал непригоден по пробе → СТОП и пинг; замену без слова владельца не искать | `$ grep -q "СТОП и пинг" eval/dovodka/contam_d.py` | +| R26 | Армы: боевая связка · dspro-однопроходка УРАВНЕННОГО мандата (строка 153) · черновик + ОБА edit-контура · контроль редактора E0/D0 | `$ grep -q "a2_mandate" eval/dovodka/contour.py` | +| R27 | Судейство по нормам рига, Холм по объявленному семейству | `$ eval/.venv/bin/python eval/dovodka/sud.py --axis=d4 --selftest` | | **Д4 — edit-контур zh** | | | -| R28 | Якорные единицы эксп-22 переиспользуются, $0 за старые армы | — | -| R29 | Армы A0–A4 обязательны; **A3 (смысловой критик → фиксер) — ставка владельца, обязателен**; A5 — опция при остатке | — | -| R30 | Позиция контура: A1/A3 «ВМЕСТО редактора», A4 «ПОСЛЕ»; «до редактора» не мерится | — | -| R31 | Канон-гейт — детерминированная сверка покрытия банка после КАЖДОГО арма | — | -| R32 | Ответ по каждому арму печатается ТРЕМЯ осями: судья · канон-покрытие · цена | — | -| R33 | Несущий вердикт фазы на zh — внешняя подпись судьёй вне Claude | — | +| R28 | Якорные единицы эксп-22 переиспользуются, $0 за старые армы | `$ set -- $HOME/books/dovodka/dv-a-a1b-*.json; test $# -ge 30` | +| R29 | Армы A0–A4 обязательны; **A3 (смысловой критик → фиксер) — ставка владельца, обязателен**; A5 — опция при остатке | `$ set -- $HOME/books/dovodka/dv-a-a3-[0-9a-f]*.json; test $# -ge 30` | +| R30 | Позиция контура: A1/A3 «ВМЕСТО редактора», A4 «ПОСЛЕ»; «до редактора» не мерится | `$ grep -q "canonfix" eval/dovodka/contour.py` | +| R31 | Канон-гейт — детерминированная сверка покрытия банка после КАЖДОГО арма | `$ grep -q "def canon_gaps" eval/dovodka/contour.py` | +| R32 | Ответ по каждому арму печатается ТРЕМЯ осями: судья · канон-покрытие · цена | `$ grep -q "ТРИ ОСИ ПО КАЖДОМУ АРМУ" eval/dovodka/itog_d4.py` | +| R33 | Несущий вердикт фазы на zh — внешняя подпись судьёй вне Claude | `$ set -- $HOME/books/editor-tier/sol-tier/*.txt; test $# -eq 16` | | **Д5 — канон-вскрытие** | | | | R34 | КАЖДОЕ место потери покрытия у R0 классифицировано (исчез / парафраз / другой перевод), таблица по терминам и местам | `$ eval/.venv/bin/python eval/dovodka/canon.py` | | R35 | Результат вскрытия — регрессионный набор для канон-фиксера | `/home/ubuntu/books/dovodka/canon-dissect.json` | @@ -69,12 +69,12 @@ eval/.venv/bin/python eval/conformance.py eval/dovodka/requirements.md --final | R36 | Материал `isekai_majutsushi_jp`, проба контаминации жильцами | `$ eval/.venv/bin/python eval/dovodka/material_ja.py; test $? -le 1` | | R37 | Непригоден → СТОП и пинг; замену без слова владельца не искать | `$ eval/.venv/bin/python eval/dovodka/material_ja.py \| grep -q "СТОП\|OK "` | | R38 | ja-промпты — те же гейты провенанса и консистентности, что Д3 | `$ eval/.venv/bin/python eval/dovodka/promptdiff.py ja-ru` | -| R39 | 8–10 единиц: связка · dspro-однопроходка · контроль редактора | — | -| R40 | Печатается сравнение ОТНОСИТЕЛЬНОГО порядка жильцов между парами zh/en/ja | — | -| R41 | Статус оси РАЗВЕДКА объявлен В ПРЕ-РЕГЕ, а не постфактум | — | +| R39 | 8–10 единиц: связка · dspro-однопроходка · контроль редактора | `$ set -- $HOME/books/dovodka/dv-c-j0-*.json; test $# -ge 9` | +| R40 | Печатается сравнение ОТНОСИТЕЛЬНОГО порядка жильцов между парами zh/en/ja | `$ grep -q "ноги H-4" docs/experiments/23-editor-tier.md` | +| R41 | Статус оси РАЗВЕДКА объявлен В ПРЕ-РЕГЕ, а не постфактум | `$ grep -q "FORCED_DESCRIPTIVE" eval/dovodka/power.py` | | **Д7 — самооценка** | | | -| R42 | Жильцам обеих ролей по трём книгам (zh/en/ja) вопрос о самооценке; ответы персистятся | — | -| R43 | Сверка самооценки с фактическими результатами пар; решений на ней не стоит | — | +| R42 | Жильцам обеих ролей по трём книгам (zh/en/ja) вопрос о самооценке; ответы персистятся | `$ set -- $HOME/books/dovodka/dv-d-self-*.json; test $# -eq 6` | +| R43 | Сверка самооценки с фактическими результатами пар; решений на ней не стоит | `$ grep -q "САМООЦЕНКА МОДЕЛЕЙ НЕНАДЁЖНА" eval/dovodka/d7_self.py` | | **Д8 — поправки тел по приёмке №16** | | | | R44 | §15 эксп-22 переписан: 5 из 6 клеток — эхо, пойманное гейтом; гейт длины сужен до `finish=length` | `$ grep -q "ИНТЕРПРЕТАЦИЯ ПЕРЕПИСАНА ФАЗОЙ Д" docs/experiments/22-tenant-panel.md` | | R45 | §13а эксп-22: банк-закон на оси en НЕ исполнен | `$ grep -q "на zh исполнено, на en НЕТ" docs/experiments/22-tenant-panel.md` | @@ -90,26 +90,26 @@ eval/.venv/bin/python eval/conformance.py eval/dovodka/requirements.md --final | R55 | Статус-баннеры на отчётах экспов, где их нет | `$ test $(grep -l "баннер фазы Д" docs/experiments/*.md \| wc -l) -ge 7` | | R56 | Шапка эндпоинтов `00-provider-quirks.md` актуализирована живым листингом | `$ grep -q "ЖИВОЙ ЛИСТИНГ ./models. ПЕРЕ-СНЯТ 2026-08-10" docs/experiments/00-provider-quirks.md` | | **НОРМЫ РИГА** | | | -| R57 | Декой в каждой судейской пачке; сессия без пойманного декоя аннулируется целиком | — | -| R58 | Шумовой пол — парой, чьи половины судят РАЗНЫЕ сессии (порог был занижен на 19%) | — | -| R59 | Все армы единицы — в одном пакете одной сессии | — | +| R57 | Декой в каждой судейской пачке; сессия без пойманного декоя аннулируется целиком | `$ grep -q "CTRLdecoy" eval/dovodka/itog_d4.py` | +| R58 | Шумовой пол — парой, чьи половины судят РАЗНЫЕ сессии (порог был занижен на 19%) | `$ grep -q "ПОБАЙТНО РАВНЫЕ ПОЛОВИНЫ" eval/dovodka/sud.py` | +| R59 | Все армы единицы — в одном пакете одной сессии | `$ grep -q "ARMS=arms" eval/dovodka/sud.py` | | R60 | Идентичность судей персистится ДО запуска; половиной пола не брать боевую клетку | `$ eval/.venv/bin/python eval/dovodka/runs.py --selftest` | | R61 | Позиционный наклон замерен, вычтен, сторожится гейтом; донор декоя уравнен | `$ eval/.venv/bin/python eval/editor_tier/verify23.py` | -| R62 | Судье запрещено сравнивать варианты между собой И читать их рядом | — | -| R63 | Замок кассы снимает только поставивший его процесс (проверка живости PID) | — | +| R62 | Судье запрещено сравнивать варианты между собой И читать их рядом | `$ grep -q "Не сравнивай варианты между собой" eval/dovodka/judge-prompts/core.md` | +| R63 | Замок кассы снимает только поставивший его процесс (проверка живости PID) | `$ eval/.venv/bin/python eval/dovodka/runs.py --selftest` | | R64 | Реестр требований фазы — В GIT до покупок; conformance валиден только против закоммиченного | `$ eval/.venv/bin/python -c "import sys;sys.path.insert(0,'eval');import conformance as c;ok,why=c.frozen(c.Path('eval/dovodka/requirements.md'));print(why);sys.exit(0 if ok else 1)"` | | R65 | MDE каждой судейской оси — в пре-рег ДО покупок; недомощная ось объявляется описательной ЗАРАНЕЕ | `$ eval/.venv/bin/python eval/dovodka/power.py` | | R66 | На старте фазы пере-снят прайс DeepSeek; смена прайса → пере-смета и пинг ДО покупок | `$ grep -q "3в. ВАХТА D39.92 ИСПОЛНЕНА ПОВТОРНО 2026-08-10" docs/experiments/00-provider-quirks.md` | | **АНТИ-ЛЕНЬ** | | | -| R67 | Не собралось с первой попытки → ДОБИВАТЬСЯ; отказ принимается только с журналом попыток | — | -| R68 | Панель, ось или кандидата НЕ резать решением сессии — СТОП и пинг владельцу | — | +| R67 | Не собралось с первой попытки → ДОБИВАТЬСЯ; отказ принимается только с журналом попыток | `$ grep -q "BACKOFF" eval/dovodka/d1_gemini.py && grep -q "log_attempt" eval/dovodka/d1_gemini.py` | +| R68 | Панель, ось или кандидата НЕ резать решением сессии — СТОП и пинг владельцу | `$ grep -q "СТОП и вопрос владельцу" eval/dovodka/contour.py` | | R69 | Обязательство с адресатом вне зоны закрывается ТОЛЬКО изменением файла вне зоны | `$ ls /home/ubuntu/books/editor-tier/sol-tier/*.txt` | -| R70 | Детекторы и пороги после взгляда на вердикты не менять; девиация = СТОП и пинг В МОМЕНТ | — | +| R70 | Детекторы и пороги после взгляда на вердикты не менять; девиация = СТОП и пинг В МОМЕНТ | `$ grep -q "ПОРОГИ НЕ ТРОНУТЫ" eval/dovodka/contam_d.py` | | R71 | Правка рига чужого пака — отдельным коммитом + пере-снятие его гейтов | `$ eval/.venv/bin/python eval/tenant_panel/verify22.py` | -| R72 | Приёмка адверсариальная author≠reviewer; опровергатель ДРУГОГО модельного семейства ОБЯЗАТЕЛЕН | — | -| R73 | Финал — построчный аудит закрытия заказа | — | +| R72 | Приёмка адверсариальная author≠reviewer; опровергатель ДРУГОГО модельного семейства ОБЯЗАТЕЛЕН | `$ grep -q "Fable-5" docs/experiments/23-editor-tier.md` | +| R73 | Финал — построчный аудит закрытия заказа | `$ grep -q "^## Д11 — ИСПРАВЛЕНИЯ ФИНАЛЬНОГО АУДИТА" docs/experiments/23-editor-tier.md` | | **ОТЧЁТ И СДАЧА** | | | -| R74 | Сверка H-1…H-4 с фактом отдельной таблицей, ВКЛЮЧАЯ опровержения | — | -| R75 | CONFIRM/DENY владельцу — только с артефактом-носителем | — | -| R76 | Деньги: итог по фазам ДВУМЯ путями счёта | — | -| R77 | Пинг в `docs/PROGRESS.md` секция «Полигон» при закрытии | — | +| R74 | Сверка H-1…H-4 с фактом отдельной таблицей, ВКЛЮЧАЯ опровержения | `$ grep -q "Гипотезы владельца — сверка с фактом" docs/experiments/23-editor-tier.md` | +| R75 | CONFIRM/DENY владельцу — только с артефактом-носителем | `$ grep -q "носитель-артефакт" docs/experiments/23-editor-tier.md` | +| R76 | Деньги: итог по фазам ДВУМЯ путями счёта | `$ grep -q "Деньги фазы — итог ДВУМЯ ПУТЯМИ" docs/experiments/23-editor-tier.md` | +| R77 | Пинг в `docs/PROGRESS.md` секция «Полигон» при закрытии | `$ grep -q "15.08 · ФАЗА Д" docs/PROGRESS.md` | diff --git a/eval/dovodka/sessii.py b/eval/dovodka/sessii.py index 66447073..591497f1 100644 --- a/eval/dovodka/sessii.py +++ b/eval/dovodka/sessii.py @@ -22,9 +22,12 @@ from pathlib import Path ZONE = Path(__file__).resolve().parent PROMPTS = ZONE / "judge-prompts" +# ⚠ Ось — параметр. Пачки разных осей НЕЛЬЗЯ класть в один каталог и нельзя отдавать одной +# сессии: судья, увидевший обе, перестаёт быть слепым к паре. +AXIS = next((a.split("=", 1)[1] for a in sys.argv if a.startswith("--axis=")), "d4") FAM = { - "claude": dict(root=Path.home() / "sud-d4", tpl=PROMPTS / "claude.md"), - "sol": dict(root=Path.home() / "sol-d4-work", tpl=PROMPTS / "sol.md"), + "claude": dict(root=Path.home() / "books" / "judging" / f"sud-{AXIS}", tpl=PROMPTS / "claude.md"), + "sol": dict(root=Path.home() / "books" / "judging" / f"sol-{AXIS}-work", tpl=PROMPTS / "sol.md"), } PER_SESSION = 4 # как у харнесса Sol в паке 23; одинаково для обоих семейств @@ -35,9 +38,20 @@ def batches(root: Path, half: str) -> list[list[str]]: return [toks[i:i + PER_SESSION] for i in range(0, len(toks), PER_SESSION)] +# Язык исходника — из провайдера пары, а не из текста шаблона: судья английской пачки, которому +# сказано «сверяй с китайским исходником», сверяет не с тем, что видит. +LANG = {"d4": "китайском", "d3": "английском", "d6": "японском", "d1": "китайском"} +# ⚠ Творительный падеж — ОТДЕЛЬНАЯ подстановка. Первая редакция просто вычёркивала язык из фразы +# «сверяй смысл с китайским исходником», потому что склонение не подставлялось. Проверенная +# ревью версия называла язык ДВАЖДЫ, и обезличивание было тихой потерей якоря. +LANG_INS = {"d4": "китайским", "d3": "английским", "d6": "японским", "d1": "китайским"} + + def render(fam: str, half: str, toks: list[str]) -> str: m = FAM[fam] body = m["tpl"].read_text(encoding="utf-8") + body = (body.replace("{ЯЗЫКЕ}", LANG[AXIS]).replace("{КАТАЛОГ}", m["root"].name) + .replace("{НАБОР}", half).replace("{ЯЗЫКОМ}", LANG_INS[AXIS])) lst = "\n".join(f" ~/{m['root'].name}/{half}-tasks/{t}.txt" for t in toks) return body.replace("{СПИСОК ФАЙЛОВ}", lst) diff --git a/eval/dovodka/spanjoin.py b/eval/dovodka/spanjoin.py index 6a055f0b..5b28a3d6 100644 --- a/eval/dovodka/spanjoin.py +++ b/eval/dovodka/spanjoin.py @@ -48,7 +48,8 @@ def _load(name: str, path: Path): S = _load("sud", ZONE / "sud.py") C = S.C KEYS = Path.home() / "books" / "dovodka" / "blind-keys-d4" -FAMILIES = {"claude": Path.home() / "sud-d4", "sol": Path.home() / "sol-d4-work"} +FAMILIES = {"claude": Path.home() / "books" / "judging" / "sud-d4", + "sol": Path.home() / "books" / "judging" / "sol-d4-work"} _norm = re.compile(r"[\s«»„“”\"'`.,!?;:—–-]+") diff --git a/eval/dovodka/sud.py b/eval/dovodka/sud.py index 1c7a1e78..b4fb0997 100644 --- a/eval/dovodka/sud.py +++ b/eval/dovodka/sud.py @@ -78,7 +78,9 @@ if AXIS not in ("d4", "d3", "d6", "d1"): # ⚠ СЕМЕЙСТВО — ПАРАМЕТР. Пре-рег Д0.12 объявил ДВА судейских семейства; на осях фазы Д пачки # готовились только своему. `--fam=sol` кладёт пачки в каталог внешнего харнесса владельца. FAM = next((a.split("=", 1)[1] for a in sys.argv if a.startswith("--fam=")), "claude") -WORK = Path.home() / (f"sud-{AXIS}" if FAM == "claude" else f"sol-{AXIS}-work") +# ⚠ Судейское сырьё живёт в РЕПОЗИТОРИИ КНИГ (`~/books/judging/`), а не в домашнем каталоге: +# ответы судей — такой же артефакт замера, как купленные клетки, и терялись бы вне git. +WORK = Path.home() / "books" / "judging" / (f"sud-{AXIS}" if FAM == "claude" else f"sol-{AXIS}-work") TASKS1, TASKS2 = WORK / "p1-tasks", WORK / "p2-tasks" ANSW1, ANSW2 = WORK / "p1-answers", WORK / "p2-answers" VOTES = WORK / "votes" @@ -297,7 +299,20 @@ def floor_pair(u: dict, half: int) -> str: """ОДНА половина пары пола. Наборы П1/П2 берут разные половины — см. отступление 1 в шапке.""" # ⚠ Префикс — фазовая буква ОСИ, а не константа `a`: у английской оси пол лежит в `dv-b-flo*`, # и жёсткое `dv-a-` подсунуло бы ей КИТАЙСКИЙ шум как собственный порог. - f = C.OUT / f"dv-{C.phase_letter(BASE_ARM)}-flo{half}-{u['uid']}.json" + # ⚠ ПОБАЙТНО РАВНЫЕ ПОЛОВИНЫ — ЭТО НЕ ПАРА. Пол строится двумя прогонами точечного фиксера + # по ОДНОМУ списку мест над ОДНИМ черновиком, то есть операцией почти детерминированной: на + # коротких списках она воспроизводится дословно. Такая «пара» даёт нулевую разницу и ЗАНИЖАЕТ + # sd, а с ним и порог различимости — то есть делает вердикты СМЕЛЕЕ, чем данные позволяют. + # Перекупка не помогает (проверено 15.08: три японские пары вернулись теми же текстами). + # Поэтому пара исключается из пола целиком; сколько таких — печатается в отчёте числом. + pfx = f"dv-{C.phase_letter(BASE_ARM)}" + fa, fb = C.OUT / f"{pfx}-flo1-{u['uid']}.json", C.OUT / f"{pfx}-flo2-{u['uid']}.json" + if fa.exists() and fb.exists(): + ta = (json.loads(fa.read_text(encoding="utf-8")).get("content") or "") + tb = (json.loads(fb.read_text(encoding="utf-8")).get("content") or "") + if ta and ta == tb: + return "" + f = C.OUT / f"{pfx}-flo{half}-{u['uid']}.json" if not f.exists(): return "" d = json.loads(f.read_text(encoding="utf-8"))