From eb409f2b778481f75e335afe53bd0c886b68b8e2 Mon Sep 17 00:00:00 2001 From: "Claude (backend session)" Date: Sun, 12 Jul 2026 23:34:16 +0300 Subject: [PATCH] Commit the reseed transform in eval/exp14b and note the seed-provenance lineage in the eval README, accepting the folder placement since it carries no contract citation --- eval/README.md | 4 +- eval/exp14b/exp14b_reseed_promote.py | 228 +++++++++++++++++++++++++++ 2 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 eval/exp14b/exp14b_reseed_promote.py diff --git a/eval/README.md b/eval/README.md index bb455f5..1dd7967 100644 --- a/eval/README.md +++ b/eval/README.md @@ -37,8 +37,8 @@ | `eval/exp12/` | exp12 диагноз (закрыт D30) | arms, blocks, candidate, extract, glossary_v2, judges, monitor, pack, reflow_demo, rootcause; `exp12_judges`→`exp12_pack` | | `eval/exp13/` | exp13 бейк-офф переводчиков (закрыт D32) | aggregate, blind_stage, gates, judges, monitor, translate | | `eval/exp14/` | exp14 рычаги качества (закрыт D37) | arms, common, judges, kpi, material, monitor, prompts, rank, regate, sizecurve; фундамент `exp14_common`/`exp14_prompts` | -| `eval/exp14b/` | exp14b батарея смысл-трапов (закрыт D38) | arms, judge, monitor, score; реюзят `exp14_common`/`exp14_prompts` из `../exp14/` | -| `eval/` (НЕ перемещён) | ЖИВОЙ провенанс сида v2 | `exp13_seed_v2.py`, `exp13_seed_signoff.py` (D30.5, регенерируют боевой сид) | +| `eval/exp14b/` | exp14b батарея смысл-трапов (закрыт D38) | arms, judge, monitor, score; реюзят `exp14_common`/`exp14_prompts` из `../exp14/`; + `exp14b_reseed_promote.py` = **ЖИВОЙ** reseed-провенанс сида (промоут грейдов+`四代族长`, D38.3/4, идемпотентен) | +| `eval/` (НЕ перемещён) | ЖИВОЙ провенанс сида v2 | `exp13_seed_v2.py`, `exp13_seed_signoff.py` (D30.5, регенерируют боевой сид); лестница reseed → `exp14b/exp14b_reseed_promote.py` (D38.3/4; в папке — нет контракт-пина, в отличие от `exp13_seed_*`) | > **`exp14_common.py` + `exp14_prompts.py`** живут в `eval/exp14/` и служат фундаментом exp14 И exp14b; `exp14b_*` тянут их через шим `../exp14`, поэтому переименование/перенос exp14 без правки шимов exp14b порвёт `import exp14_common`. diff --git a/eval/exp14b/exp14b_reseed_promote.py b/eval/exp14b/exp14b_reseed_promote.py new file mode 100644 index 0000000..cfe12b0 --- /dev/null +++ b/eval/exp14b/exp14b_reseed_promote.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +"""exp14b/D38.5 — reseed: промоут НАПРАВЛЕННЫХ владельцем термов draft→approved (корень терм- +дрейфа, exp14b D38 §3 / D-лог:499). Воспроизводимый провенанс-трансформ (паттерн +exp13_seed_signoff), ИДЕМПОТЕНТЕН + байт-стабилен (D30.5). Сид ВНЕ git; промоут авторизован +владельцем (D37/D38, «Да» 12.07). НЕ промоутить лишнего — approved-инвариант. + +Направлено (D-лог:499, сверено по сиду): + - грейды 甲等/乙等/丙等/丁等 → dst «разряд X» ⇒ «класс X» (D37; развести с 转→«ранг») + draft→approved + - 四代族长 → draft→approved (dst «Четвёртый глава рода» уже верна, D34/D37) +Остальные draft (南疆, 沈嬷嬷, гу-имена — D-лог:437) НЕ трогаем: «при желании владельца», не направлено. + +Verify (ИСПОЛНЕНИЕМ): diff = ровно 5 термов; повторный прогон = байт-в-байт; approved +5; +enforce-зеркало memory.go:485-503(memConfirmed-only) + :615-641(suppressContained) на синт.чанке — +после промоута инъекция отдаёт 四代族长→«Четвёртый глава рода» и грейды, 族长 не съедается→«старейшина». +""" +from __future__ import annotations +import sys +import yaml +from pathlib import Path + +BOOK = Path("/home/ubuntu/books/gu-zhenren") +SEED = BOOK / "guzhenren-seed-v2.yaml" +LOG = BOOK / "diag" / "glossary_v2_reseed_promote.md" +BAK = BOOK / "diag" / "guzhenren-seed-v2.pre-reseed.yaml" +DUMP = dict(allow_unicode=True, sort_keys=False, width=200) # ТЕ ЖЕ параметры, что писали сид (байт-стабильность) +MARK = "[reseed:approved D38.5]" +GRADE_LETTER = {"甲等": "А", "乙等": "Б", "丙等": "В", "丁等": "Г"} +# Чистые авторские заметки грейдов (D38.5: стейл-хвост «Head разряд declines / owner may prefer +# «первый/высший разряд»» вычищен по указанию оркестратора — сид пере-снапшотится, стейл не запекаем). +GRADE_NOTE = { + "А": "pinyin: jiǎděng; top aptitude grade (方正). Схема 甲乙丙丁 = класс А>Б>В>Г (все четыре); голова «класс» склоняется, буква инвариант.", + "Б": "pinyin: yǐděng; 2nd aptitude grade (漠北, 赤城).", + "В": "pinyin: bǐngděng; 3rd aptitude grade (方源).", + "Г": "pinyin: dīngděng; lowest aptitude grade.", +} + + +DIRECTED = list(GRADE_LETTER) + ["四代族长"] # ровно направленное владельцем (D-лог:499) + + +def load_path(p): + return yaml.safe_load(Path(p).read_text(encoding="utf-8")) + + +def load(): + return load_path(SEED) + + +def n_approved(d): + return sum(1 for t in d["terms"] if t.get("status") == "approved") + + +def summarize(pre, post): + """Полная 5-термовая таблица pre→post для направленных (не «что мутировало в этот прогон»).""" + a = {t["src"]: t for t in pre["terms"]} + b = {t["src"]: t for t in post["terms"]} + return [(s, f"{a[s]['dst']}/{a[s]['status']}", f"{b[s]['dst']}/{b[s]['status']}") + for s in DIRECTED if s in a and s in b] + + +def grade_forms(letter): + return [f"класса {letter}", f"классу {letter}", f"классом {letter}", f"классе {letter}"] + + +def apply_promote(data): + """Мутирует data на месте. Возвращает список (src, старое, новое). Идемпотентно.""" + applied = [] + for t in data["terms"]: + src = t.get("src") + if src in GRADE_LETTER: + letter = GRADE_LETTER[src] + new_dst = f"класс {letter}" + old = (t.get("dst"), t.get("status"), t.get("note")) + # авторская чистая заметка (не наследуем стейл-хвост «разряд declines / первый/высший») + new_note = (f"{MARK} грейд {src}→«класс {letter}» (D37: развести с 转→«ранг»; " + f"было «разряд», draft→approved). {GRADE_NOTE[letter]}") + t["dst"] = new_dst + t["type"] = "title" + t["decl"] = {"invariant": False, "forms": grade_forms(letter)} + t["status"] = "approved" + t["note"] = new_note + if old != (new_dst, "approved", new_note): + applied.append((src, f"{old[0]}/{old[1]}", f"{new_dst}/approved")) + elif src == "四代族长": + old = (t.get("dst"), t.get("status")) + note0 = t.get("note") or "" + t["status"] = "approved" + if MARK not in note0: + t["note"] = (f"{MARK} 四代族长 draft→approved (D38 §3: approved-длинный выигрывает " + f"longest-match над вложенным 族长 → 族长 больше не съедается неверно). " + note0) + if old[1] != "approved" or MARK not in note0: + applied.append((src, f"{old[0]}/{old[1]}", f"{old[0]}/approved")) + return applied + + +# ── enforce-зеркало (ТАРГЕТНЫЙ порт memory.go; при расхождении верить Go) ─────────────── +def spoiler_blocked(entry, chapter): + return chapter < int(entry.get("since_ch", 0)) # since_ch=0 у всех наших → никогда не блок + + +def find_matches(chunk, entries): + """[(start,end,idx)] по rune-индексам, прямой подстрокой (Han-ключи — без word-boundary, + suppressUnboundedPhonetic на Han не применяется, memory.go:651-654).""" + ms = [] + for idx, e in enumerate(entries): + src = e["src"] + start = 0 + while True: + i = chunk.find(src, start) + if i < 0: + break + ms.append((i, i + len(src), idx)) + start = i + 1 + return ms + + +def suppress_contained(ms, entries, chapter): + """Порт memory.go:615-641. m отсеян, если есть o: содержит m, строго длиннее, validSuppressor(o).""" + def valid_suppressor(o): + return not spoiler_blocked(entries[o[2]], chapter) + kept = [] + for i, m in enumerate(ms): + contained = any( + j != i and o[0] <= m[0] and o[1] >= m[1] and (o[1] - o[0]) > (m[1] - m[0]) and valid_suppressor(o) + for j, o in enumerate(ms)) + if not contained: + kept.append(m) + return kept + + +def render_block(ms, entries, chapter): + """Порт memory.go:485-503. memConfirmed ≈ status==approved (чистый одиночный матч, без + коллизий/post-check-неоднозначности — упрощение для синт.чанка). Дедуп по dst.""" + lines, seen = [], set() + for (s, e, idx) in ms: + ent = entries[idx] + confirmed = (ent.get("status") == "approved") and not spoiler_blocked(ent, chapter) + if not confirmed: + continue + dst = (ent.get("dst") or "").strip() + if not dst or dst in seen: + continue + seen.add(dst) + lines.append(f"- «{dst}»") + return lines + + +def enforce_mirror(data, chunk, chapter): + entries = data["terms"] + ms = find_matches(chunk, entries) + ms = suppress_contained(ms, entries, chapter) + return render_block(ms, entries, chapter) + + +# синт. чанк (МОЙ конструкт, НЕ из книги — копирайт): 四代族长, standalone 族长, 家老, грейды +CHUNK = "第四代族长传承五转之力。当代族长古月博坐镇村中,家老古月漠尘辅之。方源乃丙等资质,其兄方正甲等。" +CHAPTER = 15 # 四代族长 recurs 節15; все термы since_ch:0 → не спойлер-блок + + +def main(): + dry = "--dry" in sys.argv + # заморозить пре-reseed состояние ОДИН раз — иммутабельная истина «до» (устойчиво к повторным прогонам) + if not dry and not BAK.exists(): + BAK.write_text(SEED.read_text(encoding="utf-8"), encoding="utf-8") + pre = load_path(BAK) if BAK.exists() else load() # пре-reseed истина (не текущий, возможно уже промоутнутый, сид) + before = enforce_mirror(pre, CHUNK, CHAPTER) + n_appr_before = n_approved(pre) + + data = load() # текущий сид (мог быть уже промоутнут прошлым прогоном) + applied = apply_promote(data) # идемпотентно; applied = что реально мутировало В ЭТОТ прогон + missing = [s for s in DIRECTED if s not in {t.get("src") for t in data["terms"]}] + new_bytes = yaml.dump(data, **DUMP) + n_appr_after = n_approved(data) + + if not dry: + SEED.write_text(new_bytes, encoding="utf-8") + + after = enforce_mirror(data, CHUNK, CHAPTER) + table = summarize(pre, data) # ПОЛНАЯ 5-термовая провенанс-таблица pre(BAK)→post + + # ── лог/провенанс ── + lg = ["# Глоссарий v2 — RESEED промоут грейдов + 四代族长 (D38.5, 2026-07-12)", "", + f"Направлено (D-лог:499): грейды 甲乙丙丁→«класс А/Б/В/Г»+draft→approved, 四代族长 draft→approved.", + f"Провенанс: `eval/exp14b/exp14b_reseed_promote.py` (идемпотентен, байт-стабилен D30.5). Пре-снапшот: `{BAK.name}`.", + f"approved до/после: {n_appr_before} → {n_appr_after} (Δ={n_appr_after - n_appr_before}). " + f"Мутаций в последний прогон: {len(applied)} (0 = уже применено, идемпотентно).", "", + "| src | было (dst/status, пре-reseed) | стало |", "|---|---|---|"] + for s, o, n in table: + lg.append(f"| {s} | {o} | **{n}** |") + if missing: + lg += ["", "⚠ НЕ найдены в сиде (проверить): " + ", ".join(missing)] + delta = [x for x in after if x not in before] + lg += ["", "## Enforce-верификация (зеркало memory.go:485-503 + :615-641; при расхождении верить Go)", + f"Синт. чанк (МОЙ конструкт, не из книги): `{CHUNK}`", "", + "**editor-constraint блок ДО промоута** (грейды/四代族长 = draft → memConfirmed-фильтр их режет; " + "в 四代族长-контексте главы рода нет якоря — рядом только 家老→«старейшина»; standalone 族长→«глава клана»):", + "```", *(before or ["(пусто)"]), "```", + "**ПОСЛЕ промоута** (四代族长/грейды approved → инъектятся):", + "```", *(after or ["(пусто)"]), "```", + f"**Δ (появилось ТОЛЬКО после промоута):** {', '.join(delta) or '—'}", + "→ 四代族长-контекст теперь заякорен на «Четвёртый глава рода» (а не оставлен «старейшине»); " + "грейды инъектятся «класс А/Б/В/Г»; 转-семья (五转) рендерится «ранг» — развести соблюдено."] + if not dry: + LOG.write_text("\n".join(lg), encoding="utf-8") + + # ── консоль (self-review) ── + print(f"{'[DRY] ' if dry else ''}мутаций-в-прогон={len(applied)} missing={missing} approved {n_appr_before}→{n_appr_after}") + print("промоут-таблица (пре-reseed → пост):") + for s, o, n in table: + print(f" {s}: {o} → {n}") + print("\n-- enforce ДО (пре-reseed BAK) --"); [print(" " + x) for x in (before or ["(пусто)"])] + print("-- enforce ПОСЛЕ --"); [print(" " + x) for x in (after or ["(пусто)"])] + print("-- Δ (только после) --"); [print(" " + x) for x in (delta or ["(пусто)"])] + + # ── ассерты само-проверки (падаем громко) ── + assert len(applied) <= 5 and not missing, f"scope-нарушение: applied={applied} missing={missing}" + assert len(table) == 5 and all(n.endswith("/approved") for _, _, n in table), f"таблица не 5-approved: {table}" + # фикс проявляется ТОЛЬКО после промоута (были draft-невидимы): + assert "- «Четвёртый глава рода»" in after and "- «Четвёртый глава рода»" not in before, "四代族长 не заякорен после промоута / был виден до" + assert "- «класс А»" in after and "- «класс В»" in after, "грейды не инъектятся после промоута" + assert "- «класс А»" not in before, "грейд был виден до промоута (ожидался draft-невидим)" + # 族长/家老 — approved, корректны в ОБОИХ (家老=старейшина легитимно; НЕ должно исчезать): + assert "- «глава клана»" in after, "standalone 族长 должен инъектить «глава клана»" + print("\nOK: enforce-инварианты выполнены — 四代族长/грейды видны ТОЛЬКО после промоута; standalone 族长 цел.") + + +if __name__ == "__main__": + main()