Register the quarantined no-prompt cells as a neutral pseudo-arm gated by token count, and add the second-reading tool that splits the threshold into reader noise and text

This commit is contained in:
heaven 2026-08-23 02:31:59 +03:00
parent 0937e3ed34
commit 62710ab2d3
2 changed files with 272 additions and 7 deletions

View file

@ -489,10 +489,39 @@ def _e6_wire(uid: str) -> tuple[list[dict], str, dict]:
return msgs, EN.EDITOR2, ROSTER.call_kwargs(EN.EDITOR2, msgs)
def _rq_wire(uid: str) -> tuple[list[dict], str, dict]:
"""WIRE-АНАЛОГ `RQ` — ЗАПРОС В ТОЙ ФОРМЕ, В КОТОРОЙ ОН УХОДИЛ ДО ПОЧИНКИ СКЛЕЙКИ.
Клетки `RQ` это `RE`, у которого вендор МОЛЧА ВЫБРОСИЛ второе системное сообщение:
OpenAI-совместимый слой Gemini принимает ровно одно. Поэтому здесь НЕ зовётся `fit_msgs`
(склейка, заведённая после находки) и НЕ зовётся `rol.call_kwargs`: печатается ровно то, что
отправлял код 21.08. Печатается ОТПРАВЛЕННОЕ, а не ПОЛУЧЕННОЕ моделью второе нам недоступно
по построению, и разница видна только по счётчику: 515 токенов входа против 2951 у `RE`.
"""
src = next(u["source"] for u in chosen("en") if u["uid"] == uid)
msgs = rol_msgs("en", src, "RE")
return msgs, ARMS["RE"][0], ROSTER.call_kwargs(ARMS["RE"][0], msgs, max_out=MAX_OUT)
FOREIGN = {
"E6": {"pair": "en", "prefix": "dv-g-e6-", "model": "glm-5", "role": "editor3",
"phase": "ФД-G", "rig": "eval/dovodka/en_axis.py --e6", "wire": _e6_wire,
"что": "ВТОРОЙ редактор glm-5 поверх боевого черновика D0 (носитель гипотезы H-4)"},
# ⚠ ПСЕВДО-АРМ ПОД КАРАНТИННЫМ СУФФИКСОМ. 16 клеток `RE`, которые вендор перевёл БЕЗ ИНСТРУКЦИИ:
# OpenAI-совместимый слой Gemini принимает РОВНО ОДНО системное сообщение и молча выбрасывает
# лишние. Деньги за них списаны, тексты годны, `finish=stop` 16/16 — это оплаченный и
# незапланированный природный эксперимент «сколько весит ВЕСЬ промт».
# ⛔ ИМЯ НЕЙТРАЛЬНОЕ И СУФФИКСА В ПАКЕТЕ БЫТЬ НЕ ДОЛЖНО: читатель, увидевший «БЕЗ-ПРОМТА»,
# мерил бы подсказку. `arm_field` = "RE", потому что в самой клетке написано «RE» — она и есть
# RE, просто без инструкции; подменять поле в файле нельзя, это правка сырья.
# ⛔ ГЕЙТ ПРАВИЛЬНОСТИ ФАЙЛА — ЧИСЛОМ, А НЕ ИМЕНЕМ: у карантинных `prompt_tokens` 468548
# (медиана 515), у нормальных 29042984 (медиана 2951). Диапазон ниже разводит их с запасом,
# и перепутанный файл роняет сборку, а не уезжает в панель.
"RQ": {"pair": "en", "prefix": "dv-n-re-", "suffix": ".БЕЗ-ПРОМТА",
"model": "gemini-3.1-flash-lite", "role": "onepass", "arm_field": "RE",
"phase": "ФД-N", "rig": "eval/dovodka/rol.py --buy en RE (клетки 21.08, карантин)",
"prompt_tokens": (300, 900), "wire": _rq_wire,
"что": "тот же gemini БЕЗ ИНСТРУКЦИИ — вендор выбросил системное сообщение"},
}
@ -507,11 +536,18 @@ def foreign_defect(d: dict, arm: str, uid: str) -> str:
"""
spec = FOREIGN[arm]
got_model = d.get("model_returned") or d.get("model")
for what, got, want in (("arm", d.get("arm"), arm), ("uid", d.get("uid"), uid),
for what, got, want in (("arm", d.get("arm"), spec.get("arm_field", arm)),
("uid", d.get("uid"), uid),
("role", d.get("role"), spec["role"]),
("модель", got_model, spec["model"])):
if got != want:
return f"поле {what}={got!r}, ожидалось {want!r}"
lo_hi = spec.get("prompt_tokens")
if lo_hi:
pt = d.get("prompt_tokens")
if pt is None or not lo_hi[0] <= pt <= lo_hi[1]:
return (f"prompt_tokens={pt}, а у арма {arm} обязаны быть в {lo_hi}"
"похоже, подставлен файл другого арма")
if d.get("finish") != "stop":
return f"finish={d.get('finish')!r} — клетка оборвана либо мертва"
if not (d.get("content") or "").strip():
@ -526,7 +562,7 @@ def foreign_text(p: str, arm: str, uid: str) -> str:
if spec["pair"] != p:
raise SystemExit(f"⛔ арм {arm} существует только на паре «{spec['pair']}», запрошена «{p}»"
f"его клетки купил {spec['rig']} из кассы {spec['phase']}")
f = OUT / f"{spec['prefix']}{uid}.json"
f = OUT / f"{spec['prefix']}{uid}{spec.get('suffix', '')}.json"
if not f.exists():
raise SystemExit(f"⛔ клетки {arm} для главы {uid} нет на диске ({f.name}). Этот риг её "
f"купить не может — она из кассы {spec['phase']}, риг {spec['rig']}")
@ -938,20 +974,30 @@ def cmd_selftest() -> int:
pp = spec["pair"]
PAIRS[pp]["wire"]()
us = chosen(pp)
have = [u["uid"] for u in us if (OUT / f"{spec['prefix']}{u['uid']}.json").exists()]
have = [u["uid"] for u in us
if (OUT / f"{spec['prefix']}{u['uid']}{spec.get('suffix', '')}.json").exists()]
ck(f"{fa}: клетка есть на КАЖДОЙ главе пары {pp}", len(have) == len(us),
f"{len(have)} из {len(us)}")
if not have:
continue
fu = have[0]
real = json.loads((OUT / f"{spec['prefix']}{fu}.json").read_text(encoding="utf-8"))
real = json.loads((OUT / f"{spec['prefix']}{fu}{spec.get('suffix', '')}.json")
.read_text(encoding="utf-8"))
ck(f"{fa}: настоящая клетка гейт ПРОХОДИТ", not foreign_defect(real, fa, fu),
foreign_defect(real, fa, fu))
for name, patch in (("чужой арм", {"arm": "RG"}), ("чужая глава", {"uid": "0" * 10}),
("чужая роль", {"role": "onepass"}),
for name, patch in (("чужой арм", {"arm": "ZZZ"}), ("чужая глава", {"uid": "0" * 10}),
("чужая роль", {"role": "нетакая"}),
("чужая модель", {"model_returned": "grok-4.3"}),
("обрыв finish=length", {"finish": "length"}),
("пустой content", {"content": " "})):
("пустой content", {"content": " "}),
# ⚠ Токенный гейт есть НЕ У ВСЕХ армов, и это правильно: `E6` разводится
# уникальным префиксом файла и ролью `editor3`, а `RQ` лежит под тем же
# префиксом, что нормальный `RE`, — там числовой разводящий обязателен.
# Проверка идёт только там, где гейт объявлен: иначе она мерила бы не
# гейт, а моё желание видеть зелёное.
("файл другого арма по prompt_tokens", {"prompt_tokens": 99999})):
if "prompt_tokens" in patch and "prompt_tokens" not in spec:
continue
ck(f"{fa}: гейт РОНЯЕТ больной вход «{name}»",
bool(foreign_defect(dict(real, **patch), fa, fu)))
other = next((x for x in PAIRS if x != pp), pp)

219
eval/dovodka/vtoroe.py Normal file
View file

@ -0,0 +1,219 @@
#!/usr/bin/env python3
"""ВТОРОЕ ЧТЕНИЕ: СКОЛЬКО В ПОРОГЕ ОТ ПРИБОРА, А СКОЛЬКО ОТ ТЕКСТА. $0.
ЗАЧЕМ. У всех наших «неразличимо» до сих пор не было ПАСПОРТА РАЗРЕШЕНИЯ: порог считался по
разбросу парных разностей, но что в этом разбросе расхождение ТЕКСТОВ или расхождение ЧИТАТЕЛЕЙ
не мерил никто. Два чтения одних и тех же пакетов разделяют это ровно и без моделей: тексты у обоих
кругов побайтно одни, значит всё, чем круги отличаются, есть читатель.
РАЗЛОЖЕНИЕ, объявленное пре-регом Д33.2 ДО чисел. Для пары армов (a,b) и главы u парная разность
мест `D_r(u) = место_r(a,u) место_r(b,u)`, где r круг. Модель `D_r = T + E_r`: общий текстовый
эффект T (у кругов один, тексты те же) плюс независимый читательский шум E_r. Тогда
Var(D1) = σ²_T + σ²_E Var(D1 D2) = 2·σ²_E
ДОЛЯ ПРИБОРА = σ²_E / Var(D1) = Var(D1 D2) / (2·Var(D1))
Доля близка к 1 порог фазы есть почти чистый шум чтения, и «неразличимо» означает «прибор слеп».
Доля близка к 0 порог несёт текст, и «неразличимо» кое-что значит.
ЧЕГО ЭТОТ ПРИБОР НЕ МЕРИТ, И ЭТО ГЛАВНАЯ ОГОВОРКА: оба круга ОДНО читательское семейство
(`fable-5`). Согласие двух его чтений говорит про ШУМ и молчит про ВКУС. Ось ВЕРНОСТИ у владельца
разошлась с прибором радикально (Д30), и второе чтение этого не трогает вовсе.
И вторая: обёртку запуска ПЕРВОГО круга не хранит ни один артефакт (Д33.4). Значит в разницу
кругов входит не только читатель, но и неизвестная разница обёрток. Направление вклада неизвестно.
Запуск: vtoroe.py --tag=vendor2 [--anchor=RN] [--drop=uid8,uid8]
vtoroe.py --selftest
"""
from __future__ import annotations
import importlib.util
import json
import math
import re
import statistics as st
import sys
from pathlib import Path
REPO = Path("/home/ubuntu/projects/textmachine")
ZONE = Path(__file__).resolve().parent
for d in ("dovodka", "editor_tier", "tenant_panel", "role_topology", "bank_arbitration"):
sys.path.insert(0, str(REPO / "eval" / d))
def _load(name: str, path: Path):
spec = importlib.util.spec_from_file_location(name, path)
mod = importlib.util.module_from_spec(spec)
sys.modules[name] = mod
spec.loader.exec_module(mod)
return mod
R = _load("rol_vt", ZONE / "rol.py") # разбор порядка и раскладки — у него, второго не пишем
CH = R.CH
ZS = _load("zsud_vt", ZONE / "zsud.py") # знаковый тест — один на зону
OUT = R.OUT
READ = R.READ_DIR
def ranks_of(tag: str, p: str, ansdir: str) -> dict:
"""{uid8: {арм: место}} для одного круга. Глава без разобранного порядка выпадает ВСЛУХ."""
out, bad = {}, []
for kf in sorted((OUT / "blind-keys-rol").glob(f"rol-KEY-{tag}-*.json")):
uid8 = kf.stem.split(f"{tag}-")[1]
key = json.loads(kf.read_text(encoding="utf-8"))
if p not in key:
continue
lay = key[p]["1"]["метки"]
af = (READ / ansdir) / f"ОТВЕТ-{p}-{tag}-{uid8}.md"
if not af.exists():
bad.append((uid8, f"ответа нет в {ansdir}/"))
continue
txt = af.read_text(encoding="utf-8", errors="replace")
places = CH._ranks(R._order_of(txt, set(lay))) # noqa: SLF001
if sorted(places) != sorted(lay):
bad.append((uid8, f"порядок покрывает {len(places)} из {len(lay)} меток"))
continue
out[uid8] = {lay[lab]: pos for lab, pos in places.items()}
for uid8, why in bad:
print(f"{ansdir}/{uid8}: {why}")
return out
def spearman(a: list, b: list) -> float:
"""Пирсон по местам. Места уже средние для связок (`chtenie._ranks`), поэтому это и есть
Спирмен с поправкой на связки отдельной формулы не нужно."""
n = len(a)
if n < 3:
return float("nan")
ma, mb = st.mean(a), st.mean(b)
num = sum((x - ma) * (y - mb) for x, y in zip(a, b))
den = math.sqrt(sum((x - ma) ** 2 for x in a) * sum((y - mb) ** 2 for y in b))
return num / den if den else float("nan")
def contrasts(rk: dict, uids: list, arms: list, anchor: str) -> dict:
"""{арм: (разрыв, порог, знаковый p)} против якоря — форма порога та же, что в своде."""
out = {}
for a in arms:
if a == anchor:
continue
d = [rk[u][a] - rk[u][anchor] for u in uids]
out[a] = (st.mean(d), 2.8 * st.pstdev(d) / len(d) ** 0.5, ZS._sign_p(d)) # noqa: SLF001
return out
def verdict(gap: float, thr: float, p: float) -> str:
"""Конъюнкция пре-рега Д17.4: порог И знаковый p<0.05. Иначе — «в пределах»."""
return "РАЗЛИЧИМ" if abs(gap) > thr and p < 0.05 else "в пределах"
def report(tag: str = "vendor2", p: str = "en", anchor: str = "RN", drop: tuple = ()) -> int:
r1, r2 = ranks_of(tag, p, "r1"), ranks_of(tag, p, "r2")
uids = [u for u in sorted(set(r1) & set(r2)) if u not in drop]
if not uids:
raise SystemExit("⛔ нет глав, прочитанных ОБОИМИ кругами")
arms = sorted(r1[uids[0]])
print(f"\n=== ВТОРОЕ ЧТЕНИЕ {tag}, пара {p} ===")
print(f" глав в обоих кругах: {len(uids)} (в круге 1 всего {len(r1)}, в круге 2 {len(r2)})")
print(f" армов {len(arms)} · якорь {anchor}"
+ (f" · ВЫБРОШЕНЫ: {', '.join(drop)}" if drop else ""))
# ── (i) СОГЛАСИЕ ПОРЯДКОВ ────────────────────────────────────────────────────────────────
print(f"\n(i) СОГЛАСИЕ ПОРЯДКОВ по главе (Спирмен, {len(arms)} армов):")
rho = []
for u in uids:
r = spearman([r1[u][a] for a in arms], [r2[u][a] for a in arms])
rho.append(r)
print(f" {u}: ρ = {r:+.2f}")
print(f" медиана ρ = {st.median(rho):+.2f} · среднее {st.mean(rho):+.2f} · "
f"минимум {min(rho):+.2f} · максимум {max(rho):+.2f}")
print(f"ρ=0 значит «второе чтение не воспроизводит первое НИКАК»; ρ=1 — точная копия")
# ── (iii) СКОЛЬКО В ПОРОГЕ ОТ ПРИБОРА ────────────────────────────────────────────────────
print("\n(iii) РАЗЛОЖЕНИЕ ПОРОГА: доля ЧИТАТЕЛЯ в дисперсии парной разности")
shares, rows = [], []
for i, a in enumerate(arms):
for b in arms[i + 1:]:
d1 = [r1[u][a] - r1[u][b] for u in uids]
d2 = [r2[u][a] - r2[u][b] for u in uids]
v1 = st.pvariance(d1)
ve = st.pvariance([x - y for x, y in zip(d1, d2)]) / 2
sh = ve / v1 if v1 else float("nan")
rows.append((f"{a}{b}", v1, ve, sh))
if v1:
shares.append(min(sh, 2.0))
rows.sort(key=lambda x: -x[1])
print(f" {'пара':14s}{'Var(D1)':>10s}{'σ²читателя':>12s}{'доля':>8s}")
for name, v1, ve, sh in rows[:8]:
print(f" {name:14s}{v1:10.2f}{ve:12.2f}{sh:8.2f}")
print(f" … всего пар {len(rows)}")
print(f" ⭐ МЕДИАННАЯ ДОЛЯ ЧИТАТЕЛЯ = {st.median(shares):.2f} "
f"(среднее {st.mean(shares):.2f}, пар с долей ≥1.0: "
f"{sum(1 for x in shares if x >= 1.0)} из {len(shares)})")
print(" ⚠ доля 1.0 = читательский шум ОДИН объясняет весь наблюдённый разброс, текстового "
"вклада прибор не видит; доля >1.0 = круги расходятся СИЛЬНЕЕ, чем разошлись бы\n"
" независимые (анти-согласие), и порог тогда не мерит вообще ничего")
# ── (ii) ПЕРЕ-СЧЁТ КОНТРАСТОВ И ПЕРЕВЁРНУТЫЕ ВЕРДИКТЫ ────────────────────────────────────
c1, c2 = contrasts(r1, uids, arms, anchor), contrasts(r2, uids, arms, anchor)
print(f"\n(ii) КОНТРАСТЫ ПРОТИВ ЯКОРЯ {anchor}оба круга на ОДНИХ {len(uids)} главах:")
print(f" {'арм':5s}{'круг1 разрыв':>13s}{'порог':>7s}{'p':>8s} {'вердикт1':11s}"
f"{'круг2 разрыв':>13s}{'порог':>7s}{'p':>8s} {'вердикт2':11s}")
flips = []
for a in sorted(c1, key=lambda x: c1[x][0]):
g1, t1, p1 = c1[a]
g2, t2, p2 = c2[a]
v1, v2 = verdict(g1, t1, p1), verdict(g2, t2, p2)
if v1 != v2:
flips.append((a, v1, v2))
print(f" {a:5s}{g1:+13.2f}{t1:7.2f}{p1:8.4f} {v1:11s}"
f"{g2:+13.2f}{t2:7.2f}{p2:8.4f} {v2:11s}{' ⛔ ПЕРЕВЁРНУТ' if v1 != v2 else ''}")
print(f"\n ПЕРЕВЁРНУТЫХ ВЕРДИКТОВ: {len(flips)} из {len(c1)}"
+ ("" if not flips else "" + ", ".join(f"{a}: {x}{y}" for a, x, y in flips)))
return 0
def cmd_selftest() -> int:
bad = 0
def ck(n: str, ok: bool, d: str = "") -> None:
nonlocal bad
bad += not ok
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {n}" + (f" {d}" if d else ""))
ck("Спирмен: точная копия даёт +1", abs(spearman([1, 2, 3, 4], [1, 2, 3, 4]) - 1) < 1e-9)
ck("Спирмен: обратный порядок даёт 1", abs(spearman([1, 2, 3, 4], [4, 3, 2, 1]) + 1) < 1e-9)
ck("Спирмен: несвязанные ~0", abs(spearman([1, 2, 3, 4], [2, 1, 4, 3])) < 0.7)
# ⛔ РАЗЛОЖЕНИЕ ПРОВЕРЯЕТСЯ НА ЗАВЕДОМО ИЗВЕСТНОМ ВХОДЕ, а не на живых данных: иначе «доля 0.9»
# нечем отличить от арифметической ошибки. Два синтетических предела и один смешанный.
same = [3, -1, 2, 0, 4, -2]
ve = st.pvariance([x - y for x, y in zip(same, same)]) / 2
ck("предел А: круги СОВПАДАЮТ → доля читателя 0", abs(ve) < 1e-12, f"σ²={ve}")
# ⚠ ПЕРВАЯ РЕДАКЦИЯ ЭТОГО ГЕЙТА БЫЛА НЕГОДНОЙ и упала при первом же прогоне: «независимыми»
# я взял вектор и его ОТРИЦАНИЕ, то есть анти-корреляцию, и получил долю 2.00 вместо 1.00.
# Ошибка полезная: она показала, что доля БОЛЬШЕ единицы — законный исход и означает не
# «шум объясняет всё», а «читатели расходятся СИЛЬНЕЕ, чем разошлись бы независимо».
a1, a2 = [1, 1, -1, -1], [1, -1, 1, -1] # ортогональны: ковариация ровно 0
sh = (st.pvariance([x - y for x, y in zip(a1, a2)]) / 2) / st.pvariance(a1)
ck("предел Б: круги НЕЗАВИСИМЫ → доля читателя = 1", abs(sh - 1) < 1e-9, f"доля={sh:.2f}")
a3 = [-1, -1, 1, 1] # ровно противоположный первому
sh2 = (st.pvariance([x - y for x, y in zip(a1, a3)]) / 2) / st.pvariance(a1)
ck("предел В: круги ПРОТИВОПОЛОЖНЫ → доля 2 (законный исход, не баг)",
abs(sh2 - 2) < 1e-9, f"доля={sh2:.2f}")
ck("конъюнкция: порог пройден, p велик → в пределах", verdict(3.0, 2.0, 0.30) == "в пределах")
ck("конъюнкция: p мал, порог не пройден → в пределах", verdict(1.0, 2.0, 0.01) == "в пределах")
ck("конъюнкция: оба условия → РАЗЛИЧИМ", verdict(3.0, 2.0, 0.01) == "РАЗЛИЧИМ")
print(f"\n{'ПРИБОР ГОДЕН' if not bad else f'ПРОВАЛОВ: {bad}'}")
return bad
if __name__ == "__main__":
a = sys.argv[1:] or ["--selftest"]
if a[0] == "--selftest":
sys.exit(1 if cmd_selftest() else 0)
_tag = next((x.split("=", 1)[1] for x in a if x.startswith("--tag=")), "vendor2")
_anch = next((x.split("=", 1)[1] for x in a if x.startswith("--anchor=")), "RN")
_drop = tuple(next((x.split("=", 1)[1].split(",") for x in a if x.startswith("--drop=")), []))
sys.exit(report(_tag, "en", _anch, _drop))