Re-freeze phase 1 tail: raise its ceiling to the owner-approved figure and align the markdown gate with the production sanitizer

This commit is contained in:
Claude (backend session) 2026-08-08 17:59:20 +03:00
parent 930a3792c3
commit 05e2d57fd7
4 changed files with 102 additions and 23 deletions

View file

@ -113,8 +113,8 @@ def floor_of(pass_: str):
def hook(u: dict):
if pass_ not in ("p2zh", "p3"):
return None
a = P2.edit_text(P2.ANCHOR, u["uid"])
b = P2.edit_text(P2.ANCHOR, u["uid"], floor=True)
a = P2.edit_text(P2.ANCHOR, u["uid"], floor=1)
b = P2.edit_text(P2.ANCHOR, u["uid"], floor=2)
return (a, b) if a.strip() and b.strip() else None
return hook
@ -164,6 +164,57 @@ def cmd_plan(pass_: str) -> None:
print(f"агент-запусков израсходовано {len(runs_used())} из {CAP}")
# Сколько единиц отдаётся ОДНОЙ судейской сессии. Кап промта — «пакет до ~20 текстов»; здесь
# считается по фактическому числу меток прохода, а не на глаз.
def per_run(pass_: str) -> int:
n_arms = len(arm_map(pass_)) + 3 # + декой и пара пола
return max(1, 20 // max(1, n_arms))
def cmd_dispatch(pass_: str) -> None:
"""Группы заданий на агент-запуски + дословный промт судьи. Ничего не запускает сам."""
setup(pass_)
tasks = sorted((OUT / f"aj-{pass_}-tasks").glob("*.txt"))
if not tasks:
print("заданий нет — сначала --emit")
return
k = per_run(pass_)
groups = [tasks[i:i + k] for i in range(0, len(tasks), k)]
used = len(runs_used())
print(f"проход {pass_}: заданий {len(tasks)} · по {k} на сессию · сессий {len(groups)}")
print(f"агент-запусков израсходовано {used}, останется {CAP - used - len(groups)} из {CAP}")
if used + len(groups) > CAP:
print("⛔ КАП НЕ ВМЕЩАЕТ ПРОХОД — стоп и пинг, не резать молча")
return
print("\nПРОМТ СУДЬИ (дословно, один на сессию; список файлов подставляется):\n")
print(JUDGE_PROMPT.format(files="<файлы сессии>", answers=OUT / f"aj-{pass_}"))
print("\nГРУППЫ:")
for i, g in enumerate(groups, 1):
print(f" сессия {i}: " + " ".join(str(p) for p in g))
JUDGE_PROMPT = """Ты — литературный редактор-эксперт в роли СУДЬИ. Работай сам, не делегируй \
задачу другим агентам: раздача пакетов разным исполнителям смешивает их пороги строгости и \
делает числа несравнимыми.
Файлы заданий: {files}
Для КАЖДОГО файла: прочитай его целиком и выполни ровно то, что в нём написано оцени каждый \
помеченный вариант ОТДЕЛЬНО против исходника, посчитай ошибки по четырём осям и запиши ответ \
файлом ровно по тому пути, который назван внутри задания (каталог {answers}).
Жёсткие правила:
* обоснование ПОЧЕМУ обязательно для каждой НЕНУЛЕВОЙ оси и обязано содержать цитату в кавычках; \
число без цитаты не принимается разбором и на диск не попадает;
* ноль по оси обоснования не требует;
* НЕ открывай ничего, кроме файлов заданий: ни ключей раскладки, ни кода, ни соседних каталогов. \
Метки Т1..Тn слепые намеренно;
* порядок меток ничего не значит и перемешан;
* не сравнивай варианты между собой только каждый против исходника.
Верни одной строкой: сколько файлов заданий обработано и куда записаны ответы."""
def main() -> None:
a = sys.argv[1:]
if not a:

View file

@ -48,7 +48,11 @@ TAGS = {
"Ф2": ("tp2-draft-*.json", "tp2-edit-*.json", "tp2-floor-*.json"),
"Ф3": ("tp3-ed-*.json",),
}
CEILINGS = {"Ф0": 0.40, "Ф1": 1.00, "Ф2": 2.00, "Ф3": 1.60}
# ⚠ ПОТОЛОК Ф1 ПОДНЯТ 08.08 С $1.00 ДО $1.05 СЛОВОМ ВЛАДЕЛЬЦА, объявлено ДО траты. Причина:
# проекционный гард остановил скрин перед `gemini-3.1-pro-preview` — ОБЯЗАТЕЛЬНЫМ кандидатом Ф3
# по промту. Его цена — единственная неизвестная в смете Ф3 (оценка $0.80 из $1.60), и без замера
# половина фазы стояла бы на прайс-странице вместо провода. Пакетный потолок $5.00 не двигался.
CEILINGS = {"Ф0": 0.40, "Ф1": 1.05, "Ф2": 2.00, "Ф3": 1.60}
PACK_CEILING = 5.00

View file

@ -98,16 +98,20 @@ def units_all() -> list[tuple[dict, bool]]:
return [(u, False) for u in M.units_zh()] + [(u, True) for u in M.units_en()]
def tag_draft(model: str, uid: str, attempt: int = 1, floor: bool = False) -> str:
base = "tp2-floor" if floor else "tp2-draft"
# ⚠ ПОЛ КЛАДЁТСЯ ОТДЕЛЬНОЙ ПАРОЙ, а не «боевая клетка + повтор». Иначе одна половина пола
# побайтно совпала бы с армом `E0` в том же судейском пакете: судья увидел бы один текст дважды,
# а контроль «близнец» сработал бы на боевом арме. Эксп-21 обошёл это тем, что его пол лежал на
# ДРУГОМ входе (§build_unit), — здесь то же достигается двумя независимыми прогонами `p1`/`p2`.
def tag_draft(model: str, uid: str, attempt: int = 1, floor: int = 0) -> str:
base = f"tp2-floor-p{floor}" if floor else "tp2-draft"
return f"{base}-D-{model}-{uid}" + ("" if attempt == 1 else f"-r{attempt}")
def tag_edit(model: str, uid: str, floor: bool = False) -> str:
return f"{'tp2-floor' if floor else 'tp2-edit'}-E-{model}-{uid}"
def tag_edit(model: str, uid: str, floor: int = 0) -> str:
return f"{f'tp2-floor-p{floor}' if floor else 'tp2-edit'}-E-{model}-{uid}"
def draft_text(model: str, uid: str, floor: bool = False) -> str:
def draft_text(model: str, uid: str, floor: int = 0) -> str:
"""ГОДНЫЙ черновик: последняя попытка, прошедшая гейт. Пусто — годного нет."""
pre = tag_draft(model, uid, floor=floor)
best = ""
@ -118,12 +122,12 @@ def draft_text(model: str, uid: str, floor: bool = False) -> str:
return best
def edit_text(model: str, uid: str, floor: bool = False) -> str:
def edit_text(model: str, uid: str, floor: int = 0) -> str:
f = OUT / f"{tag_edit(model, uid, floor)}.json"
return json.loads(f.read_text(encoding="utf-8")).get("content", "") if f.exists() else ""
def buy_draft(led, model: str, u: dict, en: bool, floor: bool = False) -> dict:
def buy_draft(led, model: str, u: dict, en: bool, floor: int = 0) -> dict:
blk = None if en else PR.glossary_block(PR.terms_in(u["source"]))
msgs = PR.translator_msgs(u["source"], blk, en=en)
rec = {}
@ -137,7 +141,7 @@ def buy_draft(led, model: str, u: dict, en: bool, floor: bool = False) -> dict:
return rec
def buy_edit(led, model: str, u: dict, en: bool, floor: bool = False) -> dict:
def buy_edit(led, model: str, u: dict, en: bool, floor: int = 0) -> dict:
draft = draft_text(model, u["uid"], floor)
if not draft:
return dict(skipped=False, content="", no_draft=True)
@ -207,21 +211,30 @@ def cmd_edit() -> None:
def cmd_floor() -> None:
"""Вторая генерация ЯКОРНОГО арма end-to-end на тех же zh-единицах."""
"""ШУМОВОЙ ПОЛ: ДВА независимых прогона якорного арма end-to-end на тех же zh-единицах.
Истинная разница между `p1` и `p2` ноль по построению, поэтому измеренный разброс и есть
шум пайплайна (генерация черновика + генерация редактуры + судья). Он применяется к Ф2 И Ф3:
перевес ниже пола печатается вердиктом «ниже порога различимости».
"""
led = MONEY.Guarded("Ф2", default_expect=0.01)
for u in M.units_zh():
d = buy_draft(led, ANCHOR, u, False, floor=True)
if d.get("skipped"):
print("⛔ потолок Ф2 — стоп")
return
e = buy_edit(led, ANCHOR, u, False, floor=True)
print(f" пол {u['uid'][:6]} черновик {len(d.get('content', '')):5d} зн · "
f"редактура {len(e.get('content', '')):5d} зн")
time.sleep(0.15)
for p in (1, 2):
for u in M.units_zh():
d = buy_draft(led, ANCHOR, u, False, floor=p)
if d.get("skipped"):
print("⛔ потолок Ф2 — стоп")
return
e = buy_edit(led, ANCHOR, u, False, floor=p)
if e.get("skipped"):
print("⛔ потолок Ф2 — стоп")
return
print(f" пол p{p} {u['uid'][:6]} черновик {len(d.get('content', '')):5d} зн · "
f"редактура {len(e.get('content', '')):5d} зн")
time.sleep(0.15)
print(f"потрачено Ф2 ${led.spent():.6f}")
def draft_cost(model: str, uid: str, floor: bool = False) -> float:
def draft_cost(model: str, uid: str, floor: int = 0) -> float:
"""Цена черновика = ВСЕ попытки: ре-ген оплачен, значит он в цене роли."""
pre = tag_draft(model, uid, floor=floor)
return sum(json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0)

View file

@ -55,7 +55,18 @@ CYR_MIN = 0.5 # Г4: доля
ECHO_MAX = 0.15 # Г2: боевой порог disposition.go
_RE_HAN = re.compile(r"[㐀-鿿]")
_RE_MD = re.compile(r"^\s{0,3}#{1,6}\s|\*\*", re.M)
# ⚠ ДЕТЕКТОР MARKDOWN ИСПРАВЛЕН ПО ВСКРЫТИЮ КЛЕТОК (правило D39.61: перед сравнительным выводом
# открыть единицы). Первая версия (унаследована из скрина эксп-21) ловила ЛЮБОЕ `**` и потому
# помечала как «протёкшую разметку» строку `***` — стандартный русский типографский разделитель
# сцены, которым модель верно передала смену сцены в исходнике. Из-за этого ПРОВАЛ получали
# `glm-5` (ратифицированный резервный редактор) и `gemini-3.1-flash-lite` — то есть дефект
# детектора менял состав панели.
# Здесь детектор приведён к БОЕВОМУ (`checks/sanitizer.go:320` + D33.2): ATX-заголовок = решётки,
# пробел и БУКВА/ЦИФРА (декоративный разделитель решётками боевой гейт тоже не считает), плюс
# настоящий жирный спан `**текст**`, который боевой санитайзер не ловит, но в прозе он мусор.
# Порог «нарушений более чем в одной клетке» НЕ менялся — менялось только то, что считается
# нарушением.
_RE_MD = re.compile(r"^\s{0,3}#{1,6}\s+[^\W\d_]|\*\*(?=\S)(?:(?!\*\*).)+\*\*", re.M)
_RE_PREAMBLE = re.compile(r"^\s*(вот|ниже|here is|here's)\b[^\n:]{0,80}:", re.I)
_RE_REFUSAL = re.compile(
r"не могу|не буду|отказ|прости(те)?, но|cannot (assist|help|comply)|i'm sorry", re.I)