Pin the signature at seventy-nine on both prompts and record the execution log: the auto-bank grew between purchases and was driven to a verified fixed point before any cell was bought

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcWeV2yiMWTjkeaQfSsYuL
This commit is contained in:
heaven 2026-09-02 11:49:08 +03:00
parent c08b547825
commit 590256aa35
6 changed files with 904 additions and 16 deletions

View file

@ -738,11 +738,18 @@ def covariate_check(arm_data, units, contrasts, out, dl):
return {"rho": float(rho), "slope": float(b), "p": float(p), "n": len(x), "length_effect": lenfit}
def money(arm_data, base_spend, out, sanction):
"""§9.5 — агрегат вручную: механизма нет, база считается ОДИН раз."""
def money(arm_data, base_spend, out, sanction, outside=0.0):
"""§9.5 — агрегат вручную: механизма нет, база считается ОДИН раз.
`outside` деньги, ушедшие провайдеру из БД, которая потом была пере-создана. Из леджера
копии они исчезают, из счёта нет, и агрегат «база + Σ(копия база)» их не видит. Слагаемое
заведено исполнением: $0.018243 ушли в p7-low на терминолога до пере-копирования руки.
"""
out.append("\n## ДЕНЬГИ — АГРЕГАТ ВРУЧНУЮ (§9.5)")
out.append(f" база : ${base_spend:.6f}")
total = base_spend
if outside:
out.append(f" потрачено вне баз : ${outside:.6f} ⚠ из леджеров не видно, но провайдеру ушло")
total = base_spend + outside
for arm in ALL_ARMS:
s = arm_data[arm]["spend"]
delta = s - base_spend
@ -763,6 +770,8 @@ def main():
ap.add_argument("--stand", default=str(Path.home() / "books/gu-zhenren/probe-4axes"))
ap.add_argument("--manifest", help="манифест пре-рега (sha256 промптов и запиненная сигнатура)")
ap.add_argument("--sanction", type=float, default=9.50)
ap.add_argument("--outside", type=float, default=0.018243,
help="потрачено вне текущих баз (пере-созданные БД); в агрегат входит")
ap.add_argument("--json", help="куда сложить машинный результат")
a = ap.parse_args()
B = Path(a.stand)
@ -883,7 +892,7 @@ def main():
e2 = e2_cost_per_delivered(arm_data, units, out)
e5 = e5_safety(arm_data, out)
cov = covariate_check(arm_data, units, d1, out, unit_len)
total = money(arm_data, base_spend, out, a.sanction)
total = money(arm_data, base_spend, out, a.sanction, a.outside)
result = {"e0": {k: v for k, v in e0.items()}, "e1": e1, "e2": {k: v for k, v in e2.items()},
"e3": e3, "e4": e4, "e5": e5, "covariate": cov,

View file

@ -44,10 +44,15 @@ BASE_DB="${BASE_SNAPSHOT_DB:-$(ls -1t "$B"/backups/*.db 2>/dev/null | head -1)}"
[ -z "$BASE_DB" ] && { echo "⛔ не найден бэкап базы в $B/backups — из чего сделаны копии?"; exit 1; }
BASE=$(spend_of "$BASE_DB")
aggregate() { # израсходовано = база + Σ(копия база)
$PY - "$BASE" "$B" <<'PYEOF'
# ⚠ ПОТРАЧЕНО ВНЕ ТЕКУЩИХ БАЗ. Деньги, ушедшие провайдеру из БД, которая потом была пере-создана,
# из леджера копии ИСЧЕЗАЮТ, а из счёта — нет. Агрегат «база + Σ(копия база)» их не увидит.
# Заведено после того, как $0.018243 ушли в p7-low на терминолога и рука была пере-копирована.
OUTSIDE="${OUTSIDE:-0.018243}"
aggregate() { # израсходовано = база + Σ(копия база) + потрачено вне баз
$PY - "$BASE" "$B" "$OUTSIDE" <<'PYEOF'
import sqlite3, sys, pathlib
base = float(sys.argv[1]); B = pathlib.Path(sys.argv[2]); tot = base
base = float(sys.argv[1]); B = pathlib.Path(sys.argv[2]); tot = base + float(sys.argv[3])
for arm in ["p7-off","p7-low","p9-off","p9-low","glm-off"]:
p = B/"arms"/f"{arm}.db"
if not p.exists(): continue
@ -62,19 +67,24 @@ ceiling_for() { $PY -c "print(f'{$BASE + $1*($SANCTION-$BASE):.4f}')"; }
echo "════════════════════════════════════════════════════════════════════════"
echo "ПАЧКА: юнитов $N, сид $SEED, санкция \$$SANCTION"
echo "база взята из $BASE_DB ⇒ \$$BASE"
echo "база взята из $BASE_DB ⇒ \$$BASE · потрачено вне баз \$$OUTSIDE"
echo "════════════════════════════════════════════════════════════════════════"
# ⛔ ПЕРЕД ПЕРВОЙ КЛЕТКОЙ: каждая копия обязана нести РОВНО базовый расход. Копия, сделанная не из
# этого бэкапа (или тронутая), сломала бы агрегат так, что этого не было бы видно ни в одной сумме.
for arm in p7-off p7-low p9-off p9-low glm-off; do
S=$(spend_of "$B/arms/$arm.db")
if [ "$(printf '%.6f' "$S")" != "$(printf '%.6f' "$BASE")" ]; then
echo "⛔ КОПИЯ $arm НЕСЁТ \$$S ВМЕСТО БАЗОВЫХ \$$BASE — агрегат посчитал бы неверно. СТОП."
# ⚠ После пред-полёта копия несёт базу ПЛЮС свои купленные клетки, поэтому равенства уже нет.
# Проверяется то, что осталось проверяемым: копия НЕ МОЖЕТ нести МЕНЬШЕ базы — иначе она сделана
# не из этого бэкапа, и вычитаемое в агрегате неверно.
if $PY -c "import sys; sys.exit(0 if $S + 1e-9 < $BASE else 1)"; then
echo "⛔ КОПИЯ $arm НЕСЁТ \$$S — МЕНЬШЕ базы \$$BASE. Сделана не из этого бэкапа. СТОП."
exit 1
fi
U=$(sqlite3 "file:$B/arms/$arm.db?mode=ro" "select count(*) from chunk_status where stage='edit';")
printf " %-8s леджер \$%s · edit-юнитов %s\n" "$arm" "$S" "$U"
done
echo "✓ все пять копий несут ровно базовый расход \$$BASE"
echo "✓ все пять копий сделаны из бэкапа \$$BASE и несут свои клетки"
for (( u=FROM_UNIT; u<=N; u++ )); do
ORDER=$($PY "$REPO/eval/dovodka/poryadok.py" --unit "$u" --seed "$SEED") || { echo "⛔ порядок не получен"; exit 1; }
@ -84,7 +94,19 @@ for (( u=FROM_UNIT; u<=N; u++ )); do
LOG="$B/logs/$arm.log"
CELL="$B/logs/$arm.u$u.log" # ⚠ отдельный лог клетки: tail общего может прочитать
# строку ПРЕДЫДУЩЕГО прогона; плюс он даёт время клетки
# ⛔ ПАУЗА КУПЛЕНА ИСПОЛНЕНИЕМ, А НЕ ОСТОРОЖНОСТЬЮ: `preflightBackup` (cmd/tmctl/backup.go) пишет
# точку восстановления в <каталог project_db>/backups/<ts>.db с точностью до СЕКУНДЫ и ОТКАЗЫВАЕТ
# стартовать, если файл уже есть («refusing to overwrite a restore point»). Пять рук лежат в одном
# каталоге, и запуск двух в одну секунду роняет вторую кодом 1 ещё до всякой работы. Поймано на
# $0-гарде оси: три руки из пяти упали именно так. В пачке это остановило бы прогон на сотне клеток.
sleep 2
echo "$arm (--ceiling-usd $CEIL) $(date -u +%H:%M:%SZ)"
# ⚠ Канал сырых тел включается ТОЛЬКО на первом юните пачки: у референса `glm-off` клетка
# юнита 1 была куплена $0-гардом БЕЗ канала, и его проводная улика не снята. Одного юнита
# достаточно; дальше канал выключен, чтобы логи не росли на сотне клеток.
BODIES=""
[ "$u" = "$FROM_UNIT" ] && BODIES="1"
LOG_LLM_BODIES="$BODIES" LOG_LEVEL="$([ -n "$BODIES" ] && echo debug || echo info)" \
"$B/bin/tmctl" translate \
--config "$B/arms/$arm.book.yaml" \
--keys-file "$KEYS" \
@ -115,7 +137,7 @@ for (( u=FROM_UNIT; u<=N; u++ )); do
N_NOW=$(sqlite3 "file:$DB?mode=ro" "select count(*) from chunk_status where stage='edit';" 2>/dev/null || echo -1)
AFTER=$(spend_of "$DB")
printf " юнитов редактуры: %s (ждём %s) · леджер руки \$%s · ключи %s\n" "$N_NOW" "$u" "$AFTER" "$KEYS_NOW"
if [ "$N_NOW" != "$u" ]; then
if [ "$N_NOW" != "$u" ]; then # юнит 1 куплен пред-полётом, поэтому счёт совпадает с номером
echo "РУКА $arm РАЗЪЕХАЛАСЬ: редакторских юнитов $N_NOW вместо $u."
echo " Дальше руки сравнивали бы РАЗНЫЕ юниты — блокировка трудности сломана. СТОП."
exit 5

203
eval/dovodka/predpolet.sh Executable file
View file

@ -0,0 +1,203 @@
#!/usr/bin/env bash
# predpolet.sh — ПРЕД-ПОЛЁТ: один юнит × пять клеток, и всё, что обязано быть проверено ДО пачки.
#
# Порядок намеренный: сначала $0-ГАРД ОСИ на всех пяти копиях, и только потом деньги. Если ось
# схлопнулась, это видно ДО первой платной клетки, а не после ста.
#
# ⛔ --resnapshot НЕ ДАЁТСЯ НИКОГДА.
set -uo pipefail
B="${B:-$HOME/books/gu-zhenren/probe-4axes}"
REPO="${REPO:-/home/ubuntu/projects/textmachine}"
KEYS="${KEYS:-$REPO/backend/.env}"
PY="$REPO/eval/.venv/bin/python"
SEED=20260902
SANCTION=9.50
UNIT="${UNIT:-1}"
PIN="$REPO/eval/dovodka/prereg-4axes/prereg.json"
declare -A W=( [p7-off]=0.30 [p9-off]=0.30 [p7-low]=0.1666666667 [p9-low]=0.1666666667 [glm-off]=0.0666666667 )
ro() { sqlite3 "file:$1?mode=ro" "$2" 2>/dev/null; }
spend_of() { ro "$1" "select coalesce(sum(committed_usd+reserved_usd),0) from spend;" || echo 0; }
BASE_DB="${BASE_SNAPSHOT_DB:-$(ls -1t "$B"/backups/*.db 2>/dev/null | head -1)}"
BASE=$(spend_of "$BASE_DB")
echo "═══ ПРЕД-ПОЛЁТ · юнит $UNIT · база \$$BASE (из $BASE_DB) ═══"
# ─────────────────────────────────────────────────────────────────────────────────────────────
# ЭТАП 1 — $0-ГАРД ОСИ. Потолок «база + $0.0001» отказывает ПЕРВОЙ ЖЕ редакторской резервации,
# но к этому моменту движок уже: разрешил черновики из чекпойнтов за $0, прогнал майнинг и
# терминолог по чекпойнтам за $0, ВЫЧИСЛИЛ edit-снапшот и завёл edit-джоб. То есть провенанс
# оси ложится в БД БЕЗ ЕДИНОГО ПЛАТНОГО ВЫЗОВА.
# ─────────────────────────────────────────────────────────────────────────────────────────────
echo; echo "── ЭТАП 1: гард оси за \$0 (потолок отказывает первой edit-резервации)"
# ⛔ ПОТОЛОК ГАРДА — «база + 0.04», А НЕ «+0.0001». Куплено ошибкой: при +0.0001 леджер отказывал и
# ТЕРМИНОЛОГУ, а его отказ — не halt, а `break` с логом (terminologist.go:807), то есть банк «не
# двигался» лишь потому, что не на что было купить. Ложная неподвижная точка. 0.04 лежит НИЖЕ
# редакторской резервации (≈16000·3.96/1e6 + 6000·1.32/1e6 ≈ $0.071) и ВЫШЕ батча терминолога
# (≈$0.012): если точка ложная, терминолог купит — и сверка леджера это УВИДИТ.
TINY=$($PY -c "print(f'{$BASE + 0.04:.6f}')")
for arm in p7-off p7-low p9-off p9-low glm-off; do
# ⛔ ПАУЗА КУПЛЕНА ИСПОЛНЕНИЕМ, А НЕ ОСТОРОЖНОСТЬЮ: `preflightBackup` (cmd/tmctl/backup.go) пишет
# точку восстановления в <каталог project_db>/backups/<ts>.db с точностью до СЕКУНДЫ и ОТКАЗЫВАЕТ
# стартовать, если файл уже есть («refusing to overwrite a restore point»). Пять рук лежат в одном
# каталоге, и запуск двух в одну секунду роняет вторую кодом 1 ещё до всякой работы. Поймано на
# $0-гарде оси: три руки из пяти упали именно так. В пачке это остановило бы прогон на сотне клеток.
sleep 2
BEFORE=$(spend_of "$B/arms/$arm.db")
"$B/bin/tmctl" translate --config "$B/arms/$arm.book.yaml" --keys-file "$KEYS" \
--verify-bank --max-units 1 --ceiling-usd "$TINY" > "$B/logs/axis-$arm.log" 2>&1
rc=$?
AFTER=$(spend_of "$B/arms/$arm.db")
printf " %-8s rc=%-2s леджер %s → %s" "$arm" "$rc" "$BEFORE" "$AFTER"
if [ "$(printf '%.6f' "$BEFORE")" != "$(printf '%.6f' "$AFTER")" ]; then
echo " ⛔ ДЕНЬГИ ПОТРАЧЕНЫ НА ГАРДЕ — так быть не должно, СТОП"; exit 1
fi
echo " ✓ \$0"
if [ "$rc" != "4" ]; then
echo " ⛔ ожидался код 4 (денежный отказ), получен $rc:"
tail -2 "$B/logs/axis-$arm.log" | sed 's/^/ /' | cut -c1-200
exit 1
fi
# ⛔ ОТКАЗ ОБЯЗАН НАЗВАТЬ СТАДИЮ edit. Если он назовёт terminology, значит терминолог пере-предложил
# хвост и попросил СВЕЖИЙ батч — тогда банк в копии собрался бы иначе, чем в базе, и это надо
# понять ДО денег, а не после.
if ! grep -q 'denied estimate=.*\/edit)' "$B/logs/axis-$arm.log"; then
echo " ⛔ отказ НЕ на стадии edit:"; grep -o 'denied estimate=[^)]*)' "$B/logs/axis-$arm.log" | tail -1 | sed 's/^/ /'
exit 1
fi
done
# ⛔ УСТОЙЧИВОСТЬ ПРОГОН-К-ПРОГОНУ: весь цикл пачки — это 20 повторных прогонов каждой руки, и если
# второй прогон даёт другой edit-снапшот, цикл сломается на втором юните. Проверяется дважды на одной
# копии: тот же snapshot_id и ноль свежих платных строк.
echo; echo "── УСТОЙЧИВОСТЬ: второй прогон той же руки обязан дать ТО ЖЕ"
S1=$(sqlite3 "file:$B/arms/glm-off.db?mode=ro" "select distinct snapshot_id from jobs where stage='edit';")
N1=$(sqlite3 "file:$B/arms/glm-off.db?mode=ro" "select count(*) from request_log where tm_hit=0 and cost_usd>0;")
sleep 2
"$B/bin/tmctl" translate --config "$B/arms/glm-off.book.yaml" --keys-file "$KEYS" \
--verify-bank --max-units 1 --ceiling-usd "$TINY" > "$B/logs/axis-glm-off-2.log" 2>&1
S2=$(sqlite3 "file:$B/arms/glm-off.db?mode=ro" "select distinct snapshot_id from jobs where stage='edit';")
N2=$(sqlite3 "file:$B/arms/glm-off.db?mode=ro" "select count(*) from request_log where tm_hit=0 and cost_usd>0;")
D2=$(grep -c 'denied by a USD ceiling' "$B/logs/axis-glm-off-2.log")
echo " glm-off прогон 1: снапшот ${S1:0:12}… строк с ценой $N1"
echo " glm-off прогон 2: снапшот ${S2:0:12}… строк с ценой $N2 · denied=$D2"
if [ "$S1" != "$S2" ] || [ "$N1" != "$N2" ]; then
echo " ⛔ ПРОГОН-К-ПРОГОНУ НЕУСТОЙЧИВ — цикл пачки сломается на втором юните. СТОП."; exit 1
fi
[ "$D2" != "0" ] && echo " ⚠ в повторе есть denied by a USD ceiling — терминолог упёрся в потолок гарда"
echo " ✓ устойчиво"
echo; echo "── СВЕРКА ОСИ ПО СНАПШОТАМ (до единой платной клетки)"
$PY - "$B" "$PIN" <<'PYEOF'
import json, sqlite3, sys, pathlib
B, pin = pathlib.Path(sys.argv[1]), json.loads(pathlib.Path(sys.argv[2]).read_text())
SHA = pin["prompt_sha256"]
WANT = {"p7-off": (SHA["P7"], "off", "deepseek-v4-pro"), "p7-low": (SHA["P7"], "low", "deepseek-v4-pro"),
"p9-off": (SHA["P9"], "off", "deepseek-v4-pro"), "p9-low": (SHA["P9"], "low", "deepseek-v4-pro"),
"glm-off": (SHA["P9"], "off", "glm-5")}
edit_ids, draft_ids, bad = {}, {}, []
for arm, want in WANT.items():
con = sqlite3.connect(f"file:{B/'arms'/(arm+'.db')}?mode=ro", uri=True)
snaps = dict(con.execute("select snapshot_id, payload from snapshots"))
for stage, bucket in (("edit", edit_ids), ("draft", draft_ids)):
ids = {r[0] for r in con.execute("select snapshot_id from jobs where stage=?", (stage,))}
bucket[arm] = ids
con.close()
for sid in edit_ids[arm]:
st = next((s for s in json.loads(snaps[sid]).get("stages", []) if s["name"] == "edit"), None)
if st is None:
bad.append(f"{arm}: в снапшоте {sid[:12]} нет стадии edit"); continue
got = (st.get("prompt_sha256"), st.get("reasoning"), st.get("model"))
print(f" {arm:8s} edit {sid[:12]}… sha {got[0][:12]}… reasoning '{got[1]}' model {got[2]}")
if got != want:
bad.append(f"{arm}: на проводе {got}, а запинено {want}")
# ⛔ САМАЯ СИЛЬНАЯ ПРОВЕРКА: у edit-снапшотов пяти рук всё, КРОМЕ плана стадий, обязано совпадать
# побайтно. Иначе «пять разных snapshot_id» пройдёт, а руки будут различаться НЕ осью: например
# разным memory_version, если авто-банк собрался в копиях по-разному. Базовый прогон оставил банк
# ЧАСТИЧНО консолидированным (consolidated=51, unanswered=57, batches_dropped=5) — он константа
# только если в пяти копиях свернулся ОДИНАКОВО, и вот это здесь и проверяется.
rest = {}
for arm in WANT:
con = sqlite3.connect(f"file:{B/'arms'/(arm+'.db')}?mode=ro", uri=True)
snaps = dict(con.execute("select snapshot_id, payload from snapshots")); con.close()
for sid in edit_ids[arm]:
pl = json.loads(snaps[sid]); mv = pl.get("memory_version")
pl.pop("stages", None)
rest.setdefault(arm, []).append((json.dumps(pl, sort_keys=True), mv))
mvs = {arm: v[0][1] for arm, v in rest.items() if v}
print(" memory_version по рукам:", {a: (m or "")[:12] for a, m in mvs.items()})
if len(set(mvs.values())) != 1:
bad.append(f"memory_version РАЗНЫЙ у рук: {mvs} — банк в копиях свернулся по-разному, "
f"редактор получил РАЗНЫЙ вход, и это НЕ ось")
bodies = {arm: v[0][0] for arm, v in rest.items() if v}
if len(set(bodies.values())) != 1:
bad.append("edit-снапшоты различаются НЕ ТОЛЬКО планом стадий — сравни payload вручную")
else:
print(" ✓ всё, кроме плана стадий, у пяти edit-снапшотов побайтно одинаково")
alle = set().union(*edit_ids.values()) if edit_ids else set()
alld = set().union(*draft_ids.values()) if draft_ids else set()
print(f" различных EDIT-снапшотов: {len(alle)} (нужно 5)")
print(f" различных DRAFT-снапшотов: {len(alld)} (нужно РОВНО 1 — иначе черновики перекуплены)")
if len(alle) != 5: bad.append(f"edit-снапшотов {len(alle)} ≠ 5 — ОСЬ СХЛОПНУЛАСЬ")
if len(alld) != 1: bad.append(f"draft-снапшотов {len(alld)} ≠ 1 — черновики НЕ общие")
if bad:
print("\n⛔ ГАРД ОСИ НЕ ПРОШЁЛ:")
for b in bad: print(" ·", b)
sys.exit(1)
print(" ✓ ось на месте, черновики общие — и всё это установлено за $0")
PYEOF
[ $? -ne 0 ] && { echo "СТОП: платить за схлопнувшийся крест нельзя."; exit 1; }
if [ "${ONLY_AXIS:-0}" = "1" ]; then
echo; echo "ONLY_AXIS=1 — остановлено ПЕРЕД платным этапом. Ни одна клетка не куплена."
exit 0
fi
# ─────────────────────────────────────────────────────────────────────────────────────────────
# ЭТАП 2 — ПЯТЬ ПЛАТНЫХ КЛЕТОК ОДНОГО ЮНИТА. С включённым каналом сырых тел запросов:
# это единственный раз, когда он нужен, и он даёт ПОБАЙТНУЮ улику того, что уехало на провод.
# ─────────────────────────────────────────────────────────────────────────────────────────────
echo; echo "── ЭТАП 2: пять платных клеток юнита $UNIT (канал сырых тел ВКЛЮЧЁН)"
ORDER=$($PY "$REPO/eval/dovodka/poryadok.py" --unit "$UNIT" --seed "$SEED")
echo " порядок: $ORDER"
for arm in $ORDER; do
CEIL=$($PY -c "print(f'{$BASE + ${W[$arm]}*($SANCTION-$BASE):.4f}')")
BEFORE=$(spend_of "$B/arms/$arm.db")
sleep 2 # см. пояснение про preflightBackup выше
echo "$arm (--ceiling-usd $CEIL) $(date -u +%H:%M:%SZ)"
LOG_LLM_BODIES=1 LOG_LEVEL=debug "$B/bin/tmctl" translate \
--config "$B/arms/$arm.book.yaml" --keys-file "$KEYS" \
--verify-bank --max-units 1 --ceiling-usd "$CEIL" \
> "$B/logs/pre-$arm.log" 2>&1
rc=$?
AFTER=$(spend_of "$B/arms/$arm.db")
D=$($PY -c "print(f'{$AFTER-$BEFORE:.6f}')")
echo " rc=$rc · клетка стоила \$$D · леджер руки \$$AFTER"
case $rc in
0|2) ;;
*) echo " ⛔ код $rc — СТОП, вопрос владельцу"; exit "$rc" ;;
esac
done
echo; echo "── АССЕРТЫ ПРЕД-ПОЛЁТА (исполняемые стоп-критерии §5.1, не осмотр глазами)"
$PY "$REPO/eval/dovodka/predpolet_proverka.py" "$B"
ASSERT_RC=$?
echo; echo "── АГРЕГАТ ПОСЛЕ ПРЕД-ПОЛЁТА"
TOT=$BASE
for arm in p7-off p7-low p9-off p9-low glm-off; do
S=$(spend_of "$B/arms/$arm.db")
TOT=$($PY -c "print(f'{$TOT + $S - $BASE:.6f}')")
done
echo " израсходовано: \$$TOT из \$$SANCTION"
echo
if [ "${ASSERT_RC:-1}" -ne 0 ]; then
echo "⛔ АССЕРТЫ ПРЕД-ПОЛЁТА НЕ ПРОШЛИ — ПАЧКУ НЕ НАЧИНАТЬ, вопрос владельцу."
exit 1
fi
echo "ДАЛЬШЕ: ФРИЗ №2 (пин сигнатуры) — и только потом пачка."

View file

@ -0,0 +1,249 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""predpolet_proverka.py — ассерты пред-полёта. Исполняемые, а не «посмотрела глазами».
Промт §5.3 требует от пред-полёта пять вещей: сигнатуру Δprompt_tokens, пять разных снапшотов,
кириллицу 0.99, эхо 0 и гейты референса. Гарды прибора при n=1 их ПЕЧАТАЮТ, но не ОСТАНАВЛИВАЮТ:
`chetyre.py` рассчитан на пачку. Поэтому стоп-критерии пачки исполняются здесь, и каждый с кодом.
Ни одного платного вызова: только чтение баз и логов.
"""
import json
import math
import pathlib
import re
import sqlite3
import sys
B = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else pathlib.Path.home() / "books/gu-zhenren/probe-4axes")
PIN = json.loads((pathlib.Path(__file__).parent / "prereg-4axes/prereg.json").read_text())
COEF = PIN["коэффициент_восстановления_размышления"]
LOW_THRESHOLD = PIN["деньги"]["порогены_клетки_low"]
ARMS = ["p7-off", "p7-low", "p9-off", "p9-low", "glm-off"]
CROSS = ARMS[:4]
fails, warns = [], []
def ro(db, q, a=()):
con = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
con.row_factory = sqlite3.Row
r = con.execute(q, a).fetchall()
con.close()
return r
def cjk_count(t):
return sum(1 for c in (t or "") if 0x4E00 <= ord(c) <= 0x9FFF or 0x3400 <= ord(c) <= 0x4DBF)
def cyr_share(t):
import unicodedata
letters = [c for c in (t or "") if unicodedata.category(c).startswith("L")]
if not letters:
return 0.0
return sum(1 for c in letters if "CYRILLIC" in unicodedata.name(c, "")) / len(letters)
print("" * 92)
print("АССЕРТЫ ПРЕД-ПОЛЁТА — стоп-критерии пачки, исполняемые")
print("" * 92)
# ── 1. КЛЕТКИ: цена, попытка 0, доставка ────────────────────────────────────────────────────
print("\n## 1. КЛЕТКИ ЮНИТА")
cells = {}
for arm in ARMS:
db = B / "arms" / f"{arm}.db"
rows = ro(db, """
select rl.chapter, rl.chunk_idx, rl.prompt_tokens pt, rl.cached_tokens ct,
rl.completion_tokens cmpl, rl.cost_usd, rl.finish_reason fin, rl.model_requested model,
cp.attempt, cp.response_text resp
from request_log rl left join checkpoints cp on cp.request_hash = rl.request_hash
where rl.stage='edit' and rl.tm_hit=0 order by rl.id""")
if not rows:
fails.append(f"{arm}: НИ ОДНОЙ редакторской строки — клетка не куплена")
continue
a0 = next((r for r in rows if r["attempt"] == 0), None)
if a0 is None:
fails.append(f"{arm}: нет попытки 0 (реконструкция через checkpoints не нашла attempt=0)")
continue
st = ro(db, "select chapter, chunk_idx, disposition, flag_reason, cost_usd, final_hash, content_hash "
"from chunk_status where stage='edit'")
unit_cost = sum(r["cost_usd"] for r in st)
shipped = ""
if st and st[0]["final_hash"]:
sh = ro(db, "select response_text from checkpoints where request_hash=?", (st[0]["final_hash"],))
shipped = sh[0][0] if sh else ""
rh = None if arm == "glm-off" else a0["cmpl"] - COEF * len(a0["resp"] or "")
cells[arm] = {
"key": (a0["chapter"], a0["chunk_idx"]), "pt": a0["pt"], "ct": a0["ct"], "cmpl": a0["cmpl"],
"cost0": a0["cost_usd"], "fin": a0["fin"], "model": a0["model"], "resp": a0["resp"] or "",
"attempts": len(rows), "unit_cost": unit_cost, "shipped": shipped,
"content_hash": st[0]["content_hash"] if st else "", "disp": st[0]["disposition"] if st else "?",
"flag": st[0]["flag_reason"] if st else "", "rhat": rh,
}
c = cells[arm]
print(f" {arm:8s} юнит {c['key']} · попыток {c['attempts']} · pt {c['pt']} (кэш {c['ct']}) · "
f"cmpl {c['cmpl']} · finish {c['fin']} · клетка ${c['cost0']:.6f} · юнит ${c['unit_cost']:.6f}")
print(f" диспозиция {c['disp']}{'/' + c['flag'] if c['flag'] else ''} · сырой ответ "
f"{len(c['resp'])} зн · отгружено {len(c['shipped'])} зн · "
f"{('%.0f' % c['rhat']) if c['rhat'] is not None else '0 (по построению)'}")
if len(cells) != 5:
fails.append(f"клеток куплено {len(cells)} из 5")
# ── 2. ⛔ ОДИН И ТОТ ЖЕ ЮНИТ У ПЯТИ РУК ──────────────────────────────────────────────────────
print("\n## 2. БЛОКИРОВКА ТРУДНОСТИ: пять рук обязаны редактировать ОДИН юнит")
keys = {a: c["key"] for a, c in cells.items()}
print(f" ключи: {keys}")
if len(set(keys.values())) != 1:
fails.append(f"руки взяли РАЗНЫЕ юниты: {keys} — блокировка трудности сломана")
else:
print(" ✓ юнит один")
# ── 3. ⛔ ПОБАЙТНАЯ ИДЕНТИЧНОСТЬ ВХОДА ВНУТРИ ПРОМПТА ────────────────────────────────────────
print("\n## 3. content_hash — побайтная сигнатура отрендеренных сообщений")
ch = {a: c["content_hash"] for a, c in cells.items()}
for a, h in ch.items():
print(f" {a:8s} {h[:24] or '(пусто)'}")
# ⚠ только НЕПУСТЫЕ хеши: на пустом стенде оба множества были бы {None} и «совпадение»
# отрапортовалось бы ложно — ровно тот класс ложной находки, за который здесь ловят.
p7 = {h for h in (ch.get("p7-off"), ch.get("p7-low")) if h}
p9 = {h for h in (ch.get("p9-off"), ch.get("p9-low"), ch.get("glm-off")) if h}
if len(p7) != 1 and p7:
fails.append(f"content_hash РАЗНЫЙ внутри пары P7: {p7} — руки видят разный вход, а должны только разный ЭФФОРТ")
if len(p9) != 1 and p9:
fails.append(f"content_hash РАЗНЫЙ внутри группы P9: {p9}")
if p7 and p9 and (p7 & p9):
fails.append("content_hash СОВПАЛ между P7 и P9 — ось промпта не доехала до сообщений")
if len(p7) == 1 and len(p9) == 1 and not (p7 & p9):
print(" ✓ внутри P7 одинаков, внутри P9 одинаков, между P7 и P9 РАЗЛИЧЕН — ровно как должно быть")
# ── 4. СИГНАТУРА Δprompt_tokens ─────────────────────────────────────────────────────────────
print("\n## 4. СИГНАТУРА Δprompt_tokens (∅ low) — её пинит ФРИЗ №2")
sig = {}
for label, off, low in (("P7", "p7-off", "p7-low"), ("P9", "p9-off", "p9-low")):
if off in cells and low in cells:
sig[label] = cells[off]["pt"] - cells[low]["pt"]
print(f" {label}: {cells[off]['pt']} {cells[low]['pt']} = {sig[label]}")
if not sig:
fails.append("сигнатура не снята — нет пар попыток 0")
# ── 5. ⛔ КИРИЛЛИЦА И ЭХО — СТОП-КРИТЕРИИ ПАЧКИ ─────────────────────────────────────────────
print("\n## 5. КИРИЛЛИЦА ≥0.99 И ЭХО = 0 (по СЫРОМУ ответу: санитайзер эхо вырезает)")
for arm, c in cells.items():
raw_cjk, ship_cjk = cjk_count(c["resp"]), cjk_count(c["shipped"])
raw_cyr, ship_cyr = cyr_share(c["resp"]), cyr_share(c["shipped"])
print(f" {arm:8s} сырой: CJK {raw_cjk:4d} кириллица {raw_cyr:.4f} | отгружено: CJK {ship_cjk:4d} "
f"кириллица {ship_cyr:.4f}")
if arm in CROSS:
if raw_cjk > 0:
fails.append(f"{arm}: ЭХО в сыром ответе ({raw_cjk} иероглифов) — стоп-критерий пред-полёта")
if raw_cyr < 0.99:
fails.append(f"{arm}: кириллица {raw_cyr:.4f} < 0.99 — стоп-критерий пред-полёта")
else:
if raw_cjk > 0:
warns.append(f"референс glm-off: CJK {raw_cjk} в сыром ответе — ожидаемая зона эха "
f"(thinking выключен), в E5 идёт ОТДЕЛЬНОЙ строкой, пачку не блокирует")
# ── 6. ГЕЙТЫ РЕФЕРЕНСА ──────────────────────────────────────────────────────────────────────
print("\n## 6. ГЕЙТЫ РЕФЕРЕНСА glm-5")
if "glm-off" in cells:
g = cells["glm-off"]
resid = g["cmpl"] - COEF * len(g["resp"])
rel = abs(resid) / g["cmpl"] if g["cmpl"] else 1.0
print(f" остаток completion {COEF}·len(resp) = {g['cmpl']} {COEF * len(g['resp']):.0f} = {resid:+.0f}"
f" ({rel:.1%} от completion)")
print(" ⚠ «R̂ = 0 по построению» — определение. УЛИКА, что thinking действительно выключен, — "
"именно этот остаток.")
if rel > 0.15:
warns.append(f"референс: остаток {rel:.1%} > 15% — на модели с выключенным thinking ожидался "
f"близкий к нулю; либо тумблер не доехал, либо у glm другой токенизатор")
# ── 7. ⛔ ЦЕНА КЛЕТКИ low ПРОТИВ ПОРОГА, ОБЪЯВЛЕННОГО ДО ЧИСЕЛ ──────────────────────────────
print(f"\n## 7. ЦЕНА КЛЕТКИ low ПРОТИВ ПОРОГА ${LOW_THRESHOLD} (объявлен в пре-реге ДО покупки)")
lows = [cells[a]["cost0"] for a in ("p7-low", "p9-low") if a in cells]
offs = [cells[a]["cost0"] for a in ("p7-off", "p9-off") if a in cells]
if lows:
m = sum(lows) / len(lows)
print(f" low: {['$%.6f' % x for x in lows]} ⇒ среднее ${m:.6f}")
print(f" ∅ : {['$%.6f' % x for x in offs]}")
if m > LOW_THRESHOLD:
fails.append(f"СРЕДНЯЯ КЛЕТКА low ${m:.6f} > порога ${LOW_THRESHOLD} — СТОП И ВОПРОС ВЛАДЕЛЬЦУ "
f"до основной покупки (выше в замороженной смете сценария нет)")
else:
print(f" ✓ ${m:.6f} ≤ ${LOW_THRESHOLD} — иду дальше")
for a in ("p7-off", "p9-off"):
if a in cells and cells[a]["cost0"] > 0.20:
warns.append(f"{a}: клетка ${cells[a]['cost0']:.6f} > $0.20 — отметка (симметрия к порогу low), "
f"не остановка")
# ── 8. ПРОВОДНАЯ УЛИКА ИЗ СЫРЫХ ТЕЛ ─────────────────────────────────────────────────────────
print("\n## 8. ПРОВОДНАЯ УЛИКА — что реально уехало в теле запроса")
for arm in ARMS:
log = B / "logs" / f"pre-{arm}.log"
if not log.exists():
warns.append(f"{arm}: лога {log.name} нет — проводная улика не снята")
continue
txt = log.read_text(errors="replace")
n_ex = txt.count("llm exchange")
# ⚠ ТЕЛО ЛОГИРУЕТСЯ ЭКРАНИРОВАННЫМ: slog печатает request="{\"max_tokens\":16000,...}".
# Первая редакция грепа искала неэкранированные кавычки, ничего не находила и рапортовала
# «ключа нет» — то есть дефект ИНСТРУМЕНТА выглядел как находка о проводе. Поймано
# положительным контролем: `grep -c reasoning_effort` даёт 1 там, где регулярка давала 0.
def find(key, val=r'\\?"([\w.\-]+)\\?"'):
# экранированы ОБЕ пары кавычек: \"reasoning_effort\":\"low\"
return sorted(set(re.findall(r'\\?"' + key + r'\\?":\s*' + val, txt)))
eff = find("reasoning_effort")
think = sorted(set(re.findall(r'\\?"thinking\\?":\s*\{\\?"type\\?":\s*\\?"(\w+)', txt)))
mt = sorted(set(int(x) for x in find("max_tokens", r'(\d+)')))
tmp = sorted(set(find("temperature", r'([\d.]+)')))
mdl = find("model")
print(f" {arm:8s} обменов {n_ex} · reasoning_effort {eff or ''} · thinking {think or ''} · "
f"max_tokens {mt or ''} · temperature {tmp or ''} · model {mdl or ''}")
# ⛔ ПОЛОЖИТЕЛЬНЫЙ КОНТРОЛЬ НА ГРЕП: «ключа нет» обязано отличаться от «лога нет».
if n_ex == 0:
warns.append(f"{arm}: в логе НОЛЬ строк «llm exchange» — «ключа нет» здесь ничего не доказывает")
continue
want_eff = "low" if arm.endswith("low") else None
if want_eff and eff != [want_eff]:
fails.append(f"{arm}: в теле reasoning_effort {eff}, а должно быть ['{want_eff}']")
if not want_eff and arm != "glm-off" and eff:
fails.append(f"{arm}: в теле ЕСТЬ reasoning_effort {eff}, а у ∅-руки ключа быть не должно")
if arm == "glm-off" and think != ["disabled"]:
fails.append(f"{arm}: thinking в теле {think}, а должно быть ['disabled']")
# ── 9. ПОТОЛОК ВЫВОДА ОДИНАКОВ ВО ВСЕХ ЧЕТЫРЁХ РУКАХ КРЕСТА ─────────────────────────────────
print("\n## 9. ПОТОЛОК ВЫВОДА (требование §14: одинаков во всех руках креста)")
mts = {}
for arm in ARMS:
log = B / "logs" / f"pre-{arm}.log"
if log.exists():
m = re.findall(r'msg="calling model".*?max_tokens=(\d+)', log.read_text(errors="replace"))
edits = sorted(set(int(x) for x in m))
mts[arm] = edits
print(f" {arm:8s} max_tokens в вызовах: {edits}")
cross_mt = {a: [x for x in v if x >= 16000] for a, v in mts.items() if a in CROSS}
flat = {tuple(v) for v in cross_mt.values() if v}
if len(flat) > 1:
fails.append(f"потолок вывода РАЗЛИЧЕН между руками креста: {cross_mt} — конфаунд, убивший прошлый замер")
elif flat:
print(f"у четырёх рук креста потолок один: {sorted(flat)[0]}")
if "glm-off" in mts:
print(f"у референса свой потолок ({mts['glm-off']}): у glm-5 нет min_max_tokens, «равный потолок» "
f"на него НЕ распространяется — это печатается рядом с его числом")
# ── ИТОГ ────────────────────────────────────────────────────────────────────────────────────
print("\n" + "" * 92)
for w in warns:
print(f"{w}")
if fails:
print("\n⛔ ПРЕД-ПОЛЁТ НЕ ПРОШЁЛ:")
for f in fails:
print(f" · {f}")
print("\nПАЧКУ НЕ НАЧИНАТЬ. Это стоп-критерии §5.1, а не рекомендации.")
sys.exit(1)
print("✓ ПРЕД-ПОЛЁТ ПРОЙДЕН — все стоп-критерии §5.1 исполнены командой, а не осмотрены глазами.")
print(f" сигнатуру {sig} пинит ФРИЗ №2 — до пачки.")
sys.exit(0)

View file

@ -0,0 +1,381 @@
# ЖУРНАЛ ИСПОЛНЕНИЯ — что случилось ПОСЛЕ фриза №1 (`c08b547`)
Пре-регистрация заморожена и не правится. Всё, что исполнение показало дальше, ложится сюда:
находки, отступления, деньги — по порядку, с командами.
---
## Ш1 · КЛЮЧЕВАЯ ПРОБА, $0
```
tmctl translate --config $B/book.yaml --keys-file <polygon>/backend/.env --ceiling-usd 0.0001
→ pre-flight: backed up the project DB to backups/20260902T004943Z.db (integrity_check green)
→ draft wave started snapshot=8d5721f65596 chunks=35 workers=4
→ book USD ceiling reached ($0.0001) … denied estimate=$0.012052, ch1/chunk0/draft
RC=4
```
**Критерий прохождения — деньги, а не код выхода:** `spend` = 0 · `request_log` = 0 строк ·
`request_log where cost_usd>0` = 0 · `checkpoints` = 0 · `chunk_status[edit]` = 0.
**Положительный контроль на сам критерий:** те же четыре запроса к базе холодного прогона дают
`$0.436110` и `38` строк — команда умеет находить деньги там, где они есть.
Побочно установлено: ключи всех четырёх провайдеров резолвятся (ошибки `CheckKeys` не было);
`langpack_version = langpack-v2-x13fce767eada` — непустой, значит русский слой загрузился;
в снапшоте черновой волны РОВНО одна стадия — `translator`, то есть пер-волновой фолд подтверждён
на живой базе.
---
## Ш3 · БАЗОВЫЙ ПРОГОН — **RC = 3**, редактура не куплена
| строка | вызовов | $ |
|---|---|---|
| черновик `deepseek-v4-flash` | 36 | 0.168200 |
| хопы эскалации `deepseek-v4-pro` | 5 | 0.088463 |
| терминолог | 3 | 0.013484 |
| классификатор | 1 | 0.002756 |
| **ИТОГО** | | **0.272903** при поясе 0.60 |
`chunk_status[edit]` = 0 · `request_log[edit]` = 0 · `jobs[edit]` = 0, при положительном контроле
(тот же запрос к `coldrun-v16` даёт 3). Все 35 черновых чанков `ok`, флагнутых 0, эскалаций 5 —
эхо-черновики вычищены, что и было задачей базового прогона.
**Терминолог обрезан бюджетом:** `batches_dropped=5`, `classify_batches_dropped=6`,
`consolidated=51`, `unanswered=57`. Банк консолидирован ЧАСТИЧНО.
**Решение словом, а не умолчанием: бюджет НЕ поднимаю.** Банк — константа во всех пяти руках,
поэтому на ось усилия и промпта он не влияет; поднять его — пере-покупка ради вопроса, которого
замер не задаёт. Ограничение уходит в область переноса: результат получен на частично
консолидированном банке.
---
## Ш4 · ПЯТЬ КОПИЙ
`tmctl backup``backups/20260902T072159Z.db``cp` в пять `arms/<рука>.db`.
sha256 всех пяти совпал (`036f76f7e36a8e96…`), у каждой леджер `$0.272903`, черновиков 35, edit 0.
---
## ⭐ Ш5-ЭТАП-1 · ОСЬ ДОКАЗАНА ЗА $0
План собирался проверять ось ПОСЛЕ покупки. Приём: дать копии потолок «база + $0.0001». Движок
бесплатно поднимает черновики из чекпойнтов, гоняет майнинг и терминолог по чекпойнтам, **вычисляет
edit-снапшот и заводит edit-джоб** — и только потом упирается в отказ резервации.
```
p7-off edit ba1fdded853f… sha 2b84dee34b08… reasoning 'off' model deepseek-v4-pro
p7-low edit ebf9779789ec… sha 2b84dee34b08… reasoning 'low' model deepseek-v4-pro
p9-off edit 50456adb5e42… sha 1ad4544e564a… reasoning 'off' model deepseek-v4-pro
p9-low edit 818b46153d4f… sha 1ad4544e564a… reasoning 'low' model deepseek-v4-pro
glm-off edit fe10bbf000cd… sha 1ad4544e564a… reasoning 'off' model glm-5
memory_version у всех пяти: d08d11eb642e
EDIT-снапшотов 5 · DRAFT-снапшотов 1 · леджер каждой руки не сдвинулся
```
Плюс проверка сильнее плановой: у пяти edit-снапшотов **всё, кроме плана стадий, побайтно
одинаково**. Без неё «пять разных id» прошло бы и при разных банках.
### Находка Ш5-1: `preflightBackup` роняет прогоны, стартовавшие в одну секунду
Первый запуск гарда: три руки из пяти вернули код 1 —
`store: backup .../arms/backups/20260902T072300Z.db already exists (refusing to overwrite a restore
point)`. Точка восстановления именуется с точностью до СЕКУНДЫ, пять рук лежат в одном каталоге.
**В пачке это остановило бы прогон на сотне клеток.** Лечение: пауза 2 с перед каждым вызовом в
`predpolet.sh` и `pachka.sh`. Денег не стоило.
---
## ⛔⛔ Ш5-ЭТАП-2 · ГЛАВНАЯ НАХОДКА ИСПОЛНЕНИЯ: АВТО-БАНК РАСТЁТ МЕЖДУ ПОКУПКАМИ
Первая платная клетка (`p7-low`):
```
rc=1 · потрачено $0.018243 · edit-клеток куплено 0
tmctl: job guzhenren-probe4/ch1/edit was started under snapshot ebf9779789ec, current config
renders snapshot 64fc22983b6c — what moved: memory_version — that is the BANK, not a config
or prompt edit: the auto-bank grew between purchases
```
**Механизм.** Терминолог перезапускается в КАЖДОЙ руке и, пока у него остался бюджет, доращивает
авто-банк. Банк — вход редактора.
| | база | рука `p7-low` |
|---|---|---|
| `consolidated` | 51 | **81** |
| `unanswered` | 57 | **27** |
| `batches_dropped` | 5 | 3 |
| `memory_version` | `d08d11eb642e` | **`67d54521e64d`** |
| edit-снапшот | `ebf9779789ec` | **`64fc22983b6c`** |
Все $0.018243 — терминолог ($0.013276 render + $0.004967 classify), ни одной edit-клетки.
**Почему это угроза дизайну, а не побочка гарда.** Гард лишь показал раньше. Без него первая рука
завела бы джоб под своим банком и купила клетку, а на ВТОРОМ юните банк дорос бы снова — и рука
упёрлась бы в тот же отказ уже с оплаченными клетками. Хуже: банк мог вырасти по-разному в разных
руках, и тогда редактор получил бы **разный вход** при внешне исправном кресте. Это прямое
разрушение блокировки трудности, на которой стоит весь замер. Ни план, ни я этого не предвидели.
**Банк НЕ в неподвижной точке:** накоплено render `$0.02676` из `$0.05`, classify `$0.007723` из
`$0.02` — головы ещё на пару батчей.
### Что при этом НЕ грозит — проверено, а не предположено
* Предупреждение «the reverse section is capped … re-proposes on the next run» **не срабатывало ни
разу**: `grep -c 'reverse section is capped'` = 0 в обоих логах, при положительном контроле —
тот же греп находит 14 и 10 других `level=WARN` в тех же файлах.
* Дельта майнинга **стабильна**: `terms=102` и в базе, и в руке. Растёт только консолидация.
* Бюджет режется **ДО первого вызова** от накопленного расхода (`terminologist.go:792`), поэтому
исчерпание монотонно и неподвижная точка достижима.
### Лечение — проверено исполнением за $0
Довести терминолога до неподвижной точки В БАЗЕ и только потом копировать. Мешало то, что любой
прогон, дошедший до edit-волны, заводит edit-джоб, а он уехал бы в пять копий, совпав со снапшотом
лишь одной руки. Проверена гипотеза «draft-only конфиг»:
```
конфиг БЕЗ стадии edit, прогон на копии базы, потолок отказывает всему:
draft wave started snapshot=8d5721f65596 ← ТОТ ЖЕ, что у базы
bank-mining: --verify-bank has nothing to stop before in a draft-only pipeline
edit-джобов заведено: 0 · потрачено: $0.000000
```
Опасение, что `finalStageWave` перекинет фолд `repair` на черновую волну и сдвинет снапшот, снято:
гейт `repair` выключен, `repairSnapshot` даёт `nil` в обоих случаях.
**Процедура:** гонять базу `pipeline-bank-only.yaml`, пока `memory_version` не перестанет меняться
между двумя подряд прогонами (измеримая неподвижная точка) → новый бэкап → пять новых копий →
заново $0-гард оси → платный пред-полёт.
**Потолок стоимости лечения — остаток замороженных бюджетов: `$0.036516` + `$0.017244` = ≤ $0.0538.**
**Что это меняет и чего не меняет.** Меняет ПОРЯДОК сборки стенда, который §10 плана прямо оставляет
исполнителю. Не меняет ни дизайн, ни правило решения, ни потолок, ни рамку выборки, ни сид, ни
модели. Отвергнутые альтернативы: `gates.terminology.enabled: false` в руках (банк стал бы ДРУГИМ,
чем в базе: без терминолога `attachConsolidatedDst` берёт dst из сырого порядка черновиков);
поднять `budget_usd` (правка замороженного числа, и неподвижной точки не гарантирует);
`--resnapshot` (запрещён промтом).
---
## ⚠ ДЕНЕЖНАЯ ЛОВУШКА, НАЗВАННАЯ ДО ТОГО, КАК ОНА СРАБОТАЛА
`$0.018243`, потраченные в `p7-low`, при пере-копировании ИСЧЕЗНУТ из леджера копии — а провайдеру
они уже ушли. Агрегат §9.5 («база + Σ(копия база)») их не увидит и занизит расход.
В агрегат вводится постоянное слагаемое **«потрачено вне текущих баз» = $0.018243**, и оно
переносится во все последующие подсчёты.
---
## ДЕНЬГИ НА ЭТОТ МОМЕНТ
```
база $0.272903
вне текущих баз (p7-low) $0.018243
────────────────────────────────────
ИЗРАСХОДОВАНО $0.291146 из санкции $9.50
```
---
## ⛔ НАХОДКА В МОЁМ ЖЕ ГАРДЕ: ЛОЖНАЯ НЕПОДВИЖНАЯ ТОЧКА
**Класс: показания собственного инструмента приняты за факт о мире.** Тот же класс, что и снятая
находка про `pairs/` (§9-КВАТЕР пре-рега), и тот, из-за которого этот промт вообще написан.
$0-гард оси с потолком «база + $0.0001» показал «банк не двинулся, леджер не сдвинулся». Я прочла
это как «банк стабилен». На деле потолок отказывал и ТЕРМИНОЛОГУ, а его отказ — не остановка, а
`break` с логом (`terminologist.go:807`: «call denied by a USD ceiling; remaining terms left
unchanged»). Банк не двигался потому, что не на что было купить.
Улика, снятая после того, как это назвал агент-контролёр:
```
$ grep -c 'denied by a USD ceiling' logs/axis-*.log logs/base.log
axis-p7-off:2 axis-p7-low:2 axis-p9-off:2 axis-p9-low:2 axis-glm-off:2 base:0
ПОЛОЖИТЕЛЬНЫЙ КОНТРОЛЬ: тот же греп на 'ceiling' в axis-p7-off даёт 6
```
Во всех пяти логах гарда терминолог был заблокирован деньгами; в базовом прогоне (потолок $0.60) —
ни разу. **Лечение: потолок гарда обязан лежать НИЖЕ редакторской резервации, но ВЫШЕ батча
терминолога** — тогда ложная точка проявляется покупкой, а не тишиной.
## СТАБИЛИЗАЦИЯ БАНКА — ТРИ ИТЕРАЦИИ, ТОЧКА ДОКАЗАНА
Терминальное условие — не «`memory_version` не изменился» (это можно получить и купив батч, который
ничего не сконсолидировал), а **ноль свежих платных строк И ни одного отказа по деньгам**.
```
итерация 1 потолок $0.3729 строк с ценой 45→48 consolidated 81 memory_version 6cf3bbb07c4c
итерация 2 потолок $0.3862 строк с ценой 48→51 consolidated 101 memory_version e1815faab405
итерация 3 потолок $0.4081 строк с ценой 51→51 denied=0 memory_version НЕ ИЗМЕНИЛСЯ
✅ НЕПОДВИЖНАЯ ТОЧКА
```
Банк: **51 → 101 консолидированный терм из 108**, `unanswered` 57 → 7.
**Стоимость лечения: $0.035199** (леджер базы $0.272903 → $0.308102) — в предсказанном потолке
≤ $0.0538, который был выведен из ЗАМОРОЖЕННЫХ бюджетов и потому не мог быть превышен.
**$0-альтернатива, рассмотренная и отвергнутая по улике:** `gates.terminology.budget_usd: 0` в
руках заморозил бы банк бесплатно (`terminologist.go:770-776` — оплаченные батчи допускаются мимо
бюджета). Но такой конфиг **не загрузился бы вовсе**: `pipeline.go:1078` — `budget_usd must be > 0
when the gate is enabled (a gate that can never spend is a silent no-op)`, код 10 на старте.
Прочитан планировщик, не прочитан валидатор — поймано до запуска, ценой ноль.
## ПЕРЕ-СБОРКА И ГАРД НА СТАБИЛИЗИРОВАННОМ БАНКЕ
Старые копии снесены (несли джобы под старым снапшотом, а `p7-low` — иной банк). Новый бэкап
`backups/20260902T075300Z.db`, пять новых копий, sha256 совпал, edit-джобов 0, база **$0.308102**.
```
p7-off edit 5d19943a7771… sha 2b84dee34b08… reasoning 'off' model deepseek-v4-pro
p7-low edit 9fcf3c1c3eb1… sha 2b84dee34b08… reasoning 'low' model deepseek-v4-pro
p9-off edit 4fb1efc79dab… sha 1ad4544e564a… reasoning 'off' model deepseek-v4-pro
p9-low edit b76ab168d95b… sha 1ad4544e564a… reasoning 'low' model deepseek-v4-pro
glm-off edit a4598c85a210… sha 1ad4544e564a… reasoning 'off' model glm-5
memory_version у всех пяти: e1815faab405 · EDIT-снапшотов 5 · DRAFT-снапшотов 1
```
### Устойчивость прогон-к-прогону — то, на чём стоит цикл из 20 повторов
Два подряд прогона `p9-off` с потолком `база + $0.015` (НИЖЕ edit-резервации креста $0.0704, ВЫШЕ
батча терминолога ≈$0.0126 — то есть ложная точка проявилась бы покупкой):
```
прогон 1: rc=4 · снапшот 4fb1efc79dab→4fb1efc79dab · строк с ценой 51→51 · леджер не сдвинулся · denied=0
прогон 2: rc=4 · снапшот 4fb1efc79dab→4fb1efc79dab · строк с ценой 51→51 · леджер не сдвинулся · denied=0
```
### ⚠ ОТСТУПЛЕНИЕ: референсная клетка куплена ПЕРВОЙ, а не пятой
Гард с потолком `база + $0.04` купил клетку `glm-off`: у `glm-5` НЕТ флора `min_max_tokens`, её
редакторская резервация **$0.020191** влезла в потолок, тогда как у рук креста она **$0.070426**
(отказ назвал `ch1/chunk0/edit` — стадия та, что нужно).
```
glm-off (1,0) · glm-5 · pt 5478 · cmpl 2310 · $0.012870 · finish stop · ответ 7605 знаков
max_tokens в вызове: 4637 ← план предсказывал ≈4.5 тыс. для референса без флора: сходится
```
**Клетка принята, а не пере-куплена.** Она законна — это клетка пред-полётного юнита; куплена вне
порядка сида (первой вместо пятой), то есть БЕЗ выигрыша префикс-кэша, что для референса
консервативно; референс не входит в крест и в E1. Пере-покупка стоила бы ещё $0.0129 и не дала бы
ничего измеримого. Отступление записано здесь.
**Побочно — улика, что thinking у референса действительно выключен:** остаток
`2310 0.3644·7605 = 461` вместо тысяч положительных. Отрицательный знак — свойство ЧУЖОГО
токенизатора (план это и предупреждал), а не размышления.
## ДЕНЬГИ ПОСЛЕ СТАБИЛИЗАЦИИ
```
база (стабилизированная) $0.308102
вне текущих баз (p7-low) $0.018243 ← улика logs/orphaned-spend-p7-low.tsv, sha256 2b15d1e7…
клетка glm-off $0.012870 ← уже в леджере копии, в агрегат войдёт как (копия база)
─────────────────────────────────────
ИЗРАСХОДОВАНО $0.339215 из санкции $9.50
```
---
## ✅ ПРЕД-ПОЛЁТ ПРОЙДЕН — все стоп-критерии §5.1 исполнены командой
Команда: `./eval/.venv/bin/python eval/dovodka/predpolet_proverka.py ~/books/gu-zhenren/probe-4axes` → RC=0
### Пять клеток юнита (1,0) = «Глава 26»
| рука | pt | кэш | cmpl | R̂ | finish | клетка $ | ответ, зн |
|---|---|---|---|---|---|---|---|
| `p7-off` | 5696 | 0 | 14276 | **11506** | stop | 0.064052 | 7602 |
| `p7-low` | 5617 | 0 | 4593 | **1889** | stop | 0.025603 | 7420 |
| `p9-off` | 5752 | 0 | 6673 | **3920** | stop | 0.034018 | 7554 |
| `p9-low` | 5673 | 5632 | 3303 | **585** ⚠ | stop | 0.013382 | 7458 |
| `glm-off` | 5478 | 0 | 2310 | 0 по построению | stop | 0.012870 | 7605 |
`p9-low`: R̂ = 585 < 700 по правилу плана о ВЕЛИЧИНЕ такой клетки вывод не делается.
⚠ n = 1: числа приведены как улика исправности прибора, а НЕ как результат. Правило решения §4
требует Вилкоксона по 20 юнитам, и оно не пересматривается.
### ⭐ ПРОВОДНАЯ УЛИКА — план утверждал, что её не существует
```
p7-off reasoning_effort — max_tokens 16000 temperature 0.4 model deepseek-v4-pro
p7-low reasoning_effort ['low'] max_tokens 16000 temperature 0.4 model deepseek-v4-pro
p9-off reasoning_effort — max_tokens 16000 temperature 0.4 model deepseek-v4-pro
p9-low reasoning_effort ['low'] max_tokens 16000 temperature 0.4 model deepseek-v4-pro
```
Ключ стоит РОВНО у `low`-рук и отсутствует у `∅`-рук; потолок вывода, температура и модель
побайтно одинаковы. Требование §14 «потолок вывода одинаков во всех руках» — **исполнено на
проводе, а не выведено из кода**.
**Дефект инструмента, пойманный положительным контролем.** Первая редакция ассертов рапортовала
«у `low`-рук ключа НЕТ» — то есть дефект МОЕГО грепа выглядел как находка о проводе. Тело логируется
экранированным (`request="{\"reasoning_effort\":\"low\"}"`), а регулярка искала неэкранированные
кавычки. Поймано тем, что `grep -c reasoning_effort` давал 1 там, где регулярка давала 0.
Класс: показания собственного инструмента приняты за факт о мире — третий случай за сессию.
### Блокировка трудности и ось промпта — побайтно
```
ключи юнита: все пять рук = (1, 0)
content_hash: p7-off = p7-low = e3db15ac06a4ff04…
p9-off = p9-low = glm-off = 5282b3b56371e582…
между P7 и P9 РАЗЛИЧЕН
```
`content_hash` — сигнатура отрендеренных сообщений. Совпадение внутри пары доказывает, что руки
видят ПОБАЙТНО ОДИН вход и различаются только эффортом; различие между парами — что ось промпта
дошла до сообщений, а не осталась в конфиге.
### Безопасность
Кириллица **0.9995** во всех пяти руках, CJK **0** и в сыром ответе, и в отгружаемом — включая
референс, от которого план ждал зону эха. Стоп-критериев не сработало.
### Референс
Остаток `2310 0.3644·7605 = 461` (20% от completion). Отрицательный — значит размышления нет
вовсе, а коэффициент 0.3644 просто не подходит чужому токенизатору. План это предупреждал; для E1
референс не используется, и «R̂ = 0 по построению» остаётся верным.
⚠ Проводная улика референса НЕ снята: его клетка куплена $0-гардом без канала тел. Снимется на
первом юните пачки — канал включён именно там.
---
## ⛔ ФРИЗ №2 — СИГНАТУРА Δ`prompt_tokens` ЗАПИНЕНА
```
P7: prompt_tokens(p7-off) 5696 prompt_tokens(p7-low) 5617 = 79
P9: prompt_tokens(p9-off) 5752 prompt_tokens(p9-low) 5673 = 79
допуск ±2 токена; >20% юнитов вне допуска ⇒ ПРОБА АННУЛИРУЕТСЯ
```
**Число совпало с единственным известным ранее — «на en дважды ровно 79».** Совпадение получено
на ДРУГОЙ паре, ДРУГОЙ модели и ДРУГОМ материале, то есть это независимое подтверждение, а не
воспроизведение конвенции. С этого момента сигнатура — гейт, а не наблюдение.
---
## ДЕНЬГИ ПЕРЕД ПАЧКОЙ
```
база (стабилизированная) $0.308102
вне текущих баз $0.018243
p7-off $0.064052
p7-low $0.025603
p9-off $0.034018
p9-low $0.013382
glm-off $0.012870
─────────────────────────────────────
ИЗРАСХОДОВАНО $0.476269 из $9.50 · остаток $9.023731
юнит из пяти клеток стоил $0.149925
проекция на 19 юнитов $2.8486
ПРОЕКЦИЯ ИТОГА $3.3248 из $9.50
```
**Клетка втрое дешевле сметы.** План закладывал $7.5 на крест из расчёта ∅ $0.09 и low $0.05;
факт — ∅ $0.0340.064 и low $0.0130.026. Причина в том, что `low` режет размышление сильнее
закладывавшегося. ⛔ **Это НЕ основание добирать юниты:** рамка выборки заморожена диапазоном глав,
и §12 промта запрещает добор после просмотра чисел. Остаток санкции остаётся неизрасходованным.

View file

@ -176,7 +176,10 @@
"P7": "2b84dee34b0861637a055fee60372d2553f85ffa4c1b535c1e781f5167e8c3a1",
"P9": "1ad4544e564a9b4049231e79e961a9cbb85bc39de07c5ce72cdbc8d92cc68999"
},
"signature": {},
"signature": {
"P7": 79,
"P9": 79
},
оэффициент_восстановления_размышления": 0.3644,
"деньги": {
"санкция_владельца_пиковая": 9.5,
@ -211,12 +214,14 @@
"arms/p9-low.book.yaml": "75ba5b5cf12b33f4d77d4e8a5c765f407bcf1a7a516f5edf5758bf20927e4aef",
"arm-glm-off.pipeline.yaml": "54ddc4a29136422e6f47e65b8f654ff000e89aaaabf8ae9eac89d176c40b5008",
"arms/glm-off.book.yaml": "1693c44b5b07f097fd91d748be6dc1e40df65ce793475eafa8b63f104bd845f5",
"eval/dovodka/chetyre.py": "d52f7adaefae85e450237696cea723c70c966a56f2a430158e642e557daf5d29",
"eval/dovodka/chetyre.py": "15332ed541ab5b17a72ed5435764cc6940f1d8f9ff8d1dda5d138683a375360f",
"eval/dovodka/poryadok.py": "fcd9f2f805a24961fefe7ce2ad2b1de73472e09639ae13a39d47bce1ba910413",
"eval/dovodka/narezka.py": "6035a18bc4732965dd7a7dcb64f224e3be9d364bc72181f8e6bcc9233a8421dc",
"eval/dovodka/proverka_pribora.py": "552aafccd5e43d1660eb2dd986b9dcfdbbb62dfced2db31af229ef55bdc39d7b",
"eval/dovodka/pachka.sh": "2e0fa9ac42ead3aa9918551e9aabcb7a2d7591829b1a7ebb8ce44633e6b37f7e",
"манифест движка (stand/engine-manifest.json; переименован: .gitignore:24 `*.db.*.json` выбросил бы оригинальное имя)": "58d833c55b6cef5d98a20220f51d5c98a8fdd1f294ec7933c90a9ee2c288aeaa"
"eval/dovodka/pachka.sh": "81563110cfe9d154f743c1a2ab02ea8d75fe57fbbb33f71ef72658bbee1eafc6",
"манифест движка (stand/engine-manifest.json; переименован: .gitignore:24 `*.db.*.json` выбросил бы оригинальное имя)": "58d833c55b6cef5d98a20220f51d5c98a8fdd1f294ec7933c90a9ee2c288aeaa",
"eval/dovodka/predpolet.sh": "0b0a3280825c0e578fa1d32360066c2d828c7825181218ee09f9c6ae078cafcb",
"eval/dovodka/predpolet_proverka.py": "8abc94a47d5a8f393fd4428a24e4a44da5be387b5ca711aece7f823d7efc555a"
},
"ш1_ключевая_проба": {
"команда": "tmctl translate --config $B/book.yaml --keys-file <polygon>/backend/.env --ceiling-usd 0.0001",
@ -226,5 +231,24 @@
ерновой_снапшот": "8d5721f6559624d9a1f5c1b3d2f8c35dbaf25443febdbcbd46ade2886974f607",
"langpack_version": "langpack-v2-x13fce767eada",
"критерий": "spend=0, request_log=0 строк, checkpoints=0, chunk_status[edit]=0; положительный контроль — те же запросы к coldrun-v16 дают $0.436110 и 38 строк"
},
риз_2_сигнатура": {
"снято": "пред-полётом 02.09, юнит (1,0), попытка 0",
"P7": "prompt_tokens(p7-off) 5696 prompt_tokens(p7-low) 5617 = 79",
"P9": "prompt_tokens(p9-off) 5752 prompt_tokens(p9-low) 5673 = 79",
"допуск": "±2 токена; >20% юнитов вне допуска ⇒ проба аннулируется",
"замечание": "число совпало с единственным известным ранее (79 на английской паре и на риге), хотя пара, модель и материал другие — это независимое подтверждение, а не совпадение конвенции"
},
"базовый_прогон_факт": {
еджер_после_стабилизации": 0.30810196,
"бэкап_копий": "backups/20260902T075300Z.db",
"memory_version": "e1815faab405",
"consolidated": 101,
"unanswered": 7
},
"потрачено_вне_баз": {
"usd": 0.018243,
"улика": "logs/orphaned-spend-p7-low.tsv",
"sha256": "2b15d1e72cfa4555cd8e25763c31db240537fb19cf31a9b24719dd40a306504f"
}
}