Compare commits

...

110 commits

Author SHA1 Message Date
56ec3bda44 Fix my own timing claim: 21:49 was the task file's creation, not the result, so the addendum was written blind mid-panel rather than after the numbers 2026-09-02 22:16:12 +03:00
947ce4b503 Correct the addendum's own claim: it was written blind but ten minutes after the panel wrote its result, so it is not a pre-registration and must not be counted as one 2026-09-02 22:14:38 +03:00
394e2bd536 Add the null-guard resolution addendum before any judging numbers arrive: at ten pairs the p-threshold catches only 9:1, so tie-rate comparison against battle pairs becomes the diagnostic 2026-09-02 21:59:40 +03:00
f8d4e40d78 Freeze the second blind key before judging: thirty battle pairs on manifest-stitched drafts plus ten low-against-low active nulls 2026-09-02 21:48:28 +03:00
480684fce1 Freeze the second pre-registration before any paid call: manifest-authoritative unit stitching, unit-level permutation test, two primary lenses and low-against-low active nulls 2026-09-02 21:31:52 +03:00
d8c9ec280d Record the blind-read result after adversarial attack, with seven errata naming the truncated draft that left the accuracy lens without a reference on a third of the text 2026-09-02 20:32:11 +03:00
be5af14d3f Give the blind pairs neutral aliases so the filename cannot tell a judge which arm or control it is reading 2026-09-02 19:40:38 +03:00
dd23f452d5 Freeze the blind-read pre-registration and its key before any judging, with null controls that void the instrument if judges pick winners on identical texts 2026-09-02 19:38:52 +03:00
590256aa35 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
2026-09-02 11:49:08 +03:00
c08b547825 Freeze the probe stand before the first paid cell: configs, scripts, hashes and the ceiling rule, with the deviations from the procedure named rather than silently applied
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcWeV2yiMWTjkeaQfSsYuL
2026-09-02 10:12:10 +03:00
dc602e9df6 Freeze the pre-registration: design, models, decision rule, ceiling, sampling frame and seed are now fixed, and the known rough edges are named rather than hidden 2026-09-02 02:48:22 +03:00
6b9639a1a9 Close the last four: the pre-flight now flies five cells, the empty-output rule binds only the cross arms, the few-shot fork is decided, and the arm order is a contract 2026-09-02 02:47:10 +03:00
61dd1c61f8 Rewrite the price erratum a third time: both earlier figures divided by eleven rows where only eight were calls, so the direction holds but the magnitude is one point seven 2026-09-02 02:40:38 +03:00
b8b01b22f4 Repair my own botched substitution and mark every stale ceiling figure as history rather than leaving two live numbers 2026-09-02 02:33:48 +03:00
04cfd4f2c4 Raise the ceiling to nine and a half on the owner word, with the arithmetic that shows it covers two scenarios of three and loses only the reference cell in the worst 2026-09-02 02:33:16 +03:00
583b4ff047 Wire the reference cell through the whole plan instead of one paragraph, fix the invented backup flag, and name the four ways the reference would lie 2026-09-02 02:20:22 +03:00
c55ff2bbbf Correct my own erratum: both sides of the comparison were censored by their output ceilings, so the direction is closer to two and a third than four 2026-09-02 02:18:41 +03:00
1595dff6b2 Answer the owner's objection inside the design: add the cost decomposition endpoint, one reference cell on the reserve editor, and a transfer scope that forbids widening the claim 2026-09-02 00:33:25 +03:00
6de6e954ef Record that our price ratio understates: the editor now spends four times the output tokens it did in July, after the vendor swapped weights under an unchanged slug 2026-09-02 00:32:23 +03:00
79d66f258a Name the models: neither the plan nor the prompt mentioned a single one, so an executing session could not know what to configure 2026-09-02 00:24:30 +03:00
c8b97f37bc Write the handoff prompt for the probe session to orchestrator norm, with the procedure as its law and the three costly ambiguities named up front 2026-09-02 00:20:04 +03:00
04ed58c8a4 Fix the recovery file so a post-compaction session lands on the procedure instead of the forbidden rig command, and knows the owner already decided 2026-09-02 00:15:11 +03:00
14884ed802 Turn the plan into a procedure: the fresh-eyes check found it unbuildable without invention, so the stand, the arms, the commands, the exit codes and the manual ceiling aggregate are now written down 2026-09-02 00:14:27 +03:00
b226c373a4 Record the owner decision in the plan itself: variant A, seventeen chapters, ceiling of eight dollars sixty in peak terms 2026-09-02 00:01:03 +03:00
8ac0635612 Polish to freeze: the sampling frame moves to the top of the guards, variants are defined by chapter range rather than unit count, and the tie method is pinned after checking the library 2026-09-01 23:53:22 +03:00
86c0030b62 Close the last five: censoring is now symmetric with an explicit tie method, the sampling frame is pinned, and every figure exists in exactly one place 2026-09-01 23:50:47 +03:00
c1c6224644 Close the skeptic's nine: the unit arithmetic gets its missing step back, the ledger inherits base spend across copies, and the freeze moves into the polygon zone 2026-09-01 23:46:36 +03:00
9c5bc853f6 Apply the skeptic's pre-freeze list: name the real mechanism that keeps drafts free, fix the stop condition, drop the unreachable branch, and put every ceiling in peak terms 2026-09-01 23:40:41 +03:00
d1819b0d04 Refresh the recovery file with the state a compaction must not lose: the pre-registration, the two instrument holes, and the standing consilium 2026-09-01 23:22:28 +03:00
2799ecaa8c Trace the variance erratum into the sample-size table below it, which still sized n at the superseded figure 2026-09-01 23:21:04 +03:00
c033e9c9d6 Point the variance anchor at the erratum line rather than the superseded estimate above it 2026-09-01 23:20:31 +03:00
e13d4dff47 Land plan revision four: a pre-registration built with the standing consilium, with the two instrument holes named and the ceiling left to the owner 2026-09-01 23:19:41 +03:00
fbf6173347 Clear the retracted tail for good, fix the test attribution, and record that the pro-on-low echo was in fact measured five times clean but only in the translator role 2026-09-01 23:09:06 +03:00
ff3ca9519e Record the cold run the phase did not know about, and the layout that hid it: three repositories, two worktrees, one shared artifact directory 2026-09-01 23:05:59 +03:00
c8a25ca6c6 Clear the tail of the retracted claim: the engine probe is measurable by a book profile, and the guard I cited lives in a different test than I named 2026-09-01 23:03:37 +03:00
2bc41c8f66 Move the anchor one line down to where the token sits 2026-09-01 22:48:28 +03:00
0ad751ad7e Point the capability anchor at the line carrying the token 2026-09-01 22:48:15 +03:00
3dfb8b5c9a Retract my own claim that the engine never sends the knob: ReasoningNone emits low, medium and high, and only off and empty send nothing 2026-09-01 22:47:57 +03:00
2e288c6ad7 Rewrite the plan a third time and subordinate it to the ratified next probe, after the review found three of its four promises already closed or unbuildable 2026-09-01 22:44:38 +03:00
ce30509cfb Rewrite the plan after the consilium rejected it: the fidelity instrument answered its own question by measurement, the granularity probe was already refuted by our data, and the run of chapters returns to the centre 2026-09-01 22:16:25 +03:00
851caeb449 Retract the overstated headline: our own topology measurement shows fidelity holding or rising, and our editors add no mass at all 2026-09-01 22:14:02 +03:00
ad353744ed Answer the orchestrator in the report: our multiplier does not retire the ratified one, it measures full cost where that measured price alone 2026-09-01 22:09:07 +03:00
8e0c71b7df Point the guard anchor at the line that actually carries the token 2026-09-01 22:05:07 +03:00
9fb5a6f1a7 Retract the fix I had just recorded: the missing reasoning capability is a deliberate guard under test, so describing it would strip a defence rather than patch data 2026-09-01 22:04:49 +03:00
77893978b8 Record that the reasoning knob reaches the model on the polygon wire but the engine never sends it, because the model registry declares no reasoning capability 2026-09-01 22:03:10 +03:00
2d5ee71375 Make the mvp-plan anchor verifiable with an expectation token 2026-09-01 21:48:12 +03:00
74a16138fc Land the consilium on research/29: the editor buys fluency at the cost of fidelity, our panel is blind to that axis, and the zh edit ratio is four times the English one we had been quoting 2026-09-01 21:48:00 +03:00
8d4dc3b5f7 Rewrite the research prompt against the live orchestrator norm and fix what the refuter found: the zone belongs to the orchestrator and one hard-contour fact contradicted its own source 2026-08-30 23:55:54 +03:00
ebff194631 Write the harness-topology research prompt to orchestrator norm: a blocking repository sync precedes any search, because this phase proposed already-finished work five times in one day 2026-08-30 23:36:42 +03:00
df5a79b33c Make the four new anchors verifiable: expectation tokens where the target is a file, note number where the target is the decisions log 2026-08-30 20:15:12 +03:00
cdc5a5f156 Land stage 0 item 1: the bank holds its terms, and the audit found on disk a production-profile run, a July measurement of the same question, and a ratified editor-price input this phase never executed 2026-08-30 20:10:54 +03:00
62f8f6bef9 Land the consilium: what the editor does to the text, eight retracted conclusions with the class of each error, and a staged plan that starts free 2026-08-30 19:16:55 +03:00
c3c8e3100e Land the second audit: my own corrections had left both live claims standing, mixed price bases, and erased the editor defect-contraction that the phase did measure 2026-08-30 13:30:40 +03:00
f1fd873f00 Retract the contraction argument: the rank metric cannot fall below one, so twin divergence measured the instrument floor rather than the editor 2026-08-30 13:23:51 +03:00
b749bf1f7c Land the auditor findings: the book column was a median of a bimodal cost, the guard claim contradicted the code it cited, and the panel carries an unbalanced positional slope 2026-08-30 13:10:57 +03:00
3b39c78f2f Mark the panel verdict as an overclaim: the null covers whole-chapter refinement on an unvalidated instrument, not the second pass as a class 2026-08-30 13:02:31 +03:00
860c337387 Teach the rig that DeepSeek peak pricing is weekdays only, and record the vendor wording with the near-miss that a single page read caused 2026-08-30 12:55:42 +03:00
cbef5feebd Re-point the ping anchor after the polygon entry grew again 2026-08-30 12:27:51 +03:00
1a069de24d Re-check the Gemini price list live: one pin carries the post-promo rate and is double what the vendor charges today 2026-08-30 12:27:40 +03:00
d96e502e80 Correct how the luna price gap was quoted: an empty cell sat in the denominator and the ratio is bimodal, so decompose it into price and thinking volume 2026-08-30 12:10:12 +03:00
9586020052 Land the second circle: the finding repeats, so the cheap one-pass stays indistinguishable from both the expensive one-pass and the bundle 2026-08-30 12:04:39 +03:00
8326e35d07 Raise the cap by five so the second circle fits, and record that my own arithmetic left it one session short 2026-08-30 12:02:06 +03:00
aba746c598 Record the blind luna panel: the cheap one-pass is indistinguishable from both the expensive one-pass and the battle bundle in the first circle 2026-08-30 02:54:34 +03:00
c83aea8a0b Raise the agent-session cap on the owner word so the luna panel and its second circle both fit 2026-08-30 02:43:05 +03:00
6d85a91b20 Retract the time-shift reading of the thirty-eight-fold gap: it is three sigma against the only spread estimate we have, and price the repeats that would settle it 2026-08-30 02:33:15 +03:00
8837a0aa02 Report the interleaved run: the default path itself moved thirty-eight-fold in nine days, which measures the confound and leaves the knob magnitude unestablished 2026-08-30 02:13:39 +03:00
ea566eca46 Give the new code anchors their expectation tokens so the docs gate can verify what they point at 2026-08-30 01:52:23 +03:00
2acaedc910 Record that the effort knob now reaches the anchor, retract my claim that no unconfounded design fits the money, and freeze the interleaved pre-registration 2026-08-30 01:51:47 +03:00
7fabdd0634 Correct the battle-config comment that justified the vendor default by a premise the 30.08 probe retired, leaving the configuration itself untouched 2026-08-30 01:51:47 +03:00
cabc158f63 Record the probe: the effort knob on the anchor does act but only twofold, which lands in the pre-registered middle band and stops the purchase 2026-08-30 01:04:44 +03:00
5086cfeed9 Re-point the stale-ping anchor after the polygon entry grew 2026-08-30 00:58:46 +03:00
75e8b44571 Record the third silent breakage of the machine move: every API key name in the polygon env lost its underscore, which blocks the sanctioned purchase 2026-08-30 00:58:38 +03:00
6bc10f9669 Write the post-compaction restoration file: the sanctioned purchase is one command away with its decision rule frozen, and the seven mistakes of this session are listed so the next one does not repeat them 2026-08-30 00:56:26 +03:00
87dd12485e Freeze the pre-registration of the anchor at low effort, with the one-cell probe rule written before the probe and the ranking endpoint declared out of reach on sessions 2026-08-30 00:53:56 +03:00
2743295ce6 Re-point the one commit hash the report cited as evidence after the branch was re-signed 2026-08-30 00:42:59 +03:00
168a6fa1fb Re-point the stale-ping anchor after the polygon entry grew again 2026-08-30 00:39:28 +03:00
906e0ee5e8 Ping the vendor mapping change and the per-cell luna verification with the effort confound and the ten-cent measurement that resolves it 2026-08-30 00:39:20 +03:00
6fa67ad38c Verify the luna one-pass gap cell by cell and name the confound nobody had: we compared the anchor at high effort against the candidate at low 2026-08-30 00:38:48 +03:00
37446be4a4 Re-run the DeepSeek watch live: the vendor changed the effort mapping it had announced, so the recorded line about the knob is struck through rather than quietly replaced 2026-08-30 00:37:14 +03:00
a503f76dd2 Land the owner's correction: our anchor editor cannot lower its reasoning at all, so the topology is what buys back the switch the vendor does not give 2026-08-30 00:32:31 +03:00
a04d51d0ff Re-point the stale-ping anchor once more after the polygon entry grew 2026-08-30 00:25:24 +03:00
ced608be05 Ping the corpus sweep and the register of lost intentions, and hand the orchestrator the paragraph-extractor debt the polygon promised to raise and never did 2026-08-30 00:25:16 +03:00
a52db870a5 Give the plan anchor its expectation token 2026-08-30 00:24:37 +03:00
1a45446ccc Register what the phase planned and lost between course documents, including the owner's own draft-role fork that every later plan dropped 2026-08-30 00:21:21 +03:00
c623a0aed0 Land the corpus sweep the owner sent me back for: the eleven-dollar book was a July price list, the draft swap is worth six percent, and the one-pass verdict rests on an instrument that reads one chapter 2026-08-29 23:50:46 +03:00
1326acadb1 Split the cheap line in two: luna has an unmeasured censorship axis, gemini has a measured one that fails, and the report was calling both the same thing 2026-08-29 23:40:49 +03:00
92deca09f7 Re-point the stale-ping anchor after the ping grew 2026-08-29 23:24:18 +03:00
3087e92b62 Carry the money answer into the answer section and the polygon ping 2026-08-29 23:24:10 +03:00
c05e16e08c Add the money answer the phase never assembled: the pair costs ninety percent editor, a swappable editor makes it six times cheaper at indistinguishable quality, and the market study's per-book figure reconciles once thinking tokens are counted 2026-08-29 23:23:34 +03:00
87bee5e0ed Point the stale-ping anchor at the line it actually sits on 2026-08-29 23:04:51 +03:00
7a74cfbf90 Land the adversarial pass over my own work: one blocker on a decoy control I read after the fact, six important overstatements, and the disposition of each 2026-08-29 23:04:15 +03:00
1fbdaedd46 Head the phase report with a map: the answer it was built for sits last, six thousand lines from the top 2026-08-29 22:58:47 +03:00
f7bd1086cf Ground the prose-not-defects claim on the decoy, the power ceiling and the third instrument instead of a bare non-significant p 2026-08-29 22:54:07 +03:00
c119b4dd79 Restore the archive anchor token with a substring that is not the string the stale gate greps for 2026-08-29 22:52:43 +03:00
67e462205e Correct my own ping: the delivery check failed on three lines, not two, and record the historical duplicate tokens the acceptance found in the session journal 2026-08-29 22:52:26 +03:00
76d789d2a6 Act on the numbers refuter: unify the sign convention in the contrast matrix, stop overstating how the machine-reading tally was attributed, and drop one unearned digit 2026-08-29 22:51:58 +03:00
7bb6c26c62 Tell the orchestrator that one backlog anchor moved because I added an import to its target 2026-08-29 22:47:22 +03:00
a705ff251c Give the experiments index the two rows it was missing for packs 22 and 23 2026-08-29 22:45:41 +03:00
f52466a811 Report the delivery check red with a diagnosis per line, fix the checker that counted table headings as requirements, and record the gate I almost turned green by quoting it 2026-08-29 22:44:37 +03:00
056a8698a1 Give the archive-slice anchor its expectation token 2026-08-29 22:36:03 +03:00
d0e2b1a6b5 Ping the polygon section: both head questions are answered, the meta-question still needs its instrument, and two gates the machine move broke are declared rather than fixed green 2026-08-29 22:30:03 +03:00
438df9c824 Answer the owner's two head questions from what survived a second reading, a second family or a second instrument, and say plainly that the meta-question still has no instrument 2026-08-29 22:17:45 +03:00
705c875db8 Land the third panel-0 circle: a repeat by the same family costs as much as a change of family on every declared metric, and a third of the threshold is instrument noise 2026-08-29 22:14:52 +03:00
9e9e757a07 Give the polygon README the two steps a clean machine needs, the map of the phase-D zone it described as one file, and the mtime gates the move silently broke 2026-08-29 22:08:47 +03:00
079e7879e9 Act on the orchestrator ping: soften three Gemini claims past their source, and answer its open question with the arithmetic that needed no new purchase 2026-08-29 22:06:48 +03:00
fad71792e0 Freeze the pre-registration of a third panel-0 circle so the phase finally gets a within-family noise baseline for the thresholds every passport prints 2026-08-29 22:01:34 +03:00
c500dcfbbf Close step four: the reader tells a full role prompt from its absence at 1.33 thresholds, while the deterministic counter cannot, so the prompt buys prose and not defect-freedom 2026-08-29 21:57:17 +03:00
2a091ad5f0 Give the three provider-quirk anchors their expectation tokens so the hook can verify what they point at 2026-08-29 21:41:46 +03:00
3fff642a43 Correct the erratum I published an hour ago: the engine hole it declared open was closed on 28 August, so only the cost of the pre-fix defect changes 2026-08-29 21:41:29 +03:00
d7a202edc6 Freeze the step-4 pre-registration and prove from bought cells that Gemini drops the FIRST system message, so the engine loses the stage prompt rather than the glossary 2026-08-29 21:32:01 +03:00
49 changed files with 17124 additions and 25 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
# Эксперименты полигона — карта
Один эксперимент = один отчёт `NN-*.md` (методика + цифры + честные ограничения). Здесь — **индекс**: что установил → чем ратифицирован → статус. Каждая строка сверена по шапке самого дока + D-логу (**досведено 25.07, голова D39.28**: добавлены exp15/exp16; **ре-сверка 01.08, голова D39.64**: новых экспериментов нет, `00-provider-quirks` синхронизирован с катовером DeepSeek-0731 по D39.61; **ре-сверка 04.08, голова D39.105** — новых экспериментов карты нет, банк-арбитраж = research/24, выдан промт пробы 18, см. хвост карты; **досведено 08.08, голова D39.117** — добавлены строки 1821, следующий — эксп-22). Карта **скриптов** (какой `.py` какому эксперименту) — в [`eval/README.md`](../../eval/README.md) §Карта; здесь — про **выводы**.
Один эксперимент = один отчёт `NN-*.md` (методика + цифры + честные ограничения). Здесь — **индекс**: что установил → чем ратифицирован → статус. Каждая строка сверена по шапке самого дока + D-логу (**досведено 25.07, голова D39.28**: добавлены exp15/exp16; **ре-сверка 01.08, голова D39.64**: новых экспериментов нет, `00-provider-quirks` синхронизирован с катовером DeepSeek-0731 по D39.61; **ре-сверка 04.08, голова D39.105** — новых экспериментов карты нет, банк-арбитраж = research/24, выдан промт пробы 18, см. хвост карты; **досведено 08.08, голова D39.117** — добавлены строки 1821, следующий — эксп-22; **досведено 29.08 полигоном: добавлены строки 22 и 23**, дыра найдена аудитом доков 28.08). Карта **скриптов** (какой `.py` какому эксперименту) — в [`eval/README.md`](../../eval/README.md) §Карта; здесь — про **выводы**.
**Легенда статуса:** ⭐ЖИВОЙ-reference (читать при работе) · ЖИВОЙ (открытый инструмент/протокол) · закрыт (есть review-header/⚠-баннер; вывод ратифицирован, тело не редактируем — D23.3) · settled (одноразовый бенч, вопрос закрыт).
@ -37,6 +37,9 @@
| [`20-editor-role`](20-editor-role.md) | Роль редактора: починка по флагу дёшева при ИДЕАЛЬНОЙ детекции; поиск дороже правки; решающий замер §5.2 не персистирован («со слов» — позже опровергнут эксп-21). Exploratory, без пре-рег-фриза; ревью-шапка первична | D39.108; §5.2 superseded D39.117 п.4 | закрыт |
| [`21-role-topology`](21-role-topology.md) | **Топология бьёт жильца** (нога 2×2; `A/F3` +2.50 Holm 0.0004, движки dspro/glm-5 неразличимы) · черновик двигает ~70% эффекта финала · закон-блок обязателен (два независимых подтверждения) · точечный ремонт/обратная связка/роутинг — отклонены · разложение шума НЕ установлено. ⚠ Все zh-величины предварительны (материал узнан 4/5); пять редакций, судейство финала — агенты ($0) | **D39.117** (гейты: `verify_report.py` 90/90 · `itog.py`) | закрыт |
| [`22-tenant-panel`](22-tenant-panel.md) | **Жильцы редакторской роли на двух парах.** Тот же промт, разные модели: `dspro` первый на zh; на en порядок иной. Второй проход показан различимо лучше ОДНОГО дешёвого — но клейм позже сужен фазой Д до «различимо хуже ГОЛЫЙ черновик». ⚠ §15 переписан фазой Д: 5 клеток из 6 — эхо, пойманное гейтом; §13а — банк-закон на en НЕ исполнен | пере-читается фазой Д (Д8, Д11) | закрыт, тело правлено эрратами |
| [`23-editor-tier`](23-editor-tier.md) | **Сильный тир редакторской роли + ФАЗА Д «доводка».** Несущее: голый черновик различимо хуже в двух семействах · вся середина архитектур неразличима ⇒ схемы решает ЦЕНА · буфер архитектуры замерен (слабый жилец редактором ≡ боевой, он же однопроходкой хуже — 3 круга, 2 семейства, 2 прибора) · вендор-вердикт по конъюнкции (Д31) · **вес всего ролевого промта = 1.33 порога, и он лежит на ПРОЗЕ, а не на браке** (Д36) · **шум прибора = 0.34 порога** (Д37) · ответ на два главных вопроса владельца (Д38). ⛔ Вопрос 0 (консистентность на всю книгу) прибора НЕ имеет | ⚠ приёмка фазы — у параллельного оркестратора; сверка `--final` сдана КРАСНОЙ с диагнозом (Д39) | **ИДЁТ** |
*Пропуски нумерации:* `05` снят до прогона (вопрос закрыт литературой — урок «тест только если исход переворачивает решение», eval/README §Правила-3).
## Дуга качества 12 → 13 → 14 → 14b

View file

@ -37,7 +37,7 @@
| `role_topology/` | **exp-21 (топология ролей)** | `absjudge.py` — абсолютный судейский риг (слепые метки · декой · пол · обоснование цитатой · семейство в ключе · Холм), `battery.py` — 11 детерминированных проверок, `buy.py` — касса с проекционным гардом, `bakeoff.py`/`pair_en.py` — армы zh/en, `itog.py`/`verify_report.py` — образец гейтов чисел, `ruff.toml` — линтер зоны |
| `tenant_panel/` | **exp-22 (панель жильцов)** | `material.py` — добыча и отбор невиданного среза, `contam.py` — проба контаминации, `bank.py` — банк книги + метрика покрытия канона, `roster.py` — ростер и цены с провенансом, `money.py` — касса с фриз-гейтом (покупка отказывает незакоммиченному коду) и `configure()` для пере-настройки под другой пак, `judge.py` — тот же `absjudge`, настроенный на другую панель, `sol.py` — пакеты внешнему судье (⚠ конструкция признана негодной, §16 отчёта) |
| `editor_tier/` | **exp-23 (сильный тир редактора и граница `glm-5`)** | `screen.py` — скрин сильного тира, `panel.py` — панель редактур поверх якорной базы, `judge.py` — два прохода (`tier`/`border`) + режим `--run <прогон>` для ОТДЕЛЬНЫХ прогонов того же прохода (репликация), `money.py` — касса эксп-22 через `configure`, `itog23.py`/`verify23.py` — пере-счёт и гейт чисел (сторожат пол, порог, декой, наклон и СВЕЖЕСТЬ разбора), `solscore.py` — счёт ответов ВНЕШНЕГО судьи вне семьи Claude |
| `dovodka/` | **фаза Д (доводка 22/23, заказ владельца 10.08)** | `runs.py` — журнал агент-сессий: запись ДО запуска суб-агента, кап 80, замок против ГОНКИ сессий по одному токену (в паке 23 гонка стоила соответствия голосов и сырья на 5 единицах) |
| `dovodka/` | **фаза Д (доводка 22/23, заказ владельца 10.08)** — ⚠ **не один скрипт, а зона из ~40**; ниже только несущие, остальные читать по шапкам файлов | `rol.py` — ГЛАВНЫЙ инструмент: сборка промтов, покупка, слепые панели, сверка сборки (`--verify-read`), своды (`--score-arch`), реестр клеток чужих фаз `FOREIGN` · `schet.py` — детерминированный счётчик дефект-классов против эталона, снятого по исходнику вслепую · `vtoroe.py` — разложение порога на шум читателя и текст по двум кругам чтения · `sysmsg.py` — улика «какое системное сообщение выживает у Gemini» · `money_d.py` — касса фазы: потолки, фриз-гейт, атомарный замок · `runs.py` — журнал агент-сессий: запись ДО запуска суб-агента, кап 200 (снят владельцем 20.08 со 100), замок против ГОНКИ сессий по одному токену (в паке 23 гонка стоила соответствия голосов и сырья на 5 единицах) · `power.py` · `naklon.py` · `sud.py` · `contour.py` · `zakhod.py` — мощность, позиционный наклон, судейский риг, edit-контуры, армы захода. Отчёт фазы — `docs/experiments/23-editor-tier.md`, курс — `eval/dovodka/PLAN-22-08.md` |
> ⚠ **Две мины импорта, на которые наступает каждый новый пак.**
> (1) Модуль `probe.py` существует И в `editor_contract/`, И в `editor_harness/` — какой из них
@ -45,6 +45,21 @@
> (`probe.__file__`), а не догадываться.
> (2) Сырьё паков живёт в `~/books/<пак>/`; путь зашит константой. Если каталог переехал —
> харнесс молча видит пустоту и печатает «голосов нет» вместо ошибки.
> ⛔⛔ **И ОН ПЕРЕЕХАЛ.** С 24.08 книги лежат в `<репозиторий>/books` и версионируются ОТДЕЛЬНЫМ
> git-репозиторием (`CLAUDE.md`), а риги ходят по `Path.home()/"books"`**65 вхождений в 31 файле**.
> На чистой машине это значит, что НИ ОДИН риг полигона не запускается, причём молча.
> **Подъём зоны на новой машине — два шага, оба обязательны:**
> ```
> ln -s <репозиторий>/books ~/books # контракт путей ригов, см. строку 217 бэклога
> python3 -m venv eval/.venv && eval/.venv/bin/pip install -r eval/requirements.txt
> ```
> ⚠ Симлинк — восстановление контракта, а НЕ починка: корень книг обязан стать одной разрешающей
> функцией (env `TM_BOOKS` → репозиторий → `~`). До тех пор каждая чистая машина упирается в ту же стену.
> ⚠ **Переезд 24.08 обнулил mtime всего сырья** (335 клеток Ф2 имеют ОДИН mtime). Любой гейт,
> отделяющий «до» от «после» ВРЕМЕНЕМ ФАЙЛА, больше не пере-снимается: `tenant_panel/verify22.py:174-185`
> печатает границу двойной оплаты как `$0.000000` и роняет R71 реестра фазы Д. Гейт НЕ правится под
> зелень — диагноз в `docs/experiments/23-editor-tier.md` Д35.2. **Норма впредь: время покупки и время
> ответа писать ПОЛЕМ В АРТЕФАКТ, а не полагаться на файловую систему.**
### 18+ / корпус-билдеры
| Скрипт | Эксперимент | Заметки |

View file

@ -41,6 +41,7 @@
from __future__ import annotations
import os
import re
import subprocess
import sys
from pathlib import Path
@ -65,8 +66,25 @@ def frozen(src: Path) -> tuple[bool, str]:
return True, ""
REQ_ID = re.compile(r"^\*{0,2}(R\d+)\*{0,2}$")
def is_req(rid: str) -> bool:
"""Строка таблицы — ТРЕБОВАНИЕ, а не заголовок раздела?
ЗАЧЕМ ОТДЕЛЬНАЯ ФУНКЦИЯ, А НЕ УСЛОВИЕ НА МЕСТЕ. Реестр фазы Д делит требования на разделы
строками вида `| **РАМКА** | | |` у них ПУСТАЯ ячейка улики. Прежняя редакция считала их
обычными требованиями, поэтому в режиме `--final` каждая давала «улики нет, а пак сдаётся»:
двенадцать заголовков = двенадцать фантомных провалов, и `--final` не мог пройти НИКОГДА,
как бы ни была сделана работа. Заодно врал итог: «требований 89» при 77 настоящих.
Правка НЕ делает зелёным НИ ОДНО реальное требование она убирает из счёта строки, которые
требованиями не являются. Проверено гейтом ниже на заведомо больном входе.
"""
return bool(REQ_ID.match(rid.strip()))
def rows(md: str) -> list[tuple[str, str, str]]:
"""Строки таблицы требований: (ID, требование, улика)."""
"""Строки таблицы: (ID, требование, улика). Заголовки разделов тоже здесь — фильтрует `is_req`."""
if MARK not in md:
raise SystemExit(f"в документе нет маркера {MARK} — реестр требований не объявлен")
tail = md.split(MARK, 1)[1]
@ -125,6 +143,8 @@ def check(ev: str) -> tuple[str, str]:
def main() -> int:
args = [a for a in sys.argv[1:] if a]
if "--selftest" in args:
return selftest()
if not args:
print(__doc__)
return 1
@ -158,6 +178,9 @@ def main() -> int:
selfnames = {path.name, *(Path(a).name for a in args if a.endswith(".md"))}
selfnames |= {f.name for f in (REPO / "docs" / "experiments").glob("2[23]-*.md")}
for rid, req, ev in rows(md):
if not is_req(rid):
print(f"\n── {rid.strip('*')} " + "" * max(0, 60 - len(rid)))
continue
st, detail = check(ev)
if st == "ОЖИДАЕТ" and mode == "--final":
st = "ПРОВАЛ"
@ -169,7 +192,7 @@ def main() -> int:
bad += st == "ПРОВАЛ"
pend += st == "ОЖИДАЕТ"
print(f"[{st:8s}] {rid:6s}{mark} {req[:70]}" + (f"{detail}" if detail else ""))
total = len(rows(md))
total = sum(1 for rid, _r, _e in rows(md) if is_req(rid))
print(f"\nтребований {total} · ожидают {pend} · провалов {bad}")
print(f"улик вида «греп своего же документа» ({'сам-себя'}): {selfref} из {total}"
+ (" ⚠ они доказывают, что НАПЕЧАТАНО, а не что СДЕЛАНО" if selfref else ""))
@ -178,5 +201,42 @@ def main() -> int:
return 1 if bad else 0
def selftest() -> int:
"""ГЕЙТ ПРАВКИ «заголовок раздела — не требование». Роняется на заведомо больном входе.
Норма фазы: гейт, который не может упасть, ничего не сторожит. Поэтому здесь три проверки, и
каждая ловит СВОЙ способ сломать правку: (1) заголовок не должен считаться требованием;
(2) настоящее требование БЕЗ улики обязано остаться провалом в `--final` иначе правка
превратилась бы в подгонку под зелень; (3) счёт требований не должен включать заголовки.
"""
bad = 0
def ck(ok: bool, what: str) -> None:
nonlocal bad
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {what}")
bad += 0 if ok else 1
fake = (MARK + "\n"
"| ID | требование | улика |\n"
"|---|---|---|\n"
"| **РАМКА** | | |\n"
"| R1 | требование с уликой | `$ true` |\n"
"| R2 | требование БЕЗ улики | — |\n"
"| **НОРМЫ** | | |\n")
rs = rows(fake)
ids = [r[0] for r in rs]
ck(len(rs) == 4, f"разобрано строк таблицы: {len(rs)} (ждём 4, вместе с заголовками)")
ck([r for r in ids if is_req(r)] == ["R1", "R2"],
f"требованиями считаются только R-строки: {[r for r in ids if is_req(r)]}")
ck(not is_req("**РАМКА**") and not is_req("**НОРМЫ РИГА**"),
"заголовок раздела требованием НЕ считается")
ck(is_req("R77") and is_req("**R64**"), "R-строка считается требованием и в жирном начертании")
ck(check("")[0] == "ОЖИДАЕТ", "требование без улики — ОЖИДАЕТ (в --final станет ПРОВАЛом)")
ck(check("`$ false`")[0] == "ПРОВАЛ", "падающая улика — ПРОВАЛ")
ck(check("`$ true`")[0] == "OK", "проходящая улика — OK")
print("\nГЕЙТ СВЕРКИ ГОДЕН" if not bad else f"\n⛔ ПРОВАЛОВ {bad}")
return 1 if bad else 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,227 @@
<!-- ⚠ ИТОГ КОНСИЛИУМА 30.08.2026. Три эксперта `claude-fable-5`: архитектор (шесть раундов,
первый — с ЧИСТЫМ контекстом по условию владельца), скептик замера (четыре раунда),
разведка внешнего ландшафта (один проход). Диалоговые реплики вычищены: после компакта они
читались бы как открытые поручения. Норма формы — `KONSILIUM-22-08.md`: колонка СТАТУСА
обязательна, потому что именно она не даёт выдать желаемое за проверенное. -->
# КОНСИЛИУМ 30.08 — АРХИТЕКТУРА ГЕНЕРАЦИИ, МОДЕЛИ, УБОРКА
> ⚠⚠ **ПОПРАВКИ ОТ 30.08 ПОСЛЕ ИСПОЛНЕНИЯ СТАДИИ 0 п.1 — ЧИТАТЬ ПРЕЖДЕ ТЕЛА.** Разбор — **Д50 и
> Д51** отчёта `docs/experiments/23-editor-tier.md`. Консилиум строился на неполной карте
> собственного архива, и четыре его положения этим затронуты:
>
> 1. ⛔ **§5 п.1 назначал аудит `acceptance`. Объект выбран неверно:** рядом лежит более боевой
> `rerun` (11.07, промты `v1-reflow`, редактор-билингв, 57/57 чанков), а также **`rerun2-dspro`
> (23.07) — настоящий БОЕВОЙ профиль с редактором `deepseek-v4-pro`** на 5 главах. «Боевого
> профиля нет без Шага 7» верно про ОБЪЁМ, не про наличие.
> 2. ✅ **Сам п.1 ИСПОЛНЕН, вывод: гипотеза «банк держит» в терминной половине НЕ ОПРОВЕРГНУТА**
> взвешенная доля промахов 0.231.29% при пороге 2%, эффективная консистентность 99.3100%.
> Прибор `eval/dovodka/bank.py` независимо воспроизвёл июльский замер, включая поимённое
> совпадение редчайшего класса. **Работа уже делалась в июле; её скрипты погибли при переезде
> (были вне git) — ПЯТАЯ тихая поломка того же класса.**
> 3. ⛔ **§4 «вся наша фактура по дешёвым моделям АНГЛИЙСКАЯ» — неверно для ВЫБОРА РЕДАКТОРА.**
> Три редактора отсужены на боевой китайской паре (`rerun2/RERUN_REPORT.md`, 56 игр на арм,
> шумовой пол замерен): dspro 0.679 > glm-5 0.589 >> mistral 0.232.
> 4. ⛔⛔ **§7 и §6 п.3 ставят вопрос не там.** Открытый вопрос не «резать ли редактора», а «кто в
> нём живёт по нынешней цене» — и он адресован ФАЗЕ Д ратифицированной нотой **D39.137 от
> 15.08** («вход ратификации жильцов при фазе Д»), не исполненной. Замер сделан (Д51):
> **в пике `deepseek-v4-pro` дороже `glm-5` в 1.62.1 раза**, в офф-пик — от ×1.07 дороже до
> ×0.81 дешевле. ⚠ Это ХУЖЕ для dspro, чем оценка самой D39.137 (×1.59 дешевле в офф-пик),
> потому что та считала на чужих токенах, а dspro тратит вдвое больше своих.
> ⛔ Боевой конфиг `backend/configs/pipeline-c1.yaml:65` до сих пор обосновывает выбор словами
> «×2 дешевле glm» — устаревшее обоснование в живом файле; пинг бэкенду сдан в Д51.3.
**Заказ владельца дословно:** (1) насколько сложной делать архитектуру харнесса — качество без
взлёта цены, с учётом банка памяти; что делает черновая модель; нет ли сдвигов в ландшафте;
(2) на каких моделях выгодно ехать по цене/качеству; (3) нет ли ошибок в замерах и ошибок в выводах
из правильных замеров — структурировать и выбросить лишнее; (4) план, возвращающий на рельсы.
**Отдельным заказом:** спросить архитектуру у fable-5 **с чистого листа**, затем подкармливать
контекстом и заставлять отстаивать позицию. Исполнено: шесть раундов, из них раунды 26 —
последовательные удары нашими числами. **Архитектор дважды отозвал собственные позиции**, один раз
— уже вписанную мной в отчёт; скептик четырежды ломал мои правки, в том числе правки правок.
---
## 1. ЧТО УСТАНОВЛЕНО СЕГОДНЯ (всё — $0, из артефактов на диске)
| # | факт | улика | статус |
|---|---|---|---|
| 1 | **Редактор — ремонтник ЛЁГКОГО КАСАНИЯ:** меняет **4.05.8% знаков** черновика, касаясь ~40% предложений | побайтная дистанция 16 глав, `autojunk=False` | замерено |
| 2 | **Правки частично воспроизводимы:** `d(Z0,ZF)` = 0.71 от суммы правок, ни одной главы выше 1, на трёх ниже 0.5 — там правки СХОДЯТСЯ | те же 16 глав | замерено |
| 3 | **Два прогона редактора в 3.5× ближе друг к другу, чем к независимой генерации** (6.5% против 22.9%, оба на n=16) | калибровка `Z0``RN`; ⚠ прежние 21.9%/3.4× считались с молчаливым выбросом главы `100b088f71` (клетка `RN` пуста при `finish=stop`) | замерено |
| 4 | **Редактор выдаёт главу целиком, но видимая глава — лишь 7% completion-токенов стадии; 93% это РАЗМЫШЛЕНИЕ** (297 200 всего, 277 807 размышления на 32 клетках) | клетки редактора | замерено |
| 5 | **Гейты насыщены нулём** на боевых моделях: род 0/15, язык 0/15 у всех четырёх армов панели | `schet.py --report RN RL Z0 ZF` | замерено |
| 6 | ⛔ **«зелено по гейтам ⇒ приемлемо» МЕРТВО** — вся различительная работа читателя идёт выше их потолка | 5 + Д36.10 + Д20.2 | замерено |
| 7 | **Второй прибор тоже не различает `luna`:** счётчик даёт p ≥ 0.25 по всем контрастам | `schet.py` на панели luna1 | замерено |
| 8 | ⛔ **Класс «банк» горит в 6 главах из 15 у боевой связки** — канонная форма реже термина в исходнике ПРИ банке в промте | там же | замерено, требует чтения глазами |
| 9 | ⭐ **16 честных повторов `deepseek-v4-pro` на побайтно одном входе СУЩЕСТВУЮТ** (пары `e0`/`zf`; `prompt_tokens` совпадают до токена на всех 16) | клетки | замерено ⇒ **эррата к Д47.7** |
| 10 | **Настоящий шум размышления: 1 sd = ×2.8**; наши ×38 = **2.5 sd** — не исключительное событие. ⚠ Пары куплены в РАЗНЫЕ тарифные эпохи (июльский пин против пере-пина 16.08), но систематического сдвига нет: средний лог-сдвиг 16 пар ≈ 0.06 (×0.95). ⚠ «Побайтно один вход» доказан равенством СЧЁТЧИКОВ `prompt_tokens` 16/16, не сверкой байтов | 9 | замерено |
| 11 | **Связи текстового расхождения близнецов с читательским разрывом НЕ ВИДНО** (0.07…0.22 по кругам), ⚠ но при n=15 доверительный интервал ≈ [0.56; +0.44] — **мощности нет, «не видно» ≠ «нет»** | 15 глав | замерено слабо |
| 12 | ⛔ **Ранговые вердикты панелей о ПОРЯДКЕ силы не имеют:** позиционный наклон 0.5 места воспроизводится в обоих кругах (раскладки те же), армы по позициям не уравнены | позиционный пересчёт | замерено |
| 13 | **Движковый глоссарий несёт `gender` (вкл. `hidden`), спойлер-окна, склонения — они ЗАСЕЯНЫ СИДОМ и движок возит их в промты.**`source='seed'` у 49/49 — сам движок их не пополнял; `until_ch`=0 у всех, окно раскрытия не проставлено | база: 49 записей, 49/49 склонений, 14 полов | замерено ⇒ **эррата к Д38.3 и Д32.16** |
| 14 | **`acceptance`-прогон = 25 глав, 130 чекпоинтов (119 непустых), глоссарий 49 записей.** ⚠ **НЕ боевой профиль:** редактор `glm-5` с погашенным размышлением (не `deepseek-v4-pro`), прогон 10.07.2026, июльские промты; `first_person` и `speech`**0 из 49** | `pipeline-acceptance.yaml:35-41`, база | замерено |
| 15 | **Таблица `glossary_revisions` в движке ЕСТЬ, ревизий во всех трёх прогонах — НОЛЬ** | там же | замерено |
| 16 | **Пик DeepSeek — только по будням** 0104 и 0610 UTC ⇒ выходные целиком вдвое дешевле | вендор-страница, живое чтение | замерено |
| 17 | **Отказов у дешёвой линии нет:** `luna` 0 из 749, `deepseek` 0 из 1651; у `gemini` 12 — **все в роли СУДЬИ** | корпус | замерено |
| 18 | ⛔ **Цензур-ось НЕ МЕРЕНА у `luna` и `glm-5`** (у `gemini` мерена и против него); обе экспертизы назвали её главной дырой независимо | — | открыто |
## 2. ЧТО СНЯТО СЕГОДНЯ — МОИ ОШИБКИ И ИХ КЛАСС
| снято | чем было | класс ошибки |
|---|---|---|
| «редактор — лотерея, разброс правки ×1040» | артефакт `SequenceMatcher(autojunk=True)`: на текстах >199 знаков «мусором» объявляется каждый символ чаще 1% — все частые буквы и пробел | **дефолт библиотеки принят за свойство мира** |
| «расхождение выходов > каждой правки ⇒ пересэмплировщик» | два РЕМОНТНИКА, правящих в разных местах, дают расхождение вплоть до СУММЫ правок | **логическая ошибка при верных числах** |
| «однопроходка вдвое дешевле связки, $62 против $127» | медиана ДВУГОРБОГО распределения; книга — сумма ⇒ ≈×1.3 | статистика не по предмету |
| «запас гарда ~2×, проверено по коду» | ожидание гарда — МАКСИМУМ прошлых цен ($0.131016), а не $0.0214 планировщика | **клейм «по коду» без исполнения кода** |
| «×38 — событие 1 из 500» | логнормальность опровергнута собственными данными | украшение количественной формой |
| «сигнатура пересэмплировщика, а НЕ ремонтника» | ранговая метрика не может опуститься ниже 1.0 при потолке 1.67 | **чтение шумового пола как механизма** |
| «в корпусе нет чистых повторов одного промта» | их шестнадцать | не искали там, где лежит |
| «полей пола в банке нет» | верно только про эвал-банк; **движок поля имеет, и они засеяны сидом** (⚠ сам движок их не пополнял: ревизий 0; голосовые колонки пусты) | **фаза не знала собственный бэкенд** |
**Сквозной урок дня, и он дороже любой отдельной правки:** из пяти моих исправлений скептик нашёл
новые ошибки в трёх. **Правка правки — не безопаснее правки.** Проверять надо ВСЕ производные числа
поправки, а не то, ради которого она делалась.
## 3. АРХИТЕКТУРА: ЧТО РЕШЕНО
| # | решение | носитель | статус |
|---|---|---|---|
| 1 | Единица — **глава, один вызов** | конвейер | сошлось независимо (наша практика + архитектор вслепую) |
| 2 | **Черновик отвечает за всё НЕОБРАТИМОЕ** (полнота · референты · канон · структура); стиль обратим и в контракт не входит | промт + $0-гейты | принято без замера |
| 3 | **Гейты — страховка от катастроф, НЕ предиктор качества** | факт 56 | замерено |
| 4 | **«Точечно или сплошняком» — ложная развилка:** наш редактор уже СПЛОШНОЙ ПО ОХВАТУ и ТОЧЕЧНЫЙ ПО ОБЪЁМУ | факт 1 | замерено |
| 5 | **Двухпроходность — свойство пары «модель × роль × гранулярность»**, а не архитектуры | Д42.4 + внешний замер | замерено/принято |
| 6 | ⭐ **Дифф-интерфейс редактуры** — модель отдаёт не текст, а список якорёванных замен с категорией; аппликатор детерминированный, потолок объёма | не построено | **открыто, цена $23 на прототип** |
| 7 | **Пре-скан книги до перевода** (слом храповика первой догадки) | не построено | открыто, цена $35 |
| 8 | Не трогать: **банк · гейты · кэш · роутинг · близнецовый контроль как практику** | — | принято |
| 9 | **Латинский квадрат раскладок ПО ПОСТРОЕНИЮ** в любые будущие панели | риг | обязательно, $0 |
**Обоснование п.6 — ДВАЖДЫ ослабленное, и это надо читать прежде выгоды.** (1) Довод «ампутировать
каскад самообусловливания» снят вместе с «лотереей»: каскада нет, редактор трогает 46%.
(2) ⛔ Довод «выход дорогой стадии почти целиком — переписывание неизменного» **тоже неверен**:
видимая глава — лишь **7%** completion-токенов, 93% это размышление (факт 4). ⇒ дифф-интерфейс
режет арифметически **порядка $8 на книгу** у `pro`-редактора, а не $100; у редактора с погашенным
размышлением (`glm-5`) срез значим, потому что там видимый выход и есть почти весь completion.
**Сожмётся ли размышление от смены формата — ГИПОТЕЗА, её и проверяет прототип.** Что остаётся
бесспорным: байт-стабильность нетронутого (инкрементальная перевалидация гейтов), наблюдаемый и
ограниченный потолком объём правки, и категория у каждой правки. Встроенный kill-switch: пересечение множеств тронутых спанов двух прогонов < 0.4 провал.
## 4. МОДЕЛИ: ЧТО РЕШЕНО
⚠⚠ **ВСЯ ЭТА ТАБЛИЦА ПРЕДВАРИТЕЛЬНА ДО СТАДИИ 0 п.3.** Колонка «качество» построена на ранговых
вердиктах панелей, а факт 12 этого же документа объявляет их не имеющими силы о ПОРЯДКЕ до
уравнивания раскладок. Держится независимо от рангов **только одно**: «голый черновик различимо хуже» — подтверждено
вторым, счётным прибором (`schet.py --report ZD Z0`: класс «банк» +16, p=0.0039).
**Про `luna` так сказать НЕЛЬЗЯ:** счётчик её не РАЗЛИЧАЕТ (p ≥ 0.25, факт 7), а молчание
прибора, чувствительность которого доказана только на грубом эффекте `ZD`, — не подтверждение
эквивалентности. **Оба прибора молчат ниже своего потолка; эквивалентность НЕ УСТАНОВЛЕНА** и
требует объявленного δ (§6 п.1). Всё прочее — **открыто**.
| форма | книга 1500 глав | качество |
|---|---|---|
| голый дешёвый черновик | ~$7 | ⛔ различимо хуже |
| однопроходка `luna` | $7 | неразличимо (оба прибора туповаты) |
| однопроходка `glm-5` без думания | $9 | ⛔ различимо хуже |
| **черновик → редактор `glm-5`** | **~$1720** | неразличимо: 3 круга, 2 семейства, второй прибор |
| однопроходка `pro` | ~$9099 | неразличимо |
| **боевая связка (черновик → редактор `pro`)** | **$127** | якорь |
* **Крупнейший денежный рычаг — замена редактора на `glm-5` (~×4.46)**, и упирается он не в
деньги, а в **неразрешённый конфликт приборов на zh** (Д42.4).
* ⛔ **Вся наша фактура по дешёвым моделям — АНГЛИЙСКАЯ.** Боевая пара китайская.
* ⛔ **Цензур-ось поимённо, потому что валить их в одну строку запрещено собственной Д41.4:**
у **`gemini`** она МЕРЕНА и против него (12 отказов `PROHIBITED_CONTENT`, все в роли судьи);
у **`luna`** и **`glm-5`** — **не мерена ни разу**. Кандидат в прод не идёт независимо от цены.
* **Скидочные режимы, не требующие ни одного решения:** выходные целиком офф-пик (÷2), батч 50%
универсален, кэш-чтение 90%. ⚠ Тарифная погода ≠ климат: несущую архитектуру на скидочное окно
не ставят, котировка — датированной строкой в манифест.
## 5. ПЛАН
### Стадия 0 — $0; исполнитель и оговорки названы по пунктам
**Проверено ревью: три обещания заголовка держатся не все.** «$0» — да, все пункты суть анализ
артефактов на диске. «Ноль агент-сессий» — держится для пп. 1, 3, 4, 5; **п. 2 (классификация
правок) есть труд суждения и может стоить сессию** (свободно 4 из 220). «Не требует решений
владельца» — держится, ЕСЛИ не превращать скрипт п.1 в постоянный приёмочный гейт: гейт — зона
бэкенда и лендинг оркестратора, то есть уже решение. Здесь он остаётся ПРЕДЛОЖЕНИЕМ.
1. **Аудит `acceptance`-прогона** — держит ли банк 49 терминов через 25 глав; соблюдена ли
дисциплина `hidden` до раскрытия; склонения против записанных форм. **Прямой замер Вопроса 0.**
Порог: >2% взвешенно ИЛИ любая сущность топ-50 ⇒ гипотеза «банк держит» ложна в сильной форме.
**Он НЕ заменяет Шаг 7 целиком, вопреки первой редакции этого документа.** Закрывает
**терминную** половину Вопроса 0 — и на ИЮЛЬСКОМ профиле с редактором `glm-5`. **Голосовая
половина (пол/родство/голоса рассказчика) им не закрывается ПО ПОСТРОЕНИЮ:** `first_person` и
`speech` в глоссарии пусты (0 из 49), а Д32.16 прямо предупреждает, что без засева
характер-листов замер даёт ложноотрицательный результат. Голосовая половина и текущий боевой
профиль остаются Стадии 3 либо Шагу 7.
⭐ Предложение (не пункт плана): сделать скрипт постоянным приёмочным гейтом — решение владельца.
2. **Разбор диффов редактора по категориям** — что именно он чинит: канон, вёрстку или прозу.
⭐ Если 80% массы правок механические, редактор заменяется детерминированным слоем + дешёвой
моделью, и спор о ярусе закрыт бесплатно.
3. **Позиционный аудит ВСЕХ купленных панелей** (~150 пакетов на диске) + латинский квадрат по
построению в будущие. Без этого ни один вердикт по моделям невозможен.
4. **Эрраты — все три сделаны 30.08** (Д47.7, Д38.3, Д32.16). Остаётся **грепом проверить, что
сильная форма нигде не всплыла**: «полей пола … НЕТ», «механизма не существовало».
5. **Базис Жаккара для kill-switch Стадии 1** — пересечение множеств тронутых спанов у нынешних
`Z0`/`ZF`, из тех же артефактов. Без него порог 0.4 висит в воздухе.
6. **Уборка:** пересчёт денег суммами на одном базисе; протяжка непротянутых чисел
(греп «62 · вдвое · 6.3 · 7.1 · ×711»); таблица переноса «что из очереди Д38.6 не переехало».
### Стадия 1 — единицы долларов. Ответ на «какие вызовы»
**Прототип дифф-редактора** на тех же 16 главах: тот же промт, выход — список замен. Меряем объём
правки · **пересечение множеств тронутых спанов двух прогонов** · цену выхода И РАЗМЫШЛЕНИЯ.
**Порог kill-switch (Жаккар < 0.4) назначен ДО того, как посчитан базис.** Жаккар тронутых спанов
у нынешних `Z0`/`ZF` считается за $0 из тех же артефактов и обязан войти в Стадию 0 — иначе порог
висит в воздухе.
### Стадия 2 — $0.20.6. Ответ на «на каких моделях»
**Цензур-ось дешёвой линии на боевом жанре**, с пре-регом: порог отказов, убивающий вендора,
объявляется ДО пробы; wire-лог отделяет отказ от таймаута.
### Стадия 3 — единицы долларов, на КИТАЙСКОЙ паре
Решётка на 25 главах `acceptance`: **дорогая однопроходка · связка · дифф-ремонт**. Рефери —
бесплатный счётчик + статистика правок. Снимает главную оговорку фазы (вся фактура английская).
### ⛔ Чего в плане НЕТ намеренно
* **панели за $1015 с ранговым эндпойнтом — упираются в собственный ПОЛ прибора.** Близнецы
расходятся на **1.331.53 места** при 6.5% различия текста (факт 3 + Д48.5). В этом полу есть
ДОКАЗАННАЯ читательская компонента: позиционный наклон 0.5 места, воспроизводящийся в обоих
кругах (факт 12), и повтор чтения одним семейством — 0.34 порога (Д37.3/Д38.5).
⛔ **Сколько в полу текста, а сколько читателя — НЕ РЕШЕНО, и подавать это решённым нельзя:**
факт 11 без мощности, а ратифицированный глосс самой Д37.3 гласит обратное — «треть разброса
сам прибор, **две трети — текст**». ⇒ **Панель с полом такой высоты купит очередное
«неразличимо» независимо от того, из чего пол состоит** — и это единственное, что нужно для
решения. ⚠ Однозначно читательская из трёх опор только одна: наклон раскладки, он в тексте не
живёт по построению;
* **ручка размышления** — при шуме ×2.8 эффект ×1.4 неизмерим в наших бюджетах;
* **Шаг 7 в прежнем виде** — ⚠ НЕ «заменён»: аудит `acceptance` закрывает лишь терминную половину
Вопроса 0 на июльском профиле (§5 п.1). Шаг 7 остаётся носителем ГОЛОСОВОЙ половины и текущего
боевого профиля — переносится, а не выбрасывается;
* **резать редактора решением сессии** — $28 не стоят решения на приборе, объявленном молчащим;
и дифф-интерфейс может сделать вопрос беспредметным, срезав цену стадии арифметикой.
## 6. НА ВЛАДЕЛЬЦЕ
1. **Запас эквивалентности δ.** Вилка с ценником: **0.5** → n≈62 на плечо (доллары; якорь — сам
позиционный артефакт равен 0.5) · **0.35** → n≈126 ($510) · **0.2** → n≈386 (только живые люди).
⚠ Выбрать 0.2 = выбрать, что доказательство эквивалентности дёшево не наступит никогда.
Заземление: **δ ≤ половины цены той порчи, которую владелец отверг бы, глядя в текст.**
2. **Кап агент-сессий** — свободно 4 из 220; читательская половина плана без него стоит.
3. **Судьба редакторской стадии** — после стадий 0 и 1, не раньше.
## 7. ЧТО КОНСИЛИУМ НЕ ЗАКРЫЛ
* **Расхождение экспертов по редактору осталось.** Архитектор: резать сейчас. Скептик: редактор
ДОКАЗАННО сжимает брак (сильнейший контраст фазы), резать по нулю на прозе — сверхзаявка в
обратную сторону. **Не сведено намеренно** — это решается стадией 0.2, а не голосованием.
* **H0, вынесенная архитектором отдельно:** ось прозы, вокруг которой построен проект, может лежать
ниже порога безразличия читателя. Наши нули — слабое свидетельство ЗА. Проверяется рынком, не
харнессом; решение владельца.
* **Внешний ландшафт:** zh→ru отсутствует во всей свежей литературе; масштаб 1500 глав никем
публично не отработан с замером; независимых замеров коммерческих новелла-переводчиков нет.

540
eval/dovodka/PLAN-01-09.md Normal file
View file

@ -0,0 +1,540 @@
# ПЛАН ФАЗЫ Д — РЕДАКЦИЯ 4: ПРЕ-РЕГ РАТИФИЦИРОВАННОЙ ПРОБЫ
> Редакции 13 отвергнуты консилиумом; их разбор — §8, сохранён как класс ошибок фазы.
> Редакция 4 собрана с постоянным консилиумом (архитектор замера · скептик · казначей) и несёт
> ПРЕ-РЕГИСТРАЦИЮ, а не намерения. Платные замеры санкционированы владельцем 01.09 — ⚠ **без числа,
> и число нужно до фриза** (§6).
## 1. ЧТО ЭТО ЗА ЗАМЕР И ПОЧЕМУ ИМЕННО ОН
**Носитель:** D39.184 п.5 — «Следующий платный замер — контролируемая проба редактора по ЧЕТЫРЁМ
осям (§14 отчёта холодного прогона): трудность чанка БЛОКИРОВАТЬ, длину входа КОНТРОЛИРОВАТЬ,
промпт по SHA, эффорт `off` против `low`». ⚠ Ноты нет в ветке `polygon` (реестр обрывается на
D39.170) — она в `main`; это следствие расхождения веток, а не отсутствия решения.
**Вопрос:** ПОЧЕМУ редактор дорог. Не «кто лучше» (мерено) и не «сколько продавать» (гейчено
отдельно, §7).
## 2. ДИЗАЙН — КРЕСТ 2×2 ВНУТРИ ЮНИТА
| рука | промпт | `stages[edit].reasoning` | что уходит на провод |
|---|---|---|---|
| P₇·E∅ | `editor.md` из `e89874b^` | `"off"` | ключа `reasoning_effort` НЕТ ⇒ вендор-дефолт `high` |
| P₇·E_low | тот же | `"low"` | `reasoning_effort:"low"` |
| P₉·E∅ | `editor.md` HEAD | `"off"` | ключа нет |
| P₉·E_low | тот же | `"low"` | `low` |
Промпты различаются одной строкой; **метка `v3-discourse-reflow` у обоих одна**, поэтому берём по
SHA — снапшот их различает (`snapshot.go:288`=`prompt_sha256`).
⭐ **МОДЕЛИ (их не было ни в одной прошлой редакции — поймано владельцем 02.09):**
редактор — **`deepseek-v4-pro`** (он и есть предмет: `off` против `low`) · **референс — `glm-5`** (`thinking` выключен, `few_shot: false`, не фактор) · черновик —
`deepseek-v4-flash` с `low` · хоп эскалации черновика — `deepseek-v4-pro`, наследует эффорт стадии ·
терминолог и классификатор — `deepseek-v4-flash` с `low` · **судья не вызывается вовсе**.
Состав снят с отработавшего `coldrun-v16` и менять его нельзя: смета и сопоставимость стоят на нём.
* **БЛОКИРУЕТСЯ трудность:** каждая рука видит ВСЕ юниты. Реализация: один базовый прогон (черновая
волна + майнинг + терминолог), затем БД копируется **×5** (четыре руки креста + референс `glm`).
**Черновики выживают за $0 НЕ ре-пином** (`repin.go` — механизм смены БАНКА, при смене
промпта/эффорта он даёт «Nothing is re-pinnable»), **а потому, что снапшот ПЕР-ВОЛНОВЫЙ**
(`snapshot.go:225-231`=`waveDraft`): правка edit-стадии draft-снапшот не двигает, и черновики
идут обычным resume.
⛔ **Базовый прогон ОБЯЗАН остановиться ДО edit-волны, и это делает только `--verify-bank`.**
Без флага движок авто-продолжает с неподписанным банком и покупает edit-юнит в том же процессе —
тогда каждая копия потребует `--resnapshot` и перекупит базовые edit-юниты ×4.
**Условия копирования:** движок остановлен (WAL закрыт), `book_id` тот же (он входит в
`request_hash`), в `book.yaml` меняются ТРИ поля — `project_db`, `pipeline` и `ceilings` (последнее — потому что касса наследует базовый расход, §5.4).
* **КОНТРОЛИРУЕТСЯ длина входа:** `len(draft)` и `prompt_tokens` — ковариата. ⚠ Честно: чанки движка
узки по `prompt_tokens` (44126946: там доминируют исходник и системное), **но сама длина черновика
широка — 4276171 знак, разброс ×14**, и она входит в модель ковариатой (§4). Прежняя формулировка
«контролируется, а не измеряется» снята как противоречащая §4.
* **ФИКСИРУЕТСЯ:** модель и дата вендор-доки · бинарь `tmctl` по sha · `models.yaml` по sha ·
потолок вывода · `temperature 0.4` · `few_shot: false` · санитайзер ON ·
`escalation.budget_usd: 0` **ТОЛЬКО в ПЯТИ конфигах рук** (четыре креста + референс). ⛔ **В БАЗОВОМ прогоне он обязан быть
> 0**, иначе эхо-черновики останутся флагнутыми: в холодном прогоне 5 хопов на 25 вызовов,
$0.103 на 10 глав ⇒ **≈$0.25 на 24 главы, и это отдельная строка сметы**.
⚠ **обоснование прежней редакции («иначе флагнутый юнит уйдёт хопом»)
ЛОЖНО и снято: редактор эскалировать не может по построению** (`pipeline.go:1012`=`escalate_to is
only allowed on a translator role`). Настройка инертна для edit-волны; держим её как гигиену, а не
как защиту.
* **РАНДОМИЗИРУЕТСЯ** порядок ЧЕТЫРЁХ рук креста внутри юнита. ⭐ **Контракт `poryadok.py`, дословно:
четыре руки креста — перестановкой от сида, `glm-off` — ВСЕГДА последним пятым.** ⭐ **референс `glm` идёт ПЯТЫМ,
фиксированно последним** — тогда при срабатывании денежного гарда теряется референс, а не пара,
на которой стоит E1. **Интерливинг во времени** — пять клеток юнита в
окне 1020 минут, чтобы дрейф сервинга не разошёлся между руками.
**Останов цикла НЕ по коду выхода:** штатная остановка по объёму приходит **и как 0, и как 2**,
различает её только строка `VOLUME CEILING:` в выводе. `|| break` (урок Д47.6) здесь либо рвёт
цикл на первом штатном стопе, либо не рвёт никогда ⇒ **цикл разбирает вывод на `VOLUME CEILING:`**.
⚠ Окно 1020 мин достижимо не всегда: латентности edit-вызовов 43216 с, ретрай добавляет ~200 с
юнит с обрывом в обеих `∅`-руках упирается в верх окна.
⭐ **ПЯТАЯ, РЕФЕРЕНСНАЯ КЛЕТКА НА ЮНИТ: `glm-5` в БОЕВОЙ форме (размышление выключено) ×
сегодняшний промпт.** Не фактор креста — **ценовой контроль**: тот же черновик, тот же банк, тот же
день. ~$0.02 за клетку, **≈$0.5 на 24 юнита**.
**Зачем:** без неё вывод звучит «pro стал дешевле на ×N», а с ней — «цена доставленного знака у
pro@low против glm@off СЕГОДНЯ». Это ровно та таблица, которой не хватает Д51: там pro посчитан на
ИЮЛЬСКИХ токенах (2 798 на вызов), а сегодня один вызов даёт 12 203 — отношение ×2.14 занижено.
**Почему `glm` НЕ вводится третьей осью:** её «low» в движке не ступень — `on_extra_body` у неё не
объявлен, при `low` мержится пустое и едет вендор-дефолт. Значит ось на `glm` = «выкл/вкл», а на
`pro` = «уровень» — **фактор означал бы РАЗНОЕ на разных уровнях**, и крест 2×2×2 не дал бы чистого
взаимодействия. Плюс удвоение рук (считано при прежнем потолке $8.6) срезало бы n до ~12 и MDE до ×2.7 — проба
перестала бы видеть даже ×2.5.
**ЧЕТЫРЕ ОГОВОРКИ РЕФЕРЕНСА, без которых он даст ложное число** (найдены скептиком 02.09):
1. **Его потолок вывода НЕ 16000.** У `glm-5` в реестре нет флора `min_max_tokens` («0 = no floor»
для моделей с выключаемым размышлением) ⇒ потолок = `EstimateTokens(draft)·2.2` ≈ 4.5 тыс.
Для E1 неважно (референс в него не входит), но для E0/E2 «равный потолок» на него НЕ
распространяется — и это записывается рядом с числом.
2. ⛔ **`R̂` для референса обязан быть 0 ПО ПОСТРОЕНИЮ, а не по формуле:** у него другой токенизатор,
и формула напечатала бы «думает ±300 токенов» на модели с выключенным размышлением. Статья
«размышление» = 0, «видимый выход» = весь `completion_tokens`.
3. ✅ **`few_shot: false` — РЕШЕНО, развилка закрыта** (нельзя фризить документ с открытой развилкой:
этот флаг меняет байты конфига, его sha256 и статью «вход» в E0). Боевой резервный арм держит
few-shot включённым, поэтому слово «боевая форма» из описания референса **снято**: референс
отличается от `P₉·E∅` **ровно моделью**, и только так он остаётся чистым ценовым контролем.
4. ⚠ **Референс на билингвальном входе — зона эха.** Его эхо-клетки дадут `delivered = 0` и раздуют
цену знака не из-за цены. **E5 считается по референсу ОТДЕЛЬНО.**
**Единица наблюдения** — клетка (юнит × рука); **единица анализа** — юнит.
**Требование §14 «потолок вывода одинаков во всех руках» выполняется само:** `stagerun.go:109-125`
даёт `base = max(EstimateTokens(draft)·2.2, min_max_tokens)`; при МАКСИМАЛЬНОМ черновике 6171 знак это
≈4.5 тыс. < 16000 **16000 у всех юнитов во всех ЧЕТЫРЁХ РУКАХ КРЕСТА по построению** (референс своя история, §2), ретрай 32000 тоже одинаков.
⚠ Числа прежней редакции были не те: реальные черновики **4276171 знак** (не «~7 тыс.»), а
«44126946» — это `prompt_tokens` трёх edit-вызовов (исходник + черновик + системное), не длина
черновика. Механизм ломается только выше ~21 800 русских знаков или ~7 300 CJK-знаков эха.
## 3. ⛔ ДВЕ ДЫРЫ ПРИБОРА — БЕЗ НИХ ЗАМЕР НЕ ИЗМЕРИТ СВОЙ ПРЕДМЕТ
**Дыра 1. Движок НЕ ВИДИТ размышление DeepSeek по построению.** `provider_openai.go:23`
=`ReasoningTokens stays 0 to avoid double-billing`: у subset-провайдеров размышление считается
ВНУТРИ `completion_tokens`, и поле остаётся нулём. Проверено: во всех движковых базах
`sum(reasoning_tokens>0) = 0`. А предмет замера — именно размышление (9596% цены).
**Лечение без правки Go, с замеренной точностью:** `R̂ = completion_tokens 0.3644·len(response_text)`.
Коэффициент — видимый выход pro-редактора на zh→ru; **пере-снят мной независимо: 0.3644 токена/знак,
n=16, sd 0.0069**. Ошибка типично ~3% видимого выхода, **худшая клетка 5.3%**; пренебрежима при размышлении в тысячах и заметна
лишь при малом размышлении. Коэффициент **пинится в пре-реге**.
**Границы формулы, названные до покупки** (проверено независимо на 185 клетках рига с отчётным
`reasoning_tokens`: 0.3670.369, sd 0.0070.010): (1) **любой CJK в выходе** ломает оценку — иероглиф
≈1 токен, видимая часть недооценивается, `R̂` завышается; направление консервативно для E1, но
раздувает клетки E5; (2) при `R̂ < ~700` относительная ошибка достигает 2070% — у мелких клеток
`low` вывод о величине не делается; (3) брать сырую строку попытки, **не `sanitized_export`** — та
несёт обрезанный текст и пустой `usage_json`; (4) типичная ошибка ~3%, худшая клетка — **5.3%**.
**Дыра 2. У движка нет проводного артефакта** — стадийный лог эффорт не печатает. Лечение:
(а) снапшот фолдит `Reasoning` и `PromptSHA256`**у пяти рук пять разных `snapshot_id`**, это
запись в БД, что конфиг был в силе; (б) сигнатура `prompt_tokens(∅) prompt_tokens(low)` по юнитам
(на en дважды ровно **79**). ⚠ Оба — косвенные; побайтной улики у движка нет.
## 3-БИС. ⭐ ОБЛАСТЬ ПЕРЕНОСА — ПИШЕТСЯ ДО ЗАМЕРА, ЧТОБЫ ВЫВОД НЕ РАСШИРИЛИ МОЛЧА
⚠ **Повод: владелец 02.09 — «мы фактически измеряем, как ведёт себя дипсик; исследование однобокое».
Он прав, и лечится это не расширением креста (§7), а честной границей вывода.**
**УСТАНАВЛИВАЕТСЯ** — на одной модели `deepseek-v4-pro` **в тех весах, что сервятся в дни прогона**
(вендор объявил новые веса 13.08 при неизменном слаге), при маппинге эффорта от 30.08, на паре
zh→ru, на нашей нарезке (~2 тыс. знаков исходника на юнит), с нашей инъекцией банка, на двух
конкретных SHA `editor.md`:
1. эффект ОТПРАВКИ `reasoning_effort:"low"` против неотправки ключа — на объём размышления и на цену
доставленного знака;
2. эффект одной строки промпта;
3. **разложение цены по статьям** (E0 ниже) — у ЭТОЙ модели.
**НЕ УСТАНАВЛИВАЕТСЯ, и это пишется тем же абзацем:**
* «размышление вообще» — в реестре **четыре разных механизма**: у DeepSeek уровень без выключения,
у GLM тумблер, у grok явное «none», у Gemini выключить нельзя;
* «семейство DeepSeek» — у `flash` другие веса и своя эхо-статистика; общее у них лишь таблица
маппинга и сигнатура входа;
* перенос на английскую пару — размышление на плотном китайском идёт в разы больше.
**СРОК ГОДНОСТИ.** Вендор за месяц сменил дефолт эффорта (31.07), веса (13.08) и таблицу маппинга
(30.08). ⇒ **вывод действителен до следующего изменения вахты и ПЕРЕ-СНИМАЕТСЯ пред-полётной парой
(~$0.25), а не цитируется.**
⭐ **ШАБЛОН КЛЕЙМА, обязательный для любого, кто будет ссылаться на результат:**
> «у `deepseek-v4-pro` (веса 0813) на zh→ru при нашей нарезке ручка `low` даёт ×N [ДИ] —
> на N-й день после смены маппинга 30.08»
**Число без этого квалификатора — ошибка класса Д52.5** (вендор-факт принят за свойство системы).
## 4. ПРАВИЛО РЕШЕНИЯ — ЗАПИСАНО ДО ЧИСЕЛ
**Метрики клетки:** `R̂` · `completion_tokens` · `cost_usd` (сумма ВСЕХ строк юнита в руке, ретраи
включены) · `delivered` = знаки отгружаемого текста при годной диспозиции, иначе **0** ·
`cost/delivered` · `finish` попытки 0 · `flag_reason`.
* ⭐ **E0 (ответ на вопрос «ПОЧЕМУ дорого», $0, на тех же данных).** Для каждой руки на каждом юните
цена раскладывается по статьям: вход · кэш · видимый выход · размышление · ретраи, сложенные по
ВСЕМ попыткам. Печатаются доли и их сдвиг между руками.
**Зачем он введён:** ратифицированные оси меряют «сколько режет ручка», а заказ владельца был
«почему редактор дорог» — это шире. Разложение и есть ответ: «у пары без ручки размышление = X%
цены знака, ретраи = Y%; на `low` — X/Y; промпт даёт Z». ⚠ Одна статья невосстановима никакой
пробой и объявляется заранее: **что из подорожания дали новые веса и смена дефолта вендором**
июльское состояние сервинга не вернуть.
* **E1 (главный):** по-юнитный контраст `log R̂(low) log R̂(∅)`; **Вилкоксон знаково-ранговый**,
α=0.05 двусторонне; медиана и 95% ДИ Ходжеса–Лемана печатаются как ВЕЛИЧИНА.
**Исходов ДВА, а не три** — прежняя редакция обещала третий и была неправа: ветвь
«эквивалентность» (ДИ целиком внутри полосы) при наших n **недостижима** — полуширина ДИ в
логарифме ≈0.43 при n=32 против полуширины полосы 0.22, для эквивалентности нужно n≈117.
**Решение:** `p<0.05` ⇒ эффект есть, величина = медиана с ДИ. Иначе ⇒ **«НИЖЕ РАЗРЕШЕНИЯ
ЗАМЕРА»**, и это НЕ «эффекта нет».
⚠ Критерий — **только `p<0.05`**: со-условие «ДИ вне [0.8;1.25]» снято, потому что оно строже
таблицы мощности (требовало бы истинного эффекта ≈×2.3 против заявленных ×1.84) и потому что сама
полоса — заимствованная конвенция биоэквивалентности, к объёму размышления не обоснованная.
Полоса остаётся **описательной**: попадание в неё печатается, решения не несёт.
**Цензура на потолке — правило записано до чисел, СИММЕТРИЧНО.** В холодном прогоне 1 из 4
`∅`-строк вернула `length` ровно на 16000 при непустом тексте: там `R̂`**нижняя граница**, а
ретрай на 32000 есть ДРУГОЙ розыгрыш. ⇒ **E1 считается по попытке 0**; клетка с `length` входит
правой цензурой (`R̂ ≥` наблюдённого). Знак пары определяется так:
* цензурирована `∅` — знак достоверен, пока цензурированная `∅` ≥ `low`; иначе **ничья**;
* цензурирована `low` — знак достоверен, только если `∅` ≤ наблюдённой границы `low`; иначе **ничья**;
* цензурированы обе — **ничья**.
**«Ничья» ≠ «выброшено», и это надо задать в коде явно:** стандартный знаково-ранговый Вилкоксон
нули по умолчанию ВЫБРАСЫВАЕТ; сохраняет их вариант Пратта. **Скрипт эндпойнта обязан назвать
метод обработки нулей**, иначе правило плана и тест разойдутся молча. ✅ Проверено на машине
01.09: `scipy 1.18.0`, у `wilcoxon` умолчание `zero_method='wilcox'` (нули ВЫБРАСЫВАЮТСЯ), вариант
Пратта поддержан ⇒ **в скрипте пинится `zero_method='pratt'`**.
**Величина под цензурой — НИЖНЯЯ ГРАНИЦА:** медиана и ДИ Ходжеса–Лемана при цензурированных
клетках занижают эффект, поэтому величина печатается со знаком **«≥»**. Иначе `p<0.05` при малой
медиане прочтётся как «эффект мал», когда он может быть велик.
* **E2:** то же для `log(cost/delivered)` — отдельно по попытке 0 (равный потолок) и по итогу юнита
(боевая цена). ⭐ Средний член цены за знак — константа, поэтому разница рук сидит в
**ρ = R̂/delivered**. ⚠ Оговорка: 24-я клетки юнита бьют префикс-кэш, вход дешевеет ~$0.0060.009;
рандомизация порядка это усредняет, но добавляет ~10% шума на клетку.
* **E3:** ось промпта — те же тесты. Прогноз, калибрующий удивление: |эффект| < ×1.25.
* **E4 (восстановлен — без него крест не даёт ничего сверх двух пар):** взаимодействие промпт×эффорт,
контраст `(P₉E_low P₉E∅) (P₇E_low P₇E∅)`. sd контраста вдвое больше ⇒ MDE ≈ ×3.3 при n=32:
**эндпойнт описательный, выводов не несёт** — но печатается, иначе крест не отличим от двух пар.
* **E5 (безопасность, блокирует любую рекомендацию):** доля клеток с эхом/`cjk_artifact` и с
`length` по рукам. Если у `low` эхо-клеток ≥3 сверх `∅`**«риск эха на low» независимо от цены**.
* **Ковариата длины входа** — требование §14, которое Вилкоксон принять не может. Восстановлено
ОТДЕЛЬНОЙ моделью: `log R̂ ~ effort + prompt + log(len_draft) + (1|unit)`, **как проверка
устойчивости E1/E3**, не как решающий тест.
* **Провал прибора:** сигнатура Δ`prompt_tokens` вне полосы у >20% юнитов ⇒ проба аннулируется,
деньги называются. ⛔ **Полоса НЕ задана заранее и не может быть**: единственное известное число
(79) снято на en и на риге. ⇒ **Пред-полёт ЗАМЕРЯЕТ сигнатуру и пинит её коммитом**, и лишь
после этого она становится гейтом.
**Но «полосу» из пред-полёта не вывести:** один юнит даёт две сигнатуры (P₇ и P₉), разброса в
них нет. Поэтому правило допуска объявляется ЗДЕСЬ, до замера, и опирается на контроль 05.08:
при побайтно одинаковых `messages` провайдер повторяет `prompt_tokens` **в точности** ⇒ сигнатура
ожидается КОНСТАНТОЙ. **Допуск: ±2 токена** от значения, снятого пред-полётом (запас на неучтённую
серверную вставку, не на шум). Клетка вне допуска — «сигнатура не воспроизвелась»; >20% таких
юнитов ⇒ проба аннулируется.
**Мощность.** В кресте 2×2 контраст главного эффекта на юните имеет sd = σ (не σ√2) — поэтому n
вдвое меньше, чем в простой паре. Источник σ: **эррата Д47.7** (`23-editor-tier.md:7201`=`sd одного розыгрыша = 1.02`, 16 пар
`e0`/`zf`, побайтно один вход) — ⚠ **на английской паре и роли редактора; на zh не переносится**,
поэтому планируем σ=1.2 с чувствительностью 1.0/1.4.
**Плановое число — ×2.0 при σ=1.2** (z-аппроксимация с поправкой на ранговый тест). ⚠ Три версии,
гулявшие по документам, суть одна формула в разных конвенциях: ×1.82 при σ=1.02 · ×1.88 при σ=1.056
(несмещённая sd, t-квантили без поправки) · ×2.022.08 при σ=1.2. t-квантили при n=24 добавляют ~3%.
Решение владельца ни одна из трёх не двигает.
| σ | ×2.5 | ×2.0 | ×1.5 | MDE при n=24 | MDE при n=32 |
|---|---|---|---|---|---|
| 1.02 | 11 | 18 | 51 | ×1.82 | ×1.68 |
| 1.2 | 15 | 25 | 70 | ×2.02 | ×1.84 |
| 1.4 | 20 | 34 | 95 | ×2.27 | ×2.03 |
⛔ **Эффект ×1.40 (наблюдённый внутри блока в Д47) при любом n ≤ 32 ниже разрешения — записано
ДО покупки.**
## 5. ГАРДЫ
0. ⛔ **РАМКА ВЫБОРКИ — пинится ДО всего остального, иначе «24 юнита» читаются как ОТБОР.**
В пре-рег заносится: срез исходника (диапазон глав, файл, его sha256) · **новый каталог книги**
⚠ не `coldrun-v16`, где три оплаченных edit-юнита заставят `--resnapshot` на каждой из четырёх
копий · **все юниты этих глав в порядке движка, без выбора** · `n` = сколько их получится
(ориентир холодного прогона: 14 юнитов на 10 глав) · и **входит ли юнит пред-полёта в `n`**
(решение: **входит** — это первый юнит в порядке движка, прогоняемый во всех четырёх руках).
1. **Пред-полёт: один юнит × ПЯТЬ клеток** (четыре руки креста + референс, ~$0.27) → сигнатура
Δ`prompt_tokens` СНЯТА и запинена коммитом (по одной на P₇ и P₉; полосой она станет только после
этого) · **пять разных `snapshot_id`** · ⭐ **референс проходит свои гейты именно здесь**: эхо,
`R̂ = 0`, потолок ≈4.5 тыс. — иначе его первая клетка выстрелит в пачке мимо всех проверок · кириллица ≥0.99 · эхо 0. Только потом пачка.
2. **Зелень на пустоте:** `delivered = 0` ⇒ клетка идёт в E5 и исключается из E2; если таких >20% в
руке — **прибор ОТКАЗЫВАЕТ**, а не печатает «в пределах».
**Правило действует ТОЛЬКО на четыре руки креста.** Референс `glm@off` работает на
билингвальном входе — это зона эха с НЕИЗМЕРЕННОЙ экспозицией, и правдоподобная доля пустых у
него обнулила бы E1, в который он не входит вовсе. **Референс с >20% пустых просто не печатается
в E0**, а его доля идёт в E5 отдельной строкой.
3. **Эхо-гейт включён во всех руках.**`low` ≠ выключение: мина вооружается ТУМБЛЕРОМ, а уровни
эффорта едут с размышлением включённым. Гейт всё равно остаётся.
4. **Касса — и здесь ловушка, которую видно только в коде.** `ledger.go:58`=`FROM spend WHERE book_id`
считает потолок по `book_id`, а **копия БД несёт базовый расход под тем же `book_id`** (в холодном
прогоне это $0.4361). ⇒ `ceilings.book_usd` каждой копии = **база + доля руки + запас**, то есть в
`book.yaml` меняются ТРИ поля (`project_db`, `pipeline`, `ceilings`), либо потолок задаётся
`--ceiling-usd` на каждый запуск. И **число владельцу подаётся ОДНОЙ суммой**: база + пред-полёт +
N юнитов × 4 руки креста + референс — иначе базовый расход посчитается четырежды или не посчитается вовсе.
Отказ леджера = **СТОП и вопрос владельцу**; добор после чисел запрещён; потолок сессия не поднимает.
5. **Фриз — и он обязан лежать в зоне полигона.** Пре-рег и скрипт эндпойнта коммитятся в
`eval/dovodka/`. ⚠ ПЯТЬ `pipeline.yaml` и два `editor.md` физически живут в каталоге книги —
это ОТДЕЛЬНЫЙ репозиторий с открытым вопросом «кто коммитит книги», поэтому в зону полигона
кладутся **их замороженные копии вместе с sha256**; иначе «коммитами ДО первой клетки» не
исполнимо по канону. Всё — ДО первой платной клетки.
## 6. ⚠ ДЕНЬГИ — И ЗДЕСЬ НУЖНО ЧИСЛО ВЛАДЕЛЬЦА
**Все числа ниже — в ПИКОВЫХ терминах, и иначе быть не может:** леджер движка букирует пик всегда
(`models.yaml:180`=`держим пик`). Потолок кассы, `--ceiling-usd` и число владельца — тоже пиковые;
офф-пик — это счёт вендора по факту, а не потолок.
По леджеру холодного прогона: клетка `∅`**$0.054** (среднее четырёх ненулевых edit-строк, включая
ретрай на 32000; попытка 0 — $0.048 при n=3); обрыв случился у **1 строки из 4**.
**Клетка `low` ≈ $0.04 — ИСТОЧНИКА НЕТ:** движковых edit-вызовов на `low` не существует вовсе, а
хопы на `low` — роль переводчика ($0.0040.040). Это **предположение, а не замер**, и его проверяет
пред-полёт.
**Арифметика юнита, по шагам (её пропуск в прошлой редакции ломал все производные):** попытка 0
у `∅` = $0.048; обрыв на 16000 случается у ~25% и добавляет ретрай на 32000 (+$0.070) ⇒
**эффективная `∅` ≈ $0.048 + 0.25·0.070 ≈ $0.066**, с запасом на разброс — **$0.09**.
Юнит (2 × `∅` + 2 × `low`) ≈ 2·0.09 + 2·0.04 = **$0.26**.
| строка сметы | клеток | расчётно, ПИК |
|---|---|---|
| базовый прогон (черновая волна + майнинг + терминолог) | — | ≈ $0.6 |
| эскалация эхо-черновиков в базовом прогоне (`budget_usd` > 0) | ~12 хопов | ≈ $0.25 |
| пред-полёт (1 юнит × 5 клеток) | 5 | ≈ **$0.27** |
| **вариант A:** 24 юнита × 4 руки креста (+20% запаса) | 96 | ≈ $7.5 |
| ⭐ референс `glm-5@off`, 24 клетки (+20%) | 24 | ≈ **$0.6** |
| **вариант B:** 32 юнита × 4 руки (+20% запаса) | 128 | ≈ $10.0 |
**ОДНОЙ СУММОЙ, как и надо просить у владельца** (иначе база посчитается четырежды или ни разу —
леджер считает потолок по `book_id`, а его несут все четыре копии):
* **вариант A (~17 глав, ожидаемо ~24 юнита): $8.6 — ⚠ ЭТО ИСТОРИЯ, потолок поднят до $9.5 ниже** — разрешающая способность ×2.02;
* **вариант B (~24 главы, ожидаемо ~32 юнита): $11.1** — разрешающая способность ×1.84.
**Вариант задаётся ДИАПАЗОНОМ ГЛАВ, а не числом юнитов.** «24» и «32» — ожидание по пропорции
холодного прогона (14 юнитов на 10 глав), а не цель: юниты не добираются и не обрезаются. Прогон
идёт до конца диапазона либо до срабатывания денежного гарда — что наступит раньше.
**Чувствительность, названная до покупки и выведенная из своих же клеток:** если `low` на zh режет
размышление слабо (×1.4, как внутри блока Д47), клетка `low` дорожает до $0.050.065 ⇒ юнит
2·0.09 + 2·(0.05…0.065) = **$0.280.31** ⇒ 32 юнита = **$8.969.92 до запаса**. Сравнивать это надо
со строкой ЮНИТОВ ($10.0), а не с полной суммой: база, эскалация и пред-полёт ($1.1) тратятся при
любом срезе. **Зазор 0.810.4%** ⇒ гард с заметной вероятностью остановит раньше 32-го юнита.
Это штатный исход: **стоп и вопрос владельцу, добор после чисел запрещён.**
**Мои прежние сметы ($1.7 и $2.50) занижены втрое** и снимаются: они считали «выход» как видимый
текст, тогда как размышление у DeepSeek сидит ВНУТРИ `completion_tokens`. Это тот же класс, что
«смета ×10» редакции 1.
**РЕШЕНИЕ ВЛАДЕЛЬЦА: ВАРИАНТ A — 17 глав. ПОТОЛОК $9.5** (01.09 — «17 глав разрешаю», $8.6;
02.09 поднят до **$9.5** словом «да, можно поднять» после того, как в дизайн вошла референсная
клетка). Вариант B (~24 главы) не берётся.
**Почему именно $9.5 — посчитано, а не округлено:** база 17 глав $0.45 + четыре руки креста
(24 юнита × 2×$0.09 + 2×`low`) + референс 24×$0.02, всё с 20% на ретраи.
| сценарий цены клетки `low` | сырое | с запасом | влезает юнитов |
|---|---|---|---|
| центральный $0.04 | $7.17 | **$8.60** | 24 из 24 |
| пессимистичный $0.05 | $7.65 | **$9.18** | 24 из 24 |
| худший $0.065 | $8.37 | $10.04 | **22 из 24** (MDE ×2.02 → ×2.06) |
⇒ $9.5 покрывает два сценария из трёх целиком; в худшем теряются два юнита, и это штатный исход —
референс идёт последним, поэтому теряется он, а не пара, на которой стоит вывод.
**Потолок $9.5 — В ПИКОВЫХ ТЕРМИНАХ и СЕССИЕЙ НЕ ПОДНИМАЕТСЯ.** Срабатывание гарда = стоп и
вопрос владельцу; добор после просмотра чисел запрещён.
Прежняя развилка (снята решением): вариант A $8.6 (MDE ×2.02) или вариант B $11.1 (MDE ×1.84). Обе суммы — из таблицы выше и больше нигде в этом
файле не повторяются. Санкция «платные замеры разрешаю» числа не содержит, а по канону потолок
сессия не поднимает.
## 7. ЧЕГО В ЗАМЕРЕ НЕТ НАМЕРЕННО
Калибровки ставки платформы (гейчено, §14 прямо) · судейства качества — качество `low` вне скоупа,
только $0-гейты и E5 · правок `models.yaml`, capabilities, Go · смены модели, черновика,
дифф-контракта · креста «размышление вкл/выкл» пилота · сравнения с июльским базлайном как
каузального: между июлем и сегодня сменились веса модели, дефолт, маппинг и потолок — проба
разделяет рычаги СЕГОДНЯ.
**И отдельно: из этой пробы НЕ следует рекомендация менять боевой `pipeline-c1.yaml`.** Флип там
пинит тест (`echo_mine_test.go:217`, `TestBoevoyConfigEditorDsproAndGrokReasoning`) и двигает
снапшот edit-волны ⇒ `--resnapshot` и перекупка волны.
## 8. РЕЕСТР ОШИБОК ЧЕТЫРЁХ РЕДАКЦИЙ — сохранено, потому что класс один
| # | что утверждал | чем опровергнуто |
|---|---|---|
| 1 | «прибор верности нужен первым» | вопрос закрыт замером: редакторы не дописывают (рост знаков 0.4% и 0.6%) |
| 2 | «проба единицы выдачи» | гипотезу опровергают наши же данные; арм запрещён; смета ×10 |
| 3 | «контур ни разу не ездил целиком» | `coldrun-v16` прошёл НАКАНУНЕ |
| 4 | «канон мерен на 12 главах» | я сам намерил на 25 главах подряд сутками ранее |
| 5 | «засеять `first_person`/`speech`» | поля мёртвые: не копируются в рендер, не входят в хеш |
| 6 | «движок ручку не шлёт вообще» | `capability.go:236` шлёт `low|medium|high`; доказано на проводе |
| 7 | «гейт запретит» | гейт в другом тесте и пинит только шиппинг-конфиги |
| 8 | «риг потерял бы роль редактора» | `tenant_panel/prompts.py:141`=`def editor_msgs` грузит боевой `editor.md` |
| 9 | «эхо на `pro`@`low` не мерено» | 5 хопов в `coldrun-v16`, эхо 0 — ⚠ но роль переводчика, не редактора |
| 10 | сметы $1.7 / $2.50 | занижены втрое: размышление внутри `completion_tokens` |
**Сквозной класс — один:** показания инструмента, собственной памяти или своей ветки приняты за
факт о мире. Лечение, выведенное из десяти случаев: **отрицательный результат предъявляется с
положительным контролем**, а прежде «этого нет» — смотреть каталог артефактов, а не только доки
своей ветки.
---
# 9. ПРОЦЕДУРА — КОМАНДЫ, А НЕ АРГУМЕНТЫ
> ⚠ **Этот раздел дописан 02.09 после проверки исполнимости свежей сессией.** Её вердикт был:
> «план написан как аргумент, а не как процедура — из него нельзя набрать ни одной команды», и
> объём домысливания оказался больше, чем то, что план задаёт. Все якоря при этом были верны:
> дефект был не в фактуре, а в форме. Ниже — то, чего не хватало.
## 9.0 ⛔ ТРИ МЕСТА, ГДЕ ДОМЫСЛИВАНИЕ СТОИТ ДЕНЕГ ИЛИ ЗАМЕРА
Прочти их ПЕРВЫМИ; каждое проверено исполнением.
1. **`--verify-bank` НЕ ГАРАНТИРУЕТ останова.** Флаг единственный, кто вообще может остановить
прогон до редакторской волны, но при ПУСТОЙ дельте майнинга движок авто-продолжает
(`mining.go:152-164`, флаг лишь повышает уровень лога). Если это случится, базовый прогон купит
редакторские юниты, и каждая из четырёх копий потребует пересборки снапшота с перекупкой.
**ПОЯС:** базовому прогону ставится `--ceiling-usd` чуть выше ожидания ЧЕРНОВОЙ волны, чтобы
редакторская покупка упёрлась в денежный отказ (код 4), а не прошла молча.
2. **Ось промпта реализуется ТОЛЬКО через `stages[edit].prompt_override`.** Промпт резолвится
конвенцией `<prompts_root>/<пара>/<роль>.md`, а `prompts_root` живёт в `pairs/zh-ru.yaml`,
общем для всех конфигов каталога. ⇒ без `prompt_override` четыре руки схлопнутся в две:
**платишь за четыре, меришь две**. Ловится гардом «четыре разных `snapshot_id`» — снимать его
запросом к `snapshots` каждой из четырёх баз.
3. **Потолка $9.5 не существует как механизма.** Леджер считает по `book_id`, а таблица `spend`
лежит ВНУТРИ проектной базы: четыре копии сторожат каждая себя и каждая уже несёт базовый
расход. ⇒ **агрегат ведётся вручную** (§9.5), и санкционированную сумму держит только он.
## 9.1 СТЕНД
**Образец — `~/books/gu-zhenren/coldrun-v16/`** (его `book.yaml`, `pipeline.yaml`, `pairs/zh-ru.yaml`).
⚠ Каталог берётся как ОБРАЗЕЦ, но не как рабочее место: в нём три оплаченных редакторских юнита,
которые заставят пересборку снапшота на каждой копии.
⛔ **Пути на `models`, `langpack_root`, `prompts_root` обязаны смотреть в
`/home/ubuntu/projects/textmachine-main/`**, а не в своё дерево: **в `polygon` нет
`backend/configs/langpacks/ru`** (проверено: там только `zh`, `zh-ru`), а загрузчик на отсутствующий
каталог молча возвращает умолчания — русский слой читателя потеряется во всех четырёх руках тихо.
`backend/prompts` и `models.yaml` в двух деревьях побайтно совпадают, расходится только `langpacks`.
```bash
B=~/books/gu-zhenren/probe-4axes # новый каталог, не coldrun-v16
mkdir -p $B/{bin,logs,arms}
cp ~/books/gu-zhenren/coldrun-v16/{book.yaml,pipeline.yaml} $B/
cp -r ~/books/gu-zhenren/coldrun-v16/pairs $B/
# исходник: 17 глав, вариант A
eval/.venv/bin/python eval/dovodka/narezka.py --from 26 --to 42 --out $B/guzhenren-ch26-42.gb18030.txt
# бинарь — из main-дерева, sha пинится в пре-рег
cd /home/ubuntu/projects/textmachine-main/backend && go build -o $B/bin/tmctl ./cmd/tmctl
sha256sum $B/bin/tmctl
```
## 9.2 ЧЕТЫРЕ РУКИ
Четыре копии `pipeline.yaml` в `$B/arms/`, различающиеся РОВНО двумя строками стадии `edit`:
| файл | `prompt_override` | `reasoning` |
|---|---|---|
| `p7-off.yaml` | `$B/editor-p7.md` | `"off"` |
| `p7-low.yaml` | `$B/editor-p7.md` | `"low"` |
| `p9-off.yaml` | `$B/editor-p9.md` | `"off"` |
| `p9-low.yaml` | `$B/editor-p9.md` | `"low"` |
| ⭐ `glm-off.yaml` (**референс, не рука креста**) | `$B/editor-p9.md` | `"off"`, модель **`glm-5`** |
```bash
cd /home/ubuntu/projects/textmachine
git show e89874b^:backend/prompts/zh-ru/editor.md > $B/editor-p7.md # июльский
git show HEAD:backend/prompts/zh-ru/editor.md > $B/editor-p9.md # сегодняшний
sha256sum $B/editor-p7.md $B/editor-p9.md # ОБА в пре-рег
```
⚠ **В образце `coldrun-v16/pipeline.yaml` комментарий утверждает, что у `pro` ручки эффорта нет
вовсе** — это протухло. Копируя файл, комментарий поправить или удалить, иначе следующий читатель
получит противоречие своему же плану.
## 9.3 ПОРЯДОК
**Коды выхода, без которых штатный стоп читается как падение:** `0` — чисто · `2` — завершено с
флагами · **`3` — стоп подписи банка** (`WaveSignatureStop`) · **`4` — денежный отказ**
(`CeilingHalt`). Объёмный стоп приходит и как `0`, и как `2`; различает только строка
`VOLUME CEILING:` в выводе.
```bash
# 1. БАЗОВЫЙ ПРОГОН: черновая волна + майнинг + терминолог, стоп ДО редактуры
$B/bin/tmctl translate --config $B/book.yaml --verify-bank --ceiling-usd 1.20 2>&1 | tee $B/logs/base.log
# ожидаемый код 3 (стоп подписи). Код 0/2 ⇒ ⛔ СТОП: дельта была пуста, редактура куплена —
# к владельцу, не продолжать.
# 2. КОПИИ — только через backup, не cp (живые -wal/-shm теряют данные)
$B/bin/tmctl backup --config $B/book.yaml # ⚠ флага --out НЕТ: пишет в $B/backups/<ts>.db
cp $B/backups/<ts>.db $B/arms/p7-off.db # …и так все ПЯТЬ копий
# в каждой копии свой project_db, pipeline и ceilings
# ⛔ если project_db копии не существует, движок заведёт ПУСТУЮ базу и купит черновую волну заново
# 3. ИНТЕРЛИВИНГ: юнит за юнитом, руки в порядке из сида
for u in $(seq 1 $N); do
for arm in $(eval/.venv/bin/python eval/dovodka/poryadok.py --unit $u --seed 20260902); do
$B/bin/tmctl translate --config $B/arms/$arm.yaml --max-units 1 --ceiling-usd <своя> \
2>&1 | tee -a $B/logs/$arm.log
rc=$?; grep -q 'VOLUME CEILING:' $B/logs/$arm.log && break 2
[ $rc -eq 4 ] && { echo "⛔ ПОТОЛОК — СТОП, вопрос владельцу"; exit 4; }
done
done
```
**Увидел требование `--resnapshot` — СТОП, не давать флаг.** Он перекупит все уже оплаченные
клетки. ⚠ Предупреждение планировщика о будущем `--resnapshot` на майнящей книге печатается на
КАЖДОМ ограниченном прогоне (десятки раз) — оно ожидаемо и не есть требование.
## 9.4 РАНДОМИЗАЦИЯ
Порядок рук внутри юнита — детерминированная перестановка от сида; **сид объявляется в пре-реге**
(`20260902`), выпавший порядок пишется в манифест. Без запиненного сида пре-рег не является
пре-регом.
## 9.5 АГРЕГАТ ПОТОЛКА — ВРУЧНУЮ, ПОТОМУ ЧТО МЕХАНИЗМА НЕТ
```bash
for db in $B/arms/*.db $B/*.db; do
echo -n "$(basename $db): "; sqlite3 "$db" "select round(sum(committed_usd+reserved_usd),4) from spend;"
done
```
База считается ОДИН раз (она в каждой копии), поэтому: **израсходовано = база + Σ(копия база)**.
Санкция владельца — **$9.5 в пиковых терминах**; при достижении — стоп и вопрос, добор запрещён.
## 9.6 ЧЕГО ЕЩЁ НЕТ И ЧТО ПИСАТЬ ПЕРЕД ФРИЗОМ
* **скрипт эндпойнта** (`chetyre.py`): ключ юнита, реконструкция попытки 0 (в `request_log` нет
колонки `attempt` — она в `checkpoints`), определение `delivered`, восстановление размышления,
правило цензуры, Вилкоксон с `zero_method='pratt'`;
* ⚠ **медиану и доверительный интервал Ходжеса–Лемана придётся писать руками**: `scipy 1.18.0`
установлен и `pratt` поддерживает, но интервала у `wilcoxon` не отдаёт (проверено);
* ⚠ **ковариатная модель не запустится**: `statsmodels` в `eval/.venv` не установлен;
* `poryadok.py` — генератор порядка рук от сида.
---
# 10. ⛔ ФРИЗ ПРЕ-РЕГИСТРАЦИИ
**Настоящим коммитом пре-регистрация ЗАМОРОЖЕНА.** Дата: 02.09.2026. Объявлено владельцу вслух.
**Что заморожено и после этого коммита НЕ ПРАВИТСЯ:**
* дизайн — крест 2×2 внутри юнита + референсная клетка пятой, фиксированно последней;
* модели — редактор `deepseek-v4-pro`, референс `glm-5` (`thinking` выключен, `few_shot: false`),
черновик и терминолог `deepseek-v4-flash`, судья не вызывается;
* правило решения §4 — эндпойнты E0/E1/E2/E3/E4/E5, критерий `p<0.05`, два исхода, правило цензуры,
метод обработки ничьих, порог «ниже разрешения замера»;
* мощность и её конвенция — плановое **×2.0 при σ=1.2**;
* потолок — **$9.5 в пиковых терминах**, санкция владельца 02.09;
* рамка выборки — главы книги **2642**, все юниты в порядке движка, без отбора;
* сид рандомизации — **20260902**;
* коэффициент восстановления размышления — **0.3644 токена/знак**;
* область переноса §3-БИС и обязательный шаблон формулировки вывода.
**Что фризом НЕ покрыто и остаётся работой исполнителя** (форма — на его усмотрение, требования —
в §9.6 и в промте): скрипты `chetyre.py` и `poryadok.py` · сборка стенда · тексты конфигов.
**Известные шероховатости, названные при фризе, а не скрытые:** смета существует в двух
структурах (обе ниже потолка, расхождение в базе $0.45 против $0.85 и в двойном счёте пред-полёта);
в полировочных местах остались формулировки «четыре руки» там, где теперь пять. Ни то, ни другое не
меняет дизайн, правило решения и потолок.
**Отступление от замороженного = пинг владельцу, а не правка.** Если исполнение покажет, что
что-то из замороженного неверно — это НАХОДКА, которая записывается и предъявляется, а не
исправляется молча.

View file

@ -0,0 +1,210 @@
# Промт: полигон-сессия «Контролируемая проба редактора по четырём осям»
> Составлен полигон-сессией фазы Д 02.09 по слову владельца. Исполнитель — ОТДЕЛЬНАЯ сессия,
> онбординг у неё СВОЙ. Форма — по `docs/ORCHESTRATOR_SESSION_PROMPT.md` §«Пишешь промт сессии?»
> (12 обязательных блоков).
## 1. Какая проблема и что решит твой результат
Проект переводит художественные книги zh→ru мультиагентным LLM-конвейером. **Самая дорогая стадия —
редактор**, и платим мы там в основном не за выданный текст, а за «размышление» модели перед ответом:
у этого поставщика оно тарифицируется внутри выходных токенов и составляет большую часть счёта.
**Проблема:** мы не знаем, чем управляется эта цена. На книге в 2283 главы редактор стоит порядка
полусотни долларов, и мы не можем сказать, платим ли за то, что модель много думает, или за то, что
она много пишет.
**Что решит твой результат:** контролируемая проба разделит рычаги. Если снижение усилия режет
расход и текст не портится — книга дешевеет вдвое по самой дорогой статье. Если не режет — мы
перестаём искать там, где нет.
**Повод, который ты обязана не повторить.** Предыдущая сессия за четверо суток одиннадцать раз
утверждала или предлагала то, что уже сделано либо опровергнуто в репозитории. Класс один:
**показания инструмента, собственной памяти или своей ветки принимались за факт о мире**. Реестр —
§8 плана и §Д52.5 отчёта фазы.
## 2. Зона записи и git
**Зона:** `eval/` + `docs/experiments/` + секция «Полигон» в `docs/PROGRESS.md`. Ветка `polygon`.
Артефакты прогона — в `~/books/gu-zhenren/probe-4axes/` (это ОТДЕЛЬНЫЙ git-репозиторий `books/`,
видимый обоим рабочим деревьям).
**Ты НЕ коммитишь ничего, КРОМЕ пре-рег-фриза** — это единственное исключение канона, и он
объявляется владельцу вслух ДО коммита. Форма коммита — только с явным списком путей
(`git commit -- <путь>`); `git add -A`, `git add .`, `git commit -a` запрещены.
⛔ Никогда не читать `.env`. ⛔ Никаких `reset --hard`, перезаписи истории, `checkout` поверх грязного
дерева: рядом работают параллельные сессии.
## 3. Карта чтения (≤5 позиций; дальше — только по её ссылкам)
1. **`CLAUDE.md`** — канон: цели, роли, зоны, гардрейлы, дисциплина чтения.
2. ⭐ **`eval/dovodka/PLAN-01-09.md`ЗАКОН этой сессии.** Читать целиком; **§9 «ПРОЦЕДУРА» —
первым**, там команды, коды выхода и три места, где домысливание стоит денег.
3. **`eval/dovodka/SESSION-30-08.md` §0-ЧЕТЫРЕ** — состояние фазы и реестр ошибок. Остальное в файле
исторично; ⛔ §5 «КОМАНДЫ» — команды АВГУСТОВСКОГО РИГА, не твои, там есть помеченная
«не запускать никогда».
4. **`~/books/gu-zhenren/coldrun-v16/`** — образец стенда: `book.yaml`, `pipeline.yaml`,
`pairs/zh-ru.yaml`, и `REPORT.md` §14 «Форма следующего замера» (⚠ в файле ДВА раздела «## 14»,
нужен второй). ⛔ Каталог — образец, а не рабочее место.
5. **`docs/experiments/00-provider-quirks.md`** — провайдерские ловушки. Канон требует читать ПЕРЕД
правкой вызовов; здесь особо записи про ручку размышления и эхо-мину.
## 4. Жёсткий контур — **делай РОВНО так**, отступление = пинг владельцу
1. ✅ **Санкция владельца: вариант A — 17 глав, потолок $9.5 в ПИКОВЫХ терминах** (01.09 дано $9.5; 02.09 поднято до $9.5, когда в дизайн вошла референсная клетка `glm-5`).
⛔ Потолок сессией не поднимается. Срабатывание гарда = **стоп и вопрос владельцу**; добор после
просмотра чисел запрещён. К владельцу за этим решением ходить не надо — оно принято.
2. **Замер ратифицирован** (D39.184 п.5 — ⚠ нота есть только в ветке `main`, в `polygon` реестр
обрывается на D39.170). Дизайн менять нельзя: четыре оси с РАЗНЫМИ ролями — трудность чанка
блокировать, длину входа контролировать, промпт брать по SHA, усилие `off` против `low`.
3. **Пре-рег фризится коммитом ДО первой платной клетки.** Фриз объявляется владельцу вслух.
4. **Правило решения записано в §4 плана и НЕ пересматривается после просмотра чисел.**
5. **Стенд смотрит в `/home/ubuntu/projects/textmachine-main/`** для `models`, `langpack_root`,
`prompts_root`: в `polygon` нет `backend/configs/langpacks/ru`, а загрузчик молча подставляет
умолчания — русский слой читателя потеряется тихо.
## 4-БИС. ⭐ МОДЕЛИ — ФИКСИРОВАНЫ, МЕНЯТЬ НЕЛЬЗЯ
**Предыдущая редакция этого промта и план не называли моделей ВООБЩЕ** (ноль упоминаний, поймано
владельцем 02.09). Ниже — состав, снятый с реально отработавшего конфига `coldrun-v16` и с его
леджера; **это часть жёсткого контура: смена модели убивает сопоставимость с холодным прогоном,
на котором стоит вся смета.**
| стадия / роль | модель | усилие размышления | зачем |
|---|---|---|---|
| **редактор** (стадия `edit`) | **`deepseek-v4-pro`** | ⭐ **ЭТО И ЕСТЬ ПРЕДМЕТ ЗАМЕРА**: `off` против `low` | боевой редактор, ратифицированный интерим-жилец |
| черновик (стадия `draft`) | `deepseek-v4-flash` | `low` | боевой черновик; в пробе — константа |
| хоп эскалации черновика | `deepseek-v4-pro` | наследует эффорт стадии (`low`) | ловит эхо-чанки; в базовом прогоне обязателен |
| терминолог (`gates.terminology`) | `deepseek-v4-flash` | `low` | консолидация банка; в пробе — константа |
| классификатор типов | `deepseek-v4-flash` | `low` | подфаза терминолога |
| ⭐ **референс** (пятая клетка) | **`glm-5`** | `off` = thinking ВЫКЛЮЧЕН, `few_shot: false` | ценовой контроль «тот же день, те же юниты»; **не фактор креста** |
| **судья** | ⛔ **НЕ НУЖЕН И НЕ ВЫЗЫВАЕТСЯ** | — | качество при сниженном усилии ВНЕ скоупа (§12) |
**Цепочка эскалации базового прогона** — как в образце: `default: [deepseek-v4-pro, glm-5.1,
gemini-3.1-pro-preview]`. ⚠ В ЧЕТЫРЁХ РУКАХ `escalation.budget_usd: 0` (эскалации нет), **в базовом
прогоне он обязан быть > 0**, иначе эхо-черновики останутся флагнутыми.
**Ключ `ZAI_API_KEY` обязателен** — без него проверка ключей упадёт громко на старте (референс идёт к другому поставщику).
**Единственная модель, чей расход в пробе НЕИЗВЕСТЕН, — редактор на `low`:** таких вызовов у
движка не делали ни разу. Смета стоит на предположении; проверяет пред-полёт (§5.3).
**Модели для ТВОИХ субагентов самопроверки** — это другое и на счёт замера не влияет:
рекомендация канона `claude-fable-5`, ⚠ норма владельца 01.09 — **один `fable` на самое тяжёлое,
остальные дешевле**; модель агенту задавать ЯВНО.
## 5. Что делать
### 5.1 Собрать стенд. **Делай РОВНО так** — §9.1 плана.
Резалка исходника — `eval/dovodka/narezka.py` (её не существовало до 02.09; формат GB18030 + CRLF +
первая строка обязана быть заголовком). Вариант A = главы книги **2642**.
**Мина нумерации:** движок не умеет смещение глав — срез пронумеруется как 117. Соответствие
держится ТОЛЬКО записью в пре-реге.
### 5.2 Написать недостающее. **Реши сам и аргументируй** — форма твоя, требования ниже.
Не существует и нужно тебе:
* **`chetyre.py`** — скрипт эндпойнта. Требования: ключ юнита (в `request_log` нет колонки `attempt`,
она в `checkpoints` — реконструкцию назови явно) · операциональное определение «доставлено» ·
восстановление размышления по формуле §3 плана · правило цензуры §4 (обе стороны) · Вилкоксон с
`zero_method='pratt'` · ⚠ **медиану и доверительный интервал Ходжеса–Лемана писать руками:**
`scipy 1.18.0` установлен и `pratt` поддерживает, но интервала у `wilcoxon` не отдаёт (проверено).
**Гейт против зелени на пустоте обязателен:** нулевой знаменатель ⇒ прибор ОТКАЗЫВАЕТ кодом 2,
а не печатает «в пределах».
* **`poryadok.py`** — порядок рук от сида (сид объявлен в плане: `20260902`).
* ⚠ Ковариатная модель §4 требует `statsmodels`, которого в `eval/.venv` нет. Решить: ставить или
объявить эту проверку неисполненной. **Молча пропустить нельзя.**
### 5.3 Пред-полёт. **Делай РОВНО так** — §5.1 плана.
Один юнит × ПЯТЬ клеток (четыре руки креста + референс `glm-5`, ~$0.27): сигнатура разницы входных токенов СНИМАЕТСЯ и пинится коммитом ·
пять разных отпечатков снапшота (иначе ось промпта схлопнулась — см. §9.0 п.2) · кириллица ≥0.99 ·
эхо 0. **Только после этого — пачка.**
**Клетка с `low` — предположение, а не замер:** движковых редакторских вызовов на `low` не
существует вовсе, и смета стоит на нём. Если пред-полёт даст цену заметно выше ожидания —
**стоп и пересчёт с владельцем ДО основной покупки**, не после.
### 5.4 Пачка и счёт. **Делай РОВНО так** по §9.3, **аргументируй сам** только форму отчёта.
## 6. Оси ревью твоей работы (приёмка пойдёт по ним; вправе заменить с аргументом)
1. **Деньги** — сошёлся ли расход с агрегатом §9.5, не пробит ли потолок, объявлен ли фриз до покупки.
2. **Дизайн** — не схлопнулась ли ось промпта, одинаков ли потолок вывода во всех руках,
воспроизвелась ли сигнатура.
3. **Интерпретируемость** — можно ли из твоих чисел вынести вывод при ЛЮБОМ исходе, включая «ниже
разрешения замера».
## 7. Мандат самопроверки ИСПОЛНЕНИЕМ (обязателен; самоотчёт не принимается)
Самоотчёт «проверено» в этом проекте регулярно оказывался ложным и стоил денег.
1. **Ревью собственного кода и собственных запросов к моделям** — до первой платной клетки:
прочитай вслух тело запроса каждой руки и убедись, что оно отличается ровно тем, чем должно.
2. **Ревью полученных результатов** — числа пересними командой, не глазами.
3. **Адверсариальный проход по СВОЕЙ ГОТОВОЙ работе.****Субагенты разрешены явно** — иначе
дефолт-запрет харнесса тихо победит. **Модель агенту задавай ЯВНО и знай, сколько их работает**
(норма CLAUDE.md; рекомендация — `claude-fable-5`, ⚠ норма владельца 01.09: **один `fable` на
самое тяжёлое, остальные дешевле**). Направление: (а) не схлопнулась ли ось; (б) не подогнан ли
порог под результат; (в) верна ли арифметика денег; (г) что в отчёте утверждается без улики.
4. **Тест на вырожденность:** если букву пункта удовлетворяет пустое исполнение («перечитал сам») —
пункт не выполнен. Нужен артефакт: вывод команды, вердикт агента, лог.
5. Каждую пойманную собой ошибку **запиши в отчёт с классом**.
## 8. Записка-план и комплектность
После эхо (§10) выложи ≤15 строк: что собираешь, что пишешь, когда фризишь, когда покупаешь.
**И сверь план с §9 процедуры МЕХАНИЧЕСКИ** — таблицей «шаг §9 → делаю/не делаю → почему».
Непокрытый шаг либо планируется, либо его пропуск подписывается словом.
## 8-БИС. Отчёт: обязательные секции
Отчёт сдаётся секцией в `docs/experiments/23-editor-tier.md` (зона полигона). Обязательны:
1. **Результат по эндпойнтам**с командами получения каждого числа.
2. ⛔ **«ЧТО НЕ УДАЛОСЬ И ЧТО НЕ ПРОВЕРЕНО»** — обязательная секция, не косметика. Сюда: что не
успела, что не смогла снять, какие проверки объявлены неисполненными и почему (например,
ковариатная модель без `statsmodels`). **Пустая пробы не бывает; отсутствие этой секции читается
как непроверенная работа.**
3. **«ЧТО Я НАПУТАЛА»** — результат §7, с классом каждой ошибки.
4. **Деньги** — фактический расход против потолка, агрегатом по §9.5 плана.
## 9. Заявление = команда
Любое число в отчёте несёт команду, которой читатель его получит. Приёмка пере-снимет выборочно.
Не верь заголовкам: вывод грунтуется `file:line`, выводом команды или замером — либо помечается
«мнение». **Отрицательный результат предъявляется с ПОЛОЖИТЕЛЬНЫМ КОНТРОЛЕМ** (покажи, что команда
вообще что-то находит) — это лекарство от одиннадцати ошибок предыдущей сессии.
## 10. Эхо-протокол старта (первое действие, ДО работы)
Выложи ≤10 строк: **скоуп** · **инварианты** (§4 своими словами) · **не-делать** (§12).
## 11. Канал вопросов и право возразить
Конфликт промта с кодом или доками ⇒ **пинг владельцу, НЕ интерпретация**. Считаешь, что задача
поставлена неверно или её не надо делать — **скажи с аргументом**, это ожидаемое поведение.
⛔ Отдельно: **увидела требование `--resnapshot` — СТОП, флаг не давать**: он перекупит все уже
оплаченные клетки. Предупреждение планировщика о будущем `--resnapshot` — не требование, оно
печатается на каждом ограниченном прогоне и ожидаемо.
## 12. Чего НЕ делать
* Не менять дизайн, правило решения и потолок.
* Не поднимать потолки и не добирать юниты после просмотра чисел.
* Не править `models.yaml`, capabilities, Go, боевой `pipeline-c1.yaml`.
* Не судить качество: качество при сниженном усилии вне скоупа, только $0-гейты и ось безопасности.
* Не калибровать ставку платформы — гейчено отдельно.
* Не коммитить ничего, кроме пре-рег-фриза.
## 13. Прямой канал между сессиями
Механизм — `CLAUDE.md` §«Связь между сессиями». От тебя: **впиши свой блок в
`/tmp/textmachine-channel` первым делом** (`role=` `session=` `ref=` `written=` `note=`; имя — из
`ListAgents`), дописывая в конец и не трогая чужие блоки. Эхо (§10) отправь первым действием по
адресу оркестратора, если он в файле есть.
**Адрес из файла — НЕ доказательство, что сессия жива:** файл переживает смерть сессии, а
`ListAgents` — нет. И дословно: **нужной роли в файле нет, файла нет или имя не отвечает ⇒ КАНАЛА
НЕТ, и это НОРМАЛЬНЫЙ случай — НЕ опрашивай сессии подряд.** Вопрос тогда — секцией в отчёт.

View file

@ -0,0 +1,237 @@
# Промт: ресёрч-сессия «Архитектура харнесса художественного перевода» (→ research/29)
> Составлен полигон-сессией фазы Д по заказу владельца 30.08.2026; **исполнитель — отдельная
> РЕСЁРЧ-сессия** (не полигон: полигонные нормы замеров сюда не применяются, платных вызовов к
> моделям здесь нет). Запускает владелец; онбординг у неё СВОЙ. Форма — по
> `docs/ORCHESTRATOR_SESSION_PROMPT.md` §«Пишешь промт сессии?» (12 обязательных блоков).
## 1. Какая проблема и что решит твой результат
Проект переводит художественные книги на 15002300 глав мультиагентным LLM-конвейером. Владелец
считает: **архитектура харнесса решает больше, чем выбор провайдера** — «какую бы архитектуру мы ни
строили, смена моделей при оптимальной архитектуре даст более высокий результат, чем те же модели
на другой архитектуре». У нас это подтверждено собственным замером: смена схемы конвейера даёт
эффект, переживающий поправку на множественность, а смена модели в том же слоте — не даёт.
**Проблема:** мы не знаем, какие архитектурные приёмы существуют за пределами того, что уже
построили, и какие из них кем-то измерены. Мы перестали генерировать идеи и рискуем строить
вслепую.
**Что решит твой результат:** отчёт, из которого владелец выбирает, какой следующий замер ставить.
Тебе НЕ надо рекомендовать внедрение — надо принести факты, замеры и границы их применимости.
**Повод, который ты обязан не повторить.** Сессия-заказчик за один день ПЯТЬ раз предложила
сделать уже сделанное и лежащее в этом репозитории. Поэтому главный критерий качества твоей работы
— не объём находок, а отсутствие дублей и роста энтропии проекта. **Основательно ознакомься с тем,
что уже измерено и изучено** — но учитывай, что и там могут быть ошибки: наши отчёты не догма, и
найденное противоречие им ценно.
## 2. Зона записи и git
**Пишешь ровно один файл: `docs/research/29-harness-topology-survey.md`** (номер проверен свободным
30.08). Больше — ничего: ни `backend/`, ни `platform/`, ни `frontend/`, ни чужих доков.
**Зонная оговорка, и её надо прочесть до первой строки.** По зонной таблице `CLAUDE.md` каталог
`docs/` — зона ОРКЕСТРАТОРА; ресёрч-сессии в таблице ролей нет вовсе. Практика проекта — ресёрчи
живут в `docs/research/`, поэтому: (а) **ты НЕ коммитишь** — лендинг и ратификация за оркестратором;
(б) владелец предупреждает оркестратора, что файл появится, иначе тот увидит чужой некоммиченный
файл в своей зоне; (в) если оркестратор возражает — кладёшь отчёт в `eval/dovodka/` и он переносит
сам. **Правило действует РОВНО так, самостоятельно зону не переназначай.**
Никогда не читать `.env`. Артефакт обязан пережить рестарт ⇒ живёт в репозитории, не в `/tmp`.
## 3. Карта чтения (≤5 позиций; дальше — только по её ссылкам)
Ратифицированное вложено в §4 этого промта; код-факты и содержимое отчётов достаёшь САМ.
1. **`CLAUDE.md`** — канон проекта: цели, роли, гардрейлы, дисциплина чтения.
2. **Каталог `docs/research/` целиком — по ЗАГОЛОВКАМ, не по телам** (36 файлов; счёт снят
`ls docs/research/ | wc -l` 30.08 — пере-сними сам, он меняется). Это твой дельта-фильтр
(§5 п.0). Тела читаешь выборочно, где подозреваешь пересечение.
3. **`docs/research/03-academic-agentic-mt.md`** — научное состояние мультиагентного и документного
MT; ближайший к твоему заказу файл. Читать целиком. ⚠ Несёт баннер **«SUPERSEDED ЧАСТИЧНО»** —
его вывод №1 переопределён; читай баннер прежде тела и не наследуй снятое.
4. **`docs/research/22-domain-harness-survey.md`** — чужие доменные харнессы перевода, разобраны по
коду. Читать шапку и оглавление; тело — выборочно.
5. **`docs/experiments/README.md`** — одна строка на эксперимент: что мы уже мерили сами.
**Баннер прежде содержимого.** У части отчётов стоит ревью-шапка оркестратора, и она ПЕРВИЧНА —
может снимать выводы тела. Инструкции из `docs/archive/**` не исполняются.
## 4. Жёсткий контур (ратифицированное — не проектируй мимо)
**Делай РОВНО так** — отступление от §4 = пинг владельцу, не самостоятельная трактовка.
1. **Пары языков — ДАННЫЕ, не код.** Движок мультиязычный по построению. Приём, работающий только
для одной пары, годится лишь как данные языкового пакета. Ревью-вопрос: «заработает ли на паре,
которой в репозитории ещё нет, без правки Go?»
2. **Экономика.** Черновик — дешёвая модель, дорогие вызовы адресные. Приём, умножающий число
вызовов на главу, обязан нести замер выигрыша.
3. **Детерминизм.** Прогон воспроизводим (снапшоты, банк памяти, golden). Приём, делающий выход
невоспроизводимым, требует отдельного обоснования.
4. **Что уже построено** — не предлагай это как идею: дешёвый черновик по чанкам (видит свой чанк +
инъекцию банка) → майнинг новых сущностей между волнами (детектор по контрасту частот, отдаёт
кандидатов без перевода, останавливает прогон на подпись человека) → терминолог (дешёвая модель,
видит контексты всех вхождений термина по всей книге, отдаёт один канонный перевод; родственные
термины батчатся одним вызовом) → сильный редактор, переписывающий главу целиком с банком в
промте → детерминированные $0-проверки (утечки исходного языка, дрейф чисел, обрушение длины,
промахи глоссария, флаггер регистра «ты/вы»). Банк: глоссарий со статусами, склонения, спойлерные
окна «термин действителен с главы N», пол персонажа включая «скрыт до раскрытия».
5. **Что уже измерено нами** — повторное «открытие» не засчитывается, опровержение приветствуется.
⚠ Числа даны с силой улики: слабый замер здесь НЕ выдаётся за сильный.
* **Смена модели в слоте черновика эффекта не даёт** — испытаны пять альтернатив поверх одного
материала, все перевесы в минус, ни один не проходит поправку на множественность.
* **Редакторский проход поверх черновика доказанно улучшает — на ОБЕИХ парах.** zh→ru: голый
черновик различимо хуже связки (+4.12 при пороге 2.39 и +4.92 при 2.10 в двух кругах). en→ru:
`n=16, +1.56, p=0.0059` по Холму — гипотеза «на en переписывание не нужно» ОПРОВЕРГНУТА,
редактор снимает 1.56 ошибки на единицу (2.78 → 1.22).
⚠ Есть и второй, СЛАБЫЙ замер на en (`n=6`), давший «+0.00» при интервале [2.50; +2.33] и не
прошедший поправку. Он не противоречит первому: его интервал ВКЛЮЧАЕТ +1.56 — у него просто
нет мощности. Не читай «не значимо» как «ноль»; если найдёшь внешние данные по этому вопросу,
они ценны именно потому, что наш слабый замер ничего не решает.
* **Два разных сильных редактора между собой неразличимы** (`+0.06`, p=0.95) ⇒ выбор жильца этой
роли у нас — вопрос цены, а не качества.
* **Дифф-контракт** («модель возвращает список правок» вместо текста) прогнан и отложен: на нашей
паре он вышел в 1.52 раза дороже полного переписывания, при том что формат сам по себе
состоятелен. Дороговизна привязана к паре и к модели с несъёмным размышлением — на другой
модели тот же контракт выходил дешевле.
## 5. Что делать
### 0. Синк с репозиторием — БЛОКИРУЮЩИЙ. **Делай РОВНО так.**
Слово владельца: «не переоткрывать, что уже открыто; обязательно синк с репозиторием, чтоб не
плодить сущности — супер обязательный пункт перед ресёрчем».
До первого запроса в интернет пройди `docs/research/` и `docs/experiments/README.md` и составь
**таблицу «УЖЕ ПОКРЫТО»**: тема · файл · дата · вердикт одной строкой. Она — первый раздел отчёта.
Нет таблицы ⇒ поиск не начат. Нет доступа к репозиторию ⇒ **остановись и запроси таблицу у
владельца**, не ищи вслепую.
Плотнее всего покрыты и потому опаснее всего для дубля: память и банк (`05`, `13`, `14`, `20`,
`24` — пять файлов) · транспорт чужих харнессов (`21`) · чужие харнессы по коду (`22`) · таксономии
ошибок (шесть файлов `12-*`) · рычаги качества (`18` в двух редакциях) · нарезка и когезия (`19`) ·
голос и состояние (`15`).
### 1. Поиск. **Реши сам и аргументируй** — глубину, источники и порядок выбираешь ты.
Ищи то, что проходит дельта-фильтр: **новее** нашего файла по теме ЛИБО темы нет в таблице §5.0.
Оси (направление, не потолок):
1. **Топологии, которых у нас нет:** план-затем-письмо; раунды критики и сколько окупается; дебаты
агентов; итеративная само-ревизия; отбор из N кандидатов судьёй; «сначала смысловой скелет,
потом стиль»; стилевые образцы из самого произведения как few-shot; двухпроходка «черновик всей
книги → полное переписывание».
2. **Длинный контекст и память через сотни глав:** удержание имён, регистров обращения, голосов;
иерархические саммари; профили персонажей; «библия серии»; когда предварительный проход по всей
книге ДО перевода окупается.
3. **Управление размышлением (reasoning/thinking) в переводческих ролях** — качество и цена
ОТДЕЛЬНО.
4. **Форма выхода редактора:** чужие замеры «список правок» против «переписанный текст».
5. **Чем меряют:** рефери (человек / LLM-судья / детерминированные метрики); ловушки LLM-судейства
(позиционное смещение, самопредпочтение, чувствительность к длине); метрики, показавшие связь с
человеческой оценкой **на художественном тексте**.
6. **Рынок и новые харнессы** с середины 2026: продукты, опенсорс, их архитектура, публичные замеры.
Приоритет предмета — художественная проза и объёмы от книги. Пары по ценности: zh→ru · zh→en ·
любые →ru · прочее. Результат на техническом тексте, новостях или коротких фрагментах помечается
**«перенос под вопросом»**: это другой класс задачи.
### 2. Правило годности находки. **Делай РОВНО так.**
Находка идёт в отчёт при выполнении всех трёх условий:
1. **дельта-строка** — «в наших отчётах этого нет, потому что …» либо «дополняет `research/NN`: у
него было X, здесь новое Y»;
2. **ссылка** — URL, статья с датой и авторами, репозиторий с путём к файлу;
3. **разделено «заявлено» и «измерено»**; для измеренного: размер выборки · языковая пара · жанр ·
кто судил · была ли поправка на множественность.
**Заявление = команда.** Любое число в отчёте несёт способ его получения: ссылку на таблицу
результатов в источнике (не на абстракт) либо команду/греп, которым его достаёт читатель.
Приёмка пере-снимет выборочно.
## 6. Оси ревью твоей работы (приёмка пойдёт по ним; вправе заменить с аргументом)
1. **Дубль** — есть ли в отчёте то, что уже лежит в `docs/research/` или `docs/experiments/`.
2. **Улика** — открывается ли ссылка, дословна ли цитата, то ли там число.
3. **Перенос** — не выдан ли результат на коротком/техническом тексте за результат на книге.
## 7. Мандат самопроверки ИСПОЛНЕНИЕМ (обязателен; самоотчёт не принимается)
Самоотчёт «проверено» в этом проекте регулярно оказывался ложным. Поэтому — названный механизм и
проверяемый артефакт:
1. **Открой каждую свою ссылку** и сверь цитату, дату, авторов и число (число — из таблицы
результатов, не из абстракта). **Ссылка, которую ты не открыл, из отчёта удаляется.**
2. **Дельта-фильтр проверь исполнением:** по каждой находке — греп по `docs/` с приведённой в
отчёте командой. Артефакт — колонка «чем проверено» в таблице находок.
3. **Адверсариальный проход по СВОЕЙ готовой работе.****Субагенты РАЗРЕШЕНЫ явно** — запускай,
веер и глубину выбираешь под предмет. ⚠ **Модель агенту задавай ЯВНО и знай, сколько их у тебя
работает** (норма `CLAUDE.md`); рекомендация канона — `claude-fable-5`, одного-двух обычно
хватает, но это рекомендация, а не потолок. Мандат агенту: «найди в этом отчёте отсутствующее и
противоречащее». Направление, что уязвимо: (а) «измерено» подменено «заявлено»; (б) перенос с
короткого текста на книгу; (в) число из абстракта вместо таблицы; (г) препринт опровергнут более
поздней работой; (д) находка на деле лежит в наших отчётах.
4. **Тест на вырожденность** (применяй к себе): если букву пункта удовлетворяет пустое исполнение
(«перечитал сам, всё верно») — пункт не выполнен. Нужен артефакт: список открытых URL, вывод
грепа, вердикты субагентов.
5. Каждую пойманную собой ошибку **запиши в отчёт** с классом ошибки (§8 п.5).
## 8. Выход — файл `docs/research/29-harness-topology-survey.md`
**Состав разделов 15 — «делай РОВНО так»** (по нему пойдёт приёмка); **порядок находок внутри
раздела 2, глубина каждой и объём — «реши сам и аргументируй».**
1. **«УЖЕ ПОКРЫТО»** — таблица §5.0. Первым разделом, без исключений.
2. **Находки** по осям §5.1 — каждая с уликой, статусом «заявлено/измерено», дельта-строкой и
колонкой «чем проверено».
3. **Противоречия нашим выводам** (§4 п.5) — отдельным разделом, с обеими ссылками. Опровержение
нашего вывода ценнее нового факта.
4. ⛔ **«ЧЕГО НАЙТИ НЕ УДАЛОСЬ» и «ЧТО НЕ ПРОВЕРЕНО»** — обязательная секция. Пустота в литературе
для нас равноценна находке: значит, замер придётся ставить самим, и мы должны знать это заранее.
Сюда же — что ты не успел, не смог открыть, где источник платный.
5. **«ЧТО Я НАПУТАЛ»** — результат §7, с классом каждой ошибки.
**Записка-план перед работой. Делай РОВНО так.** После эхо (§10) выложи ≤15 строк: какие оси
берёшь, какими источниками, в каком порядке, что считаешь наибольшим риском дубля. **И сверь план
с заказом МЕХАНИЧЕСКИ** — таблицей «ось §5.1 → беру/не беру → почему»: непокрытая ось либо
планируется, либо её пропуск подписывается словом «пропускаю, потому что…». Это позволяет владельцу
поправить курс до того, как потрачено время.
**Не давай рекомендаций «внедрить X».** Факты, замеры, границы применимости — решение за
владельцем. Плотность, а не объём; шаблонные оговорки не нужны.
## 9. Канал вопросов и право возразить
Конфликт промта с кодом или доками ⇒ **пинг владельцу, НЕ самостоятельная интерпретация**. Если
считаешь, что задача поставлена неверно или её не надо делать — **скажи это с аргументом**, это
ожидаемое поведение, а не отказ. Не уверен в форме исполнения — спроси ДО работы, одной строкой.
## 10. Эхо-протокол старта (первое действие, ДО работы)
Выложи ≤10 строк: **скоуп** (что делаешь) · **инварианты** (§4, своими словами) · **не-делать**
(§11). Это проверка, что промт понят, и одновременно проверка канала (§12).
## 11. Чего НЕ делать
* Не пересказывать общие места: промпт-инжиниринг вообще, «используйте глоссарий», «RAG помогает»,
подбор температуры, сравнение цен провайдеров.
* Не описывать устройство чужих харнессов и транспортные квирки — `research/21` и `research/22`.
* Не пересказывать таксономии ошибок перевода — шесть файлов `12-*`.
* Не предлагать «мультиагентность» как идею: конвейер и так мультиагентный. Нужны КОНКРЕТНЫЕ
топологии с замерами.
* Не верить заголовкам: эрудиция ≠ улика. Вывод грунтуется ссылкой и цитатой либо помечается
«мнение». Аномалия проверяется у первоисточника, а не додумывается.
* Ничего не коммитить. Не читать `.env`.
## 12. Прямой канал между сессиями
Механизм описан в `CLAUDE.md` §«Связь между сессиями» — здесь не повторяется. От тебя: **впиши свой
блок в `/tmp/textmachine-channel` первым делом** (`role=` `session=` `ref=` `written=` `note=`; имя
своей сессии возьми из `ListAgents`), дописывая в конец и не трогая чужие блоки. Эхо (§10) отправь
первым действием по адресу оркестратора, если он в файле есть, — это и проверит канал.
**Адрес из файла — НЕ доказательство, что сессия жива:** файл переживает смерть сессии, а
`ListAgents` — нет. Сверься с `ListAgents` перед отправкой. И дословно: **нужной роли в файле нет,
файла нет или имя не отвечает ⇒ КАНАЛА НЕТ, и это НОРМАЛЬНЫЙ случай — НЕ опрашивай сессии подряд.**
Вопрос тогда — секцией в отчёт; владелец прочитает и пере-передаст.

View file

@ -0,0 +1,314 @@
# СЕССИЯ 2930.08 — ЖУРНАЛ И ПРОМТ ВОССТАНОВЛЕНИЯ ПОСЛЕ КОМПАКТА
> ⚠⚠ **ЭТОТ ФАЙЛ — ПРОМТ ДЛЯ СЕССИИ ПОСЛЕ КОМПАКТА.** Заведён по слову владельца 30.08 («контекст
> приближается к компакту, напиши себе скратчпад»). Читать ЦЕЛИКОМ, не грепом. Норма D39.121:
> сохраняются список изменённых файлов · незакрытые находки и диспозиции · обязательства и
> открытые вопросы · команды.
> ⚠ Персист, не scratch (норма владельца 01.08): файл лежит в зоне и коммитится, потому что
> эфемерный scratchpad умирает вместе с сессией.
## 0-ЧЕТЫРЕ. ⭐⭐ СОСТОЯНИЕ НА 01.09 — САМОЕ СВЕЖЕЕ, ЧИТАТЬ ПЕРВЫМ
**Твой план — `eval/dovodka/PLAN-01-09.md`, РЕДАКЦИЯ 4.** Это пре-регистрация, а не намерения.
Редакции 13 отвергнуты консилиумом; их разбор сохранён в §8 плана как класс ошибок.
**ЧТО ДЕЛАЕМ:** ратифицированную пробу редактора по ЧЕТЫРЁМ осям (D39.184 п.5 — ⚠ нота в `main`,
в `polygon` реестр обрывается на D39.170). Крест 2×2 внутри юнита: промпт по SHA × эффорт
`off`/`low`. Исполняется ДВИЖКОМ, отдельным профилем книги.
**ВЛАДЕЛЕЦ РЕШИЛ 01.09: ВАРИАНТ A — 17 ГЛАВ, ПОТОЛОК $9.5 (01.09 было $8.6; 02.09 поднят словом владельца под референсную клетку)** (дословно «17 глав разрешаю»).
⛔ Потолок в ПИКОВЫХ терминах и сессией НЕ ПОДНИМАЕТСЯ: срабатывание гарда = стоп и вопрос
владельцу, добор после чисел запрещён. К владельцу за этим решением ходить НЕ НАДО — оно принято.
**ДВЕ ДЫРЫ ПРИБОРА, без которых замер не измерит свой предмет:**
1. **Движок НЕ ВИДИТ размышление DeepSeek** (`provider_openai.go:23``ReasoningTokens stays 0`).
Лечение: `R̂ = completion_tokens 0.3644·len(response_text)`; коэффициент пере-снят независимо
(0.3644 ток/знак, n=16, sd 0.0069).
2. **У движка нет проводного артефакта** — только снапшот-провенанс (4 разных `snapshot_id`) и
сигнатура `Δprompt_tokens` (на en дважды ровно 79).
**КАК ИСПОЛНЯТЬ — §9 «ПРОЦЕДУРА» плана.** Там команды, коды выхода и три места, где домысливание
стоит денег. Читать его ДО всего остального в плане.
**Стенд обязан смотреть в `/home/ubuntu/projects/textmachine-main/`** для `models`,
`langpack_root` и `prompts_root`: в `polygon` НЕТ `backend/configs/langpacks/ru`, а загрузчик молча
возвращает умолчания — русский слой читателя потеряется тихо.
**Образец стенда** — `~/books/gu-zhenren/coldrun-v16/` (конфиги), рабочий каталог — НОВЫЙ.
**Резалка исходника** — `eval/dovodka/narezka.py` (её не существовало до 02.09; формат GB18030 +
CRLF + первая строка обязана быть заголовком).
**Мина нумерации:** движок не умеет смещение глав — срез 2642 пронумеруется как 117.
**ПОСТОЯННЫЙ КОНСИЛИУМ ПОДНЯТ И ЖИВ** — архитектор замера · скептик · казначей. ⭐ Не поднимать
новых: у них загружен контекст фазы, писать `SendMessage`. ⚠ После рестарта окружения `ListAgents` их НЕ вернёт — тогда поднимать заново с онбордингом (норма CLAUDE.md). Норма владельца 01.09: **один `fable`
на самое тяжёлое, остальные дешевле**.
**ОДИННАДЦАТЬ ОШИБОК ЗА ЧЕТВЕРО СУТОК, КЛАСС ОДИН** — показания инструмента, собственной памяти
или своей ветки приняты за факт о мире. Реестр — §Д52.5 и §8 плана. Из свежих и дорогих:
* «движок ручку не шлёт» — простояло сутки, разошлось по трём носителям и двум сессиям; на деле
`capability.go:236` шлёт `low|medium|high`, доказано НА ПРОВОДЕ;
* «контур ни разу не ездил целиком» — `coldrun-v16` прошёл НАКАНУНЕ (Д53);
* «канон мерен на 12 главах» — я сам намерил на 25 сутками раньше;
* якорь на строку со СТАРОЙ оценкой sd вместо строки с эрратой.
**ЛЕЧЕНИЕ, ВЫВЕДЕННОЕ ИЗ ОДИННАДЦАТИ:** отрицательный результат предъявляется с ПОЛОЖИТЕЛЬНЫМ
контролем; прежде «этого нет» — смотреть **каталог артефактов** (`ls ~/books/<книга>/`), а не только
доки своей ветки: три репозитория, два рабочих дерева, один общий каталог артефактов (Д53.4).
**ВИСИТ НА ВЛАДЕЛЬЦЕ:** (1) потолок пробы; (2) **слияние `polygon` → `main`** — весь отчёт фазы
(+2685 строк) и правки квирков только у нас, `main` до сих пор учит, что `low` на `pro` не работает;
оркестратор №22 (`textmachine-main-0e`) ждёт ответа и сам мержить не будет.
## 0-ТРИ. ⭐⭐ СОСТОЯНИЕ НА ВЕЧЕР 30.08 — САМОЕ СВЕЖЕЕ, ЧИТАТЬ ПЕРВЫМ
**Стадия 0 п.1 ИСПОЛНЕНА.** Разбор — **Д50 и Д51** в `docs/experiments/23-editor-tier.md`, прибор —
`eval/dovodka/bank.py` (в git намеренно), поправки к курсу — врезка в шапке `KONSILIUM-30-08.md`.
1. ✅ **Банк держит термины.** Взвешенная доля промахов CONFIRMED 0.231.29% при пороге 2%;
эффективная консистентность 99.3100%. Гипотеза в терминной половине НЕ ОПРОВЕРГНУТА.
2. ⭐⭐ **Главное найдено не замером, а на диске.** Лежат: более боевой прогон `rerun`; **БОЕВОЙ
профиль `rerun2-dspro` (редактор `deepseek-v4-pro`, 5 глав)**; июльский замер того же вопроса;
**отсуженная на КИТАЙСКОЙ паре тройка редакторов** (dspro 0.679 > glm 0.589 >> mistral 0.232).
3. ⭐⭐ **Ратифицированный вход фазы Д, висевший с 15.08, исполнен (Д51).** D39.117: жилец редактора —
вопрос ЦЕНЫ. D39.137: посылка «dspro ×2 дешевле glm» перевёрнута. Замер на одной нагрузке
собственными токенами: **в пике dspro дороже glm в 1.62.1×**, в офф-пик от ×1.07 дороже до ×0.81
дешевле. Дешёвый ход без смены модели — **гнать редакторскую волну в офф-пик** (÷2).
4. ⛔ **Пинги, сданные секцией (чужие зоны, сам не правил):** `pipeline-c1.yaml:65` и
`pipeline-arm-glm.yaml:6` обосновывают выбор устаревшим «×2 дешевле glm»; `docs/PROGRESS.md:610`
несёт снятую сильную форму «полей пола в банке нет вовсе, механизма не существовало».
5. ⛔ **ПЯТАЯ тихая поломка переезда:** июльские скрипты замера (`d10_consistency.py` и др.) были ВНЕ
git и погибли. Класс: **что вне git, то переезд убивает молча.**
6. ⚠ **Четыре ложные тревоги собственного прибора, пойманные чтением, — Д50.4.** Из них главная:
**прибор напечатал «гипотеза не опровергнута» на НУЛЕВОМ знаменателе**, когда парсер не понял
формат. Заведён гейт `assert_measured` (код 2). Плюс: непереведённые чанки · односимвольные
ханьские ключи вне автомата · регистр и чередование согласных.
⚠ И поправка к себе: «долг полигона по мн.ч. не выполнен» — НЕВЕРНО, есть `guzhenren-seed-v2.yaml`;
читал первый найденный файл, не проверив, единственный ли он.
**Остаток Стадии 0:** п.2 (разбор диффов по категориям) · п.3 (позиционный аудит панелей + латинский
квадрат) · п.5 (базис Жаккара) · п.6 (уборка). П.4 — греп исполнен, находка в п.4 выше.
## 0-БИС. ⭐⭐ ГЛАВНОЕ ПОСЛЕ КОНСИЛИУМА 30.08 — ОСТАЛЬНОЕ НИЖЕ ИСТОРИЧНО
**Твой курс лежит в `eval/dovodka/KONSILIUM-30-08.md`.** Там итог шести раундов с архитектором,
четырёх со скептиком и одного с разведкой: что установлено, что СНЯТО, план по стадиям, и что
осталось на владельце. Читать его целиком; всё, что ниже в этом файле, — состояние ДО консилиума.
**Начинать со Стадии 0.** ⚠ Её заголовок в консилиум-документе уточнён ревью: **«$0» держится**;
**«ноль агент-сессий» — для пп. 1, 3, 4, 5**, а классификация правок (п.2) есть труд суждения и
может стоить сессию (свободно 4 из 220); **«не требует решений владельца» — только если постоянный
приёмочный гейт остаётся ПРЕДЛОЖЕНИЕМ**, а не пунктом плана.
1. **аудит `~/books/gu-zhenren/acceptance/guzhenren-acc-a.db`** (130 чекпоинтов, из них 119
непустых; 25 глав движковым путём; глоссарий 49 записей). ⛔ **Профиль НЕ боевой:** редактор
`glm-5` с погашенным размышлением, прогон 10.07.2026, июльские промты.
**Он НЕ заменяет Шаг 7:** закрывает **терминную** половину Вопроса 0 на июльском профиле;
**голосовая половина не закрывается ПО ПОСТРОЕНИЮ**`first_person` и `speech` в глоссарии
пусты (0 из 49), а Д32.16 предупреждает, что без засева характер-листов замер даёт
ложноотрицательный результат;
2. **разбор диффов редактора по категориям** — канон/вёрстка/проза; если механических 80%, редактор
заменяется детерминированным слоем, и спор о ярусе закрыт бесплатно;
3. **позиционный аудит всех ~150 купленных панелей** + латинский квадрат по построению;
4. эрраты и уборка (списки — в консилиум-документе §5).
**Чего НЕ делать:** панели за $1015 с ранговым эндпойнтом · ручку размышления · Шаг 7 в прежнем
виде · резать редакторскую стадию решением сессии.
⚠ **Четыре вещи, которые сегодня оказались НЕ ТАК. Атрибуция честная, потому что «ищи ниже» уже
дважды показывало не туда:** «повторов нет» — записано **ниже по этому файлу**; «вдвое дешевле» и
«полей пола нет» жили в `docs/experiments/23-editor-tier.md` и сняты баннерами там; «платим за
переписывание неизменного» — в ПЕРВОЙ редакции консилиум-документа, снято там же.**
* «однопроходка вдвое дешевле связки» — **неверно**, ≈×1.3 (медиана двугорбого; книга есть сумма);
* «редактор выдаёт главу целиком ⇒ платим за переписывание неизменного» — **неверно**: видимая
глава лишь **7%** completion-токенов стадии, 93% это размышление; дифф-интерфейс режет ~$8 на
книгу у `pro`, а не $100;
* «в корпусе нет чистых повторов одного промта» — **их шестнадцать**, шум размышления **×2.8**,
и наши ×38 суть 2.5 sd, а не «1 из 500»;
* «полей пола в банке нет» — **верно только про эвал-банк**; движок поля имеет, и они **засеяны
сидом** (`source='seed'` 49/49). ⚠ Сам движок их не пополнял (ревизий 0), голосовые колонки
`first_person`/`speech` пусты, `until_ch`=0 у всех.
⚠ **И методическая мина, стоившая мне сегодня публикации ложного вывода:**
`difflib.SequenceMatcher` по умолчанию (`autojunk=True`) на текстах длиннее 199 знаков объявляет
«мусором» каждый символ чаще 1% — для русского это все частые буквы и пробел. Дистанция раздувается
вчетверо. **Всегда `autojunk=False`.**
## 0. КТО ТЫ И ГДЕ
Полигон-сессия TextMachine **с полномочиями оркестратора в зоне полигона** (слово владельца 29.08:
можешь коммитить). Зона записи: `eval/` + `docs/experiments/` + секция «Полигон» в `docs/PROGRESS.md`.
Ветка **`polygon`**, дерево держи чистым.
**Подпись коммитов.** 30.08 владелец сменил имя в `gitconfig` на `heaven <vojochatdev@gmail.com>`
и попросил перегенерить локальные коммиты ветки. Сделано `filter-branch`-ем по диапазону
`28c2d0f..polygon`: 35 коммитов пере-подписаны, **хеш дерева HEAD не изменился** (`24bb72e4…` до и
после — доказательство, что содержимое цело), авторские даты сохранены, `origin/polygon` остался
предком (push пройдёт без force). Бэкап: `~/tm-backup-2026-08-30/polygon-before-rewrite.bundle`,
старая история в `refs/original/refs/heads/polygon`.
## 1. ⛔ ЧТО БЕЖИТ ПРЯМО СЕЙЧАС — ПОКУПКА, САНКЦИОНИРОВАННАЯ И НЕ СДЕЛАННАЯ
**Владелец 30.08 дословно: «Да, по поводу дипсика — бери».**
Пре-рег **Д45** отчёта `docs/experiments/23-editor-tier.md`**заморожен коммитом `87dd124`**.
Арм `RNL` заведён в `rol.py` (`ARMS` + `THINK_LEVEL["RNL"]="low"`), провод прочитан вслух, промт
сверен побайтно с `RN` (`True`). Фильтр `--only=` добавлен, селфтесты зелены. **Клетки НЕ куплены.**
**Следующая команда, ровно она:**
```
eval/.venv/bin/python eval/dovodka/rol.py --buy en RNL --only=001e6ac4
```
**ПРАВИЛО РЕШЕНИЯ ПО ЭТОЙ ОДНОЙ КЛЕТКЕ (записано ДО неё, Д45.1) — не пере-открывать:**
* `reasoning_tokens` **< 5 000** ручка работает, покупаешь остальные 15: `--buy en RNL`;
* **≥ 10 000** ⇒ ручка НЕ работает, СТОП, 15 клеток не покупаются, пинг в квирк-бюллетень
«вендор-дока разошлась с поведением»;
* **5 00010 000** ⇒ СТОП и вопрос владельцу.
⛔⛔ **ПОКУПКА ЗАБЛОКИРОВАНА 30.08 — И НЕ ДЕНЬГАМИ.** `--buy` упал на `KeyError: 'DEEPSEEK_API_KEY'`.
Причина найдена и НЕ обойдена: **в `eval/.env` у ВСЕХ СЕМИ ключей пропали подчёркивания**
`DEEPSEEKAPIKEY` вместо `DEEPSEEK_API_KEY`, и так же `GEMINI`/`OPENAI`/`XAI`/`ZAI`/`KIMI`/`MISTRAL`.
Код читает `os.environ["DEEPSEEK_API_KEY"]` (`roster.py:41,45`), `load_dotenv` отрабатывает, но
имени такого нет. ⚠ Значения ключей я не читал и не читаю — снято только СПИСОК ИМЁН и признак
непустоты. **Файл владельца НЕ ТРОГАЮ, обёртку-переименовалку НЕ пишу:** это ровно тот молчаливый
workaround, который канон запрещает («ошибка тихо становится нормой»). Чинит владелец одной
правкой; после этого команда покупки — та же, что выше.
**Это ТРЕТЬЯ тихая поломка от переезда машины**, после путей `~/books` (65 вхождений в 31 файле)
и обнулённых mtime (Д35.2). Класс один: **переезд ломает окружение молча, и каждая поломка
обнаруживается только исполнением.**
**КЛЮЧИ ПОЧИНЕНЫ ВЛАДЕЛЬЦЕМ 30.08, ПРОБА КУПЛЕНА.** Все семь имён в `eval/.env` восстановлены,
живой вызов прошёл. **Результат пробы — СРЕДНЯЯ ПОЛОСА: `reasoning_tokens` = 7 748** (правило
5 00010 000) ⇒ **СТОП, пятнадцать клеток НЕ куплены, вопрос владельцу.** Разбор — Д45.5.
Коротко: ручка на `pro` ДЕЙСТВУЕТ (размышление ×2.52 вниз, цена ×2.16, промт побайтно тот же),
но не на порядок, как ждал пре-рег. Эхо не пере-вооружилось (кириллица 1.000, латиницы 0).
⚠ Денег на пачку впритык: свободно $0.247, оценка $0.180.56 (проба была ХУДШЕЙ главой панели).
⛔⛔ **ПОСЛЕ КОМПАКТА 30.08 ДОБАВЛЕНЫ СЕКЦИИ Д46 и Д47, И ОНИ МЕНЯЮТ ДИСПОЗИЦИЮ.**
1. ⭐ **Бесплатная улика важнее купленной.** При побайтно одинаковых `messages` вызов без параметра
дал `prompt_tokens` **3270**, с `low`**3191**: **79**, серверная реакция. Два контроля
детерминизма: замер 05.08 (дефолт и `low` дали ОДИНАКОВЫЕ 2124) и пред-полётные пробы рига
(`84 · 84 · 84 · 84`, четвёртая — 30.08, через смену сервинга). ⇒ **параметр ДОХОДИТ до модели**,
и это стоило $0. Квирк-бюллетень: новая запись **3г**, статус закрыт в части «доходит ли».
2. ⛔ **Пре-рег Д45 конфаундирован — и НЕ дрейфом, как я написал сначала, а СМЕНОЙ ВЕНДОРСКОГО
СЕРВИНГА** между 21.08 и 30.08 (её же зафиксировала вахта Д44). Дрейф — второй и слабее: у него
есть знак, и он работает против нуля. Лечение у обоих одно: **оба арма в ОДНОМ блоке, вперемежку**.
3. ⭐ **Неконфаундированный замер КУПИТЬ МОЖНО, и он вчетверо дешевле замороженного** — дизайн Д:
интерливинг `RN2` против `RNL` на восьми дешёвых главах, **$0.0780.133** при свободных $0.2472,
n=8, минимальный двусторонний p = 0.0078. Пре-регистрация — **Д47**, ⛔ **не куплено**.
⛔⛔ **Д47 ЗАПУЩЕН ПО СЛОВУ ВЛАДЕЛЬЦА («Запускай») И ОСТАНОВЛЕН ГАРДОМ ПОСЛЕ ПЕРВОЙ ЖЕ ПАРЫ.**
Списано **$0.134804**, ФД-N теперь 3.187585 из 3.30 (свободно $0.1124), потолок НЕ поднимался.
**Результат:** побайтно один промт, одна глава, ручка не слалась ни разу — 21.08 модель думала
**466** токенов, 30.08 — **17 757**, то есть **×38**. ⚠⚠ **Причина НЕ УСТАНОВЛЕНА** (Д47.7): по
лучшей оценке разброса (sd логарифма 0.82 ⇒ одно sd = ×2.3) это ≈3.1 sd — редко, но возможно;
«модель сдвинулась» и «выпал хвост» обе живы, чистых повторов одного промта на роли `onepass` в
корпусе НЕТ. ⛔ Устояло то, что важнее: **одиночные розыгрыши сравнивать нельзя ни при какой
гипотезе** — это осуждает и дизайн А, и мой дизайн Д. Внутри блока ручка дала ×1.40 (меньше, чем ×2.52 через девять дней — конфаунд эффект
РАЗДУВАЛ); годных пар 1 из 8, p=1.0 ⇒ **величина НЕ УСТАНОВЛЕНА**, добор глав запрещён. ⭐ Побочно:
`prompt_tokens` = 3256 и 21.08, и 30.08, а `low` двигает их ровно на 79 — Клейм 1 подтверждён на
боевом промте. ⭐ **Цена ответов посчитана (Д47.8):** эффект ×2.5 — 13 пар (≈$1.1), ×2.0 — 22 (≈$1.8),
×1.4 — 93 (≈$7.6); «сдвиг или кости» — 4 повтора без ручки, ≈$0.31. ⛔ **Приоритет не сюда:**
ручка даёт ×1.42.5, а смена модели на луну — ×711, и она упирается в кап сессий (нужно 16,
свободно 14), а не в деньги. Разбор — **Д47.6**,
носитель вывода — `eval/dovodka/stupen.py` (написан ДО клеток).
**Операционное:** отказ гарда возвращает код 1, но в ФОНОВОМ shell `set -e` цикл не остановил —
семь итераций отработали вхолостую ($0). Следующий цикл покупок несёт `|| break` в теле.
**Прежняя развилка (закрыта):** (а) пятнадцать клеток по фризу Д45 —
**НЕ покупать** ни при каком ответе; (б) Д47 — по его слову. **Величина среза остаётся НЕ
УСТАНОВЛЕННОЙ, и так и записана.**
⚠ **Первая редакция Д46 утверждала «ни один неконфаундированный дизайн не влезает» — ЭТО БЫЛО
НЕВЕРНО** (пропущена дешёвая восьмёрка). Сломал адверсариальный проход, разбор — Д46.6. Там же —
единственная находка прохода, которую я ОТКЛОНИЛ (корреляция по порядку вызовов: у всех 16 клеток
`RN` один mtime, порядка не существует).
**Зачем замер (одной фразой):** развести конфаунд Д44.6 — весь разрыв «`luna` дешевле якоря в 8.1×»
снят с якоря, которому ручку размышления **не слали вовсе** (= вендор-дефолт `high`), против
испытуемого на явном `low`. По норме Д28 это разные армы.
**Деньги:** касса ФД-N, потрачено $3.014505 при потолке $3.30, свободно **$0.285**, смета ~$0.16.
⛔ Потолок сессией НЕ поднимать. Гард остановит сам — порядок «одна клетка, потом пачка» для этого.
⛔ **Ранговый эндпойнт (слепое чтение) в этой сессии НЕ снимается и объявлен таким ДО покупки:**
он стоит 16 агент-сессий, свободно **14 из 200** (израсходовано 186). Кап — слово владельца 20.08,
поднимать решением сессии запрещено. Панель эмитируется и передаётся, либо владелец поднимает кап.
## 2. ЧТО СДЕЛАНО ЭТОЙ СЕССИЕЙ (секции отчёта — единственный носитель)
| секция | что там |
|---|---|
| **Д35** | переезд машины: окружение поднято · **mtime обнулены ⇒ гейты на mtime не пере-снимаются** · Gemini выбрасывает ПЕРВОЕ системное (улика `sysmsg.py`) |
| **Д36** | **Шаг 4 плана 22.08 ЗАКРЫТ**: вес ролевого промта ≈1.3 порога, и он на ПРОЗЕ, не на браке |
| **Д36.8а** | ⛔ блокер приёмки: **декой не оказался последним**, буква фриза требовала аннулировать панель — диспозиция за приёмкой |
| **Д37** | третий круг панели-0: **шум прибора 0.34 порога**, долг Д34.5 закрыт |
| **Д38** | ⭐ **ОТВЕТ на два главных вопроса владельца** + статус Вопроса 0 |
| **Д39** | сдача: сверка `--final` КРАСНАЯ, диагноз на каждую из трёх строк |
| **Д40** | приёмка исполнением своей же работы: 1 блокер + 6 важных, все с диспозициями |
| **Д41** | ⭐ деньги: связка $127/книга, **90% — редактор**, замена редактора → $20 |
| **Д42** | ⭐ сплошное чтение корпуса: «$11 за книгу» = июльский прайс · смена черновика = 6% · конфликт приборов |
| **Д43** | ⛔ **реестр потерянного**: ~25 намерений, растерянных между планами |
| **Д44** | ⭐ у якоря НЕ БЫЛО выключателя размышления · **живая вахта 30.08: вендор изменил таблицу, `low→low` теперь и у `pro`** · `temperature` на dspro молча игнорируется |
| **Д45** | пре-рег покупки (см. §1) |
## 3. ⛔ ОБЯЗАТЕЛЬСТВА И ВОПРОСЫ, ВИСЯЩИЕ НА ВЛАДЕЛЬЦЕ
0. **Ручка размышления: запускать ли Д47** — интерливинг на восьми дешёвых главах, $0.0780.133,
влезает в свободные $0.2472 без поднятия потолка. ⛔ Пятнадцать клеток по фризу Д45 не покупать
ни при каком ответе. Если Д47 не запускается — фаза закрывается на «параметр доходит (замерено),
величина среза не установлена (и записана как неустановленная)».
1. **Диспозиция по декою (Д36.8а)** — считать Шаг 4 закрытым по цели или пере-собирать панель по букве.
2. **Кап агент-сессий**: 186/200, ранговый эндпойнт Д45 требует 16.
3. **Шаг 7 (~$2 + движковый прогон)** — единственный прибор под Вопрос 0; в резерв $1.26 не влезает.
4. **Пере-выбор ЧЕРНОВОЙ роли** (`PLAN-17-08.md:105-119`) — его собственная развилка, потерянная
всеми планами; приз 6%, разбор в Д42.5а.
5. Прочее — Д43.1 (семь развилок).
## 4. ⛔ ЧЕГО НЕ ДЕЛАТЬ
* **Не править реестр `eval/dovodka/requirements.md`** — ни строки. Три красных в `--final` (R37,
R71, R77) диагностированы в Д39.2 как ПРОТУХШИЕ УЛИКИ, а не невыполненная работа. Правка улик в
момент сдачи = «дописать таблицу под результат».
* **Не чинить гейты под зелень** (`verify22.py` не тронут ни одним коммитом сессии — проверяемо).
* **Не пере-эмитировать существующие теги** (`p0`, `p4`, `arch2`, `vendor2`): раскладка есть функция
соли и НАБОРА армов, пере-эмиссия превращает уже полученные ответы в мусор.
* **Не поднимать потолки касс и кап сессий** решением сессии.
* **Не читать `.env`.**
* Не считать `cost_usd` в клетках списанными деньгами: `roster.cost()` вычисляет их из пина;
весь леджер фазы — оценка (Д32.11, долг В21 перенесён с доказательством невозможности).
## 5. КОМАНДЫ ⛔ ОСТОРОЖНО: ЭТО КОМАНДЫ РИГА, А НЕ ТЕКУЩЕЙ РАБОТЫ
⛔⛔ **НЕ НАЧИНАЙ ОТСЮДА.** Ниже — команды полигонного рига фазы Д (август), и одна из них помечена
«НЕ ЗАПУСКАТЬ НИКОГДА». Текущая работа — ДВИЖКОВАЯ проба; её команды в
`eval/dovodka/PLAN-01-09.md` §9 «ПРОЦЕДУРА». Сюда смотреть только за историей.
```
eval/.venv/bin/python eval/dovodka/rol.py --selftest # ОБЯЗАТЕЛЕН перед покупкой
eval/.venv/bin/python eval/dovodka/rol.py --wire en --arm=RNL
eval/.venv/bin/python eval/dovodka/rol.py --buy en RNL --only=001e6ac4 # ← пробная клетка
eval/.venv/bin/python eval/dovodka/rol.py --buy en RNL # ⛔ НЕ ЗАПУСКАТЬ НИКОГДА: дизайн А, конфаундирован
# Д47, ТОЛЬКО по слову владельца — интерливинг по главе, пара за парой, восемь глав из Д47.2:
# for u in b065a92dc0 5a8f48d24a 100b088f71 49ca859c94 8e50448cea c3517980c7 3bd6481182 26c3bff1d4; do
# rol.py --buy en RN2 --only=$u && rol.py --buy en RNL --only=$u ; done
eval/.venv/bin/python eval/dovodka/schet.py --report RNL RN RL # П2, $0
eval/.venv/bin/python eval/dovodka/money_d.py --report | --selftest
eval/.venv/bin/python eval/dovodka/runs.py --report
eval/.venv/bin/python eval/dovodka/sysmsg.py # улика квирка Gemini
eval/.venv/bin/python eval/conformance.py eval/dovodka/requirements.md --final
python3 docs/scripts/counts.py --lint | grep ✗ # якоря доков
```
⚠ Окружение на чистой машине: `ln -s <репо>/books ~/books` + venv (`eval/README.md`, врезка про переезд).
## 6. ЧТО Я НАПУТАЛ ЗА СЕССИЮ — чтобы не повторить
1. Объявил дефект движка живым, **не посмотрев в код** — он был закрыт лендингом `7d0c6f2` 28.08.
2. Напечатал контроль декоя пройденным **по критерию, которого не было во фризе** (блокер приёмки).
3. Свалил `luna` и `gemini` в одну строку «цензур-ось не мерена» — у gemini она **мерена и против него**.
4. Подал «однопроходка дешевле» без границы «прибор читает ОДНУ главу», хотя блокер про банк нашёл
сам владелец 16.08.
5. Написал «плана про луну нет» — план есть, `PLAN-17-08.md` §1б, и его потеряли все.
6. Чуть не позеленил гейт R77 тем, что **процитировал строку, которую гейт грепает**.
7. Три неточности чисел (знаки в матрице, описание метода класса Б, ложная точность) — поймал
адверсариальный пере-счёт.
**Общее у всех семи: вывод публиковался раньше, чем проверялся первоисточник.** Ловили их —
адверсариальные опровергатели и сплошное чтение корпуса, а не мои гейты.

397
eval/dovodka/bank.py Normal file
View file

@ -0,0 +1,397 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
bank.py АУДИТ УДЕРЖАНИЯ БАНКА на движковых прогонах 蛊真人 (zhru, 25 глав).
Стадия 0 п.1 плана `eval/dovodka/KONSILIUM-30-08.md` §5. Стоимость $0: только артефакты на диске.
ПОПРАВКА КУРСА, НАЙДЕННАЯ ИСПОЛНЕНИЕМ 30.08 ЧИТАТЬ ПРЕЖДЕ ЧИСЕЛ
Консилиум назначил аудит прогона `acceptance`, НЕ ЗНАЯ ДВУХ ВЕЩЕЙ:
1. РЯДОМ ЛЕЖИТ БОЛЕЕ ПОЗДНИЙ И БОЛЕЕ БОЕВОЙ ПРОГОН `rerun` (11.07, `~/books/gu-zhenren/rerun/`).
Он собран из боевого `pipeline-c1.yaml` post-D33 байт-в-байт: промты `v1-reflow` (не `v0-draft`),
редактор БИЛИНГВ (D30.1), переведены все 57 чанков (у `acceptance` 54 из 57). Движковых
промахов там 27 против 55. ОБЪЕКТ АУДИТА ОБА, и `rerun` важнее.
Боевым он всё равно НЕ является: редактор `glm-5`, а не `deepseek-v4-pro`.
2. ЭТУ РАБОТУ УЖЕ ДЕЛАЛИ В ИЮЛЕ, и результат сильнее плановой заявки. `docs/archive/
PROGRESS-2026-07-10-13.md:149` (замер B5 D10, независимый скрипт `rerun/d10_consistency.py`):
presence 91.0% (647/711) · occurrence 95.8% (3072/3207)
по типам: имена 98.6% · клички 100% · места 96.0% · термины 94.9% · ТИТУЛЫ 78.7%
промахи: decl_gap 0 · inflection_gap 54 · genuine_absent 10 эффективная 98.6%
Сами СКРИПТЫ не пережили переезд машины лежали ВНЕ git (`PROGRESS-2026-07-10-13.md:190`
перечисляет их поимённо; в каталоге остались только данные). Это ЧЕТВЁРТАЯ тихая поломка
переезда после путей `~/books`, обнулённых mtime и подчёркиваний в `.env`. Класс один:
ЧТО ВНЕ GIT, ТО ПЕРЕЕЗД УБИВАЕТ МОЛЧА. Этот файл в git намеренно.
Июльские числа здесь не пересказываются как свои: они ЭТАЛОН СВЕРКИ. Совпадение = две
независимые реализации через полтора месяца согласились. Расхождение = находка, а не шум.
ПРЕ-РЕГИСТРАЦИЯ ПРАВИЛО РЕШЕНИЯ ЗАПИСАНО ДО ПЕРВОГО ЧИСЛА
ЧТО МЕРЯЕМ. Вопрос 0 в его ТЕРМИННОЙ половине: удерживает ли банк канонные формы через 25 глав.
Голосовая половина им НЕ закрывается ПО ПОСТРОЕНИЮ колонки `first_person`/`speech` пусты 0/49
(Д32.16, консилиум §5 п.1).
ЕДИНИЦА пара (термин, чанк), в которой ключ термина СРАБОТАЛ в китайском оригинале чанка. Ровно
это условие включает инжекцию записи в промт, поэтому только такие пары и подсудны.
ДИСПОЗИЦИИ (`backend/internal/membank/memory.go:121,650-663` прочитано, не пересказано):
approved CONFIRMED (авторитетно) · auto/draft AMBIGUOUS (кандидат, обязательности нет).
Блок ограничений редактора CONFIRMED-only (`memory.go:745-750`).
ОСНОВНОЙ ЭНДПОЙНТ CONFIRMED. С AMBIGUOUS-записи спрашивать соблюдения нельзя: движок её
таковой не объявлял, и `mempostcheck.go:52-64` прямо зовёт наказание за неё инверсией контракта.
ПРИНЯТОЕ МНОЖЕСТВО ФОРМ = базовый `dst` `decl.forms`. Это РАТИФИЦИРОВАННОЕ D24.4, а не мой
выбор: проверка одних косвенных давала 37 ложных промахов из 55 на этих же данных (nominative_gap).
ТРИ СТРУКТУРНЫХ ИСКЛЮЧЕНИЯ каждое установлено кодом или данными, ни одно не «для красоты»:
1. ЧАНКИ БЕЗ ПЕРЕВОДА. Гейт покрытия пометил их флагом и не переводил вовсе; в параллельном
файле у них нет секции `[ПЕРЕВОД]`. Отсутствие термина там дефект ПОКРЫТИЯ, не банка.
Печатается отдельной строкой, а не заминается.
2. ОДНОСИМВОЛЬНЫЕ ХАНЬСКИЕ КЛЮЧИ без `allow_short` (, ). `memory.go:319` УДАЛЯЕТ такой ключ
из автомата записи не инжектировались НИКОГДА, спрашивать за них нельзя. (В живом коде есть
ещё и демот таких промахов, `mempostcheck.go:122`, но он от D39.39 ПОЗЖЕ этих прогонов.)
3. STICKY (scene-inertia, A5) ключа в чанке нет по построению, `mempostcheck.go:31-33`
исключает их сознательно. Тоже печатается числом.
ПРАВИЛО РЕШЕНИЯ (порог консилиума §5 п.1; двусмысленность его второй половины разрешена ЗДЕСЬ,
до чисел, и разрешение объявлено явно):
A. взвешенная доля промахов CONFIRMED > 2% «банк держит» ложна в сильной форме;
B. «любая сущность топ-50» двусмысленна записей всего 49, и буквальное чтение убивает гипотезу
одной случайной строкой. Читаю как проверку на СИСТЕМАТИЧЕСКИЙ провал ядра: термин из ТОП-10
по весу ключа с долей промахнутых чанков >= 20%.
Обе печатаются всегда и не пересматриваются после просмотра чисел.
КЛАССИФИКАЦИЯ ПРОМАХА (июльская номенклатура, чтобы числа были сравнимы):
decl_gap принятая форма есть, но её не увидели бы БЕЗ union D24.4 (контроль: должен быть 0);
inflection_gap основа dst присутствует в форме, которой в сиде НЕТ (обычно мн.ч.) читатель
видит верный термин, это дефект ЗАСЕВА, а не дрейф;
genuine_absent основы нет вовсе КАНДИДАТ дрейфа, требующий чтения глазами. НЕ дрейф сам по себе.
Граница признана честно: определитель основы здесь ЭВРИСТИКА общего префикса, а не морфология
(в движке стеммер настоящий). Поэтому `genuine_absent` объявляется КАНДИДАТОМ и подлежит чтению.
ДВА РЕЖИМА МАТЧИНГА, потому что эталоны построены по-разному:
native longest-match, как автомат движка (古月 внутри 古月漠北 не считается);
substring независимая подстрока на термин, как июльский скрипт: он этим и обошёл alias-слепое
пятно D24.2, поймав 古月Гуюэ ×2. Числа июля сравнивать нужно с ЭТИМ режимом.
ВТОРОЙ КОНТУР СВЕРКА С ДВИЖКОВЫМ ПОСТ-ЧЕКОМ (`postcheck_gate: false` флаггер пишет промахи в
`retrieval_state`). Расхождение ЛЮБОГО знака находка:
движок нашёл, я нет ложная тревога гейта (ожидаемо: базы писаны ДО фикса D24.4);
я нашёл, движок нет слепота гейта то, чего он не смотрит.
ЧЕГО ПРИБОР НЕ УМЕЕТ: он не читает смысл термин в правильной форме не на своём месте он засчитает.
"""
import argparse
import json
import re
import sqlite3
import sys
from collections import defaultdict
from pathlib import Path
RUNS = {
"acceptance": {
"db": Path.home() / "books/gu-zhenren/acceptance/guzhenren-acc-a.db",
"parallel": Path.home() / "books/gu-zhenren/acceptance/guzhenren-25ch-parallel.txt",
"book_id": "guzhenren-acc-a",
"note": "10.07, промты v0-draft, редактор glm-5 монолингв, 54/57 чанков переведены",
},
"rerun": {
"db": Path.home() / "books/gu-zhenren/rerun/guzhenren-rerun.db",
"parallel": Path.home() / "books/gu-zhenren/rerun/rerun-parallel.txt",
"book_id": None, # берётся из базы: имя книги у пере-прогона своё
"note": "11.07, промты v1-reflow, редактор glm-5 БИЛИНГВ, 57/57 чанков — ближе к боевому",
},
}
WEIGHTED_MISS_MAX = 0.02
TOP_N = 10
CORE_MISS_MAX = 0.20
# Эвристика основы для КЛАССИФИКАЦИИ промаха (на сам вердикт не влияет — метрики A и B считают
# промахи, а не классы, поэтому это число ничего подогнать не может).
# ⚠ 4 знака ЛОМАЮТСЯ НА ЧЕРЕДОВАНИИ СОГЛАСНЫХ: «очистить»[:4] = «очис», а в тексте стоит «очищает» —
# и верно переведённый термин уезжает в `genuine_absent`, то есть в кандидаты дрейфа. Поймано
# чтением текста 30.08 на 炼化 → «очистить и подчинить»: все 7 его «промахов» суть верный перевод
# при ПУСТОМ `decl.forms`. 3 знака чередование переживают; цена — более грубое сито.
STEM_MIN = 3
CHAPTER_RE = re.compile(r"^【 Глава (\d+) 】", re.M)
# ⚠ Флаг в скобках есть у `acceptance` и ОТСУТСТВУЕТ у `rerun` (`--- чанк 0 ---`). Первая редакция
# требовала скобок — и на `rerun` разобрала НОЛЬ чанков, после чего прибор напечатал «порог не
# пробит» и «гипотеза не опровергнута» НА ПУСТОМ ЗНАМЕНАТЕЛЕ. Отсюда же гейт `assert_measured`.
CHUNK_RE = re.compile(r"^--- чанк (\d+)(?: \[([^\]]*)\])? ---$", re.M)
HAN_RE = re.compile(r"[一-鿿]")
def load_glossary(con, book_id):
aliases = defaultdict(list)
for term_id, alias in con.execute(
"select term_id, alias from glossary_aliases where book_id=?", (book_id,)
):
aliases[term_id].append(alias)
rows = []
for (tid, src, dst, typ, status, gender, decl, allow_short) in con.execute(
"select id, src, dst, type, status, gender, decl, allow_short "
"from glossary where book_id=? order by id",
(book_id,),
):
forms = []
if decl:
try:
forms = [f for f in json.loads(decl).get("forms", []) if f]
except json.JSONDecodeError:
pass
keys = [src] + aliases[tid]
# Структурное исключение 2: односимвольный ханьский ключ без allow_short в автомат не попал.
eligible = [k for k in keys if allow_short or not (len(k) == 1 and HAN_RE.match(k))]
rows.append(
{
"id": tid, "src": src, "dst": dst, "type": typ, "status": status,
"gender": gender, "keys": eligible, "banned": [k for k in keys if k not in eligible],
"decl_forms": forms, "accepted": sorted({dst, *forms}, key=len, reverse=True),
"confirmed": status == "approved",
"stem": dst[:STEM_MIN] if len(dst) >= STEM_MIN else dst,
}
)
return rows
def split_chunks(text):
out = []
chapters = list(CHAPTER_RE.finditer(text))
for i, cm in enumerate(chapters):
ch = int(cm.group(1))
body = text[cm.end(): chapters[i + 1].start() if i + 1 < len(chapters) else len(text)]
marks = list(CHUNK_RE.finditer(body))
for j, km in enumerate(marks):
seg = body[km.end(): marks[j + 1].start() if j + 1 < len(marks) else len(body)]
translated = "[ПЕРЕВОД]" in seg
zh, ru = (seg.split("[ПЕРЕВОД]", 1) if translated else (seg, ""))
out.append((ch, int(km.group(1)), km.group(2) or "", zh.replace("[ОРИГ]", ""), ru,
translated))
return out
def assert_measured(what, n, ctx):
"""ГЕЙТ ПРОТИВ ЗЕЛЕНИ НА ПУСТОТЕ. Прибор, ничего не измеривший, обязан ОТКАЗАТЬ, а не напечатать
«порог не пробит»: пустой знаменатель читается как успех и ровно так и был прочитан 30.08, когда
парсер не понял формат `rerun` и вердикт «гипотеза не опровергнута» встал на нуле наблюдений."""
if n <= 0:
print(f"\n⛔⛔ ПРИБОР ОТКАЗЫВАЕТ: {what} = 0 ({ctx}). Вердикт НЕ выносится: "
"нулевой знаменатель — это не «в пределах», это отсутствие замера.")
sys.exit(2)
def count_keys(zh, terms, mode):
"""mode=native — longest-match (автомат движка); mode=substring — независимо на термин (июль)."""
hits = {}
if mode == "substring":
for t in terms:
n = sum(zh.count(k) for k in t["keys"])
if n:
hits[t["id"]] = n
return hits
occupied = bytearray(len(zh))
ordered = sorted(
(t for t in terms if t["keys"]),
key=lambda t: max(len(k) for k in t["keys"]), reverse=True,
)
for t in ordered:
n = 0
for key in sorted(t["keys"], key=len, reverse=True):
start = 0
while (p := zh.find(key, start)) >= 0:
if not any(occupied[p:p + len(key)]):
occupied[p:p + len(key)] = b"\x01" * len(key)
n += 1
start = p + 1
if n:
hits[t["id"]] = n
return hits
def engine_state(con, book_id):
st = {}
for ch, idx, n_miss, detail, sticky, spoil, evict, exact in con.execute(
"select chapter, chunk_idx, n_postcheck_miss, postcheck_detail, n_sticky, "
"n_spoiler_blocked, n_evicted, n_exact_hits from retrieval_state where book_id=?",
(book_id,),
):
items = json.loads(detail) if detail else []
st[(ch, idx)] = {
"confirmed_miss": {d["src"] for d in items if d.get("disp") == "confirmed"},
"ambiguous_miss": {d["src"] for d in items if d.get("disp") == "ambiguous"},
"n_miss": n_miss, "sticky": sticky, "spoiler": spoil,
"evicted": evict, "exact": exact,
}
return st
def classify(term, ru):
"""Промах уже установлен: ни одной принятой формы нет. Чем он является?"""
ru_low = ru.lower()
if any(f and f.lower() in ru_low for f in term["decl_forms"]):
return "decl_gap" # контроль: обязан быть 0 при union D24.4
if term["stem"] and term["stem"].lower() in ru_low:
return "inflection_gap" # основа есть, формы в сиде нет — дефект ЗАСЕВА
return "genuine_absent" # КАНДИДАТ дрейфа, читать глазами
def audit(run, mode, verbose):
cfg = RUNS[run]
con = sqlite3.connect(f"file:{cfg['db']}?mode=ro", uri=True)
book_id = cfg["book_id"] or con.execute("select book_id from glossary limit 1").fetchone()[0]
terms = load_glossary(con, book_id)
by_id = {t["id"]: t for t in terms}
chunks = split_chunks(cfg["parallel"].read_text(encoding="utf-8"))
engine = engine_state(con, book_id)
print("=" * 98)
print(f"АУДИТ УДЕРЖАНИЯ БАНКА — прогон `{run}` ({book_id}), режим матчинга: {mode}")
print(f" {cfg['note']}")
print(" правило решения — в шапке файла, записано ДО чисел")
print("=" * 98)
banned = sorted({k for t in terms for k in t["banned"]})
print(f"записей {len(terms)}: confirmed {sum(t['confirmed'] for t in terms)}, "
f"ambiguous {sum(not t['confirmed'] for t in terms)} | "
f"ключей вне автомата (single-Han ban): {', '.join(banned) or ''}")
print(f"чанков {len(chunks)}, из них переведено {sum(c[5] for c in chunks)}; "
f"строк retrieval_state {len(engine)}")
assert_measured("разобрано чанков параллельного файла", len(chunks),
f"формат {cfg['parallel'].name} не распознан")
assert_measured("переведённых чанков", sum(c[5] for c in chunks), "нечего проверять")
fired, fired_w, miss_ch, miss_w = (defaultdict(int) for _ in range(4))
klass = defaultdict(int)
kandidaty, rows = [], []
agree = only_engine = only_mine = 0
for ch, idx, flag, zh, ru, translated in chunks:
if not translated:
continue # структурное исключение 1
hits = count_keys(zh, terms, mode)
ru_low = ru.lower()
eng = engine.get((ch, idx), {})
eng_miss_all = eng.get("confirmed_miss", set()) | eng.get("ambiguous_miss", set())
for tid, n in hits.items():
t = by_id[tid]
fired[tid] += 1
fired_w[tid] += n
# ⚠ РЕГИСТР: сравнение регистро-НЕчувствительное. Прямое `in` дало ложный промах на
# 开窍 → «открытие апертуры» (гл.5/ч1): в тексте «Открытие апертуры удалось!» — термин
# верен, заглавная лишь начинает предложение. Поймано чтением 30.08.
held = any(f and f.lower() in ru_low for f in t["accepted"])
if not held:
miss_ch[tid] += 1
miss_w[tid] += n
k = classify(t, ru)
klass[(k, t["confirmed"])] += 1
if k == "genuine_absent" and t["confirmed"]:
kandidaty.append((ch, idx, t["src"], t["dst"], t["type"], n))
eng_miss = t["src"] in eng_miss_all
if not held and eng_miss:
agree += 1
elif eng_miss and held:
only_engine += 1
rows.append(("ЛОЖНАЯ ТРЕВОГА ГЕЙТА", ch, idx, t["src"], t["dst"], t["status"], n))
elif not held and not eng_miss:
only_mine += 1
rows.append(("СЛЕПОТА ГЕЙТА", ch, idx, t["src"], t["dst"], t["status"], n))
def block(conf, label):
ids = [i for i in fired if by_id[i]["confirmed"] == conf]
c_all, c_miss = sum(fired[i] for i in ids), sum(miss_ch[i] for i in ids)
w_all, w_miss = sum(fired_w[i] for i in ids), sum(miss_w[i] for i in ids)
print(f"\n--- {label} ---")
print(f" пар (термин, чанк) со сработавшим ключом : {c_all}")
print(f" из них промах (presence-провал) : {c_miss}"
+ (f" ⇒ presence {1 - c_miss / c_all:.1%}" if c_all else ""))
print(f" вхождений ключа всего / промахнуто : {w_all} / {w_miss}"
+ (f" ⇒ occurrence {1 - w_miss / w_all:.1%}" if w_all else ""))
return (w_miss / w_all) if w_all else 0.0, c_all, c_miss
weighted, conf_all, conf_miss = block(True, "CONFIRMED — ОСНОВНОЙ ЭНДПОЙНТ")
assert_measured("пар CONFIRMED со сработавшим ключом", conf_all,
"ни один подписанный ключ не сработал — матчер или данные не те")
block(False, "AMBIGUOUS — справка: обязательности движок не объявлял")
print("\n--- классификация промахов (июльская номенклатура, для сверки) ---")
for k in ("decl_gap", "inflection_gap", "genuine_absent"):
c, a = klass[(k, True)], klass[(k, False)]
note = {"decl_gap": "контроль union D24.4 — обязан быть 0",
"inflection_gap": "дефект ЗАСЕВА (мн.ч.), не дрейф",
"genuine_absent": "КАНДИДАТ дрейфа — читать глазами"}[k]
print(f" {k:<16} confirmed {c:>4} ambiguous {a:>4}{note}")
eff_num = conf_all - klass[("genuine_absent", True)] - klass[("decl_gap", True)]
if conf_all:
print(f" эффективная консистентность CONFIRMED = {eff_num}/{conf_all} = {eff_num / conf_all:.1%}"
" (читатель видит верный термин: инфлексия вне сида ему не мешает)")
print(f"\n--- метрика B: ядро (топ-{TOP_N} по весу ключа среди CONFIRMED) ---")
core = sorted((i for i in fired if by_id[i]["confirmed"]),
key=lambda i: fired_w[i], reverse=True)[:TOP_N]
core_fail = []
print(f" {'термин':<34}{'вес':>6}{'чанков':>8}{'промах':>8}{'доля':>7}")
for i in core:
share = miss_ch[i] / fired[i]
if share >= CORE_MISS_MAX:
core_fail.append(by_id[i]["src"])
print(f" {by_id[i]['src'] + '' + by_id[i]['dst']:<34}{fired_w[i]:>6}{fired[i]:>8}"
f"{miss_ch[i]:>8}{share:>6.0%}{'' if share >= CORE_MISS_MAX else ''}")
print("\n--- ВТОРОЙ КОНТУР: сверка с движковым пост-чеком ---")
print(f" согласие : {agree}")
print(f" ⚠ ложная тревога гейта (форма В ТЕКСТЕ есть): {only_engine}"
" ← ожидаемо: базы писаны ДО фикса D24.4")
print(f" ⚠ слепота гейта (промах, гейт молчит) : {only_mine}")
if verbose and rows:
print(f" {'класс':<24}{'гл':>4}{'чанк':>6} {'src':<12}{'dst':<30}{'статус':<10}{'вес':>5}")
for kind, ch, idx, src, dst, status, n in rows:
print(f" {kind:<24}{ch:>4}{idx:>6} {src:<12}{dst:<30}{status:<10}{n:>5}")
if kandidaty:
print(f"\n--- КАНДИДАТЫ ДРЕЙФА (genuine_absent, confirmed): {len(kandidaty)} — ЧИТАТЬ ГЛАЗАМИ ---")
print(f" {'гл':>4}{'чанк':>6} {'src':<12}{'dst':<30}{'тип':<10}{'вес':>5}")
for ch, idx, src, dst, typ, n in kandidaty:
print(f" {ch:>4}{idx:>6} {src:<12}{dst:<30}{typ:<10}{n:>5}")
print("\n--- зоны, которые прибор НЕ судит (печатаются, а не заминаются) ---")
print(f" чанков без перевода (гейт покрытия) : {sum(1 for c in chunks if not c[5])}")
print(f" sticky-переносов (инерция сцены, A5) : {sum(e['sticky'] for e in engine.values())}")
print(f" вытеснено бюджетом токенов : {sum(e['evicted'] for e in engine.values())}")
print(f" спойлер-блокировок : {sum(e['spoiler'] for e in engine.values())}")
print("\n" + "=" * 98)
a_fail = weighted > WEIGHTED_MISS_MAX
print(f"МЕТРИКА A: взвешенная доля промахов CONFIRMED = {weighted:.2%} при пороге "
f"{WEIGHTED_MISS_MAX:.0%}{'⛔ ПОРОГ ПРОБИТ' if a_fail else 'в пределах'}")
print(f"МЕТРИКА B: терминов ядра с долей промахов >= {CORE_MISS_MAX:.0%}: {len(core_fail)}"
f"{'' + ', '.join(core_fail) if core_fail else ''}"
f"{'⛔ ПОРОГ ПРОБИТ' if core_fail else 'в пределах'}")
print("ВЫВОД (строгий presence): гипотеза «банк держит» в СИЛЬНОЙ форме "
+ ("⛔ ЛОЖНА" if (a_fail or core_fail) else "не опровергнута"))
print("⚠ Границы: терминная половина Вопроса 0 · июльский профиль · редактор glm-5, не боевой "
"deepseek-v4-pro.")
print("=" * 98)
def main():
ap = argparse.ArgumentParser(description="аудит удержания банка, $0")
ap.add_argument("--run", choices=[*RUNS, "both"], default="both")
ap.add_argument("--match", choices=["native", "substring"], default="native",
help="native — автомат движка; substring — как независимый июльский скрипт")
ap.add_argument("--verbose", action="store_true", help="печатать построчную сверку с гейтом")
a = ap.parse_args()
for run in ([*RUNS] if a.run == "both" else [a.run]):
audit(run, a.match, a.verbose)
print()
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,362 @@
[
{
"alias": "ed01",
"kind": "null",
"prompt": "p9",
"chapter": 7,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "low2"
},
{
"alias": "ed02",
"kind": "null",
"prompt": "p9",
"chapter": 13,
"chunk": 0,
"arm_A": "low2",
"arm_B": "p9-low"
},
{
"alias": "ed03",
"kind": "real",
"prompt": "p9",
"chapter": 10,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off"
},
{
"alias": "ed04",
"kind": "real",
"prompt": "p9",
"chapter": 12,
"chunk": 1,
"arm_A": "p9-off",
"arm_B": "p9-low"
},
{
"alias": "ed05",
"kind": "null",
"prompt": "p9",
"chapter": 12,
"chunk": 0,
"arm_A": "low2",
"arm_B": "p9-low"
},
{
"alias": "ed06",
"kind": "real",
"prompt": "p7",
"chapter": 3,
"chunk": 1,
"arm_A": "p7-low",
"arm_B": "p7-off"
},
{
"alias": "ed07",
"kind": "real",
"prompt": "p9",
"chapter": 3,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off"
},
{
"alias": "ed08",
"kind": "real",
"prompt": "p7",
"chapter": 10,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low"
},
{
"alias": "ed09",
"kind": "real",
"prompt": "p9",
"chapter": 9,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-low"
},
{
"alias": "ed10",
"kind": "null",
"prompt": "p9",
"chapter": 8,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "low2"
},
{
"alias": "ed11",
"kind": "real",
"prompt": "p7",
"chapter": 8,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-off"
},
{
"alias": "ed12",
"kind": "real",
"prompt": "p7",
"chapter": 5,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low"
},
{
"alias": "ed13",
"kind": "null",
"prompt": "p9",
"chapter": 16,
"chunk": 0,
"arm_A": "low2",
"arm_B": "p9-low"
},
{
"alias": "ed14",
"kind": "real",
"prompt": "p7",
"chapter": 16,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-off"
},
{
"alias": "ed15",
"kind": "real",
"prompt": "p9",
"chapter": 8,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-low"
},
{
"alias": "ed16",
"kind": "null",
"prompt": "p9",
"chapter": 3,
"chunk": 1,
"arm_A": "p9-low",
"arm_B": "low2"
},
{
"alias": "ed17",
"kind": "real",
"prompt": "p9",
"chapter": 6,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-low"
},
{
"alias": "ed18",
"kind": "real",
"prompt": "p7",
"chapter": 9,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low"
},
{
"alias": "ed19",
"kind": "real",
"prompt": "p9",
"chapter": 1,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-low"
},
{
"alias": "ed20",
"kind": "real",
"prompt": "p9",
"chapter": 7,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-low"
},
{
"alias": "ed21",
"kind": "real",
"prompt": "p9",
"chapter": 2,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off"
},
{
"alias": "ed22",
"kind": "real",
"prompt": "p9",
"chapter": 3,
"chunk": 1,
"arm_A": "p9-low",
"arm_B": "p9-off"
},
{
"alias": "ed23",
"kind": "real",
"prompt": "p7",
"chapter": 3,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-off"
},
{
"alias": "ed24",
"kind": "null",
"prompt": "p9",
"chapter": 3,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "low2"
},
{
"alias": "ed25",
"kind": "null",
"prompt": "p9",
"chapter": 10,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "low2"
},
{
"alias": "ed26",
"kind": "real",
"prompt": "p7",
"chapter": 6,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-off"
},
{
"alias": "ed27",
"kind": "real",
"prompt": "p9",
"chapter": 16,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off"
},
{
"alias": "ed28",
"kind": "real",
"prompt": "p7",
"chapter": 7,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-off"
},
{
"alias": "ed29",
"kind": "real",
"prompt": "p7",
"chapter": 13,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-off"
},
{
"alias": "ed30",
"kind": "real",
"prompt": "p7",
"chapter": 2,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low"
},
{
"alias": "ed31",
"kind": "real",
"prompt": "p9",
"chapter": 15,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-low"
},
{
"alias": "ed32",
"kind": "real",
"prompt": "p7",
"chapter": 12,
"chunk": 1,
"arm_A": "p7-low",
"arm_B": "p7-off"
},
{
"alias": "ed33",
"kind": "null",
"prompt": "p9",
"chapter": 15,
"chunk": 0,
"arm_A": "low2",
"arm_B": "p9-low"
},
{
"alias": "ed34",
"kind": "real",
"prompt": "p7",
"chapter": 1,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low"
},
{
"alias": "ed35",
"kind": "real",
"prompt": "p9",
"chapter": 5,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off"
},
{
"alias": "ed36",
"kind": "real",
"prompt": "p9",
"chapter": 12,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off"
},
{
"alias": "ed37",
"kind": "real",
"prompt": "p7",
"chapter": 15,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low"
},
{
"alias": "ed38",
"kind": "real",
"prompt": "p7",
"chapter": 12,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low"
},
{
"alias": "ed39",
"kind": "null",
"prompt": "p9",
"chapter": 6,
"chunk": 0,
"arm_A": "low2",
"arm_B": "p9-low"
},
{
"alias": "ed40",
"kind": "real",
"prompt": "p9",
"chapter": 13,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off"
}
]

View file

@ -0,0 +1,332 @@
[
{
"pair_id": "p7-ch9-c0",
"kind": "real",
"prompt": "p7",
"chapter": 9,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low",
"alias": "tx30"
},
{
"pair_id": "null-ch8-c0",
"kind": "null",
"prompt": "-",
"chapter": 8,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-low",
"alias": "tx09"
},
{
"pair_id": "p9-ch12-c0",
"kind": "real",
"prompt": "p9",
"chapter": 12,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off",
"alias": "tx28"
},
{
"pair_id": "p7-ch8-c0",
"kind": "real",
"prompt": "p7",
"chapter": 8,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-off",
"alias": "tx05"
},
{
"pair_id": "p9-ch8-c0",
"kind": "real",
"prompt": "p9",
"chapter": 8,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-low",
"alias": "tx02"
},
{
"pair_id": "p7-ch5-c0",
"kind": "real",
"prompt": "p7",
"chapter": 5,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low",
"alias": "tx03"
},
{
"pair_id": "p9-ch16-c0",
"kind": "real",
"prompt": "p9",
"chapter": 16,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off",
"alias": "tx17"
},
{
"pair_id": "p7-ch16-c0",
"kind": "real",
"prompt": "p7",
"chapter": 16,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-off",
"alias": "tx06"
},
{
"pair_id": "p9-ch2-c0",
"kind": "real",
"prompt": "p9",
"chapter": 2,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off",
"alias": "tx10"
},
{
"pair_id": "p9-ch3-c0",
"kind": "real",
"prompt": "p9",
"chapter": 3,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off",
"alias": "tx18"
},
{
"pair_id": "p9-ch10-c0",
"kind": "real",
"prompt": "p9",
"chapter": 10,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off",
"alias": "tx15"
},
{
"pair_id": "p7-ch3-c0",
"kind": "real",
"prompt": "p7",
"chapter": 3,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-off",
"alias": "tx25"
},
{
"pair_id": "p9-ch12-c1",
"kind": "real",
"prompt": "p9",
"chapter": 12,
"chunk": 1,
"arm_A": "p9-off",
"arm_B": "p9-low",
"alias": "tx08"
},
{
"pair_id": "p7-ch1-c0",
"kind": "real",
"prompt": "p7",
"chapter": 1,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low",
"alias": "tx14"
},
{
"pair_id": "null-ch7-c0",
"kind": "null",
"prompt": "-",
"chapter": 7,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-low",
"alias": "tx22"
},
{
"pair_id": "p7-ch10-c0",
"kind": "real",
"prompt": "p7",
"chapter": 10,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low",
"alias": "tx21"
},
{
"pair_id": "p7-ch13-c0",
"kind": "real",
"prompt": "p7",
"chapter": 13,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-off",
"alias": "tx01"
},
{
"pair_id": "p7-ch7-c0",
"kind": "real",
"prompt": "p7",
"chapter": 7,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-off",
"alias": "tx33"
},
{
"pair_id": "p9-ch7-c0",
"kind": "real",
"prompt": "p9",
"chapter": 7,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-low",
"alias": "tx29"
},
{
"pair_id": "p9-ch3-c1",
"kind": "real",
"prompt": "p9",
"chapter": 3,
"chunk": 1,
"arm_A": "p9-low",
"arm_B": "p9-off",
"alias": "tx23"
},
{
"pair_id": "p9-ch15-c0",
"kind": "real",
"prompt": "p9",
"chapter": 15,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-low",
"alias": "tx16"
},
{
"pair_id": "p7-ch6-c0",
"kind": "real",
"prompt": "p7",
"chapter": 6,
"chunk": 0,
"arm_A": "p7-low",
"arm_B": "p7-off",
"alias": "tx20"
},
{
"pair_id": "null-ch13-c0",
"kind": "null",
"prompt": "-",
"chapter": 13,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-off",
"alias": "tx19"
},
{
"pair_id": "p7-ch2-c0",
"kind": "real",
"prompt": "p7",
"chapter": 2,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low",
"alias": "tx32"
},
{
"pair_id": "p9-ch6-c0",
"kind": "real",
"prompt": "p9",
"chapter": 6,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-low",
"alias": "tx13"
},
{
"pair_id": "p7-ch12-c1",
"kind": "real",
"prompt": "p7",
"chapter": 12,
"chunk": 1,
"arm_A": "p7-low",
"arm_B": "p7-off",
"alias": "tx12"
},
{
"pair_id": "p7-ch3-c1",
"kind": "real",
"prompt": "p7",
"chapter": 3,
"chunk": 1,
"arm_A": "p7-low",
"arm_B": "p7-off",
"alias": "tx31"
},
{
"pair_id": "p9-ch9-c0",
"kind": "real",
"prompt": "p9",
"chapter": 9,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-low",
"alias": "tx11"
},
{
"pair_id": "p7-ch15-c0",
"kind": "real",
"prompt": "p7",
"chapter": 15,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low",
"alias": "tx04"
},
{
"pair_id": "p9-ch13-c0",
"kind": "real",
"prompt": "p9",
"chapter": 13,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off",
"alias": "tx24"
},
{
"pair_id": "p7-ch12-c0",
"kind": "real",
"prompt": "p7",
"chapter": 12,
"chunk": 0,
"arm_A": "p7-off",
"arm_B": "p7-low",
"alias": "tx27"
},
{
"pair_id": "p9-ch1-c0",
"kind": "real",
"prompt": "p9",
"chapter": 1,
"chunk": 0,
"arm_A": "p9-off",
"arm_B": "p9-low",
"alias": "tx07"
},
{
"pair_id": "p9-ch5-c0",
"kind": "real",
"prompt": "p9",
"chapter": 5,
"chunk": 0,
"arm_A": "p9-low",
"arm_B": "p9-off",
"alias": "tx26"
}
]

View file

@ -0,0 +1,161 @@
# Пре-регистрация 2: слепое чтение на ИСПРАВЛЕННОМ материале + активные нули
**Заморожено 02.09 ДО первого суждения и ДО платного вызова.** Заменяет
`PREREG-BLIND-READ.md`, чьи семь эррат перечислены в `RESULT-BLIND-READ.md`.
Первая редакция дала счёт 18:6:6 — он НЕ является входом сюда и НЕ влияет на правила ниже.
## 1. Что чиним и зачем
Первая редакция сломалась в двух местах, и оба чиним здесь:
- **Э-1 (главное).** Судьям показывали черновой чанк 0, тогда как стадия `edit` СКЛЕИВАЕТ
несколько черновых чанков (35 draft-единиц против 20 edit-единиц). В 10 юнитах из 15 опора
покрывала лишь 5579% судимого текста, и линза точности на трети материала работала вслепую.
⇒ Здесь черновик = **склейка всех draft-чанков, покрывающих edit-единицу**, с гейтом покрытия.
**Это дефект СБОРКИ ПАКЕТА, а не базового прогона.** Проверено исполняющей сессией пробы по
финальным черновикам (35 чанков): `finish=stop` 9 · `banknote_export` 26 · `length` **0**
обрезанных среди отгруженных нет ни одного. Единственный обрыв черновой волны (гл. 17 чанк 0)
починил ретрай. Прежняя формулировка «усечённый черновик» могла читаться как обвинение
материалу — материал цел, неверно резал его мой сборщик.
**Границы юнита берём из МАНИФЕСТА ДВИЖКА**, а не из эвристики: `<project_db>.manifest.json`,
поле `chapters[].units[]` даёт `first_chunk_idx` и `chunk_count`. Юнит собирается из чанков
`first_chunk_idx … first_chunk_idx + chunk_count 1`; недостающий чанк ⇒ юнит ОТСУТСТВУЕТ,
а не склеивается частично.
**Это капкан СТЕНДА, а не разовая описка** (установлено сверкой с сессией пробы: тот же
дефект независимо возник у неё в ковариате длины входа, где `chunk_count > 1` у 15 юнитов из 20).
Ключ `(глава, индекс)` у edit-юнита и у draft-чанка СОВПАДАЕТ, поэтому неверная склейка не даёт
ни исключения, ни пустоты, ни бросающегося в глаза расхождения длин — только тихо неверное
число. В этой книге юнит редактуры почти всегда шире одного чанка черновика.
- **Э-5.** Первичный тест был математически невалиден (ничьи в знаменателе против H₀=50% дают
ошибку I рода 17.8%). ⇒ Здесь тест — **точная перестановка меток на уровне ЮНИТА**, которая
ничьи трактует корректно и разом решает вопрос зависимости пар.
## 2. Активные нули — контроль, которого не было
Прежний нулевой контроль сравнивал текст с его побайтной копией и доказывал лишь, что судья
узнаёт копию. Референсного распределения для перекоса у замера НЕТ.
**Здесь: 10 пар `low₁` против `low₂`** — двух независимых прогонов ОДНОЙ дешёвой руки (`p9-low`)
при `temperature: 0.4`. Это чистый шум генерации: рука, промпт, усилие, черновик, снапшот и
конфиг совпадают.
**Как получается `low₂`:** копия базы `p9-low.db`, из которой удалены артефакты стадии `edit`
(чекпойнты, `chunk_status`, статус `jobs`), после чего движок вызывается тем же конфигом. Кэш
пуст ⇒ вызовы уходят заново. Ничего, кроме кэша, не меняется; стадии `draft` и `terminology`
не трогаются вовсе. ⚠ `--resnapshot` НЕ применяется. Процедура и её проверка — §7.
## 3. Материал
Те же 15 юнитов, 30 боевых пар (15 × 2 промпта), `off` против `low` на одном промпте.
Плюс 10 нулевых пар `low₁`/`low₂`. Итого 40 материалов × 3 линзы = **120 судей Opus**.
Линзы, порядок вариантов, блочная балансировка позиций и обезличенные имена файлов — как в
первой редакции (там они отработали: ключ цел, 662 цитаты из 688 лежали в своей руке, 0 в чужой).
## 4. Первичные исходы — их ДВА, и они объявлены здесь
| | Линза | Роль |
|---|---|---|
| **P1** | **точность относительно черновика** | **блокирующий**: именно он был сломан Э-1 и именно он защищает тезис «не хуже» |
| **P2** | **художественность** | содержательный: в первой редакции нёс весь сигнал (19:8, p=0.052) |
**Тест для обоих:** точная перестановка знака метки руки на уровне юнита (2¹⁵ = 32768 масок),
двусторонняя, статистика — разность числа побед. **Поправка Холма на два первичных исхода.**
**Линза естественности — вторичная и описательная.** Она в первой редакции голосовала за более
далёкий от черновика вариант в 25 случаях из 28: это детектор величины правки, а не качества.
**«Консенсус 2 из 3» первичным исходом НЕ ЯВЛЯЕТСЯ** и публикуется только как описание. Причина
записана заранее: линзы не независимы (каппа естественность×точность = 0.32, согласие ниже
случайного), и мажоритарное правило усиливает одну ось вместо усреднения трёх.
**Полный список публикуемых сравнений** (фиксируется до чисел, иных не будет): P1, P2, линза
естественности, консенсус, гард нулей, позиционный биас, связь победы с длиной, связь победы с
близостью к черновику, покрытие черновика. Девять. Вторичные подаются только с пометкой, что
поправку они не проходили.
## 5. Правило решения — покрывает всё пространство исходов
Проверяется СТРОГО в этом порядке; первая сработавшая строка и есть исход.
| № | Условие | Заключение |
|---|---|---|
| **0** | Гард нулей провален (см. §6) | ⛔ **ПРИБОР НЕГОДЕН.** Боевые числа не читаются вовсе. |
| **1** | Гейт покрытия провален (§6) | ⛔ Материал негоден, замер не состоялся. |
| **2** | P1: `low` проигрывает по точности, Холм-p < 0.05 | **Цена качества ЕСТЬ.** Экономия платится содержанием. `low` в бой не ставить. |
| **3** | P2: `low` выигрывает, Холм-p < 0.05, и строка 2 не сработала | **`low` не хуже по точности и лучше художественно** на этом материале. Гипотеза сильная, но объёмный тест обязателен. |
| **4** | P1: `off` не выигрывает значимо И P2 не значим | **Различие не предъявлено.** Ни одна рука не показала превосходства на этом n. |
| **5** | P2: `off` выигрывает, Холм-p < 0.05 | **Дорогая рука лучше художественно.** Экономия имеет цену. |
**Ни один исход не означает «ставить `low` в бой».** Решение о бое требует ещё и проверки на
объёме, где живёт дрейф терминов и голосов — к нему этот прибор слеп по построению (§8).
## 6. Гарды (проверяются ДО чтения боевых чисел)
1. **Гард нулей.** На 10 парах `low₁`/`low₂` тем же тестом. Перекос с p < 0.05 по любой из двух
первичных линз ⇒ строка 0: прибор награждает шум, боевой счёт не читается.
Ожидание при исправном приборе: перевес около нуля, много ничьих.
⚠ **ДОПОЛНЕНО 02.09 ВО ВРЕМЯ РАБОТЫ ПАНЕЛИ, вслепую. Точный статус — ниже, потому что я
сначала записал его неверно и исправляю себя.** Панель стартовала в 21:49:10 (это время
СОЗДАНИЯ файла задачи, нулевой длины, а не результата — первая редакция этой врезки приняла
его за время результата и объявила дополнение «не пре-регистрацией»; неверно). Дополнение
закоммичено в 21:59:40, когда панель ещё шла: на 22:14 журнал держал 96 вердиктов из 120.
Часть вердиктов к моменту коммита уже лежала на диске, но НЕ читалась — файл результата пуст,
журнал до этого момента не открывался.
⇒ Статус: **поправка, написанная вслепую в ходе набора данных.** Это не полный фриз до старта
панели, и как пре-рег строгого смысла не засчитывается; но и «после чисел» неверно — чисел
автор не видел. Повод (замечание сессии пробы: как отличить «прибор врёт» от «нулей мало»)
силы не теряет. Посчитано: при n=10 порог p<0.05 достигается ТОЛЬКО на
раскладах 9:1 и 10:0; расклад 8:2 даёт p=0.109 и гард его пропустит. Мощность: если прибор
реально награждает шум с силой боевого перекоса первой редакции (75%), гард сработает лишь в
**≈25% случаев**. Поэтому:
- **Непрохождение гарда НЕ ЕСТЬ доказательство исправности прибора.** Формулировка «нули
чисты» без оговорки о мощности запрещена.
- **Основной диагностический критерий — не p, а СРАВНЕНИЕ С БОЕВЫМИ.** Считаются две величины:
(а) доля определившихся (не-ничьих) на нулях против доли на боевых парах — точный тест
Фишера на таблице 2×2; (б) перекос среди определившихся на нулях против боевого.
- **Толкование:** если судьи объявляют победителя на чистом шуме так же охотно, как на разных
руках (Фишер p ≥ 0.05, то есть разницы в доле ничьих НЕ видно), это улика, что прибор не
различает шум и сигнал, — **даже когда сам перекос на нулях незначим**. Такой исход
переводит результат в «ослаблен»: боевые числа публикуются с явной пометкой, что часть
сигнала может быть шумом, и вывод строки 3 не объявляется.
- **Дополнительный порог:** если перекос на нулях среди определившихся ≥ 0.5 боевого перекоса,
это то же предупреждение независимо от p.
- Эти критерии диагностические; блокирующей остаётся только строка 0 с p < 0.05.
2. **Гейт покрытия.** Для КАЖДОГО материала: доля текста варианта, покрытая показанным черновиком,
≥ 0.90 (выравнивание по словам). Материал ниже порога в пакет не идёт. Прежние 5579%
не прошли бы этот гейт ни разу.
3. **Гейт слепоты, исправленный после Э-3:** одиночные латинские буквы тоже считаются (прежний
искал `[A-Za-z]{2,}` и пропустил 2325 букв на руку). Порог — не ноль, а СИММЕТРИЯ: разница
между руками ≤ 20%, иначе рука опознаваема.
4. **Ключ отложен**, судьям уходит только текст; md5 ключа фиксируется здесь и сверяется после.
5. **Позиции сбалансированы блочно**, 7 юнитов из 15 ставят дорогую руку первой на каждом промпте.
## 7. Процедура `low₂` и её проверка (исполняется ДО судейства)
1. `cp p9-low.db low2.db` — работаем только с копией.
2. Удалить из копии артефакты стадии `edit`; `draft`, `terminology`, снапшоты и банк не трогать.
3. Сверить ДО вызова: снапшот `edit` совпадает с `p9-low`, `memory_version` совпадает,
`draft`-тексты совпадают побайтно. Расхождение ⇒ остановка, вызовов не делать.
4. Потолок: `--ceiling-usd` на базу + резерв одной волны; ожидаемая цена ≈ **$0.66** по факту
`request_log` первой редакции. Расход сверх $1.20 ⇒ остановка и доклад владельцу.
5. После прогона сверить: `draft`-тексты не сдвинулись; `edit`-тексты ОТЛИЧАЮТСЯ от `low₁`
(иначе кэш не был сброшен и «активный ноль» вырожден — тогда гард нулей не исполнен, строка 0).
## 8. Чего замер не даст (унаследовано и не чинится здесь)
- **Дрейф терминов и голосов между главами** — пары судятся изолированно; прибор слеп.
- **Воспроизводимость дорогой руки:** `reasoning: "off"` не эмитит параметр, едет вендор-дефолт,
который DeepSeek двигал 31.07 и 30.08; фактическое усилие в артефактах не записано.
- **Неравный потолок вывода:** мысль биллится внутри `completion_tokens`, поэтому дорогая рука
упиралась в лимит в 10 и 9 клетках из 20 против 1 и 1. Ось остаётся грязной в пользу `off`.
- **Переносимость:** 0.63% книги, холодный банк, 13 из 15 юнитов — начала глав.
- **Планка владельца ≤2 претензии (D39.20)** этим прибором неизмерима: счётчик дефектов кэпится
на 4 (Э-6). Не публикуется вовсе.
- **Мощность:** n=30 при перестановочном тесте различает сдвиг примерно от 25 п.п. Отрицательный
результат меньшую разницу не исключает.

View file

@ -0,0 +1,79 @@
# Пре-регистрация: слепое парное чтение — цена сниженного усилия редактора
**Заморожено 02.09 ДО первого суждения.** Правило решения, пороги и гарды записаны раньше чисел.
Материал: проба `prereg-4axes` (см. `docs/experiments/23-editor-tier.md` §Д54). Судьи — Opus.
## 1. Вопрос
Проба ответила на цену: снижение усилия режет размышление ×3.5 и боевую цену знака ×2.5.
Качество при этом **не судилось** — вне скоупа по построению (§12 промта пробы).
**Вопрос замера:** стал ли текст хуже, когда редактор думал вчетверо меньше?
⚠ Это вопрос о РЕДАКТУРЕ, не о переводе целиком: черновик у всех рук идентичен побайтно
(проверено, sha текста совпадает по 66 клеткам), различие вносит только стадия `edit`.
## 2. Материал
15 юнитов, у которых финальный текст (`chunk_status.final_hash`) непуст у ВСЕХ четырёх рук
deepseek. Юниты с браком выпали по построению: 4 таймаута `p7-off` + 1 пустой `p9-off`.
`glm-off` ИСКЛЮЧЁН: опознаётся по латинице (6 вхождений «cultivation»), слепота по нему
невозможна, и ценовым контролем он в качество не проектировался.
**30 боевых пар** = 15 юнитов × 2 промпта (p7, p9). В каждой паре — `off` против `low` на ОДНОМ
промпте, то есть чистая ось усилия. Плюс **3 нулевых контроля**, где A и B — один и тот же текст.
## 3. Что видит судья
Черновик (общий вход редактора) + два отредактированных варианта под метками A и B. Порядок
рандомизирован блочно: высокое усилие стоит на позиции A в 7 юнитах из 15 на каждом промпте.
Судья не знает ни рук, ни того, чем варианты различаются, ни что различие вообще существует.
Три независимых судьи на пару, разные линзы: **естественность русского** · **точность
относительно черновика** · **художественность и голос**. Консенсус — большинство 2 из 3.
## 4. Первичный исход и тест
**Исход:** доля из 30 пар, где консенсус судей предпочёл руку высокого усилия.
**Тест:** двусторонний биномиальный против H₀ = 50%. Ничьи в знаменатель входят, в числитель нет.
Кластеризация признана ограничением: p7 и p9 на одном юните делят черновик, 30 пар — не 30
независимых наблюдений. Поэтому вторичный анализ — по 15 юнитам (пара засчитывается юниту, если
оба промпта согласны).
## 5. Правило решения (записано до чисел)
| Исход | Что заключаем |
|---|---|
| **E-A** доля за `off` ≥ 70% И p < 0.05 | Экономия имеет цену качества. `low` в бой не ставить без доработки. |
| **E-B** доля за `off` в [40%; 70%] ИЛИ p ≥ 0.05 | **Различие не предъявлено** на этом n. `low` допустим к проверке на объёме — но «не предъявлено» ≠ «доказано равенство». |
| **E-C** доля за `low` ≥ 70% И p < 0.05 | Сниженное усилие ЛУЧШЕ. Требует объяснения, до него в бой не ставить. |
| **E-D** нулевые контроли дали победителя в ≥2 из 3 | ⛔ **ПРИБОР НЕГОДЕН.** Результат не читается, что бы ни показали боевые пары. |
**Мощность честно:** n=30 при трёх судьях различает сдвиг примерно от 25 процентных пунктов.
Разницу в 510 п.п. этот замер не увидит, и отрицательный результат её не исключает.
## 6. Вторичные исходы (не решают, но записываются)
- **Претензии на юнит** — планка владельца из D39.20 (≤2 на главу). Считаем среднее по рукам.
- **Классы дефектов** — что именно судьи называют; сырьё для будущих детерминированных гейтов.
- **Позиционный биас** — доля побед позиции A независимо от руки. Отклонение от 50% на боевых
парах при чистых нулевых контролях = предупреждение, не отмена.
## 7. Гарды
1. **Гейт слепоты пройден до сборки:** ни латиницы, ни CJK, ни служебных меток — 0 на 60 клетках.
2. **Ключ отложен** в `KEY-do-not-show-judges.json`; судьям уходит только `pairs-blind.json`.
3. **Нулевой контроль** ловит судью, который обязан выбрать победителя (см. E-D).
4. **Позиционный баланс** блочный, а не случайный — рандомизация первой редакции дала 10/30.
5. Seed рандомизации `20260902` зафиксирован до сборки пакета.
## 8. Чего замер НЕ доказывает
- Не сравнивает `glm-5` с deepseek — он исключён.
- Не судит ось промпта: и E3 пробы (p=0.487), и обе текстовые меры (p=1.000, p=0.107) говорят
«ниже разрешения». Пары внутри промпта, а не между ними.
- Не измеряет верность китайскому оригиналу: исходник в снапшоте не хранится, опорой служит
черновик. Искажение, унаследованное от черновика, обеими руками унаследовано одинаково.
- Не заменяет чтение владельцем. Судья-модель — прибор, а планка «≤2 претензии» — его.

View file

@ -0,0 +1,108 @@
# Результат слепого чтения + эрраты к пре-регистрации
**Замер исполнен 02.09.** 99 судей Opus, 33 материала × 3 линзы, 0 ошибок агентов.
Затем — адверсариальная атака шестью линиями (Opus) по своей же работе. Ниже итог ПОСЛЕ атаки:
всё, что она сломала, помечено и исправлено в формулировках, а не спрятано.
## 1. Что устояло
| Улика | Число |
|---|---|
| Консенсус (2 из 3 линз) | **low 18 · off 6 · ничьих 6** из 30 |
| Точная перестановка с переворотом метки на уровне ЮНИТА (2¹⁵ масок) | **p = 0.0332** |
| Нулевые контроли (A и B — побайтная копия) | 9/9 голосов «ничья», 9/9 опознали идентичность |
| Позиционный биас | 35 : 44, p = 0.368 — направлен ПРОТИВ находки |
| Победитель длиннее | 9/24, p = 0.31 |
| Цитаты судей (688 штук) | 662 только в своей руке, **0 в чужой** |
| Ключ после судейства | md5 сходится с замороженным — не переписан |
**Цена по факту `request_log` (не по смете):** редактура `p7-off` $2.0000 против `p7-low` $0.6727
(**×2.97**); `p9` — $1.5917 против $0.6559 (×2.43). Дороже, чем считала фаза.
**Все четыре дефекта сборки смещают замер В ПОЛЬЗУ дорогой руки** (см. §3): выбывшие юниты —
ровно объединение провалов `off`; ретраи 11/30 против 2/30; санитайзерный ремонт только у `off`;
потолок вывода душит только `off`. Значит перевес дешёвой руки скорее **занижен**.
## 2. Что рухнуло — и это надо говорить вслух
1. **«Консенсус трёх линз» — неправда.** Консенсус совпадает с линзой художественности в 25 из 30
пар, с естественностью в 21, с точностью в 13. Художественность в одиночку даёт 19:8,
**p = 0.0522 — своего же порога не берёт**. Естественность и точность согласны лишь в 6 парах
из 22, где обе определились (каппа 0.32: согласие НИЖЕ случайного). Правило «2 из 3» здесь не
усредняет три взгляда, а усиливает один.
2. **«По точности не хуже» — НЕ ИЗМЕРЕНО.** См. эррату Э-1: в 10 юнитах из 15 линза точности не
имела опоры на 2145% текста. Там, где черновик был полным, точность даёт low 6 : off 3;
где усечён — 7 : 8. Обе выборки слишком малы.
3. **«Различаются ТОЛЬКО усилием» — ложно на уровне судимого артефакта** (§3).
4. **Правило решения §5 не исполнено.** Доля бралась из n=30 (60% < 70%), а p из n=24 (0.0227).
При последовательном n=24 срабатывает **E-C**, чья диспозиция — «*до объяснения в бой не
ставить*». При буквальном n=30 не срабатывает ни одна ветка. Вывод: **действующего
пре-регистрированного p у замера нет**, и «low можно в бой» из него не следует.
## 3. Эрраты к `PREREG-BLIND-READ.md` (ошибки автора, не правки под результат)
**Э-1 · §3 — судьям показан УСЕЧЁННЫЙ черновик.** Стадия `edit` склеивает несколько draft-чанков
(35 draft-единиц против 20 edit-единиц), а сборщик вклеил только чанк 0. Проверено исполнением:
показано 5579% текста в **10 юнитах из 15**; худший случай гл.7 — 55%. Судьи это видели и
штрафовали варианты за «сцену, которой в черновике нет» — фантомная отсебятина.
⇒ линза точности на трети текста работала без эталона.
**Э-2 · §2 — «4 таймаута p7-off» неверно.** Таймаутов в БД нет ни одного. Это `decode_error`
оплаченные ответы с нечитаемым телом, каждый ПОСЛЕ клетки, упёршейся в потолок 16000/`length`.
**Э-3 · §7.1 — «ни латиницы: 0 на 60 клетках» неверно.** Гард искал `[A-Za-z]{2,}` и пропускал
одиночные буквы: их 2325 в КАЖДОЙ руке («категория B», «категория C»). Асимметрии нет (48 у `off`
против 47 у `low`), **слепота не пробита**, но декларация была ложной.
**Э-4 · §2 — «юниты с браком выпали по построению» неполно.** Клетка гл.2 в обеих `off`-руках
помечена `sanitizer_stripped` (вырезан 公然勒索 из русского текста) и всё равно ушла судьям —
отремонтированной. Обе пары дали ничью, на счёт не влияет; но брак дорогой руки до судей не дошёл.
**Э-5 · §4 — первичный тест математически невалиден.** «Ничьи в знаменатель, не в числитель» плюс
H₀=50% даёт под истинной нулевой ожидаемую долю (1t)/2, а не 50%. При наблюдённой доле ничьих 0.20
ошибка I рода — **17.8%**, а не 5%.
**Э-6 · §6 — вторичный исход не исполнен.** Счётчик претензий кэпится: за 99 вердиктов ни одного
значения вне {2,3,4}. Средние 3.82 против 3.69 — разность двух чисел, прижатых к потолку;
сопоставление с планкой владельца ≤2 (D39.20) этим прибором невозможно.
**Э-7 · механизм «low правит смелее» СНЯТ.** Я озвучил близость к черновику 0.65 против 0.79.
Знаковый тест по 30 парам: `off` ближе в **19**, `low` в 11, **p = 0.200**. Свойством руки это не
является; разброс порождён Э-1 (близость мерилась против усечённого эталона).
## 4. Чего замер не даёт (сверх §8 пре-рега)
- **Фактическое усилие дорогой руки нигде не записано.** `reasoning: "off"` не эмитит параметр —
едет вендор-дефолт, который DeepSeek двигал 31.07 и 30.08. `reasoning_tokens` = 0 во всех
вызовах. Замер **не воспроизводим** в строгом смысле: сравнивали с движущейся мишенью.
- **Дорогая рука судилась задушенной:** `max_output_ratio: 2.2` одинаков у обеих, но мысль
биллится внутри `completion_tokens`; 10 и 9 клеток из 20 упёрлись в потолок против 1 и 1 у `low`.
- **Нулевой контроль вырожден.** Он доказал лишь, что судья узнаёт побайтную копию. Референсного
распределения для 18:6 нет: неизвестно, какой перекос даёт ЧИСТЫЙ шум одной руки при
`temperature: 0.4`.
- **Переносимость:** 0.63% книги, банк холодный, 13 из 15 юнитов — начала глав. Сшивка через
границу чанка и удержание глоссария — то, ради чего усилие покупают на длинной книге, —
представлены двумя юнитами. **Парное чтение изолированного куска слепо к дрейфу терминов и
голосов по построению** — а это цель №1 владельца.
## 5. Честная формулировка результата
> На 15 юнитах из начала книги дешёвая рука в парном слепом чтении выигрывает заметно чаще
> (18 : 6, кластер-робастный p = 0.033). Направление устойчиво к позиции, длине и величине правки,
> а все найденные дефекты сборки играют против него. Но пре-регистрированный порог не взят ни в
> одном честном прочтении, «согласие трёх линз» отсутствует, равенство по точности не измерено, а
> дорогая рука ехала на вендор-дефолте под потолком вывода.
>
> **Это рабочая гипотеза, а не решение о переводе `low` в бой.**
## 6. Следующая проверка (одна, дешёвая)
Пере-судейство тех же 30 пар на **полном склеенном черновике** (чинит Э-1 и возвращает опору
линзе точности) плюс **10 «активных нулей» low₁ против low₂** — второй независимый прогон дешёвой
руки при той же температуре. Если шум одной руки тоже даёт перекос вроде 7:2 — прибор негоден, и
это надо знать до любых денег на объёме.
Цена: один дешёвый `edit`-прогон ≈ $0.67 по факту логов, правок Go не требует; панель +20%.
**Условие: пере-регистрация ДО чисел** — один фиксированный знаменатель, одна первичная линза,
заранее объявленный список публикуемых сравнений. Иначе воспроизведём ту же свободу, из которой
взялась Э-5.

File diff suppressed because it is too large Load diff

915
eval/dovodka/chetyre.py Normal file
View file

@ -0,0 +1,915 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""chetyre.py — прибор контролируемой пробы редактора по четырём осям.
ЗАКОН: eval/dovodka/PLAN-01-09.md (пре-рег, фриз документа dc602e9). Этот файл ИНСТРУМЕНТ,
он реализует правило решения §4, а НЕ выбирает его. Всякий порог здесь запинен ДО чисел.
ГЛАВНЫЙ ГАРД ФАЙЛА: прибор ОТКАЗЫВАЕТ (код 2), а не печатает «в пределах», когда знаменатель
пуст. «Зелень на пустоте» способ, которым замер молча превращается в ничто.
РЕКОНСТРУКЦИИ, НАЗВАННЫЕ ЯВНО (каждая место, где прибор мог бы соврать)
1. НОМЕР ПОПЫТКИ. В `request_log` колонки `attempt` НЕТ (проверено `.schema`; положительный
контроль она есть в `checkpoints`). Реконструкция: `request_log LEFT JOIN checkpoints ON
request_hash`. Проверено на холодном прогоне: 4 из 5 edit-строк находят чекпойнт, пятая
`context canceled` с нулевой ценой и пустым request_hash. строки БЕЗ чекпойнта считаются
вызовами-без-ответа: они входят в деньги и в счётчик, но не несут попытки и не дают клетки.
2. «ДОСТАВЛЕНО» (`delivered`). Меряется на ОТГРУЖАЕМОМ тексте, а не по имени флага.
Отгружаемый текст = `checkpoints.response_text` по `chunk_status.final_hash` (final_hash может
быть синтетическим `tm-sanitized-v1:<sha>` он ТОЖЕ лежит в checkpoints, проверено).
delivered = 0 диспозиция `skipped`, ИЛИ final_hash не резолвится, ИЛИ отгружаемый текст не
проходит $0-предикат доставки: доля кириллицы среди букв < 0.99 либо в тексте есть CJK.
Иначе delivered = длина отгружаемого текста в знаках.
ПОЧЕМУ НЕ «flagged 0»: в холодном прогоне 1 из 3 редакторских юнитов `sanitizer_stripped`
(CJK-утечка, санитайзер её вырезал и текст ОТГРУЖЕН, 6048 знаков). По правилу «flagged 0»
гард «>20% пустых прибор отказывает» сработал бы от санитайзера, а не от предмета замера.
Флаги при этом не теряются они целиком идут в E5, разделённые по механизму.
ОБЪЯВЛЕНО ДО ЧИСЕЛ: строгий вариант (`flagged delivered = 0`) считается ВСЕГДА и печатается
рядом как заранее объявленная проверка устойчивости. Выбор основного определения сделан до
первой платной клетки и после просмотра чисел не меняется.
3. РАЗМЫШЛЕНИЕ. Движок его не видит (`provider_openai.go` `ReasoningTokens stays 0`; проверено
на базе холодного прогона: 0 строк из 68 с reasoning_tokens>0 при 38 строках с непустым
completion). Восстановление, коэффициент запинен пре-регом:
R̂ = completion_tokens 0.3644 · len(сырой response_text попытки)
Границы, названные планом ДО покупки: (1) любой CJK в выходе ломает оценку (иероглиф 1 токен)
R̂ завышается; (2) при R̂ < ~700 относительная ошибка 20-70% о величине таких клеток вывод не
делается; (3) берётся СЫРАЯ строка попытки, не `sanitized_export`; (4) типичная ошибка ~3%,
худшая клетка 5.3%.
У РЕФЕРЕНСА `glm-5` R̂ = 0 ПО ПОСТРОЕНИЮ, а не по формуле: у него другой токенизатор и
thinking выключен; формула напечатала бы «думает ±300 токенов» на модели, которая не думает.
4. ЦЕНЗУРА (правило §4, СИММЕТРИЧНО, записано до чисел). Клетка с `finish_reason='length'` на
попытке 0 цензурирована справа: её R̂ НИЖНЯЯ граница. Знак пары ( против low):
· цензурирована знак достоверен, пока R̂() R̂(low); иначе НИЧЬЯ;
· цензурирована low знак достоверен, только если R̂() R̂(low); иначе НИЧЬЯ;
· цензурированы обе НИЧЬЯ.
НИЧЬЯ ВЫБРОШЕНО: ничья = разность 0, и она СОХРАНЯЕТСЯ. Поэтому `zero_method='pratt'`
(умолчание `wilcox` нули ВЫБРАСЫВАЕТ проверено исполнением на scipy 1.18.0).
5. КОНТРАСТ КРЕСТА. Главный эффект усилия на юните среднее двух разностей:
d_u = ½[logR̂(P₇low)+logR̂(P₉low)] ½[logR̂(P₇)+logR̂(P₉)]
Именно у него sd = σ, а не σ2 (план §4 «Мощность»): var(d) = 4·(σ²/4) = σ². Ось промпта (E3)
и взаимодействие (E4) те же четыре клетки, другие знаки.
6. ДОВЕРИТЕЛЬНЫЙ ИНТЕРВАЛ ХОДЖЕСА-ЛЕМАНА РУКАМИ. `scipy.stats.wilcoxon` интервала не отдаёт
(проверено исполнением: у результата поля statistic/pvalue/count/index, метода
confidence_interval нет). Реализовано по Уолшевым средним с рангово-точной отсечкой.
"""
import argparse
import json
import math
import os
import sqlite3
import sys
import unicodedata
from pathlib import Path
import numpy as np
from scipy.stats import wilcoxon, spearmanr, t as student_t
# ── ЗАПИНЕННОЕ ПРЕ-РЕГОМ (менять здесь = менять пре-рег; после чисел запрещено) ───────────────
COEF_VISIBLE = 0.3644 # токенов на знак видимого выхода pro-редактора zh→ru
ALPHA = 0.05 # двусторонне
SIGNATURE_TOLERANCE = 2 # ±2 токена от снятого пред-полётом значения
SIGNATURE_FAIL_SHARE = 0.20 # >20% юнитов вне допуска ⇒ проба аннулируется
EMPTY_REFUSE_SHARE = 0.20 # >20% пустых в руке КРЕСТА ⇒ прибор отказывает
SMALL_RHAT = 700 # ниже — о величине клетки вывод не делается
BAND = (0.8, 1.25) # описательная полоса; решения НЕ несёт
CROSS_ARMS = ["p7-off", "p7-low", "p9-off", "p9-low"]
REFERENCE_ARM = "glm-off"
ALL_ARMS = CROSS_ARMS + [REFERENCE_ARM]
OFF_ARMS = ["p7-off", "p9-off"]
LOW_ARMS = ["p7-low", "p9-low"]
P7_ARMS = ["p7-off", "p7-low"]
P9_ARMS = ["p9-off", "p9-low"]
# Ожидаемая подпись edit-стадии каждой руки — ГАРД ПРОТИВ СХЛОПЫВАНИЯ ОСИ.
# Хеши промптов запиниваются пре-регом; сюда они приходят из манифеста, а не из головы.
EXPECTED = {
"p7-off": {"prompt": "P7", "reasoning": "off", "model": "deepseek-v4-pro"},
"p7-low": {"prompt": "P7", "reasoning": "low", "model": "deepseek-v4-pro"},
"p9-off": {"prompt": "P9", "reasoning": "off", "model": "deepseek-v4-pro"},
"p9-low": {"prompt": "P9", "reasoning": "low", "model": "deepseek-v4-pro"},
"glm-off": {"prompt": "P9", "reasoning": "off", "model": "glm-5"},
}
# Пиковые цены за 1M токенов — из backend/configs/models.yaml. ⚠ Леджер букирует ПИК всегда,
# и число владельца тоже пиковое. Сверено с леджером холодного прогона до цента:
# строка pt=4412 cmpl=12011 → 4412·1.32/1e6 + 12011·3.96/1e6 = $0.053388 против $0.053387.
PRICES = {
"deepseek-v4-pro": {"in": 1.32, "cached": 0.044, "out": 3.96}, # models.yaml:198
"deepseek-v4-flash": {"in": 0.44, "cached": 0.014, "out": 1.32}, # models.yaml:183
"glm-5": {"in": 1.00, "cached": 0.200, "out": 3.20}, # models.yaml:223
}
REFUSAL = 2 # код выхода «прибор отказывает»
class Refuse(Exception):
"""Прибор отказывает: знаменатель пуст или гард дизайна не прошёл."""
def is_cjk(ch):
o = ord(ch)
return (0x4E00 <= o <= 0x9FFF or 0x3400 <= o <= 0x4DBF or 0xF900 <= o <= 0xFAFF
or 0x3040 <= o <= 0x30FF)
def delivery_predicate(text):
"""$0-предикат доставки. Возвращает (годен, доля_кириллицы, число_CJK)."""
if not text:
return False, 0.0, 0
cjk = sum(1 for c in text if is_cjk(c))
letters = [c for c in text if unicodedata.category(c).startswith("L")]
cyr = sum(1 for c in letters if "CYRILLIC" in unicodedata.name(c, ""))
share = cyr / len(letters) if letters else 0.0
return (share >= 0.99 and cjk == 0), share, cjk
# ── ЧТЕНИЕ БАЗ ───────────────────────────────────────────────────────────────────────────────
def open_db(path):
if not Path(path).exists():
raise Refuse(f"базы нет: {path} (положительный контроль: ls каталога рук)")
con = sqlite3.connect(f"file:{path}?mode=ro", uri=True)
con.row_factory = sqlite3.Row
return con
def read_arm(path, arm):
"""Все edit-клетки одной руки. Ключ клетки — (chapter, chunk_idx)."""
con = open_db(path)
cells = {}
# Реконструкция №1: попытка приходит из checkpoints через request_hash.
rows = con.execute("""
select rl.id, rl.chapter, rl.chunk_idx, rl.model_requested, rl.model_actual,
rl.prompt_tokens, rl.cached_tokens, rl.completion_tokens, rl.reasoning_tokens,
rl.cost_usd, rl.finish_reason, rl.tm_hit, rl.ok, rl.err, rl.request_hash,
cp.attempt as attempt, cp.response_text as resp
from request_log rl left join checkpoints cp on cp.request_hash = rl.request_hash
where rl.stage = 'edit' order by rl.id
""").fetchall()
for r in rows:
key = (r["chapter"], r["chunk_idx"])
c = cells.setdefault(key, {"arm": arm, "unit": key, "calls": [], "orphan_calls": 0})
resp = r["resp"]
call = {
"id": r["id"], "attempt": r["attempt"], "model": r["model_requested"],
"prompt_tokens": r["prompt_tokens"], "cached_tokens": r["cached_tokens"],
"completion_tokens": r["completion_tokens"],
"reasoning_tokens_engine": r["reasoning_tokens"],
"cost_usd": r["cost_usd"], "finish": r["finish_reason"],
"tm_hit": r["tm_hit"], "ok": r["ok"], "err": r["err"],
"resp_len": len(resp) if resp is not None else None,
"resp": resp,
}
if r["attempt"] is None:
c["orphan_calls"] += 1
c["calls"].append(call)
# Диспозиция и отгружаемый текст
for r in con.execute("""
select cs.chapter, cs.chunk_idx, cs.disposition, cs.flag_reason, cs.first_flag_reason,
cs.attempts, cs.cost_usd, cs.final_hash, cs.detail, cs.snapshot_id,
(select c.response_text from checkpoints c where c.request_hash = cs.final_hash) as shipped
from chunk_status cs where cs.stage = 'edit'
"""):
key = (r["chapter"], r["chunk_idx"])
c = cells.setdefault(key, {"arm": arm, "unit": key, "calls": [], "orphan_calls": 0})
shipped = r["shipped"]
good, cyr, cjk = delivery_predicate(shipped or "")
c.update({
"disposition": r["disposition"], "flag_reason": r["flag_reason"],
"first_flag_reason": r["first_flag_reason"], "attempts": r["attempts"],
"unit_cost_usd": r["cost_usd"], "final_hash": r["final_hash"],
"detail": r["detail"], "unit_snapshot": r["snapshot_id"],
"shipped_len": len(shipped) if shipped else 0,
"cyr_share": cyr, "cjk_in_shipped": cjk,
"delivered": (len(shipped) if (good and r["disposition"] != "skipped") else 0),
# строгий вариант, объявленный ДО чисел
"delivered_strict": (len(shipped) if (good and r["disposition"] == "ok") else 0),
})
# Длина черновика по ЧАНКАМ. ⚠ Это ещё НЕ длина входа редакторского юнита: юнит редактуры
# охватывает несколько чанков черновика, и в этом стенде таких 15 из 20. Сборка юнита из чанков
# делается по манифесту движка — см. unit_draft_lengths().
draft_len = {}
for r in con.execute("""
select cs.chapter, cs.chunk_idx,
(select length(c.response_text) from checkpoints c where c.request_hash = cs.final_hash) as n
from chunk_status cs where cs.stage = 'draft'
"""):
draft_len[(r["chapter"], r["chunk_idx"])] = r["n"]
snaps = {r["snapshot_id"]: r["payload"] for r in con.execute("select snapshot_id, payload from snapshots")}
jobs = [dict(r) for r in con.execute("select chapter, stage, status, snapshot_id from jobs")]
spend = con.execute("select coalesce(sum(committed_usd+reserved_usd),0) from spend").fetchone()[0]
con.close()
return cells, draft_len, snaps, jobs, spend
def unit_draft_lengths(manifest_path, chunk_len):
"""Длина ВХОДА редакторского юнита = сумма его чанков черновика.
ЗАЧЕМ ОТДЕЛЬНАЯ ФУНКЦИЯ, А НЕ chunk_len НАПРЯМУЮ: юнит редактуры и чанк черновика РАЗНЫЕ
единицы. Манифест этого стенда: 20 юнитов на 35 чанков, и у 15 юнитов из 20 `chunk_count > 1`.
Взяв длину только первого чанка (ключ (глава, chunk_idx) совпадает у обоих!), ковариата длины
занизилась бы у трёх четвертей выборки и это НЕ дало бы ошибки, а тихо испортило бы проверку.
Соответствие: юнит (глава, k) есть k-й элемент `chapters[глава1].units`, а его чанки черновика
`first_chunk_idx first_chunk_idx + chunk_count 1` в той же главе.
"""
m = json.loads(Path(manifest_path).read_text())
out = {}
for ch in m.get("chapters", []):
num = ch["number"]
for k, u in enumerate(ch.get("units", [])):
total, missing = 0, 0
for ci in range(u["first_chunk_idx"], u["first_chunk_idx"] + u["chunk_count"]):
n = chunk_len.get((num, ci))
if n is None:
missing += 1
else:
total += n
out[(num, k)] = None if missing else total
return out
def rhat(call, arm):
"""Восстановленное размышление. У референса — 0 ПО ПОСТРОЕНИЮ (реконструкция №3)."""
if arm == REFERENCE_ARM:
return 0.0
if call["resp_len"] is None or call["completion_tokens"] is None:
return None
return call["completion_tokens"] - COEF_VISIBLE * call["resp_len"]
def attempt0(cell):
"""Попытка 0 клетки.
ЗАЩИТА, КУПЛЕННАЯ ЧТЕНИЕМ ДАННЫХ, А НЕ ДОГАДКОЙ: интерливинг гоняет каждую руку по 20 раз с
`--max-units 1`, и КАЖДЫЙ такой прогон дописывает в `request_log` строку-ПОВТОР уже оплаченной
клетки с `tm_hit=1`. Проверено на базе холодного прогона: у 29 строк с `tm_hit=1` ровно нули
`prompt_tokens=0, completion_tokens=0, cost_usd=0` а `request_hash` у них ТОТ ЖЕ, то есть они
соединяются с тем же чекпойнтом и несут `attempt=0`. Взять такую строку за попытку 0 значило бы
посчитать R̂ от нулей. Поэтому настоящий вызов (`tm_hit=0`) выбирается ЯВНО, а не по порядку id.
"""
hits = [c for c in cell.get("calls", []) if c["attempt"] == 0]
for c in hits:
if not c["tm_hit"]:
return c
return hits[0] if hits else None
# ── ГАРДЫ ДИЗАЙНА ────────────────────────────────────────────────────────────────────────────
def stage_of(payload, name):
p = json.loads(payload)
for s in p.get("stages", []):
if s.get("name") == name:
return s, p
return None, p
def gate_axis(arm_data, pins, out):
"""Не схлопнулась ли ось. Сильнее, чем «пять разных snapshot_id»: сверяем ПОЛЯ."""
out.append("## ГАРД 1 — ОСЬ НЕ СХЛОПНУЛАСЬ (edit-снапшот каждой руки)")
edit_ids, draft_ids, problems = {}, {}, []
for arm in ALL_ARMS:
snaps, jobs = arm_data[arm]["snaps"], arm_data[arm]["jobs"]
eids = {j["snapshot_id"] for j in jobs if j["stage"] == "edit"}
dids = {j["snapshot_id"] for j in jobs if j["stage"] == "draft"}
edit_ids[arm], draft_ids[arm] = eids, dids
seen = set()
for sid in eids:
st, _ = stage_of(snaps[sid], "edit")
if st is None:
problems.append(f"{arm}: в снапшоте {sid[:12]} нет стадии edit")
continue
got = (st.get("prompt_sha256"), st.get("reasoning"), st.get("model"))
seen.add(got)
want = EXPECTED[arm]
wsha = pins[want["prompt"]]
if got[0] != wsha:
problems.append(f"{arm}: prompt_sha256 {got[0][:12]}… ≠ запиненного {want['prompt']} {wsha[:12]}")
if got[1] != want["reasoning"]:
problems.append(f"{arm}: reasoning '{got[1]}''{want['reasoning']}'ОСЬ УСИЛИЯ НЕ УЕХАЛА НА ПРОВОД")
if got[2] != want["model"]:
problems.append(f"{arm}: model '{got[2]}''{want['model']}'")
for sha, reas, mdl in sorted(seen):
out.append(f" {arm:8s} edit: prompt_sha256 {sha[:16]}… reasoning '{reas}' model {mdl}")
all_edit = set().union(*edit_ids.values()) if edit_ids else set()
out.append(f" различных edit-снапшотов по пяти рукам: {len(all_edit)} (нужно ≥5)")
if len(all_edit) < 5:
problems.append(f"edit-снапшотов всего {len(all_edit)} — руки схлопнулись, платили за пять, мерили меньше")
all_draft = set().union(*draft_ids.values()) if draft_ids else set()
out.append(f" различных draft-снапшотов по пяти рукам: {len(all_draft)} (нужно РОВНО 1 — иначе черновики перекуплены)")
if len(all_draft) != 1:
problems.append(f"draft-снапшотов {len(all_draft)} ≠ 1: {sorted(s[:12] for s in all_draft)} — черновики НЕ общие")
return problems
def gate_signature(arm_data, pinned, out):
"""Сигнатура Δprompt_tokens(∅ low) по каждому промпту. Косвенная улика провода."""
out.append("\n## ГАРД 2 — СИГНАТУРА Δprompt_tokens (∅ low), попытка 0")
res, bad, total = {}, 0, 0
for label, (a_off, a_low) in (("P7", ("p7-off", "p7-low")), ("P9", ("p9-off", "p9-low"))):
deltas = {}
for unit in sorted(set(arm_data[a_off]["cells"]) & set(arm_data[a_low]["cells"])):
c0, c1 = attempt0(arm_data[a_off]["cells"][unit]), attempt0(arm_data[a_low]["cells"][unit])
if not c0 or not c1:
continue
deltas[unit] = c0["prompt_tokens"] - c1["prompt_tokens"]
res[label] = deltas
if not deltas:
out.append(f" {label}: нет ни одной пары попыток 0 — сигнатура НЕ СНЯТА")
continue
vals = list(deltas.values())
uniq = sorted(set(vals))
out.append(f" {label}: n={len(vals)} различных значений {uniq} медиана {int(np.median(vals))}")
pin = pinned.get(label)
if pin is None:
out.append(f" (значение НЕ ЗАПИНЕНО — это пред-полёт: оно и есть то, что пинится коммитом)")
else:
off = [u for u, v in deltas.items() if abs(v - pin) > SIGNATURE_TOLERANCE]
total += len(vals)
bad += len(off)
out.append(f" запинено {pin} ±{SIGNATURE_TOLERANCE} ⇒ вне допуска {len(off)}/{len(vals)}")
problems = []
if total and bad / total > SIGNATURE_FAIL_SHARE:
problems.append(f"сигнатура не воспроизвелась у {bad}/{total} = {bad/total:.0%} юнитов (>{SIGNATURE_FAIL_SHARE:.0%}) ⇒ ПРОБА АННУЛИРУЕТСЯ")
return res, problems
def gate_empty(arm_data, out):
"""⛔ Зелень на пустоте. Правило действует ТОЛЬКО на четыре руки креста (план §5.2)."""
out.append("\n## ГАРД 3 — ЗЕЛЕНЬ НА ПУСТОТЕ (delivered = 0)")
problems = []
for arm in ALL_ARMS:
cells = arm_data[arm]["cells"]
done = [c for c in cells.values() if "delivered" in c]
if not done:
out.append(f" {arm:8s}: ни одной завершённой клетки")
continue
empty = [c for c in done if c["delivered"] == 0]
empty_s = [c for c in done if c["delivered_strict"] == 0]
share = len(empty) / len(done)
mark = ""
if arm in CROSS_ARMS and share > EMPTY_REFUSE_SHARE:
problems.append(f"{arm}: пустых {len(empty)}/{len(done)} = {share:.0%} > {EMPTY_REFUSE_SHARE:.0%} ⇒ ПРИБОР ОТКАЗЫВАЕТ")
mark = ""
if arm == REFERENCE_ARM and share > EMPTY_REFUSE_SHARE:
mark = " (референс: в E0 не печатается, доля идёт строкой в E5 — план §5.2)"
out.append(f" {arm:8s}: пустых {len(empty)}/{len(done)} = {share:.0%}"
f" | строгий вариант {len(empty_s)}/{len(done)} = {len(empty_s)/len(done):.0%}{mark}")
return problems
# ── СТАТИСТИКА ───────────────────────────────────────────────────────────────────────────────
def hodges_lehmann(d):
"""Медиана Ходжеса-Лемана = медиана Уолшевых средних (d_i+d_j)/2, i ≤ j."""
d = np.asarray(d, dtype=float)
n = len(d)
if n == 0:
return None
w = np.array([(d[i] + d[j]) / 2 for i in range(n) for j in range(i, n)])
return float(np.median(w)), w
def hl_ci(d, alpha=ALPHA):
"""95% ДИ Ходжеса-Лемана. РУКАМИ: scipy.wilcoxon интервала не отдаёт (проверено).
Отсечка k по нормальному приближению знаково-рангового распределения с поправкой на
непрерывность. Для маленьких n это приближение ЗАЯВЛЕННОЕ ограничение, а не скрытое.
"""
d = np.asarray(d, dtype=float)
n = len(d)
if n < 6:
return None
hl, w = hodges_lehmann(d)
w = np.sort(w)
m = len(w)
from scipy.stats import norm
z = norm.ppf(1 - alpha / 2)
se = math.sqrt(n * (n + 1) * (2 * n + 1) / 24.0)
k = int(math.floor(n * (n + 1) / 4.0 - z * se - 0.5))
if k < 0:
k = 0
if k >= m // 2:
return None
return float(w[k]), float(w[m - 1 - k])
def paired_contrast(arm_data, unit, off_arm, low_arm):
"""Разность log R̂(low) log R̂(∅) одной пары, с симметричным правилом цензуры §4.
Возвращает (значение, статус) где статус {ok, tie-censored, missing, small}.
"""
co = arm_data[off_arm]["cells"].get(unit)
cl = arm_data[low_arm]["cells"].get(unit)
if not co or not cl:
return None, "missing"
a0o, a0l = attempt0(co), attempt0(cl)
if not a0o or not a0l:
return None, "missing"
ro, rl = rhat(a0o, off_arm), rhat(a0l, low_arm)
if ro is None or rl is None or ro <= 0 or rl <= 0:
return None, "missing"
cens_o = a0o["finish"] == "length"
cens_l = a0l["finish"] == "length"
if cens_o and cens_l:
return 0.0, "tie-censored"
if cens_o and not (ro >= rl):
return 0.0, "tie-censored"
if cens_l and not (ro <= rl):
return 0.0, "tie-censored"
return math.log(rl) - math.log(ro), ("ok-censored" if (cens_o or cens_l) else "ok")
def endpoint_test(name, values, out, note=""):
"""Вилкоксон с pratt + величина Ходжеса-Лемана с ДИ. Печатает вывод при ЛЮБОМ исходе."""
out.append(f"\n### {name}")
if note:
out.append(f" {note}")
vals = [v for v in values if v is not None]
if len(vals) == 0:
raise Refuse(f"{name}: годных юнитов НОЛЬ — знаменатель пуст, прибор ОТКАЗЫВАЕТ")
out.append(f" юнитов в тесте: {len(vals)} (ничьих-цензур: {sum(1 for v in vals if v == 0)})")
if len(vals) < 6:
out.append(f" ⛔ n={len(vals)} — ниже разрешения любого рангового теста; величина не печатается")
return {"n": len(vals), "p": None, "hl": None, "ci": None}
res = wilcoxon(vals, zero_method="pratt") # ⛔ pratt пинится: умолчание wilcox нули ВЫБРАСЫВАЕТ
hl, _ = hodges_lehmann(vals)
ci = hl_ci(vals)
ratio = math.exp(hl)
out.append(f" Вилкоксон (zero_method='pratt'): W={res.statistic:.1f} p={res.pvalue:.4g}")
out.append(f" Ходжес-Леман: медиана log = {hl:+.4f} ⇒ отношение ×{ratio:.3f}")
if ci:
out.append(f" 95% ДИ (руками, Уолшевы средние): log [{ci[0]:+.4f}; {ci[1]:+.4f}]"
f"×[{math.exp(ci[0]):.3f}; {math.exp(ci[1]):.3f}]")
inband = BAND[0] <= math.exp(ci[0]) and math.exp(ci[1]) <= BAND[1]
out.append(f" описательная полоса [{BAND[0]};{BAND[1]}]: ДИ {'внутри' if inband else 'НЕ внутри'}"
f" — решения НЕ несёт (план §4)")
else:
out.append(" 95% ДИ: не определён при этом n (отсечка ушла за край) — заявлено, не скрыто")
if res.pvalue < ALPHA:
out.append(f" ⇒ ВЫВОД: эффект ЕСТЬ (p<{ALPHA}). Величина — выше.")
else:
out.append(f" ⇒ ВЫВОД: НИЖЕ РАЗРЕШЕНИЯ ЗАМЕРА (p={res.pvalue:.3g}{ALPHA}). Это НЕ «эффекта нет».")
return {"n": len(vals), "p": float(res.pvalue), "hl": hl, "ci": ci}
# ── ЭНДПОЙНТЫ ────────────────────────────────────────────────────────────────────────────────
def e0_decomposition(arm_data, out):
"""E0 — ПОЧЕМУ дорого: цена по статьям, сложенная по ВСЕМ попыткам."""
out.append("\n## E0 — РАЗЛОЖЕНИЕ ЦЕНЫ ПО СТАТЬЯМ (все попытки, $0)")
out.append(f" {'рука':8s} {'клеток':>6s} {'вход':>9s} {'кэш':>9s} {'вид.выход':>10s} {'размышл.':>10s} {'ретраи':>9s} {'ИТОГО':>9s}")
table = {}
for arm in ALL_ARMS:
cells = arm_data[arm]["cells"]
art = {"in": 0.0, "cache": 0.0, "vis": 0.0, "reas": 0.0, "retry": 0.0, "total": 0.0}
n = 0
for cell in cells.values():
if not cell.get("calls"):
continue
n += 1
for c in cell["calls"]:
if c["tm_hit"] or c["completion_tokens"] is None:
continue
pr = PRICES.get(c["model"])
if not pr:
continue
fresh_in = max(0, (c["prompt_tokens"] or 0) - (c["cached_tokens"] or 0))
art["in"] += fresh_in / 1e6 * pr["in"]
art["cache"] += (c["cached_tokens"] or 0) / 1e6 * pr["cached"]
r = rhat(c, arm)
vis_tok = (c["completion_tokens"] - r) if r is not None else c["completion_tokens"]
vis_tok = max(0.0, vis_tok)
r = max(0.0, r or 0.0)
art["vis"] += vis_tok / 1e6 * pr["out"]
art["reas"] += r / 1e6 * pr["out"]
art["total"] += c["cost_usd"] or 0.0
if (c["attempt"] or 0) >= 1:
art["retry"] += c["cost_usd"] or 0.0
table[arm] = art
if arm == REFERENCE_ARM:
done = [c for c in cells.values() if "delivered" in c]
empty = sum(1 for c in done if c["delivered"] == 0)
if done and empty / len(done) > EMPTY_REFUSE_SHARE:
out.append(f" {arm:8s}НЕ ПЕЧАТАЕТСЯ: пустых {empty}/{len(done)} > {EMPTY_REFUSE_SHARE:.0%}"
f" (план §5.2; доля идёт строкой в E5)")
continue
out.append(f" {arm:8s} {n:6d} {art['in']:9.5f} {art['cache']:9.5f} {art['vis']:10.5f}"
f" {art['reas']:10.5f} {art['retry']:9.5f} {art['total']:9.5f}")
out.append("\n ДОЛИ от суммы статей (вход+кэш+видимый+размышление):")
for arm, art in table.items():
s = art["in"] + art["cache"] + art["vis"] + art["reas"]
if s <= 0:
continue
out.append(f" {arm:8s} вход {art['in']/s:5.1%} кэш {art['cache']/s:5.1%}"
f" видимый выход {art['vis']/s:5.1%} РАЗМЫШЛЕНИЕ {art['reas']/s:5.1%}"
f" | ретраи {art['retry']/art['total']:5.1%} от цены руки" if art["total"] else "")
out.append(" ⚠ Одна статья невосстановима никакой пробой и объявлена заранее: что из подорожания")
out.append(" дали НОВЫЕ ВЕСА и смена вендор-дефолта — июльское состояние сервинга не вернуть.")
return table
def cell_cost_nocache(call, arm):
"""Цена клетки, посчитанная так, будто префикс-кэша НЕ БЫЛО (весь вход по полной ставке).
ЗАЧЕМ. Порядок клеток рандомизирован, но при ЗАПИНЕННОМ сиде выпавшая перестановка на n=20
несимметрична, и это посчитано ДО чисел: `p7-low` идёт раньше `p7-off` в 15 юнитах из 20
(у пары P9 9 из 20), первой клеткой юнита `p7-low` бывает 10 раз против 1 у `p7-off`.
Первая клетка юнита кэш не бьёт и потому дороже примерно на 6000·($1.32$0.044)/1e6 $0.0077.
E2 у пары P7 систематически наклонён ПРОТИВ `low`. Сид часть пре-рега и НЕ трогается;
вместо этого печатается вторая, кэш-независимая версия E2, объявленная до просмотра чисел.
"""
pr = PRICES.get(call["model"])
if not pr or call["completion_tokens"] is None:
return None
return ((call["prompt_tokens"] or 0) / 1e6 * pr["in"]
+ (call["completion_tokens"] or 0) / 1e6 * pr["out"])
def e2_cost_per_delivered(arm_data, units, out):
"""E2 — цена доставленного знака, в четырёх заранее объявленных вариантах."""
out.append("\n## E2 — ЦЕНА ДОСТАВЛЕННОГО ЗНАКА")
out.append(" Четыре варианта объявлены ДО чисел: два по определению «доставлено» (мягкое ×")
out.append(" строгое) и два по цене (фактическая × кэш-независимая). Основной — первый.")
res = {}
VARIANTS = [
("попытка 0 · фактическая цена (ОСНОВНОЙ, равный потолок)", False, "delivered", False),
("итог юнита · фактическая цена (боевая цена, ретраи включены)", True, "delivered", False),
("попытка 0 · СТРОГОЕ delivered (flagged ⇒ 0)", False, "delivered_strict", False),
("попытка 0 · КЭШ-НЕЗАВИСИМАЯ цена (снимает перекос порядка)", False, "delivered", True),
]
for label, use_unit_cost, dkey, nocache in VARIANTS:
vals, skipped = [], 0
for u in units:
per = {}
for arm in CROSS_ARMS:
c = arm_data[arm]["cells"].get(u)
if not c or dkey not in c or c[dkey] == 0:
per = None
break
a0 = attempt0(c)
if use_unit_cost:
cost = c["unit_cost_usd"]
elif nocache:
cost = cell_cost_nocache(a0, arm) if a0 else None
else:
cost = a0["cost_usd"] if a0 else None
if not cost or cost <= 0:
per = None
break
per[arm] = math.log(cost / c[dkey])
if per is None:
skipped += 1
continue
vals.append(0.5 * (per["p7-low"] + per["p9-low"]) - 0.5 * (per["p7-off"] + per["p9-off"]))
primary = label.startswith("попытка 0 · фактическая")
if primary:
# ⛔ У ОСНОВНОГО варианта отказ прибора НЕ ПЕРЕХВАТЫВАЕТСЯ: пустой знаменатель здесь —
# это и есть «зелень на пустоте», ради которой гард существует.
res[label] = endpoint_test(f"E2 · {label}", vals, out,
note=f"пропущено юнитов (пустая доставка или нет цены): {skipped}")
else:
# У ЧУВСТВИТЕЛЬНОСТЕЙ пустота — законный исход (строгое delivered может обнулить всё),
# и она печатается как пустота, а не роняет весь отчёт.
try:
res[label] = endpoint_test(f"E2 · {label}", vals, out,
note=f"пропущено юнитов: {skipped}")
except Refuse as e:
out.append(f"\n### E2 · {label}\n ⛔ ЧУВСТВИТЕЛЬНОСТЬ НЕ СЧИТАЕТСЯ: {e}")
res[label] = None
return res
def e5_safety(arm_data, out):
"""E5 — безопасность. Блокирует любую рекомендацию, считается по механизму, не по имени.
ГЛАВНАЯ ПРАВКА ПОСЛЕ ВЕРИФИКАЦИИ: эхо считается по СЫРОМУ ответу попытки 0, а НЕ по
отгружаемому тексту. Санитайзер CJK ВЫРЕЗАЕТ, и эндпойнт, блокирующий любую рекомендацию, был
бы слеп ровно к своему предмету. Улика с холодного прогона, снята командой:
юнит (1,0), flag=sanitizer_stripped: CJK в отгруженном = 0, CJK в СЫРОМ ответе = 10.
Колонка по отгруженному остаётся ВТОРОЙ она отвечает на другой вопрос («что увидел читатель»).
"""
out.append("\n## E5 — БЕЗОПАСНОСТЬ (эхо / CJK / обрыв по длине)")
out.append(" ⚠ «CJK@сырой» — по СЫРОМУ ответу попытки 0 (санитайзер эхо вырезает, и по")
out.append(" отгруженному тексту эхо не видно вовсе). «CJK@отгр» — что осталось читателю.")
out.append(f" {'рука':8s} {'клеток':>6s} {'CJK@сырой':>9s} {'CJK@отгр':>8s} {'кир<0.99':>8s}"
f" {'flagged':>7s} {'length@0':>8s} {'санитайз':>8s} {'малый R̂':>8s}")
summary = {}
for arm in ALL_ARMS:
cells = [c for c in arm_data[arm]["cells"].values() if "delivered" in c]
if not cells:
continue
cjk_ship = sum(1 for c in cells if c["cjk_in_shipped"] > 0)
lowcyr = sum(1 for c in cells if c["cyr_share"] < 0.99)
flagged = sum(1 for c in cells if c["disposition"] == "flagged")
sanit = sum(1 for c in cells if "sanitiz" in (c["flag_reason"] or ""))
length0 = cjk_raw = small = 0
for c in cells:
a0 = attempt0(c)
if not a0:
continue
if a0["finish"] == "length":
length0 += 1
_, _, ncjk = delivery_predicate(a0["resp"] or "")
if ncjk > 0:
cjk_raw += 1
r = rhat(a0, arm)
if arm != REFERENCE_ARM and r is not None and 0 < r < SMALL_RHAT:
small += 1
summary[arm] = {"n": len(cells), "cjk_raw": cjk_raw, "cjk_shipped": cjk_ship,
"lowcyr": lowcyr, "flagged": flagged, "length0": length0,
"sanitizer": sanit, "small_rhat": small}
out.append(f" {arm:8s} {len(cells):6d} {cjk_raw:9d} {cjk_ship:8d} {lowcyr:8d}"
f" {flagged:7d} {length0:8d} {sanit:8d} {small:8d}")
out.append(f" ⚠ «малый R̂» — клетки с R̂ < {SMALL_RHAT} токенов: у них относительная ошибка формулы")
out.append(" восстановления 20-70% (план §3), и о ВЕЛИЧИНЕ таких клеток вывод не делается.")
echo_off = sum(summary.get(a, {}).get("cjk_raw", 0) for a in OFF_ARMS)
echo_low = sum(summary.get(a, {}).get("cjk_raw", 0) for a in LOW_ARMS)
out.append(f"\n эхо-клеток: у{echo_off}, у low {echo_low}")
if echo_low - echo_off >= 3:
out.append(f" ⛔ РИСК ЭХА НА low: у low на {echo_low-echo_off} эхо-клеток больше — блокирует рекомендацию НЕЗАВИСИМО ОТ ЦЕНЫ")
else:
out.append(" риск эха на low по правилу §4 (≥3 сверх ∅) НЕ предъявлен")
out.append(" ⚠ Референс glm-5 идёт СТРОКОЙ ОТДЕЛЬНО: у него thinking ВЫКЛЮЧЕН, а эхо-мина")
out.append(" вооружается именно выключением — его доля эха к оси усилия не относится.")
return summary
def covariate_check(arm_data, units, contrasts, out, dl):
"""Ковариата длины входа — с ЗАЯВЛЕННОЙ находкой о том, почему модель плана вырождена."""
out.append("\n## КОВАРИАТА ДЛИНЫ ВХОДА")
out.append(" ⛔ НАХОДКА (предъявляется, а не правится молча): модель плана")
out.append(" `log R̂ ~ effort + prompt + log(len_draft) + (1|unit)` НЕ МОЖЕТ быть проверкой")
out.append(" устойчивости E1/E3. Длина черновика — величина УНИТ-УРОВНЕВАЯ: все четыре руки")
out.append(" редактируют ОДИН И ТОТ ЖЕ черновик, это и есть блокировка трудности. В")
out.append(" сбалансированном внутри-юнитном дизайне юнит-уровневая ковариата НЕ МЕНЯЕТ")
out.append(" оценок усилия и промпта ПО ПОСТРОЕНИЮ (её вклад целиком лежит в между-юнитной")
out.append(" части, которую съедает случайный перехват). Это свойство дизайна, а не нехватка")
out.append(" библиотеки; сам наклон длины при этом оценим — он ниже, пункт (2).")
out.append(" ⇒ Вместо «проверки устойчивости» считается то, что оценимо и отвечает на ту же")
out.append(" тревогу («не длиной ли входа объясняется эффект»): зависимость ЮНИТНОГО")
out.append(" КОНТРАСТА от длины черновика.")
xs, ys = [], []
for u, d in zip(units, contrasts):
n = dl.get(u)
if d is None or not n:
continue
xs.append(math.log(n))
ys.append(d)
if len(xs) < 6:
out.append(f" n={len(xs)} — недостаточно, проверка ОБЪЯВЛЕНА НЕИСПОЛНЕННОЙ")
return None
x, y = np.array(xs), np.array(ys)
rho, prho = spearmanr(x, y)
# OLS руками: наклон, стандартная ошибка, t-тест (statsmodels не нужен и не установлен)
xm, ym = x.mean(), y.mean()
sxx = ((x - xm) ** 2).sum()
b = ((x - xm) * (y - ym)).sum() / sxx
a = ym - b * xm
resid = y - (a + b * x)
dof = len(x) - 2
s2 = (resid ** 2).sum() / dof
seb = math.sqrt(s2 / sxx)
tstat = b / seb if seb > 0 else float("nan")
p = 2 * (1 - student_t.cdf(abs(tstat), dof))
out.append(f" (1) ВНУТРИ-ЮНИТНАЯ УСТОЙЧИВОСТЬ: зависит ли САМ контраст от длины входа")
out.append(f" n={len(x)} Спирмен ρ={rho:+.3f} (p={prho:.3g})")
out.append(f" МНК контраст ~ log(len_draft): наклон {b:+.4f} ± {seb:.4f}, t={tstat:+.2f}, p={p:.3g}, df={dof}")
out.append(f"{'контраст зависит от длины входа' if p < ALPHA else 'зависимости контраста от длины входа не предъявлено'}")
# (2) Между-юнитная часть — ЭТО И ЕСТЬ коэффициент длины из смешанной модели плана.
# В сбалансированном внутри-юнитном дизайне он равен МНК-наклону по СРЕДНИМ ПО ЮНИТУ
# значениям log R̂; считается точно и без statsmodels.
ux, uy = [], []
for u in units:
n = dl.get(u)
if not n:
continue
rs = []
for arm in CROSS_ARMS:
c = arm_data[arm]["cells"].get(u)
a0 = attempt0(c) if c else None
r = rhat(a0, arm) if a0 else None
if r and r > 0:
rs.append(math.log(r))
if len(rs) == len(CROSS_ARMS):
ux.append(math.log(n))
uy.append(float(np.mean(rs)))
out.append(f" (2) МЕЖДУ-ЮНИТНАЯ ЧАСТЬ — это и есть коэффициент длины из смешанной модели плана")
lenfit = None
if len(ux) >= 6:
X, Y = np.array(ux), np.array(uy)
Xm, Ym = X.mean(), Y.mean()
sxx2 = ((X - Xm) ** 2).sum()
b2 = ((X - Xm) * (Y - Ym)).sum() / sxx2
a2 = Ym - b2 * Xm
r2d = len(X) - 2
s22 = ((Y - (a2 + b2 * X)) ** 2).sum() / r2d
seb2 = math.sqrt(s22 / sxx2)
t2 = b2 / seb2 if seb2 > 0 else float("nan")
p2 = 2 * (1 - student_t.cdf(abs(t2), r2d))
out.append(f" n={len(X)} log R̂(среднее по юниту) ~ log(len_draft): наклон {b2:+.3f} ± {seb2:.3f},"
f" t={t2:+.2f}, p={p2:.3g}")
out.append(f" ⇒ при удвоении длины черновика размышление ×{2**b2:.2f}"
f" (для сравнения: exp19 намерил ×2.78 при ×3.24 входа ⇒ наклон ≈{math.log(2.78)/math.log(3.24):.2f})")
lenfit = {"slope": float(b2), "p": float(p2), "n": len(X)}
else:
out.append(f" n={len(ux)} — недостаточно, ОБЪЯВЛЕНО НЕИСПОЛНЕННЫМ")
out.append(" ⚠ statsmodels в eval/.venv НЕ УСТАНОВЛЕН и НЕ УСТАНАВЛИВАЛСЯ. Причина — НЕ «нет сети»")
out.append(" (сеть есть, колесо под cp314 существует — проверено): в сбалансированном внутри-юнитном")
out.append(" дизайне ОБЕ величины, которые дала бы смешанная модель, вычисляются точно руками —")
out.append(" (1) и (2) выше, — а правка общего venv при живых параллельных сессиях есть риск без")
out.append(" выигрыша. Решение объявлено словом, а не умолчано.")
return {"rho": float(rho), "slope": float(b), "p": float(p), "n": len(x), "length_effect": lenfit}
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}")
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
total += delta
out.append(f" {arm:8s} леджер ${s:.6f} база ⇒ ${delta:.6f}")
out.append(f" ─────────────────────────────────────────")
out.append(f" ИЗРАСХОДОВАНО (база + Σ(копия−база)) = ${total:.6f}")
out.append(f" САНКЦИЯ ВЛАДЕЛЬЦА (пиковые термины) = ${sanction:.2f}")
out.append(f" остаток = ${sanction-total:.6f}")
if total > sanction:
out.append(" ⛔ ПОТОЛОК ПРОБИТ — стоп и вопрос владельцу, добор запрещён")
return total
# ── MAIN ─────────────────────────────────────────────────────────────────────────────────────
def main():
ap = argparse.ArgumentParser(description="прибор пробы редактора по четырём осям")
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)
out, result = [], {}
manifest = {}
if a.manifest and Path(a.manifest).exists():
manifest = json.loads(Path(a.manifest).read_text())
pins = manifest.get("prompt_sha256", {})
pinned_sig = manifest.get("signature", {})
out.append("" * 96)
out.append("ПРОБА РЕДАКТОРА ПО ЧЕТЫРЁМ ОСЯМ — ПРИБОР (chetyre.py)")
out.append(f"стенд: {B}")
out.append("" * 96)
try:
if not pins:
raise Refuse("в манифесте нет запиненных prompt_sha256 — гард оси не может быть исполнен "
"(положительный контроль: --manifest указывает на файл пре-рега)")
arm_data = {}
for arm in ALL_ARMS:
path = B / "arms" / f"{arm}.db"
cells, dlen, snaps, jobs, spend = read_arm(path, arm)
arm_data[arm] = {"cells": cells, "draft_len": dlen, "snaps": snaps, "jobs": jobs, "spend": spend}
# ⚠ ДЛИНА ВХОДА ЮНИТА — только через манифест движка: юнит редактуры охватывает несколько
# чанков черновика (в этом стенде 15 юнитов из 20), и ключ (глава, индекс) у них СОВПАДАЕТ,
# то есть ошибка была бы ТИХОЙ.
mpath = B / "guzhenren-probe4.db.manifest.json"
if not mpath.exists():
raise Refuse(f"нет манифеста движка {mpath} — длину входа юнита взять неоткуда "
f"(положительный контроль: он пишется командой `tmctl manifest`, $0)")
unit_len = unit_draft_lengths(mpath, arm_data[CROSS_ARMS[0]]["draft_len"])
base_con = open_db(B / "guzhenren-probe4.db")
base_spend = base_con.execute("select coalesce(sum(committed_usd+reserved_usd),0) from spend").fetchone()[0]
base_con.close()
out.append("\n## ИНВЕНТАРЬ")
for arm in ALL_ARMS:
cells = arm_data[arm]["cells"]
done = [c for c in cells.values() if "delivered" in c]
orph = sum(c.get("orphan_calls", 0) for c in cells.values())
out.append(f" {arm:8s} клеток {len(cells):3d} завершённых {len(done):3d}"
f" вызовов без чекпойнта {orph} леджер ${arm_data[arm]['spend']:.6f}")
problems = gate_axis(arm_data, pins, out)
sig, sp = gate_signature(arm_data, pinned_sig, out)
problems += sp
problems += gate_empty(arm_data, out)
if problems:
out.append("\n⛔ ГАРДЫ НЕ ПРОШЛИ:")
for p in problems:
out.append(f" · {p}")
raise Refuse("гарды дизайна не прошли — эндпойнты не считаются, чтобы не выдать зелень на браке")
units = sorted(set.intersection(*[set(arm_data[x]["cells"]) for x in CROSS_ARMS]))
out.append(f"\n## ЮНИТЫ, ПОЛНЫЕ ПО ВСЕМ ЧЕТЫРЁМ РУКАМ КРЕСТА: {len(units)}")
if not units:
raise Refuse("полных юнитов НОЛЬ — знаменатель пуст")
e0 = e0_decomposition(arm_data, out)
# E1 — главный
d1, statuses, dropped = [], [], []
for u in units:
p7, s7 = paired_contrast(arm_data, u, "p7-off", "p7-low")
p9, s9 = paired_contrast(arm_data, u, "p9-off", "p9-low")
statuses += [s7, s9]
if p7 is None or p9 is None:
dropped.append((u, f"P7:{s7} P9:{s9}"))
d1.append(None)
else:
d1.append(0.5 * (p7 + p9))
cens = sum(1 for s in statuses if "censored" in s)
# ⛔ ВЫБРОШЕННЫЕ ЮНИТЫ НАЗЫВАЮТСЯ ПОИМЁННО И С ПРИЧИНОЙ: «юнитов в тесте: N» без этого
# читается как «столько и было», а разница между 20 и N есть отдельный факт о приборе.
note = f"пар с цензурой: {cens} из {len(statuses)}"
if cens:
note += " ⇒ ВЕЛИЧИНА ЕСТЬ НИЖНЯЯ ГРАНИЦА, читать со знаком «≥»"
if dropped:
note += f"\n ⛔ ВЫБРОШЕНО ЮНИТОВ: {len(dropped)} из {len(units)}" + \
"; ".join(f"({a},{b}) {r}" for (a, b), r in dropped[:8])
if len(dropped) > 8:
note += f" … и ещё {len(dropped)-8}"
else:
note += f"\n выброшенных юнитов нет: в тесте все {len(units)}"
e1 = endpoint_test("E1 — ГЛАВНЫЙ: усилие low против ∅, log R̂, попытка 0", d1, out, note=note)
# E3 — ось промпта
d3 = []
for u in units:
v = {}
for arm in CROSS_ARMS:
c = arm_data[arm]["cells"].get(u)
a0 = attempt0(c) if c else None
r = rhat(a0, arm) if a0 else None
v[arm] = math.log(r) if (r and r > 0) else None
d3.append(None if any(x is None for x in v.values())
else 0.5 * (v["p9-off"] + v["p9-low"]) - 0.5 * (v["p7-off"] + v["p7-low"]))
e3 = endpoint_test("E3 — ОСЬ ПРОМПТА (сегодняшний против июльского), log R̂", d3, out,
note="прогноз, калибрующий удивление (записан до чисел): |эффект| < ×1.25")
# E4 — взаимодействие
d4 = []
for u in units:
v = {}
for arm in CROSS_ARMS:
c = arm_data[arm]["cells"].get(u)
a0 = attempt0(c) if c else None
r = rhat(a0, arm) if a0 else None
v[arm] = math.log(r) if (r and r > 0) else None
d4.append(None if any(x is None for x in v.values())
else (v["p9-low"] - v["p9-off"]) - (v["p7-low"] - v["p7-off"]))
e4 = endpoint_test("E4 — ВЗАИМОДЕЙСТВИЕ промпт×усилие", d4, out,
note="ОПИСАТЕЛЬНЫЙ: sd контраста вдвое больше, MDE ≈×3.3 при n=32 — выводов не несёт")
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, 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,
"units": len(units), "spent_usd": total, "signature": {k: {str(i): j for i, j in v.items()} for k, v in sig.items()}}
code = 0
except Refuse as e:
out.append(f"\n⛔⛔ ПРИБОР ОТКАЗЫВАЕТ (код {REFUSAL}): {e}")
out.append(" Это НЕ «в пределах» и НЕ «эффекта нет». Знаменатель пуст либо гард не прошёл.")
code = REFUSAL
text = "\n".join(out)
print(text)
if a.json:
Path(a.json).write_text(json.dumps({"ok": code == 0, "report": text, **result},
ensure_ascii=False, indent=2, default=str))
return code
if __name__ == "__main__":
sys.exit(main())

133
eval/dovodka/narezka.py Normal file
View file

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""narezka.py — вырезать диапазон глав книги в формат, который принимает движок.
Заведён 02.09 по находке проверки исполнимости: пре-рег требовал «срез исходника (диапазон глав,
файл, его sha256)», а СПОСОБА получить этот файл в проекте не было ни одного. Резалки не
существовало, и свежая сессия обязана была бы её изобрести вместе с форматом.
ФОРМАТ, СНЯТЫЙ С ЭТАЛОНА (`~/books/gu-zhenren/coldrun-v16/guzhenren-ch1-10.gb18030.txt`,
проверено `file` и `od`): кодировка **GB18030**, переводы строки **CRLF**, первая строка файла
ОБЯЗАТЕЛЬНО заголовок главы. Нарушение любого из трёх молча сдвигает нумерацию глав у движка.
**МИНА НУМЕРАЦИИ, названная до запуска.** Движок не умеет смещение глав: срез всегда нумеруется
с единицы. Вырезав главы 2642, в базе и отчётах увидишь «главы 117». Соответствие держится
ТОЛЬКО этим файлом и строкой в пре-реге. Поэтому скрипт печатает обе нумерации и пишет их в
манифест рядом с sha256.
**ТОМА.** В книге нумерация глав СБРАСЫВАЕТСЯ по томам `第二十六节` встречается несколько раз.
Поэтому режем по ПОРЯДКОВОМУ НОМЕРУ вхождения заголовка от начала файла, а не по тексту заголовка.
Использование:
python3 eval/dovodka/narezka.py --from 26 --to 42 --out <путь>.gb18030.txt
python3 eval/dovodka/narezka.py --selftest
"""
import argparse
import hashlib
import re
import sys
from pathlib import Path
SRC = Path.home() / "books/gu-zhenren/guzhenren-utf8.txt"
HEAD_RE = re.compile(r"^第[^\n]{1,20}节")
def split_chapters(text):
"""[(порядковый номер от 1, заголовок, тело с заголовком)] — по порядку вхождения, не по тексту."""
lines = text.split("\n")
starts = [i for i, ln in enumerate(lines) if HEAD_RE.match(ln)]
out = []
for k, i in enumerate(starts):
j = starts[k + 1] if k + 1 < len(starts) else len(lines)
out.append((k + 1, lines[i], "\n".join(lines[i:j])))
return out
def cut(src_path, first, last, out_path):
text = src_path.read_text(encoding="utf-8")
chapters = split_chapters(text)
if not chapters:
print("ОТКАЗ: в исходнике не найдено ни одного заголовка главы — проверь путь и кодировку")
return 2
if last > len(chapters):
print(f"ОТКАЗ: запрошена глава {last}, а в исходнике их {len(chapters)}")
return 2
picked = chapters[first - 1 : last]
body = "\n".join(ch[2] for ch in picked)
if not HEAD_RE.match(body.split("\n")[0]):
print("ОТКАЗ: первая строка среза не заголовок — движок сдвинет нумерацию")
return 2
data = body.replace("\r\n", "\n").replace("\n", "\r\n").encode("gb18030", errors="strict")
out_path.write_bytes(data)
sha = hashlib.sha256(data).hexdigest()
print(f"срез: главы книги {first}{last} → в файле пронумеруются 1{len(picked)}")
print(f" файл : {out_path}")
print(f" байт : {len(data)}")
print(f" sha256 : {sha}")
print(f" заголовков в срезе: {sum(1 for ch in picked if HEAD_RE.match(ch[1]))}")
print(f" первая строка: {picked[0][1][:60]}")
print(f" последняя глава: {picked[-1][1][:60]}")
print()
print("В пре-рег занести ИМЕННО ЭТУ пару: диапазон книги и sha256 файла.")
print(f" главы книги {first}{last} ≡ главы среза 1{len(picked)}, sha256 {sha}")
return 0
def selftest():
"""Гейты на самом скрипте: без них резалка может молча отдать не тот формат."""
ok = True
def check(name, cond):
nonlocal ok
print(("" if cond else "") + name)
ok = ok and cond
print("СЕЛФТЕСТ narezka.py")
check("исходник на месте", SRC.exists())
if SRC.exists():
chapters = split_chapters(SRC.read_text(encoding="utf-8"))
check(f"глав в исходнике > 2000 (найдено {len(chapters)})", len(chapters) > 2000)
# ⚠ Тест первой редакции проверял повтор ЦЕЛОЙ строки заголовка и был красным: строки
# уникальны, потому что несут НАЗВАНИЕ главы («第一节:纵身亡魔心仍不悔»). Повторяется
# НОМЕР — вот его и проверяем, ради этого и режем по порядку вхождения.
nums = [re.match(r"^第[^:]{1,12}节", c[1]).group(0) for c in chapters
if re.match(r"^第[^:]{1,12}节", c[1])]
check(f"НОМЕР главы в исходнике повторяется (тома): {len(nums)} заголовков, "
f"{len(set(nums))} различных номеров", len(set(nums)) < len(nums))
body = "\n".join(c[2] for c in chapters[25:42])
check("первая строка среза 2642 — заголовок", bool(HEAD_RE.match(body.split("\n")[0])))
try:
enc = body.replace("\n", "\r\n").encode("gb18030")
check("срез кодируется в GB18030 без потерь", True)
check("CRLF на месте", b"\r\n" in enc)
except UnicodeEncodeError as e:
check(f"срез кодируется в GB18030 без потерь — ОШИБКА {e}", False)
ref = Path.home() / "books/gu-zhenren/coldrun-v16/guzhenren-ch1-10.gb18030.txt"
if ref.exists():
raw = ref.read_bytes()
check("эталон холодного прогона тоже CRLF", b"\r\n" in raw)
head = raw[:60].decode("gb18030", errors="replace")
check("эталон начинается с заголовка", bool(HEAD_RE.match(head)))
print("\n" + ("РЕЗАЛКА ГОДНА" if ok else "⛔ РЕЗАЛКА НЕ ГОДНА"))
return 0 if ok else 1
def main():
ap = argparse.ArgumentParser(description="вырезать диапазон глав в формат движка")
ap.add_argument("--from", dest="first", type=int, help="первая глава книги (сквозной номер)")
ap.add_argument("--to", dest="last", type=int, help="последняя глава книги включительно")
ap.add_argument("--out", type=Path, help="кудаписать срез")
ap.add_argument("--src", type=Path, default=SRC)
ap.add_argument("--selftest", action="store_true")
a = ap.parse_args()
if a.selftest:
return selftest()
if not (a.first and a.last and a.out):
ap.error("нужны --from, --to и --out (или --selftest)")
return cut(a.src, a.first, a.last, a.out)
if __name__ == "__main__":
sys.exit(main())

165
eval/dovodka/pachka.sh Executable file
View file

@ -0,0 +1,165 @@
#!/usr/bin/env bash
# pachka.sh — интерливинг пробы по четырём осям: юнит за юнитом, пять клеток в порядке от сида.
#
# ⛔ ЧЕТЫРЕ МЕСТА, ГДЕ ЦИКЛ ИЗ §9.3 ПЛАНА СЛОМАЛСЯ БЫ МОЛЧА — здесь они исправлены, и каждое
# исправление записано в PREREG.md §9 как находка, а не как правка замороженного:
#
# 1. `rc=$?` ПОСЛЕ `| tee` возвращает код tee, а не tmctl ⇒ проверка «код 4» не сработала бы
# НИКОГДА. Здесь — ${PIPESTATUS[0]}.
# 2. `grep -q 'VOLUME CEILING:' && break 2` разорвал бы цикл после ПЕРВОЙ клетки: строка печатается
# при res.Volume != nil, то есть на КАЖДОМ прогоне с --max-units (cmd/tmctl/render.go:130).
# Здесь останов идёт по ОТСУТСТВИЮ строки «never been delivered» — она и означает LeftFresh > 0.
# 3. `--config $B/arms/$arm.yaml` — translate грузит book.yaml, а не pipeline. Здесь .book.yaml.
# 4. --ceiling-usd сравнивается с НАКОПЛЕННЫМ расходом книги (cmd/tmctl/main.go:260), а копия несёт
# базовый расход ⇒ в флаг кладётся «база + доля», а не «доля».
#
# ⛔ --resnapshot НЕ ДАЁТСЯ НИКОГДА: он перекупил бы все уже оплаченные клетки. Предупреждение
# планировщика о будущем --resnapshot печатается на каждом ограниченном прогоне и ожидаемо.
#
# Скрипт РЕЗЮМИРУЕМ: клетки, уже купленные, движок отдаёт из чекпойнтов за $0.
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
N="${N:-20}" # из пре-рега (tmctl manifest, $0)
FROM_UNIT="${FROM_UNIT:-1}"
KEYS_REF=""
# Доли санкции — ЗАПИНЕНЫ ПРЕ-РЕГОМ. Сумма = 1.0000 ⇒ агрегат ровно $9.50 при любой базе.
declare -A W=( [p7-off]=0.30 [p9-off]=0.30 [p7-low]=0.1666666667 [p9-low]=0.1666666667 [glm-off]=0.0666666667 )
# ⚠ ЧТЕНИЕ ТОЛЬКО В RO: цикл ничего в базах не меняет, и открывать их RW незачем.
spend_of() { sqlite3 "file:$1?mode=ro" "select coalesce(sum(committed_usd+reserved_usd),0) from spend;" 2>/dev/null || echo 0; }
# ⛔ БАЗА БЕРЁТСЯ ИЗ ТОГО ФАЙЛА БЭКАПА, ИЗ КОТОРОГО СДЕЛАНЫ КОПИИ, а не с живой базовой БД.
# Причина: агрегат §9.5 есть «база + Σ(копия база)», и если живую базу кто-то тронет (или её
# тронет сам движок повторным прогоном), вычитаемое уедет — молча, и агрегат соврёт в обе стороны.
# BASE_SNAPSHOT_DB задаётся снаружи; по умолчанию — самый свежий бэкап.
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")
# ⚠ ПОТРАЧЕНО ВНЕ ТЕКУЩИХ БАЗ. Деньги, ушедшие провайдеру из БД, которая потом была пере-создана,
# из леджера копии ИСЧЕЗАЮТ, а из счёта — нет. Агрегат «база + Σ(копия база)» их не увидит.
# Заведено после того, как $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 + 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
con = sqlite3.connect(f"file:{p}?mode=ro", uri=True)
s = con.execute("select coalesce(sum(committed_usd+reserved_usd),0) from spend").fetchone()[0]
con.close(); tot += s - base
print(f"{tot:.6f}")
PYEOF
}
ceiling_for() { $PY -c "print(f'{$BASE + $1*($SANCTION-$BASE):.4f}')"; }
echo "════════════════════════════════════════════════════════════════════════"
echo "ПАЧКА: юнитов $N, сид $SEED, санкция \$$SANCTION"
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 $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 и несут свои клетки"
for (( u=FROM_UNIT; u<=N; u++ )); do
ORDER=$($PY "$REPO/eval/dovodka/poryadok.py" --unit "$u" --seed "$SEED") || { echo "⛔ порядок не получен"; exit 1; }
echo; echo "──── ЮНИТ $u/$N · порядок: $ORDER"
for arm in $ORDER; do
CEIL=$(ceiling_for "${W[$arm]}")
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" \
--verify-bank \
--max-units 1 \
--ceiling-usd "$CEIL" 2>&1 | tee "$CELL" | tee -a "$LOG"
rc=${PIPESTATUS[0]} # ⛔ не $? — тот вернул бы код tee
case $rc in
0|2) ;; # 0 чисто · 2 завершено с флагами — оба штатные
3) echo "⛔ КОД 3 (стоп подписи банка) В РУКЕ $arm — этого быть не должно: банк уже предъявлен"
echo " базовым прогоном и копии его несут. СТОП, вопрос владельцу."; exit 3 ;;
4) echo "⛔ КОД 4 (денежный отказ) В РУКЕ $arm — потолок. СТОП и вопрос владельцу;"
echo " добор после просмотра чисел запрещён."; exit 4 ;;
*) echo "⛔ НЕОЖИДАННЫЙ КОД $rc в руке $arm — СТОП."; exit "$rc" ;;
esac
# ⛔ Останов по РАБОТЕ, а не по строке VOLUME CEILING: она печатается всегда.
if ! grep -q 'never been delivered' "$CELL"; then
echo "$arm: недоставленных юнитов не осталось — рука закончена"
fi
# ⛔ ИНВАРИАНТ СИНХРОННОСТИ РУК. Блокировка трудности держится на том, что k-я клетка — ОДИН И
# ТОТ ЖЕ юнит у всех пяти рук. Оборванный вызов без чекпойнта сдвинул бы одну руку на юнит
# назад, и с этого места руки сравнивали бы РАЗНЫЕ чанки — молча, без единой ошибки.
DB="$B/arms/$arm.db"
KEYS_NOW=$(sqlite3 "file:$DB?mode=ro" "select coalesce(group_concat(k),'') from (select chapter||':'||chunk_idx k from chunk_status where stage='edit' order by chapter, chunk_idx);" 2>/dev/null || echo ERR)
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 # юнит 1 куплен пред-полётом, поэтому счёт совпадает с номером
echo "РУКА $arm РАЗЪЕХАЛАСЬ: редакторских юнитов $N_NOW вместо $u."
echo " Дальше руки сравнивали бы РАЗНЫЕ юниты — блокировка трудности сломана. СТОП."
exit 5
fi
# ⛔ СВЕРКА ПО НАБОРУ КЛЮЧЕЙ, А НЕ ПО СЧЁТЧИКУ: счётчик совпал бы и у рук, купивших РАЗНЫЕ юниты
# (движок мог бы выбрать другой порядок), и рассинхрон прошёл бы молча.
if [ -z "${KEYS_REF:-}" ]; then KEYS_REF="$KEYS_NOW"; fi
if [ "$KEYS_NOW" != "$KEYS_REF" ]; then
echo "РУКА $arm КУПИЛА ДРУГИЕ ЮНИТЫ:"
echo " эта рука : $KEYS_NOW"
echo " эталон : $KEYS_REF"
echo " Блокировка трудности сломана — руки сравнивают РАЗНЫЙ материал. СТОП."
exit 5
fi
done
KEYS_REF="" # эталон набора ключей задаётся ПЕРВОЙ рукой каждого юнита
SPENT=$(aggregate)
echo " ── агрегат после юнита $u: \$$SPENT из \$$SANCTION"
if $PY -c "import sys; sys.exit(0 if $SPENT >= $SANCTION else 1)"; then
echo "⛔ АГРЕГАТ ДОСТИГ САНКЦИИ — СТОП и вопрос владельцу. Добор запрещён."; exit 4
fi
done
echo; echo "ПАЧКА ЗАВЕРШЕНА. Агрегат: \$$(aggregate) из \$$SANCTION"

100
eval/dovodka/poryadok.py Normal file
View file

@ -0,0 +1,100 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""poryadok.py — детерминированный порядок рук внутри юнита от запиненного сида.
КОНТРАКТ, дословно из плана §2 (пре-рег, фриз dc602e9):
«четыре руки креста перестановкой от сида, `glm-off` ВСЕГДА последним пятым».
Зачем именно так: при срабатывании денежного гарда теряется РЕФЕРЕНС, а не пара, на которой
стоит главный эндпойнт E1.
Зачем вообще рандомизация (план §2, §4-E2): 2-4-я клетки юнита бьют префикс-кэш и вход дешевеет
на ~$0.006-0.009. Фиксированный порядок систематически удешевлял бы одни руки и удорожал другие
рандомизация переносит это в шум вместо смещения.
СИД ЗАПИНЕН В ПРЕ-РЕГЕ: 20260902. Без запиненного сида пре-рег не является пре-регом (план §9.4).
ПЕРЕСТАНОВКА СЧИТАЕТСЯ SHA256, А НЕ `random`: `random.Random(...).shuffle` не обещает
стабильности между версиями Python, и порядок, записанный в манифест сегодня, мог бы не
воспроизвестись при пере-снятии. sha256 обещает.
Использование:
python3 eval/dovodka/poryadok.py --unit 1 --seed 20260902 # одна строка: порядок рук
python3 eval/dovodka/poryadok.py --seed 20260902 --all 24 # таблица для манифеста
python3 eval/dovodka/poryadok.py --selftest
"""
import argparse
import hashlib
import sys
CROSS = ["p7-off", "p7-low", "p9-off", "p9-low"] # руки креста — перестановка
REFERENCE = "glm-off" # референс — ВСЕГДА пятый, не перемешивается
def order(unit, seed):
"""Перестановка четырёх рук креста + референс пятым. Детерминирована (sha256)."""
digest = hashlib.sha256(f"{seed}:{unit}".encode("utf-8")).digest()
# Алгоритм Фишера-Йетса на байтах дайджеста: детерминирован и не зависит от версии Python.
arms = list(CROSS)
out = []
for i in range(len(arms)):
# берём по байту на шаг; 4 руки ⇒ максимум 4 байта из 32, хватает с запасом
j = digest[i] % len(arms)
out.append(arms.pop(j))
return out + [REFERENCE]
def selftest():
ok = True
def check(name, cond):
nonlocal ok
print(("" if cond else "") + name)
ok = ok and cond
print("СЕЛФТЕСТ poryadok.py")
o1 = order(1, 20260902)
check(f"пять клеток в порядке: {o1}", len(o1) == 5)
check("референс ВСЕГДА пятый", all(order(u, 20260902)[-1] == REFERENCE for u in range(1, 200)))
check("референс не попадает в первые четыре",
all(REFERENCE not in order(u, 20260902)[:4] for u in range(1, 200)))
check("все четыре руки креста присутствуют ровно по разу",
all(sorted(order(u, 20260902)[:4]) == sorted(CROSS) for u in range(1, 200)))
check("детерминизм: повторный вызов даёт то же", order(7, 20260902) == order(7, 20260902))
check("сид меняет порядок (иначе рандомизации нет)",
any(order(u, 20260902) != order(u, 11111111) for u in range(1, 50)))
# ⚠ ПОЛОЖИТЕЛЬНЫЙ КОНТРОЛЬ на саму рандомизацию: если бы перестановка была вырожденной
# (всегда один порядок), проба систематически смещала бы кэш-выигрыш в одни и те же руки.
distinct = {tuple(order(u, 20260902)[:4]) for u in range(1, 200)}
check(f"порядков действительно много: {len(distinct)} различных из 24 возможных", len(distinct) >= 12)
# Каждая рука обязана бывать первой (первая клетка юнита НЕ бьёт префикс-кэш и потому дороже)
firsts = {}
for u in range(1, 200):
firsts[order(u, 20260902)[0]] = firsts.get(order(u, 20260902)[0], 0) + 1
check(f"каждая рука бывает первой: {firsts}", len(firsts) == 4 and min(firsts.values()) > 20)
print("\n" + ("ПОРЯДОК ГОДЕН" if ok else "⛔ ПОРЯДОК НЕ ГОДЕН"))
return 0 if ok else 1
def main():
ap = argparse.ArgumentParser(description="порядок рук внутри юнита от сида")
ap.add_argument("--unit", type=int)
ap.add_argument("--seed", type=int, default=20260902)
ap.add_argument("--all", type=int, metavar="N", help="напечатать таблицу для N юнитов (для манифеста)")
ap.add_argument("--selftest", action="store_true")
a = ap.parse_args()
if a.selftest:
return selftest()
if a.all:
print(f"# порядок клеток внутри юнита, сид {a.seed} (пре-рег)")
for u in range(1, a.all + 1):
print(f"юнит {u:2d}: " + " ".join(order(u, a.seed)))
return 0
if a.unit is None:
ap.error("нужен --unit, или --all N, или --selftest")
print(" ".join(order(a.unit, a.seed)))
return 0
if __name__ == "__main__":
sys.exit(main())

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

@ -0,0 +1,361 @@
# ПРЕ-РЕГИСТРАЦИЯ СТЕНДА — контролируемая проба редактора по четырём осям
**Дата: 02.09.2026. Ветка `polygon`. Сессия-исполнитель `textmachine-2f`.**
**Закон: `eval/dovodka/PLAN-01-09.md`** (фриз ДОКУМЕНТА — коммит `dc602e9`).
Промт исполнителя: `eval/dovodka/PROMPT-PROBE-4AXES.md`.
> Настоящий коммит — **ФРИЗ №1: СТЕНД**, и он ложится **ДО первой платной клетки**. `dc602e9` заморозил
> дизайн, модели, правило решения, потолок, рамку и сид; здесь замораживается всё остальное, что после
> первой покупки править уже нельзя: конфиги, скрипты, sha256 и правило кассы.
> **ФРИЗ №2** — пин сигнатуры Δ`prompt_tokens`, снятой пред-полётом (план §4: полосу нельзя было задать
> заранее). Оба объявляются владельцу вслух.
Машинная копия всего перечисленного — `prereg.json` рядом (её читает `chetyre.py --manifest`).
Замороженные копии конфигов и промптов — `stand/`. Они лежат в зоне полигона, потому что физически
конфиги живут в каталоге книги, а `books/`**отдельный репозиторий**, и «фриз коммитом» там
неисполним по канону (план §5.5).
---
## 1. РАМКА ВЫБОРКИ — пинится первой, иначе «20 юнитов» читаются как ОТБОР
| что | значение |
|---|---|
| главы книги | **2642** (17 глав) |
| нумерация в движке | **117** в колонке `chapter` БД ⚠ движок не умеет смещение глав |
| файл среза | `~/books/gu-zhenren/probe-4axes/guzhenren-ch26-42.gb18030.txt` |
| sha256 среза | `1ebcc9efb0fa5006548eba8599b58d4d5a44a201562271d0ff9f1eb9b803fccf` |
| размер | 97 408 Б GB18030 · CRLF · 17 заголовков · 47 519 знаков без переводов строк |
| резалка | `eval/dovodka/narezka.py --from 26 --to 42` (вход UTF-8, GB18030 на выходе) |
| **n юнитов** | **20** |
| каталог | **новый** `~/books/gu-zhenren/probe-4axes/` — не `coldrun-v16` (там три оплаченных edit-юнита) |
| отбора нет | все юниты диапазона в порядке движка; **юнит пред-полёта ВХОДИТ в n** |
**Как снято `n`, командой (`$0`, до первой покупки):**
```
$ ~/books/gu-zhenren/probe-4axes/bin/tmctl manifest --config ~/books/gu-zhenren/probe-4axes/book.yaml
chapters=17 units=20 chunks=35
source: 97408 bytes (sha256 1ebcc9efb0fa) · encoding gb18030 · zh→ru
```
**МИНА НУМЕРАЦИИ СЛАБЕЕ, ЧЕМ БОЯЛСЯ ПЛАН — и это тоже улика, а не мнение.** План писал, что
соответствие «главы книги 2642 ≡ главы среза 117» держится ТОЛЬКО записью в пре-реге. На деле у
движка есть СВОЙ носитель: чанкер разбирает китайский номер `第二十六节` и печатает в манифесте
заголовки **«Глава 26» … «Глава 42»** при позиционных `number` 1…17. То есть соответствие
восстанавливается из артефакта прогона, а не только из этого документа. Команда:
```
$ python -c "import json;m=json.load(open('.../guzhenren-probe4.db.manifest.json'));
print([(c['number'],c['heading'],c['units_total']) for c in m['chapters']])"
(1,'Глава 26',1) (2,'Глава 27',1) (3,'Глава 28',2) … (17,'Глава 42',1) Σ units = 20
```
Три главы (28, 37, 38) дают по два юнита, остальные по одному — отсюда 20, а не 17.
**ОТСТУПЛЕНИЕ ОТ ОЖИДАНИЯ ПЛАНА, названное до покупки:** план ждал ~24 юнита по пропорции холодного
прогона (14 на 10 глав). Фактически **20**. Это не нарушение — §6 плана прямо говорит, что вариант
задаётся ДИАПАЗОНОМ ГЛАВ, а «24» есть ожидание, а не цель. **Следствие для мощности объявляется здесь,
а не после чисел:** при σ=1.2 MDE растёт с ×2.02 (n=24) до **×2.16** (n=20).
## 2. СИД И ПОРЯДОК
Сид **`20260902`**. Порядок рук внутри юнита — `eval/dovodka/poryadok.py`, перестановка на sha256
(не `random`: он не обещает стабильности между версиями Python). Контракт: четыре руки креста —
перестановкой, **`glm-off` ВСЕГДА пятый**, чтобы при денежном гарде терялся референс, а не пара, на
которой стоит E1. Выпавший порядок всех 20 юнитов — в `prereg.json` (`порядок_рук`).
## 3. КРЕСТ И МОДЕЛИ — фиксированы
| рука | промпт | `stages[edit].reasoning` | модель | что уходит на провод |
|---|---|---|---|---|
| `p7-off` | `editor-p7.md` (июльский) | `"off"` | `deepseek-v4-pro` | ключа `reasoning_effort` НЕТ ⇒ вендор-дефолт |
| `p7-low` | тот же | `"low"` | `deepseek-v4-pro` | `reasoning_effort:"low"` |
| `p9-off` | `editor-p9.md` (сегодняшний) | `"off"` | `deepseek-v4-pro` | ключа нет |
| `p9-low` | тот же | `"low"` | `deepseek-v4-pro` | `low` |
| ⭐ `glm-off` | `editor-p9.md` | `"off"` | **`glm-5`** | `extra_body {thinking:{type:disabled}}`**референс, НЕ фактор креста** |
Черновик `deepseek-v4-flash`@`low`, хоп эскалации `deepseek-v4-pro`, терминолог и классификатор
`deepseek-v4-flash`@`low`**константы во всех пяти руках**. **Судья не вызывается.**
**Провод проверен по коду, не по памяти:** `capability.go:236-239` — ветка `ReasoningNone` пишет
`reasoning_effort` только если значение не пусто и не `"off"`; у `deepseek-v4-pro` в `models.yaml`
блока `capabilities.reasoning` нет вовсе ⇒ `off` = ключ не отправлен, `low` = отправлен буквально.
У `glm-5` `control: extra_body_disable`, `off_extra_body: {thinking:{type:disabled}}`у неё `off`
это НАСТОЯЩЕЕ выключение размышления.
**Различие двух промптов — ровно одна строка** (проверено `diff`): в `editor-p9.md` к правилу о
повторах добавлена оговорка про авторский рефрен. Метка `prompt_version` у обоих одна
(`v3-discourse-reflow`), поэтому ось берётся **по SHA**, а не по метке.
## 4. SHA256 — всё, что после этого коммита не правится
Полный список — `prereg.json` (`sha256`). Несущие:
```
1ebcc9efb0fa5006548eba8599b58d4d5a44a201562271d0ff9f1eb9b803fccf срез глав 26-42
5ed4ee7ed5bc6c48102798a55567fb6752da3309caff8005f8167f8133e792fc бинарь tmctl (main @ bd2077b)
21b7735009c25679abc42e018692d3fde93beb3146b157603b5d49acda81ee01 models.yaml (побайтно один в обоих деревьях)
2b84dee34b0861637a055fee60372d2553f85ffa4c1b535c1e781f5167e8c3a1 editor-p7.md (P₇, июльский)
1ad4544e564a9b4049231e79e961a9cbb85bc39de07c5ce72cdbc8d92cc68999 editor-p9.md (P₉, сегодняшний)
3d2a7123bafc22cc6f9f93c62d2fb4cce3bb4d311437f14cbec6411fe4b1221b pairs/zh-ru.yaml
```
**Бинарь НЕ ТОТ, что вёз холодный прогон** (`e6f872df…`, main @ `065d8ac`). Между ними уехали
`volume.go` (+118), `terminologist.go` (+102), `snapshotdiff.go` (новый), `rebill.go`, `status.go`;
в `stagerun.go` изменился ТОЛЬКО текст ошибки снапшот-гарда (проверено `git diff` — формула
`max_tokens` не тронута), а `snapshot.go`, `ledger.go` и `models.yaml` не тронуты вовсе. Пере-сборка
из `065d8ac` потребовала бы `checkout` в чужом рабочем дереве — запрещено каноном. Расхождение
названо здесь, а не скрыто.
## 5. КОЭФФИЦИЕНТ ВОССТАНОВЛЕНИЯ РАЗМЫШЛЕНИЯ
`R̂ = completion_tokens 0.3644 · len(сырой response_text попытки)`**0.3644 токена/знак**, запинено.
Границы (из плана §3, названы до покупки): CJK в выходе ломает оценку и завышает `R̂`; при `R̂ < ~700`
относительная ошибка 2070% и о величине таких клеток вывод не делается; берётся СЫРАЯ строка
попытки, не `sanitized_export`; типичная ошибка ~3%, худшая клетка 5.3%.
**У референса `glm-5` `R̂ = 0` ПО ПОСТРОЕНИЮ**, а не по формуле.
**Дыра прибора подтверждена на данных:** в базе холодного прогона `sum(reasoning_tokens>0) = 0` при
68 строках; положительный контроль — 38 строк с непустым `completion_tokens`.
## 6. ДЕНЬГИ — правило запинено ДО чисел
* **Санкция владельца: $9.50 в ПИКОВЫХ терминах.** Сессией не поднимается. Гард ⇒ стоп и вопрос.
* **Пояс базового прогона: `--ceiling-usd 0.60`.**
* **Потолок руки** `i` = `base_факт + w_i · (9.50 base_факт)`, веса пропорциональны ожидаемой цене
клетки (§6 плана: ∅ $0.09, low $0.05, референс $0.02):
| рука | `w` | смысл |
|---|---|---|
| `p7-off`, `p9-off` | 0.3000 каждая | дороже всех: без ручки думают больше |
| `p7-low`, `p9-low` | 0.1667 каждая | |
| `glm-off` | 0.0667 | референс, теряется первым |
Сумма весов **1.0000**, поэтому `base + Σ(потолок_i base) = $9.50 РОВНО` при любом фактическом
`base`. Это и есть механизм, которого у леджера нет: он считает потолок по `book_id`, а `spend`
лежит ВНУТРИ проектной базы, и каждая из пяти копий несёт базовый расход под тем же `book_id`.
* **Агрегат ведётся вручную после КАЖДОГО юнита:** `израсходовано = база + Σ(копия база)`.
* ⚠ `--ceiling-usd` сравнивается с **накопленным** расходом книги, а не с расходом прогона
(`cmd/tmctl/main.go:260`) ⇒ в него кладётся «база + доля», а не «доля».
* ⚠ `--ceiling-usd` перекрывает **только** `book_usd`; `day_usd` берётся из `book.yaml`
(`stagerun.go:496`) ⇒ дневной потолок задан в каждой руке как `book_usd + $1.00`.
**⭐ ПОРОГ, ОБЪЯВЛЕННЫЙ ДО ПРЕД-ПОЛЁТА, чтобы не выбирать его после чисел.** Цена клетки `low`
единственное число сметы без источника (движковых редакторских вызовов на `low` не существует).
В репозитории есть независимый замер, которого план не использовал: `docs/experiments/23-editor-tier.md:7139`,
арм `RNL` 30.08 на `low``$0.056695` за вызов, то есть ближе к «худшему» сценарию плана ($0.065),
чем к центральному ($0.04). ⇒ **Решение: пред-полётная клетка `low` ≤ $0.065 — иду дальше** (это
худший сценарий §6 плана, потолок его покрывает ценой потери 12 юнитов, что план называет штатным
исходом). **> $0.065 — СТОП и вопрос владельцу**, потому что выше в замороженной смете сценария нет.
## 7. ОПРЕДЕЛЕНИЕ «ДОСТАВЛЕНО» — объявляется ДО чисел, вместе с проверкой устойчивости
`delivered` меряется на **отгружаемом** тексте (`checkpoints.response_text` по `chunk_status.final_hash`;
`final_hash` может быть синтетическим `tm-sanitized-v1:<sha>` и тоже лежит в `checkpoints` — проверено).
* **delivered = 0** ⟺ диспозиция `skipped`, ИЛИ `final_hash` не резолвится, ИЛИ отгружаемый текст не
проходит $0-предикат: доля кириллицы среди букв < 0.99 либо в тексте есть CJK.
* **иначе** `delivered` = длина отгружаемого текста в знаках.
**Почему НЕ «flagged ⇒ 0»:** в холодном прогоне 1 из 3 редакторских юнитов — `sanitizer_stripped`
(CJK-утечка, санитайзер её вырезал, текст ОТГРУЖЕН, 6048 знаков). По правилу «flagged ⇒ 0» гард
«>20% пустых ⇒ прибор отказывает» сработал бы от санитайзера, а не от предмета замера. Флаги при этом
не теряются — они целиком идут в E5, разделённые по механизму (санитайзер / CJK / обрыв по длине).
**Строгий вариант (`flagged ⇒ 0`) считается ВСЕГДА и печатается рядом как заранее объявленная проверка
устойчивости.**
## 8. ПРАВИЛО РЕШЕНИЯ — из §4 плана, здесь не пересматривается
`p<0.05` двусторонне ⇒ эффект есть, величина = медиана Ходжеса–Лемана с 95% ДИ. Иначе ⇒
**«НИЖЕ РАЗРЕШЕНИЯ ЗАМЕРА»**, и это НЕ «эффекта нет». Полоса [0.8; 1.25] — **описательная**, решения
не несёт. Нули (ничьи от цензуры) СОХРАНЯЮТСЯ: `zero_method='pratt'` (умолчание `wilcox` их
выбрасывает — проверено исполнением на `scipy 1.18.0`). ДИ Ходжеса–Лемана написан руками: у
`wilcoxon` в этой версии интервала нет (проверено — у результата только `statistic`/`pvalue`/`count`/`index`).
Цензура на потолке — симметрично, по §4; величина под цензурой печатается со знаком **«≥»**.
## 9. ⛔ ОТСТУПЛЕНИЯ ОТ ЗАМОРОЖЕННОЙ ПРОЦЕДУРЫ — предъявляются, а не правятся молча
План §10 требует: «если исполнение покажет, что что-то из замороженного неверно — это НАХОДКА,
которая записывается и предъявляется». Ниже — все, найденные ДО первой покупки.
| # | § | что в плане | что на самом деле | что делаю |
|---|----|---|---|---|
| 1 | §9.2 | конфиги рук в `$B/arms/` | `LoadPair` ищет пар-слой в `<каталог PIPELINE-файла>/pairs/` (`config/pipeline.go:821`), а отсутствие файла = `(nil, nil)` **тихо** (`config/pair.go:55-64`). ⚠ **ПЕРВОНАЧАЛЬНАЯ ФОРМУЛИРОВКА ЭТОЙ СТРОКИ БЫЛА ПРЕУВЕЛИЧЕНА И СНЯТА — см. §9-КВАТЕР** | конфиги рук лежат в `$B/` рядом с единственным `$B/pairs/`; в `$B/arms/` только `.book.yaml` и `.db` — решение остаётся, но обосновано отсутствием дрейфа двух копий, а не опасностью |
| 2 | §9.3 | `--ceiling-usd 1.20` на базовый прогон | правило §9.0 п.1 — «чуть выше ожидания ЧЕРНОВОЙ волны»; ожидание, пере-снятое с леджера: не-edit расход $0.436110$0.214865 = $0.221245 на 28 174 знака ⇒ ×1.6866 = $0.373 + эскалация ≈ **$0.45**. Зазор при поясе 1.20 = $0.75; резервация одной edit-клетки ≈ 16000·3.96/1e6 + 5000·1.32/1e6 ≈ **$0.070** (⚠ расчёт, не замер: потолок вывода pro = флор 16000) ⇒ в зазор влезает **порядка десяти** редакторских юнитов, а купленная в базе редактура необратима | пояс **0.60**. Асимметрия в правильную сторону: перелёт стоит перекупки, недолёт — бесплатного resume (код 4, черновики целы). ⚠ Первая же резервация черновика замерена Ш1: **$0.012052** — оценка вчетверо выше фактической цены чанка ($0.0034 по холодному прогону), потому что леджер букирует пик |
| 3 | §9.3 | `rc=$?` после `\| tee` | в сниппете плана **нет `set -o pipefail`**, а без него `$?` возвращает код `tee` ⇒ проверка `[ $rc -eq 4 ]` **не сработает никогда**. Проверено исполнением: без `pipefail` `$?` = 0, с `pipefail` = 4, `${PIPESTATUS[0]}` = 4 в обоих случаях | `${PIPESTATUS[0]}` (правда независимо от `pipefail`) |
| 4 | §9.3 | останов цикла по `grep -q 'VOLUME CEILING:'` | строка печатается при `res.Volume != nil`, то есть на **КАЖДОМ** прогоне с `--max-units` (`cmd/tmctl/render.go:130`) ⇒ `break 2` рвал бы цикл после первой же клетки | цикл ограничен **`N=20` из пре-рега**, а не выводом; строка «never been delivered» лишь ПЕЧАТАЕТСЯ как признак «рука закончена». Останавливают цикл: код 4, код 3, неожиданный код, рассинхрон рук и агрегат $9.50 |
| 5 | §9.3 | `--config $B/arms/$arm.yaml` при том, что §9.2 зовёт `$arm.yaml` копией **pipeline** | `translate` грузит **book.yaml** | пять `book.yaml` рук, различающиеся ровно тремя полями (`pipeline`, `project_db`, `ceilings`) |
| 6 | §9.1 | `day_usd` не упомянут | `--ceiling-usd` его не перекрывает; наследованный из образца `day_usd: 1.00` остановил бы руку `∅` посреди пачки | `day_usd = book_usd + $1.00` в каждой руке |
| 7 | §5.3 | «мина вооружается ТУМБЛЕРОМ, а уровни эффорта её не вооружают» | `00-provider-quirks.md:123`: «`effort:"low"` **ПЕРЕ-ВООРУЖАЕТ** эхо-мину… защита деградирует НЕПРЕРЫВНО с эффортом»; :132 — off 4/20 против low 3/20, на N=20 неразличимо | формулировка снята как неверная; критерий E5 плана опасность уже несёт, поэтому дизайн не меняется |
| 8 | §4 | ковариатная модель `log R̂ ~ effort + prompt + log(len_draft) + (1\|unit)` как проверка устойчивости E1/E3 | **не может быть проверкой устойчивости E1/E3**: `len_draft` — величина УНИТ-УРОВНЕВАЯ (все четыре руки редактируют ОДИН черновик — это и есть блокировка трудности), а в сбалансированном внутри-юнитном дизайне юнит-уровневая ковариата НЕ МЕНЯЕТ оценок усилия и промпта ПО ПОСТРОЕНИЮ. Сам наклон длины при этом оценим — он считается отдельно | считаю руками обе величины, которые она дала бы: (1) МНК контраста по `log(len_draft)` — внутри-юнитная устойчивость; (2) МНК среднего по юниту `log R̂` по `log(len_draft)` — это и есть коэффициент длины в сбалансированном дизайне. `statsmodels` **не установлен**; ⚠ причина НЕ «нет сети» (сеть есть, колесо под cp314 существует — проверено), а вырожденность модели плюс риск правки общего venv при живых параллельных сессиях |
| 9 | §9.6 | «`n` = сколько получится» | `tmctl manifest` бесплатен и даёт `units_total` **до** первой покупки | `n = 20` запинено здесь |
| 10 | §9.3 | у рук флага `--verify-bank` нет | флаг оставлен и в руках **намеренно**: в копиях банк уже предъявлен базовым прогоном, `hasUnpresentedCluster` вернёт false (`mining.go:222-230`), и движок напечатает «every cluster already presented — continuing». Это $0-улика состояния банка и страховка: если рука вдруг соберёт НОВЫЙ кластер, она встанет кодом 3 громко, а не поедет с другим банком молча | флаг подаётся всем пяти рукам; отступление названо здесь |
## 9-КВАТЕР. ⛔ МОЯ СОБСТВЕННАЯ ОШИБКА, СНЯТАЯ ИСПОЛНЕНИЕМ ДО ФРИЗА
**Класс: чтение кода — своё и агента-контролёра — принято за факт о мире.** Ровно тот класс, которым
предыдущая сессия ошиблась одиннадцать раз (§8 плана). Записано здесь, потому что фриз не должен
нести преувеличенную находку.
**Что я утверждала:** «руки в `$B/arms/` без своего `pairs/` ТИХО потеряли бы калибровку нарезки,
это сдвинуло бы `segmentationSnap` ⇒ черновой снапшот ⇒ перекупка всех черновиков ×5».
**Что показало исполнение (два независимо достаточных опровержения, оба за $0):**
1. **Без каталога `pairs/` прогон ПАДАЕТ ГРОМКО, а не молча.** `prompts_root` откатывается на
несуществующий каталог, и загрузчик отказывает ещё до всякой нарезки:
```
tmctl: config .../nopairs/pipeline.yaml:
- gates.terminology is enabled but its role prompt is missing — expected .../prompts/zh-ru/terminologist.md
- gates.terminology.classify_types is on but its prompt is missing — expected .../prompts/zh-ru/classifier.md
RC=10 (отказ, $0, ни одного вызова)
```
2. **Даже с пар-слоем БЕЗ блока `segmentation` снапшот НЕ ДВИГАЕТСЯ.** Дженерик-умолчания в
`internal/config/pipeline.go:895-905` — это `1797 / 3200 / 1.1978 / 0.3852`, то есть **ровно те же
числа**, что несёт `pairs/zh-ru.yaml`. Прогон с вырезанным блоком дал снапшот
`8d5721f6559624d9…` — побайтно тот же, что и полный конфиг.
**Что остаётся верным:** механизм тихого отката у `LoadPair` реален, и для пары, чья калибровка
ОТЛИЧАЕТСЯ от дженерик-умолчаний, ловушка сработала бы. Для zh-ru — нет. Решение держать все пять
конфигов рук рядом с одним `pairs/` остаётся в силе, но его довод теперь честный: **оно исключает
расхождение двух копий пар-слоя**, а не спасает от перекупки, которой здесь не было.
## 9-ТЕР. ТРИ РЕШЕНИЯ ПРИБОРА, ОБЪЯВЛЕННЫЕ ДО ЧИСЕЛ
Найдены верификацией собственной работы (агент-контролёр + мои пере-проверки), исправлены ДО фриза.
**(а) ⛔ E5 меряет эхо по СЫРОМУ ответу попытки 0, а не по отгружаемому тексту.** Первая редакция
прибора считала CJK по отгружаемому — а санитайзер эхо ВЫРЕЗАЕТ, и эндпойнт, блокирующий любую
рекомендацию, был слеп ровно к своему предмету. Улика, снята командой на базе холодного прогона:
```
ЮНИТ флаг CJK в отгруженном | CJK в СЫРОМ ответе попытки 0
(1,0) sanitizer_stripped 0 | 10
(1,1) — 0 | 0
(2,0) — 0 | 0
```
Колонка по отгруженному остаётся ВТОРОЙ: она отвечает на другой вопрос — что увидел читатель.
Это важно вдвойне, потому что `00-provider-quirks.md:121` прямо говорит: эхо-безопасность
РЕДАКТОРСКОЙ роли на `pro`@`low` по плотному CJK **не мерена ни разу** (пять чистых хопов 31.08 —
роль ПЕРЕВОДЧИКА). ⇒ этот замер и есть первый такой контроль в проекте, и слепой E5 обнулил бы его.
**(б) ⚠ ПЕРЕКОС ПОРЯДКА ПРИ ЗАПИНЕННОМ СИДЕ — посчитан до чисел, сид НЕ трогается.** Выпавшая
перестановка на n=20 несимметрична:
```
первой клеткой юнита: p7-low 10 · p9-low 6 · p9-off 3 · p7-off 1
пара P7: low раньше off в 15 юнитах из 20 пара P9: 9 из 20
```
Первая клетка юнита не бьёт префикс-кэш и потому дороже примерно на 6000·($1.32$0.044)/1e6 ≈
**$0.0077**. ⇒ E2 у пары P7 систематически наклонён ПРОТИВ `low`. Сид — часть пре-рега и не
меняется; вместо этого E2 печатается в ЧЕТЫРЁХ заранее объявленных вариантах: попытка 0 ×
фактическая цена (основной) · итог юнита × фактическая цена · попытка 0 × строгое `delivered` ·
попытка 0 × **кэш-независимая цена** (весь вход по полной ставке — снимает перекос целиком).
На E1 это не влияет вовсе: `R̂` от кэша не зависит.
**(в) ⚠ ДЛИНА ВХОДА ЮНИТА СОБИРАЕТСЯ ИЗ ЧАНКОВ ПО МАНИФЕСТУ.** Юнит редактуры и чанк черновика —
разные единицы: в этом стенде 20 юнитов на 35 чанков, и **у 15 юнитов из 20 `chunk_count > 1`**.
Ключ `(глава, индекс)` у них СОВПАДАЕТ, поэтому «взять первый чанк» не дало бы ни ошибки, ни
пустоты — только занижённую ковариату у трёх четвертей выборки. Прибор суммирует
`first_chunk_idx … first_chunk_idx+chunk_count1`; неполный юнит становится `None`, а не полусуммой.
Проверено прямым тестом функции (сценарий 0 контроля).
## 9-БИС. ⭐ ПРОВОДНАЯ УЛИКА ЕСТЬ — план утверждал обратное
План §3 «Дыра 2»: «У движка нет проводного артефакта — стадийный лог эффорт не печатает… Оба
свидетеля косвенные; побайтной улики у движка нет». **Это неверно, и опровержение бесплатно.**
`internal/obs/logging.go:72` `LogLLMExchange` пишет **сырое тело запроса и ответа** на уровне DEBUG,
когда включены `LOG_LLM_BODIES=1` И `LOG_LEVEL=debug`. Вызывается из `internal/llm/httpllm.go:415`
это и есть OpenAI-совместимый путь, по которому едут и `deepseek`, и `zai`. Тело режется до 32 768 Б
**с сохранением ОБОИХ концов** — это и делает улику доступной: Go маршалит `map` с сортировкой ключей,
и `reasoning_effort` ложится ПОСЛЕ длинного `messages`, то есть в ХВОСТ тела, который обрезание сохраняет. Логируются ТОЛЬКО тела —
никогда URL и никогда заголовки, поэтому ключ утечь не может (комментарий там же, :70).
**Пред-полёт идёт с включённым каналом**, и в пре-рег ложится побайтная улика: у рук `low` в теле
стоит `"reasoning_effort":"low"`, у рук `∅` этого ключа НЕТ, у референса стоит
`"thinking":{"type":"disabled"}`. Это прямо исполняет требование промта §7.1 «прочитай вслух тело
запроса каждой руки». Пачка идёт уже БЕЗ канала — он нужен один раз, а объём логов не бесплатен.
⚠ Новой экспозиции текста это не создаёт: логи ложатся в `$B/logs/` внутри каталога книги, где сам
текст книги уже лежит.
**Второе бесплатное подтверждение, которое дал сам движок.** На `manifest` конфига `glm-off` он
напечатал предупреждение, а на четырёх руках креста — нет:
```
level=WARN msg="a stage sends the SOURCE text to a model whose wire SUPPRESSES thinking —
the ratified echo zone (D19.1 point 2: reasoning-off + dense CJK is a property of the model
class, not a vendor quirk)... it costs a paid call per hit" stages=edit→glm-5 source_lang=zh
```
То есть движок сам различает: у `glm-5` при `off` размышление ВЫКЛЮЧЕНО, а у `deepseek-v4-pro` при
`off` — нет (ключ просто не отправляется, thinking остаётся включённым). Ровно то различие, на
котором стоит дизайн, — предъявлено движком, а не конспектом.
## 10. ЧТО ФРИЗ НЕ ПОКРЫВАЕТ
Сигнатуру Δ`prompt_tokens` — её нельзя было задать заранее (единственное известное значение 79 снято
на английской паре и на риге). Пред-полёт её **замеряет**, ФРИЗ №2 её **пинит**, и только после этого
она становится гейтом с допуском **±2 токена** и правилом «>20% юнитов вне допуска ⇒ проба
аннулируется».
## 11. ПРИБОР ПРОВЕРЕН ИСПОЛНЕНИЕМ, А НЕ САМООТЧЁТОМ
`eval/dovodka/proverka_pribora.py` строит синтетические проектные базы той же схемы с ЗАРАНЕЕ
ИЗВЕСТНЫМ ответом и прогоняет по ним настоящий `chetyre.py`. Прямой тест функции + девять
сценариев, всё зелёное:
```
0. сборка длины входа юнита из чанков → 350 / 7 / 24 / None (наивная дала бы 100 — МОЛЧА)
1. эффект ×2 заложен → прибор нашёл, Ходжес-Леман ×0.512 против истины ×0.500 (код 0)
2. эффекта нет → «НИЖЕ РАЗРЕШЕНИЯ ЗАМЕРА», не «эффекта нет» (код 0)
3. рука вся пустая → ⛔ ПРИБОР ОТКАЗЫВАЕТ (код 2)
4. ось схлопнулась → ⛔ ПРИБОР ОТКАЗЫВАЕТ, назвав prompt_sha256 (код 2)
5. треть ∅ цензурирована → посчитал и объявил величину нижней границей (код 0)
6. 19 строк-повторов на клетку → взял НАСТОЯЩИЙ вызов, ×0.462 против истины ×0.500 (код 0)
7. сигнатура сломана у 40% юнитов → ⛔ ПРОБА АННУЛИРОВАНА (код 2)
8. эхо ЗА САНИТАЙЗЕРОМ → E5 видит CJK@сырой=4 при CJK@отгруженном=0 (код 0)
9. баз нет вовсе → ⛔ ПРИБОР ОТКАЗЫВАЕТ, а не печатает нули (код 2)
```
⚠ Сценарии 3, 4, 7 и 9 ждут от прибора ОТКАЗА — это не подгонка под зелень, а проверка того, что
прибор умеет говорить «нет».
⚠ Сценарий 6 проверяет защиту, которой в первой редакции прибора НЕ БЫЛО, и она куплена чтением
данных, а не догадкой: интерливинг гоняет каждую руку 20 раз с `--max-units 1`, и каждый прогон
дописывает в `request_log` строку-ПОВТОР уже оплаченной клетки — `tm_hit=1`, все токены и цена
НУЛЕВЫЕ, но `request_hash` тот же, то есть она соединяется с тем же чекпойнтом и несёт `attempt=0`.
Проверено на базе холодного прогона: 29 таких строк из 68, у всех нули. Прибор выбирает настоящий
вызов явно (`tm_hit=0`), а не по порядку `id`.
Команда: `./eval/.venv/bin/python eval/dovodka/proverka_pribora.py`
**Цикл интерливинга `eval/dovodka/pachka.sh` проверен тем же способом** — на ПОДДЕЛЬНОМ `tmctl`,
который печатает то же, что настоящий, и продвигает БД руки. Проверялись не успешные ветки (они
очевидны), а **отказные**:
| подделка | что обязан сделать цикл | получено |
|---|---|---|
| рука `p9-low` не продвигается (оборванный вызов без чекпойнта) | заметить рассинхрон и встать | ⛔ «РУКА p9-low РАЗЪЕХАЛАСЬ: 0 вместо 1», **код 5** |
| движок вернул код 4 | стоп и вопрос владельцу, добор запрещён | ⛔ код **4** |
| движок вернул код 3 в РУКЕ (банк уже предъявлен базой — так быть не должно) | стоп | ⛔ код **3** |
| копия несёт НЕ базовый расход (сделана не из того бэкапа) | не начинать: агрегат соврал бы | ⛔ «КОПИЯ p9-low НЕСЁТ $0.61 ВМЕСТО БАЗОВЫХ $0.45», код **1** |
| рука купила ДРУГОЙ юнит при том же ЧИСЛЕ юнитов | заметить по НАБОРУ КЛЮЧЕЙ | ⛔ «эта рука `51:0` · эталон `1:0`», код **5** |
⚠ Инвариант синхронности рук существует потому, что блокировка трудности держится на «k-я клетка —
ОДИН И ТОТ ЖЕ юнит у всех пяти рук». Оборванный вызов сдвинул бы одну руку на юнит назад, и дальше
руки сравнивали бы РАЗНЫЕ чанки — **молча, без единой ошибки**. Проверка после каждой клетки:
сверяются НАБОРЫ КЛЮЧЕЙ `chapter:chunk_idx` всех пяти рук, а не только их число — счётчик совпал бы
и у рук, купивших разные юниты. База для агрегата берётся из ФАЙЛА БЭКАПА, из которого сделаны копии,
а не с живой базовой БД: иначе любое касание базы сдвинуло бы вычитаемое молча.
## 12. ЧЕГО В ЗАМЕРЕ НЕТ НАМЕРЕННО
Судейства качества (качество при сниженном усилии — вне скоупа; только $0-гейты и ось безопасности
E5) · калибровки ставки платформы · правок `models.yaml`, capabilities, Go, боевого `pipeline-c1.yaml` ·
смены модели, черновика, дифф-контракта · флага `--resnapshot`**никогда**.

View file

@ -0,0 +1,254 @@
{
"заголовок": "ПРЕ-РЕГИСТРАЦИЯ СТЕНДА: контролируемая проба редактора по четырём осям",
"дата": "2026-09-02",
"закон": "eval/dovodka/PLAN-01-09.md (фриз документа — коммит dc602e9)",
"промт_исполнителя": "eval/dovodka/PROMPT-PROBE-4AXES.md",
"ветка": "polygon",
"рамка_выборки": {
"главы_книги": [
26,
42
],
умерация_в_движке": [
1,
17
],
"мина": "движок не умеет смещение глав — соответствие держится ТОЛЬКО этой записью",
"знаков_в_срезе": 47519,
"байт": 97408,
"n_юнитов": 20,
ак_снято": "$B/bin/tmctl manifest --config $B/book.yaml ⇒ chapters=17 units=20 chunks=35 ($0, до первой покупки)",
"отборает": "все юниты диапазона в порядке движка; юнит пред-полёта ВХОДИТ в n",
"отступление_от_ожидания": "план ожидал ~24 юнита по пропорции холодного прогона; фактически 20. Рамка задана ДИАПАЗОНОМ ГЛАВ (§6 плана), поэтому это не отбор. Следствие для мощности: MDE при n=20 ≈ ×2.16 против ×2.02 при n=24 (σ=1.2)."
},
"сид": 20260902,
"порядок_рук": {
"1": [
"p7-low",
"p9-off",
"p9-low",
"p7-off",
"glm-off"
],
"2": [
"p7-low",
"p9-off",
"p9-low",
"p7-off",
"glm-off"
],
"3": [
"p9-low",
"p7-low",
"p9-off",
"p7-off",
"glm-off"
],
"4": [
"p7-low",
"p9-off",
"p9-low",
"p7-off",
"glm-off"
],
"5": [
"p7-low",
"p9-off",
"p7-off",
"p9-low",
"glm-off"
],
"6": [
"p9-low",
"p7-off",
"p7-low",
"p9-off",
"glm-off"
],
"7": [
"p9-low",
"p9-off",
"p7-off",
"p7-low",
"glm-off"
],
"8": [
"p7-low",
"p9-off",
"p9-low",
"p7-off",
"glm-off"
],
"9": [
"p9-low",
"p7-low",
"p7-off",
"p9-off",
"glm-off"
],
"10": [
"p7-low",
"p9-off",
"p7-off",
"p9-low",
"glm-off"
],
"11": [
"p7-low",
"p9-low",
"p7-off",
"p9-off",
"glm-off"
],
"12": [
"p9-low",
"p9-off",
"p7-off",
"p7-low",
"glm-off"
],
"13": [
"p9-off",
"p9-low",
"p7-off",
"p7-low",
"glm-off"
],
"14": [
"p7-low",
"p9-off",
"p9-low",
"p7-off",
"glm-off"
],
"15": [
"p7-off",
"p9-low",
"p9-off",
"p7-low",
"glm-off"
],
"16": [
"p9-off",
"p7-low",
"p9-low",
"p7-off",
"glm-off"
],
"17": [
"p7-low",
"p7-off",
"p9-off",
"p9-low",
"glm-off"
],
"18": [
"p7-low",
"p9-low",
"p7-off",
"p9-off",
"glm-off"
],
"19": [
"p9-low",
"p9-off",
"p7-low",
"p7-off",
"glm-off"
],
"20": [
"p9-off",
"p9-low",
"p7-low",
"p7-off",
"glm-off"
]
},
"модели": {
"редактор": "deepseek-v4-pro — ПРЕДМЕТ ЗАМЕРА: reasoning off против low",
"черновик": "deepseek-v4-flash, reasoning low — константа",
"хоп_эскалации_черновика": "deepseek-v4-pro, наследует эффорт стадии",
ерминолог_и_классификатор": "deepseek-v4-flash, reasoning low — константа",
"референс": "glm-5, reasoning off (= thinking ВЫКЛЮЧЕН: capabilities.reasoning.control=extra_body_disable), few_shot false — НЕ фактор креста",
"судья": "НЕ ВЫЗЫВАЕТСЯ"
},
"prompt_sha256": {
"P7": "2b84dee34b0861637a055fee60372d2553f85ffa4c1b535c1e781f5167e8c3a1",
"P9": "1ad4544e564a9b4049231e79e961a9cbb85bc39de07c5ce72cdbc8d92cc68999"
},
"signature": {
"P7": 79,
"P9": 79
},
оэффициент_восстановления_размышления": 0.3644,
"деньги": {
"санкция_владельца_пиковая": 9.5,
"пояс_базового_прогона": 0.6,
"правило_потолка_руки": "потолок_i = base_факт + w_i·(9.50 base_факт)",
"веса": {
"p7-off": 0.3,
"p9-off": 0.3,
"p7-low": 0.16666666666666666,
"p9-low": 0.16666666666666666,
"glm-off": 0.06666666666666667
},
"агрегат": "израсходовано = база + Σ(копия база); при полном выборе долей = $9.50 РОВНО",
"порогены_клетки_low": 0.065
},
"sha256": {
"срез исходника (главы книги 26-42 ≡ главы среза 1-17)": "1ebcc9efb0fa5006548eba8599b58d4d5a44a201562271d0ff9f1eb9b803fccf",
"бинарь tmctl (собран из textmachine-main @ bd2077b)": "5ed4ee7ed5bc6c48102798a55567fb6752da3309caff8005f8167f8133e792fc",
"models.yaml (побайтно один в обоих деревьях)": "21b7735009c25679abc42e018692d3fde93beb3146b157603b5d49acda81ee01",
"editor-p7.md — ИЮЛЬСКИЙ промпт (e89874b^)": "2b84dee34b0861637a055fee60372d2553f85ffa4c1b535c1e781f5167e8c3a1",
"editor-p9.md — СЕГОДНЯШНИЙ промпт (HEAD)": "1ad4544e564a9b4049231e79e961a9cbb85bc39de07c5ce72cdbc8d92cc68999",
"pairs/zh-ru.yaml (пар-слой, общий для базы и рук)": "3d2a7123bafc22cc6f9f93c62d2fb4cce3bb4d311437f14cbec6411fe4b1221b",
"book.yaml базы": "d39e8e2d32f673cf987cc6c8a7a616dac1909c831095dd2e629917e43077b570",
"pipeline.yaml базы": "9dcb1da8617e8ad7a18e63ecd4509102c85ad573fbc1f28c79c61fd6d2716713",
"arm-p7-off.pipeline.yaml": "68b2e066ffcffe7828d701c865a447dab84d1056f8573628b441f512233328dc",
"arms/p7-off.book.yaml": "9812867401367707d98c59743d4cb3a2adcbb70a7b142d8898c36a5637db1a69",
"arm-p7-low.pipeline.yaml": "5e4701700c7b4053161d62a3bfad2c347affc60700c221ed05725d46c4cde665",
"arms/p7-low.book.yaml": "876c02e83d07b9e761626c256de3b9c4815fad3719e555b4b4018566c59edf29",
"arm-p9-off.pipeline.yaml": "4debee2eae39a67c21a3db03ffb5475fa91b4a26b1c42e432f3606a183845038",
"arms/p9-off.book.yaml": "3367286fcdbecdbbecbbfa8aaa944e943b69d8791d07522bc0545e5152cabdeb",
"arm-p9-low.pipeline.yaml": "9b293ef6f09a15ade611491314ec760f9e4c57fe352015f6613edf67a66d0d7d",
"arms/p9-low.book.yaml": "75ba5b5cf12b33f4d77d4e8a5c765f407bcf1a7a516f5edf5758bf20927e4aef",
"arm-glm-off.pipeline.yaml": "54ddc4a29136422e6f47e65b8f654ff000e89aaaabf8ae9eac89d176c40b5008",
"arms/glm-off.book.yaml": "1693c44b5b07f097fd91d748be6dc1e40df65ce793475eafa8b63f104bd845f5",
"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": "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",
од_выхода": 4,
"потрачено_usd": 0.0,
"отказанная_оценка_первогоанка_usd": 0.012052,
ерновой_снапшот": "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"
}
}

View file

@ -0,0 +1,70 @@
# РУКА «glm-off» — ПРОФИЛЬ КНИГИ КОПИИ. Сгенерирован из $B/book.yaml.
# Отличий от базового профиля РОВНО ТРИ — ровно те, что называет план §2 «условия копирования»:
# 1. pipeline → arm-glm-off.pipeline.yaml
# 2. project_db → arms/glm-off.db (копия базовой БД, сделанная `tmctl backup`, НЕ `cp`)
# 3. ceilings → своя касса
# ⛔ book_id НЕ меняется: он входит в request_hash, и его смена обнулила бы $0-resume черновиков.
#
# КАССА, ПРАВИЛО ЗАПИНЕНО ДО ЧИСЕЛ. Леджер считает потолок по book_id (store/ledger.go), а копия
# несёт базовый расход под тем же book_id ⇒ потолок руки = базовый расход + доля руки. Доли —
# пропорционально ожидаемой цене клетки (план §6: ∅ $0.09, low $0.05, референс $0.02):
# w(p7-off)=w(p9-off)=0.300 · w(p7-low)=w(p9-low)=0.1667 · w(glm-off)=0.0667, сумма 1.000
# потолок_i = base + w_i·($9.50 base) ⇒ Σ(потолок_i base) + base = $9.50 ТОЧНО.
# Здесь book_usd посчитан при base = поясе базового прогона $0.60 ⇒ 1.19.
# ⚠ ОПЕРАТИВНЫЙ контроль — флаг `--ceiling-usd`, пересчитанный по ТОМУ ЖЕ правилу от ФАКТИЧЕСКОГО
# расхода базы: он перекрывает book_usd на прогон (main.go:287) и, что важно, СРАВНИВАЕТСЯ С
# НАКОПЛЕННЫМ расходом книги, а не с расходом прогона (main.go:260) — значит в него кладётся
# «база + доля», а не «доля».
# ⚠ day_usd задаётся ТОЛЬКО здесь: --ceiling-usd его НЕ перекрывает (stagerun.go:496 берёт
# DayUSD из книги). Держится на $1.00 выше книжного, чтобы несущим стопом был книжный.
# ПРОБА РЕДАКТОРА ПО ЧЕТЫРЁМ ОСЯМ — БАЗОВЫЙ ПРОФИЛЬ КНИГИ.
# Пре-рег: eval/dovodka/PLAN-01-09.md (фриз документа dc602e9). Промт исполнителя:
# eval/dovodka/PROMPT-PROBE-4AXES.md. Ветка polygon, сессия textmachine-2f, 02.09.2026.
#
# Тело — копия ~/books/gu-zhenren/coldrun-v16/book.yaml. Каждое отличие объявлено:
# 1. ИДЕНТИЧНОСТЬ: book_id / pipeline / source_file / project_db → этот каталог.
# ⛔ book_id ОДИН для базы и всех ПЯТИ рук: он входит в request_hash, и разный book_id
# обнулил бы $0-resume черновиков в копиях (план §2, «условия копирования»).
# 2. ИСХОДНИК: срез глав книги 2642, нарезан eval/dovodka/narezka.py.
# ⚠ МИНА НУМЕРАЦИИ: движок не умеет смещение глав — в базе и отчётах это будут главы 117.
# Соответствие держится ТОЛЬКО пре-регом.
# 3. ПОТОЛКИ: пояс базового прогона. ⚠ План §9.3 давал --ceiling-usd 1.20; это ОТСТУПЛЕНИЕ,
# записанное как находка: правило §9.0 п.1 — «чуть выше ожидания ЧЕРНОВОЙ волны», а ожидание,
# пере-снятое с леджера холодного прогона (не-edit = $0.436110 $0.214865 = $0.221245 на
# 28 174 знака ⇒ ×1.6866 = $0.373 на 47 519 знаков) плюс эскалация ⇒ ≈$0.45. При поясе 1.20
# зазор до покупки редактуры ≈ 10 юнитов по резервации — а купленная в базе редактура
# необратима: четыре из пяти копий упрутся в stagerun.go:52 и потребуют запрещённого
# --resnapshot. Пояс 0.60 несимметричен в правильную сторону: перелёт по деньгам стоит
# перекупки, недолёт — бесплатного resume (код 4, черновики сохранены).
# 4. models / langpack_root — как в образце, В MAIN-ДЕРЕВЕ: в polygon нет langpacks/ru, а
# LoadReaderWords на отсутствующий каталог молча возвращает умолчания.
# 5. Сид НЕ подключается — банк рождается майнером + банкнотой, как в холодном прогоне.
book_id: guzhenren-probe4
title: 蛊真人
source_lang: zh
target_lang: ru
genre: вебновелла
audience: взрослые читатели вебновелл
adult: false
venuti: 0.6
honorifics: keep
transcription: palladius
footnotes: minimal
register_blocklist: [терем, терема, тереме, теремом, терему, теремах]
pipeline: /home/ubuntu/books/gu-zhenren/probe-4axes/arm-glm-off.pipeline.yaml
models: /home/ubuntu/projects/textmachine-main/backend/configs/models.yaml
source_file: /home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-ch26-42.gb18030.txt
encoding: gb18030
langpack_root: /home/ubuntu/projects/textmachine-main/backend/configs/langpacks
langpack_extend: /home/ubuntu/books/gu-zhenren/langpack-extend
project_db: /home/ubuntu/books/gu-zhenren/probe-4axes/arms/glm-off.db
# Пояс базового прогона (см. п.3 шапки). day_usd держится ВЫШЕ book_usd сознательно: несущим
# предохранителем обязан быть книжный потолок. ⚠ --ceiling-usd перекрывает ТОЛЬКО book_usd
# (stagerun.go:496 берёт DayUSD из книги), поэтому дневной потолок задаётся здесь и нигде больше.
ceilings:
book_usd: 1.19
day_usd: 2.19

View file

@ -0,0 +1,125 @@
# ⚠ ФАЙЛ СГЕНЕРИРОВАН ИЗ $B/pipeline.yaml. Рука креста: glm-off.
# Отличий от базового ран-конфига РОВНО ТРИ, и все три перечислены здесь:
# 1. stages[edit].prompt_override → editor-p9.md (ОСЬ ПРОМПТА)
# 2. stages[edit].reasoning → "off" (ОСЬ УСИЛИЯ — ПРЕДМЕТ ЗАМЕРА)
# 3. escalation.budget_usd → 0 (план §2: во всех ПЯТИ конфигах рук)
# 4. stages[edit].model → glm-5 (РЕФЕРЕНС, не фактор креста:
# ценовой контроль «тот же день, те же юниты». reasoning "off" у glm-5 = thinking
# ВЫКЛЮЧЕН по-настоящему: models.yaml capabilities.reasoning.control=extra_body_disable,
# off_extra_body {thinking:{type:disabled}} — в отличие от deepseek, где "off" это
# просто НЕотправка ключа. У glm-5 НЕТ min_max_tokens ⇒ потолок вывода у неё
# max(EstimateTokens·2.2, 2048) ≈4.5 тыс., а не 16000: «равный потолок» на неё НЕ
# распространяется, и это печатается рядом с её числом. ⚠ Выключенный thinking
# ВООРУЖАЕТ эхо-мину — её E5 считается ОТДЕЛЬНО.
#
# ⛔ ФАЙЛ ЛЕЖИТ РЯДОМ С $B/pairs/, И ЭТО НЕСУЩЕЕ. LoadPair ищет пар-слой по пути
# <каталог PIPELINE-файла>/pairs/<пара>.yaml (config/pipeline.go:821), а отсутствие файла — НЕ
# ошибка: (nil, nil), тихий откат на дженерик-нарезку (config/pair.go:55-64). Дженерик двигает
# segmentationSnap ⇒ ЧЕРНОВОЙ снапшот ⇒ все черновики перекупаются в каждой руке. План §9.2 клал
# руки в $B/arms/ без pairs/ рядом — это ОТСТУПЛЕНИЕ, записанное как находка; §10 плана оставляет
# тексты конфигов исполнителю, а один общий pairs/ вдобавок исключает расхождение двух копий.
#
# ПРОБА ПО ЧЕТЫРЁМ ОСЯМ — РАН-КОНФИГ БАЗОВОГО ПРОГОНА (черновая волна + майнинг + терминолог).
#
# Тело — копия ~/books/gu-zhenren/coldrun-v16/pipeline.yaml. Отличия, каждое объявлено:
#
# A. ПУТИ: prompt_override черновика и prompts_root — в main-дереве (каталоги промптов в двух
# чекаутах побайтно идентичны, diff -r чист — проверено 02.09). mining.contrast_path ОСТАВЛЕН
# в polygon-дереве: jieba-контраст (5 МБ, gitignored) физически лежит только там; проверено
# обоими ls, в textmachine-main файла нет.
#
# B. ЭСКАЛАЦИОННЫЙ СУБ-БЮДЖЕТ: 0.08 → 0.25. В БАЗОВОМ прогоне он ОБЯЗАН быть > 0 (план §2),
# иначе эхо-черновики останутся флагнутыми — и одинаково флагнутыми во всех пяти руках, то
# есть замер потеряет часть материала. Число — строка сметы плана §6 («≈$0.25 на 24 главы»),
# она же ≈1.4× пере-снятой проекции: холодный прогон на 10 главах израсходовал на draft-pro
# $0.103305 (sqlite3 request_log, stage=draft role=translator model=deepseek-v4-pro),
# ×1.6866 по знакам = $0.174.
#
# C. ⛔ КОММЕНТАРИЙ ОБРАЗЦА ПРО ЭФФОРТ — УДАЛЁН КАК ПРОТУХШИЙ (требование плана §9.2).
# В образце стояло «у pro ручки эффорта нет вовсе (вендор-маппинг low→high)». Это неверно и
# прямо противоречит предмету ЭТОГО замера: capability.go шлёт low|medium|high, вендор сменил
# таблицу маппинга 30.08. Оставленный, он учил бы следующего читателя, что ось не существует.
#
# D. stages[edit] здесь ЕСТЬ и совпадает с рукой p9-off. Базовый прогон обязан остановиться ДО
# edit-волны (--verify-bank + пояс). Совпадение с p9-off выбрано на случай, если стоп всё же
# не сработает: тогда купленная строка ложится на снапшот РЕАЛЬНОЙ руки, а не осиротелый.
# ⚠ Сам стоп проверен по коду: mining.go stopping = VerifyBank && editWave &&
# hasUnpresentedCluster(mined, presented). На СВЕЖЕЙ базе presented пусто ⇒ любая непустая
# дельта останавливает. Не остановит только ПУСТАЯ дельта — для этого и пояс.
#
# ⚠ Всё прочее оставлено БУКВОЙ КОПИИ, включая то, что я бы выбрала иначе: сравнимость денег с
# холодным прогоном держится на конфиге, а не только на байтах исходника.
# · stages[draft].reasoning: "low" — НЕСУЩЕЕ (квирки §10: при вендор-дефолте high 8 из 9 вызовов
# возвращают finish=length с пустым телом).
# · gates.terminology.reasoning: "low" — то же для бэнк-ролей.
# · prompt_override/banknote черновой стадии — не конвенционный боевой путь; оставлен ради
# денежной сравнимости.
core: C1
version: 1
defaults:
max_output_ratio: 2.2
min_max_tokens: 2048
context:
glossary_injection: selective
glossary_token_budget: 800
cache_ttl: "5m"
waves:
workers: 4
retries:
regenerate_before_escalate: 1
stages:
- name: draft
role: translator
prompt_override: /home/ubuntu/projects/textmachine-main/backend/prompts/zh-ru/translator-banknote.md
model: deepseek-v4-flash
prompt_version: v1-reflow-banknote
temperature: 0.3
reasoning: "low"
escalate_to: deepseek-v4-pro
- name: edit
role: editor
prompt_override: /home/ubuntu/books/gu-zhenren/probe-4axes/editor-p9.md
model: glm-5
prompt_version: v3-discourse-reflow
temperature: 0.4
reasoning: "off"
few_shot: false
gates:
coverage:
enabled: false
sent_cov_min: 0.75
min_chunk_chars: 500
sanitizer:
enabled: true
regression_guard:
enabled: true
banknote:
enabled: true
terminology:
enabled: true
model: deepseek-v4-flash
budget_usd: 0.05
target_script: Cyrillic
reasoning: "low"
classify_types: true
classify_budget_usd: 0.02
voice:
enabled: true
escalation:
chains:
default: [deepseek-v4-pro, glm-5.1, gemini-3.1-pro-preview]
adult: [grok-4.3]
budget_usd: 0
mining:
contrast_path: /home/ubuntu/projects/textmachine/eval/exp16/data/jieba_dict_general_zh.txt
fanout:
candidates: 1

View file

@ -0,0 +1,70 @@
# РУКА «p7-low» — ПРОФИЛЬ КНИГИ КОПИИ. Сгенерирован из $B/book.yaml.
# Отличий от базового профиля РОВНО ТРИ — ровно те, что называет план §2 «условия копирования»:
# 1. pipeline → arm-p7-low.pipeline.yaml
# 2. project_db → arms/p7-low.db (копия базовой БД, сделанная `tmctl backup`, НЕ `cp`)
# 3. ceilings → своя касса
# ⛔ book_id НЕ меняется: он входит в request_hash, и его смена обнулила бы $0-resume черновиков.
#
# КАССА, ПРАВИЛО ЗАПИНЕНО ДО ЧИСЕЛ. Леджер считает потолок по book_id (store/ledger.go), а копия
# несёт базовый расход под тем же book_id ⇒ потолок руки = базовый расход + доля руки. Доли —
# пропорционально ожидаемой цене клетки (план §6: ∅ $0.09, low $0.05, референс $0.02):
# w(p7-off)=w(p9-off)=0.300 · w(p7-low)=w(p9-low)=0.1667 · w(glm-off)=0.0667, сумма 1.000
# потолок_i = base + w_i·($9.50 base) ⇒ Σ(потолок_i base) + base = $9.50 ТОЧНО.
# Здесь book_usd посчитан при base = поясе базового прогона $0.60 ⇒ 2.08.
# ⚠ ОПЕРАТИВНЫЙ контроль — флаг `--ceiling-usd`, пересчитанный по ТОМУ ЖЕ правилу от ФАКТИЧЕСКОГО
# расхода базы: он перекрывает book_usd на прогон (main.go:287) и, что важно, СРАВНИВАЕТСЯ С
# НАКОПЛЕННЫМ расходом книги, а не с расходом прогона (main.go:260) — значит в него кладётся
# «база + доля», а не «доля».
# ⚠ day_usd задаётся ТОЛЬКО здесь: --ceiling-usd его НЕ перекрывает (stagerun.go:496 берёт
# DayUSD из книги). Держится на $1.00 выше книжного, чтобы несущим стопом был книжный.
# ПРОБА РЕДАКТОРА ПО ЧЕТЫРЁМ ОСЯМ — БАЗОВЫЙ ПРОФИЛЬ КНИГИ.
# Пре-рег: eval/dovodka/PLAN-01-09.md (фриз документа dc602e9). Промт исполнителя:
# eval/dovodka/PROMPT-PROBE-4AXES.md. Ветка polygon, сессия textmachine-2f, 02.09.2026.
#
# Тело — копия ~/books/gu-zhenren/coldrun-v16/book.yaml. Каждое отличие объявлено:
# 1. ИДЕНТИЧНОСТЬ: book_id / pipeline / source_file / project_db → этот каталог.
# ⛔ book_id ОДИН для базы и всех ПЯТИ рук: он входит в request_hash, и разный book_id
# обнулил бы $0-resume черновиков в копиях (план §2, «условия копирования»).
# 2. ИСХОДНИК: срез глав книги 2642, нарезан eval/dovodka/narezka.py.
# ⚠ МИНА НУМЕРАЦИИ: движок не умеет смещение глав — в базе и отчётах это будут главы 117.
# Соответствие держится ТОЛЬКО пре-регом.
# 3. ПОТОЛКИ: пояс базового прогона. ⚠ План §9.3 давал --ceiling-usd 1.20; это ОТСТУПЛЕНИЕ,
# записанное как находка: правило §9.0 п.1 — «чуть выше ожидания ЧЕРНОВОЙ волны», а ожидание,
# пере-снятое с леджера холодного прогона (не-edit = $0.436110 $0.214865 = $0.221245 на
# 28 174 знака ⇒ ×1.6866 = $0.373 на 47 519 знаков) плюс эскалация ⇒ ≈$0.45. При поясе 1.20
# зазор до покупки редактуры ≈ 10 юнитов по резервации — а купленная в базе редактура
# необратима: четыре из пяти копий упрутся в stagerun.go:52 и потребуют запрещённого
# --resnapshot. Пояс 0.60 несимметричен в правильную сторону: перелёт по деньгам стоит
# перекупки, недолёт — бесплатного resume (код 4, черновики сохранены).
# 4. models / langpack_root — как в образце, В MAIN-ДЕРЕВЕ: в polygon нет langpacks/ru, а
# LoadReaderWords на отсутствующий каталог молча возвращает умолчания.
# 5. Сид НЕ подключается — банк рождается майнером + банкнотой, как в холодном прогоне.
book_id: guzhenren-probe4
title: 蛊真人
source_lang: zh
target_lang: ru
genre: вебновелла
audience: взрослые читатели вебновелл
adult: false
venuti: 0.6
honorifics: keep
transcription: palladius
footnotes: minimal
register_blocklist: [терем, терема, тереме, теремом, терему, теремах]
pipeline: /home/ubuntu/books/gu-zhenren/probe-4axes/arm-p7-low.pipeline.yaml
models: /home/ubuntu/projects/textmachine-main/backend/configs/models.yaml
source_file: /home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-ch26-42.gb18030.txt
encoding: gb18030
langpack_root: /home/ubuntu/projects/textmachine-main/backend/configs/langpacks
langpack_extend: /home/ubuntu/books/gu-zhenren/langpack-extend
project_db: /home/ubuntu/books/gu-zhenren/probe-4axes/arms/p7-low.db
# Пояс базового прогона (см. п.3 шапки). day_usd держится ВЫШЕ book_usd сознательно: несущим
# предохранителем обязан быть книжный потолок. ⚠ --ceiling-usd перекрывает ТОЛЬКО book_usd
# (stagerun.go:496 берёт DayUSD из книги), поэтому дневной потолок задаётся здесь и нигде больше.
ceilings:
book_usd: 2.08
day_usd: 3.08

View file

@ -0,0 +1,117 @@
# ⚠ ФАЙЛ СГЕНЕРИРОВАН ИЗ $B/pipeline.yaml. Рука креста: p7-low.
# Отличий от базового ран-конфига РОВНО ТРИ, и все три перечислены здесь:
# 1. stages[edit].prompt_override → editor-p7.md (ОСЬ ПРОМПТА)
# 2. stages[edit].reasoning → "low" (ОСЬ УСИЛИЯ — ПРЕДМЕТ ЗАМЕРА)
# 3. escalation.budget_usd → 0 (план §2: во всех ПЯТИ конфигах рук)
#
# ⛔ ФАЙЛ ЛЕЖИТ РЯДОМ С $B/pairs/, И ЭТО НЕСУЩЕЕ. LoadPair ищет пар-слой по пути
# <каталог PIPELINE-файла>/pairs/<пара>.yaml (config/pipeline.go:821), а отсутствие файла — НЕ
# ошибка: (nil, nil), тихий откат на дженерик-нарезку (config/pair.go:55-64). Дженерик двигает
# segmentationSnap ⇒ ЧЕРНОВОЙ снапшот ⇒ все черновики перекупаются в каждой руке. План §9.2 клал
# руки в $B/arms/ без pairs/ рядом — это ОТСТУПЛЕНИЕ, записанное как находка; §10 плана оставляет
# тексты конфигов исполнителю, а один общий pairs/ вдобавок исключает расхождение двух копий.
#
# ПРОБА ПО ЧЕТЫРЁМ ОСЯМ — РАН-КОНФИГ БАЗОВОГО ПРОГОНА (черновая волна + майнинг + терминолог).
#
# Тело — копия ~/books/gu-zhenren/coldrun-v16/pipeline.yaml. Отличия, каждое объявлено:
#
# A. ПУТИ: prompt_override черновика и prompts_root — в main-дереве (каталоги промптов в двух
# чекаутах побайтно идентичны, diff -r чист — проверено 02.09). mining.contrast_path ОСТАВЛЕН
# в polygon-дереве: jieba-контраст (5 МБ, gitignored) физически лежит только там; проверено
# обоими ls, в textmachine-main файла нет.
#
# B. ЭСКАЛАЦИОННЫЙ СУБ-БЮДЖЕТ: 0.08 → 0.25. В БАЗОВОМ прогоне он ОБЯЗАН быть > 0 (план §2),
# иначе эхо-черновики останутся флагнутыми — и одинаково флагнутыми во всех пяти руках, то
# есть замер потеряет часть материала. Число — строка сметы плана §6 («≈$0.25 на 24 главы»),
# она же ≈1.4× пере-снятой проекции: холодный прогон на 10 главах израсходовал на draft-pro
# $0.103305 (sqlite3 request_log, stage=draft role=translator model=deepseek-v4-pro),
# ×1.6866 по знакам = $0.174.
#
# C. ⛔ КОММЕНТАРИЙ ОБРАЗЦА ПРО ЭФФОРТ — УДАЛЁН КАК ПРОТУХШИЙ (требование плана §9.2).
# В образце стояло «у pro ручки эффорта нет вовсе (вендор-маппинг low→high)». Это неверно и
# прямо противоречит предмету ЭТОГО замера: capability.go шлёт low|medium|high, вендор сменил
# таблицу маппинга 30.08. Оставленный, он учил бы следующего читателя, что ось не существует.
#
# D. stages[edit] здесь ЕСТЬ и совпадает с рукой p9-off. Базовый прогон обязан остановиться ДО
# edit-волны (--verify-bank + пояс). Совпадение с p9-off выбрано на случай, если стоп всё же
# не сработает: тогда купленная строка ложится на снапшот РЕАЛЬНОЙ руки, а не осиротелый.
# ⚠ Сам стоп проверен по коду: mining.go stopping = VerifyBank && editWave &&
# hasUnpresentedCluster(mined, presented). На СВЕЖЕЙ базе presented пусто ⇒ любая непустая
# дельта останавливает. Не остановит только ПУСТАЯ дельта — для этого и пояс.
#
# ⚠ Всё прочее оставлено БУКВОЙ КОПИИ, включая то, что я бы выбрала иначе: сравнимость денег с
# холодным прогоном держится на конфиге, а не только на байтах исходника.
# · stages[draft].reasoning: "low" — НЕСУЩЕЕ (квирки §10: при вендор-дефолте high 8 из 9 вызовов
# возвращают finish=length с пустым телом).
# · gates.terminology.reasoning: "low" — то же для бэнк-ролей.
# · prompt_override/banknote черновой стадии — не конвенционный боевой путь; оставлен ради
# денежной сравнимости.
core: C1
version: 1
defaults:
max_output_ratio: 2.2
min_max_tokens: 2048
context:
glossary_injection: selective
glossary_token_budget: 800
cache_ttl: "5m"
waves:
workers: 4
retries:
regenerate_before_escalate: 1
stages:
- name: draft
role: translator
prompt_override: /home/ubuntu/projects/textmachine-main/backend/prompts/zh-ru/translator-banknote.md
model: deepseek-v4-flash
prompt_version: v1-reflow-banknote
temperature: 0.3
reasoning: "low"
escalate_to: deepseek-v4-pro
- name: edit
role: editor
prompt_override: /home/ubuntu/books/gu-zhenren/probe-4axes/editor-p7.md
model: deepseek-v4-pro
prompt_version: v3-discourse-reflow
temperature: 0.4
reasoning: "low"
few_shot: false
gates:
coverage:
enabled: false
sent_cov_min: 0.75
min_chunk_chars: 500
sanitizer:
enabled: true
regression_guard:
enabled: true
banknote:
enabled: true
terminology:
enabled: true
model: deepseek-v4-flash
budget_usd: 0.05
target_script: Cyrillic
reasoning: "low"
classify_types: true
classify_budget_usd: 0.02
voice:
enabled: true
escalation:
chains:
default: [deepseek-v4-pro, glm-5.1, gemini-3.1-pro-preview]
adult: [grok-4.3]
budget_usd: 0
mining:
contrast_path: /home/ubuntu/projects/textmachine/eval/exp16/data/jieba_dict_general_zh.txt
fanout:
candidates: 1

View file

@ -0,0 +1,70 @@
# РУКА «p7-off» — ПРОФИЛЬ КНИГИ КОПИИ. Сгенерирован из $B/book.yaml.
# Отличий от базового профиля РОВНО ТРИ — ровно те, что называет план §2 «условия копирования»:
# 1. pipeline → arm-p7-off.pipeline.yaml
# 2. project_db → arms/p7-off.db (копия базовой БД, сделанная `tmctl backup`, НЕ `cp`)
# 3. ceilings → своя касса
# ⛔ book_id НЕ меняется: он входит в request_hash, и его смена обнулила бы $0-resume черновиков.
#
# КАССА, ПРАВИЛО ЗАПИНЕНО ДО ЧИСЕЛ. Леджер считает потолок по book_id (store/ledger.go), а копия
# несёт базовый расход под тем же book_id ⇒ потолок руки = базовый расход + доля руки. Доли —
# пропорционально ожидаемой цене клетки (план §6: ∅ $0.09, low $0.05, референс $0.02):
# w(p7-off)=w(p9-off)=0.300 · w(p7-low)=w(p9-low)=0.1667 · w(glm-off)=0.0667, сумма 1.000
# потолок_i = base + w_i·($9.50 base) ⇒ Σ(потолок_i base) + base = $9.50 ТОЧНО.
# Здесь book_usd посчитан при base = поясе базового прогона $0.60 ⇒ 3.27.
# ⚠ ОПЕРАТИВНЫЙ контроль — флаг `--ceiling-usd`, пересчитанный по ТОМУ ЖЕ правилу от ФАКТИЧЕСКОГО
# расхода базы: он перекрывает book_usd на прогон (main.go:287) и, что важно, СРАВНИВАЕТСЯ С
# НАКОПЛЕННЫМ расходом книги, а не с расходом прогона (main.go:260) — значит в него кладётся
# «база + доля», а не «доля».
# ⚠ day_usd задаётся ТОЛЬКО здесь: --ceiling-usd его НЕ перекрывает (stagerun.go:496 берёт
# DayUSD из книги). Держится на $1.00 выше книжного, чтобы несущим стопом был книжный.
# ПРОБА РЕДАКТОРА ПО ЧЕТЫРЁМ ОСЯМ — БАЗОВЫЙ ПРОФИЛЬ КНИГИ.
# Пре-рег: eval/dovodka/PLAN-01-09.md (фриз документа dc602e9). Промт исполнителя:
# eval/dovodka/PROMPT-PROBE-4AXES.md. Ветка polygon, сессия textmachine-2f, 02.09.2026.
#
# Тело — копия ~/books/gu-zhenren/coldrun-v16/book.yaml. Каждое отличие объявлено:
# 1. ИДЕНТИЧНОСТЬ: book_id / pipeline / source_file / project_db → этот каталог.
# ⛔ book_id ОДИН для базы и всех ПЯТИ рук: он входит в request_hash, и разный book_id
# обнулил бы $0-resume черновиков в копиях (план §2, «условия копирования»).
# 2. ИСХОДНИК: срез глав книги 2642, нарезан eval/dovodka/narezka.py.
# ⚠ МИНА НУМЕРАЦИИ: движок не умеет смещение глав — в базе и отчётах это будут главы 117.
# Соответствие держится ТОЛЬКО пре-регом.
# 3. ПОТОЛКИ: пояс базового прогона. ⚠ План §9.3 давал --ceiling-usd 1.20; это ОТСТУПЛЕНИЕ,
# записанное как находка: правило §9.0 п.1 — «чуть выше ожидания ЧЕРНОВОЙ волны», а ожидание,
# пере-снятое с леджера холодного прогона (не-edit = $0.436110 $0.214865 = $0.221245 на
# 28 174 знака ⇒ ×1.6866 = $0.373 на 47 519 знаков) плюс эскалация ⇒ ≈$0.45. При поясе 1.20
# зазор до покупки редактуры ≈ 10 юнитов по резервации — а купленная в базе редактура
# необратима: четыре из пяти копий упрутся в stagerun.go:52 и потребуют запрещённого
# --resnapshot. Пояс 0.60 несимметричен в правильную сторону: перелёт по деньгам стоит
# перекупки, недолёт — бесплатного resume (код 4, черновики сохранены).
# 4. models / langpack_root — как в образце, В MAIN-ДЕРЕВЕ: в polygon нет langpacks/ru, а
# LoadReaderWords на отсутствующий каталог молча возвращает умолчания.
# 5. Сид НЕ подключается — банк рождается майнером + банкнотой, как в холодном прогоне.
book_id: guzhenren-probe4
title: 蛊真人
source_lang: zh
target_lang: ru
genre: вебновелла
audience: взрослые читатели вебновелл
adult: false
venuti: 0.6
honorifics: keep
transcription: palladius
footnotes: minimal
register_blocklist: [терем, терема, тереме, теремом, терему, теремах]
pipeline: /home/ubuntu/books/gu-zhenren/probe-4axes/arm-p7-off.pipeline.yaml
models: /home/ubuntu/projects/textmachine-main/backend/configs/models.yaml
source_file: /home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-ch26-42.gb18030.txt
encoding: gb18030
langpack_root: /home/ubuntu/projects/textmachine-main/backend/configs/langpacks
langpack_extend: /home/ubuntu/books/gu-zhenren/langpack-extend
project_db: /home/ubuntu/books/gu-zhenren/probe-4axes/arms/p7-off.db
# Пояс базового прогона (см. п.3 шапки). day_usd держится ВЫШЕ book_usd сознательно: несущим
# предохранителем обязан быть книжный потолок. ⚠ --ceiling-usd перекрывает ТОЛЬКО book_usd
# (stagerun.go:496 берёт DayUSD из книги), поэтому дневной потолок задаётся здесь и нигде больше.
ceilings:
book_usd: 3.27
day_usd: 4.27

View file

@ -0,0 +1,117 @@
# ⚠ ФАЙЛ СГЕНЕРИРОВАН ИЗ $B/pipeline.yaml. Рука креста: p7-off.
# Отличий от базового ран-конфига РОВНО ТРИ, и все три перечислены здесь:
# 1. stages[edit].prompt_override → editor-p7.md (ОСЬ ПРОМПТА)
# 2. stages[edit].reasoning → "off" (ОСЬ УСИЛИЯ — ПРЕДМЕТ ЗАМЕРА)
# 3. escalation.budget_usd → 0 (план §2: во всех ПЯТИ конфигах рук)
#
# ⛔ ФАЙЛ ЛЕЖИТ РЯДОМ С $B/pairs/, И ЭТО НЕСУЩЕЕ. LoadPair ищет пар-слой по пути
# <каталог PIPELINE-файла>/pairs/<пара>.yaml (config/pipeline.go:821), а отсутствие файла — НЕ
# ошибка: (nil, nil), тихий откат на дженерик-нарезку (config/pair.go:55-64). Дженерик двигает
# segmentationSnap ⇒ ЧЕРНОВОЙ снапшот ⇒ все черновики перекупаются в каждой руке. План §9.2 клал
# руки в $B/arms/ без pairs/ рядом — это ОТСТУПЛЕНИЕ, записанное как находка; §10 плана оставляет
# тексты конфигов исполнителю, а один общий pairs/ вдобавок исключает расхождение двух копий.
#
# ПРОБА ПО ЧЕТЫРЁМ ОСЯМ — РАН-КОНФИГ БАЗОВОГО ПРОГОНА (черновая волна + майнинг + терминолог).
#
# Тело — копия ~/books/gu-zhenren/coldrun-v16/pipeline.yaml. Отличия, каждое объявлено:
#
# A. ПУТИ: prompt_override черновика и prompts_root — в main-дереве (каталоги промптов в двух
# чекаутах побайтно идентичны, diff -r чист — проверено 02.09). mining.contrast_path ОСТАВЛЕН
# в polygon-дереве: jieba-контраст (5 МБ, gitignored) физически лежит только там; проверено
# обоими ls, в textmachine-main файла нет.
#
# B. ЭСКАЛАЦИОННЫЙ СУБ-БЮДЖЕТ: 0.08 → 0.25. В БАЗОВОМ прогоне он ОБЯЗАН быть > 0 (план §2),
# иначе эхо-черновики останутся флагнутыми — и одинаково флагнутыми во всех пяти руках, то
# есть замер потеряет часть материала. Число — строка сметы плана §6 («≈$0.25 на 24 главы»),
# она же ≈1.4× пере-снятой проекции: холодный прогон на 10 главах израсходовал на draft-pro
# $0.103305 (sqlite3 request_log, stage=draft role=translator model=deepseek-v4-pro),
# ×1.6866 по знакам = $0.174.
#
# C. ⛔ КОММЕНТАРИЙ ОБРАЗЦА ПРО ЭФФОРТ — УДАЛЁН КАК ПРОТУХШИЙ (требование плана §9.2).
# В образце стояло «у pro ручки эффорта нет вовсе (вендор-маппинг low→high)». Это неверно и
# прямо противоречит предмету ЭТОГО замера: capability.go шлёт low|medium|high, вендор сменил
# таблицу маппинга 30.08. Оставленный, он учил бы следующего читателя, что ось не существует.
#
# D. stages[edit] здесь ЕСТЬ и совпадает с рукой p9-off. Базовый прогон обязан остановиться ДО
# edit-волны (--verify-bank + пояс). Совпадение с p9-off выбрано на случай, если стоп всё же
# не сработает: тогда купленная строка ложится на снапшот РЕАЛЬНОЙ руки, а не осиротелый.
# ⚠ Сам стоп проверен по коду: mining.go stopping = VerifyBank && editWave &&
# hasUnpresentedCluster(mined, presented). На СВЕЖЕЙ базе presented пусто ⇒ любая непустая
# дельта останавливает. Не остановит только ПУСТАЯ дельта — для этого и пояс.
#
# ⚠ Всё прочее оставлено БУКВОЙ КОПИИ, включая то, что я бы выбрала иначе: сравнимость денег с
# холодным прогоном держится на конфиге, а не только на байтах исходника.
# · stages[draft].reasoning: "low" — НЕСУЩЕЕ (квирки §10: при вендор-дефолте high 8 из 9 вызовов
# возвращают finish=length с пустым телом).
# · gates.terminology.reasoning: "low" — то же для бэнк-ролей.
# · prompt_override/banknote черновой стадии — не конвенционный боевой путь; оставлен ради
# денежной сравнимости.
core: C1
version: 1
defaults:
max_output_ratio: 2.2
min_max_tokens: 2048
context:
glossary_injection: selective
glossary_token_budget: 800
cache_ttl: "5m"
waves:
workers: 4
retries:
regenerate_before_escalate: 1
stages:
- name: draft
role: translator
prompt_override: /home/ubuntu/projects/textmachine-main/backend/prompts/zh-ru/translator-banknote.md
model: deepseek-v4-flash
prompt_version: v1-reflow-banknote
temperature: 0.3
reasoning: "low"
escalate_to: deepseek-v4-pro
- name: edit
role: editor
prompt_override: /home/ubuntu/books/gu-zhenren/probe-4axes/editor-p7.md
model: deepseek-v4-pro
prompt_version: v3-discourse-reflow
temperature: 0.4
reasoning: "off"
few_shot: false
gates:
coverage:
enabled: false
sent_cov_min: 0.75
min_chunk_chars: 500
sanitizer:
enabled: true
regression_guard:
enabled: true
banknote:
enabled: true
terminology:
enabled: true
model: deepseek-v4-flash
budget_usd: 0.05
target_script: Cyrillic
reasoning: "low"
classify_types: true
classify_budget_usd: 0.02
voice:
enabled: true
escalation:
chains:
default: [deepseek-v4-pro, glm-5.1, gemini-3.1-pro-preview]
adult: [grok-4.3]
budget_usd: 0
mining:
contrast_path: /home/ubuntu/projects/textmachine/eval/exp16/data/jieba_dict_general_zh.txt
fanout:
candidates: 1

View file

@ -0,0 +1,70 @@
# РУКА «p9-low» — ПРОФИЛЬ КНИГИ КОПИИ. Сгенерирован из $B/book.yaml.
# Отличий от базового профиля РОВНО ТРИ — ровно те, что называет план §2 «условия копирования»:
# 1. pipeline → arm-p9-low.pipeline.yaml
# 2. project_db → arms/p9-low.db (копия базовой БД, сделанная `tmctl backup`, НЕ `cp`)
# 3. ceilings → своя касса
# ⛔ book_id НЕ меняется: он входит в request_hash, и его смена обнулила бы $0-resume черновиков.
#
# КАССА, ПРАВИЛО ЗАПИНЕНО ДО ЧИСЕЛ. Леджер считает потолок по book_id (store/ledger.go), а копия
# несёт базовый расход под тем же book_id ⇒ потолок руки = базовый расход + доля руки. Доли —
# пропорционально ожидаемой цене клетки (план §6: ∅ $0.09, low $0.05, референс $0.02):
# w(p7-off)=w(p9-off)=0.300 · w(p7-low)=w(p9-low)=0.1667 · w(glm-off)=0.0667, сумма 1.000
# потолок_i = base + w_i·($9.50 base) ⇒ Σ(потолок_i base) + base = $9.50 ТОЧНО.
# Здесь book_usd посчитан при base = поясе базового прогона $0.60 ⇒ 2.08.
# ⚠ ОПЕРАТИВНЫЙ контроль — флаг `--ceiling-usd`, пересчитанный по ТОМУ ЖЕ правилу от ФАКТИЧЕСКОГО
# расхода базы: он перекрывает book_usd на прогон (main.go:287) и, что важно, СРАВНИВАЕТСЯ С
# НАКОПЛЕННЫМ расходом книги, а не с расходом прогона (main.go:260) — значит в него кладётся
# «база + доля», а не «доля».
# ⚠ day_usd задаётся ТОЛЬКО здесь: --ceiling-usd его НЕ перекрывает (stagerun.go:496 берёт
# DayUSD из книги). Держится на $1.00 выше книжного, чтобы несущим стопом был книжный.
# ПРОБА РЕДАКТОРА ПО ЧЕТЫРЁМ ОСЯМ — БАЗОВЫЙ ПРОФИЛЬ КНИГИ.
# Пре-рег: eval/dovodka/PLAN-01-09.md (фриз документа dc602e9). Промт исполнителя:
# eval/dovodka/PROMPT-PROBE-4AXES.md. Ветка polygon, сессия textmachine-2f, 02.09.2026.
#
# Тело — копия ~/books/gu-zhenren/coldrun-v16/book.yaml. Каждое отличие объявлено:
# 1. ИДЕНТИЧНОСТЬ: book_id / pipeline / source_file / project_db → этот каталог.
# ⛔ book_id ОДИН для базы и всех ПЯТИ рук: он входит в request_hash, и разный book_id
# обнулил бы $0-resume черновиков в копиях (план §2, «условия копирования»).
# 2. ИСХОДНИК: срез глав книги 2642, нарезан eval/dovodka/narezka.py.
# ⚠ МИНА НУМЕРАЦИИ: движок не умеет смещение глав — в базе и отчётах это будут главы 117.
# Соответствие держится ТОЛЬКО пре-регом.
# 3. ПОТОЛКИ: пояс базового прогона. ⚠ План §9.3 давал --ceiling-usd 1.20; это ОТСТУПЛЕНИЕ,
# записанное как находка: правило §9.0 п.1 — «чуть выше ожидания ЧЕРНОВОЙ волны», а ожидание,
# пере-снятое с леджера холодного прогона (не-edit = $0.436110 $0.214865 = $0.221245 на
# 28 174 знака ⇒ ×1.6866 = $0.373 на 47 519 знаков) плюс эскалация ⇒ ≈$0.45. При поясе 1.20
# зазор до покупки редактуры ≈ 10 юнитов по резервации — а купленная в базе редактура
# необратима: четыре из пяти копий упрутся в stagerun.go:52 и потребуют запрещённого
# --resnapshot. Пояс 0.60 несимметричен в правильную сторону: перелёт по деньгам стоит
# перекупки, недолёт — бесплатного resume (код 4, черновики сохранены).
# 4. models / langpack_root — как в образце, В MAIN-ДЕРЕВЕ: в polygon нет langpacks/ru, а
# LoadReaderWords на отсутствующий каталог молча возвращает умолчания.
# 5. Сид НЕ подключается — банк рождается майнером + банкнотой, как в холодном прогоне.
book_id: guzhenren-probe4
title: 蛊真人
source_lang: zh
target_lang: ru
genre: вебновелла
audience: взрослые читатели вебновелл
adult: false
venuti: 0.6
honorifics: keep
transcription: palladius
footnotes: minimal
register_blocklist: [терем, терема, тереме, теремом, терему, теремах]
pipeline: /home/ubuntu/books/gu-zhenren/probe-4axes/arm-p9-low.pipeline.yaml
models: /home/ubuntu/projects/textmachine-main/backend/configs/models.yaml
source_file: /home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-ch26-42.gb18030.txt
encoding: gb18030
langpack_root: /home/ubuntu/projects/textmachine-main/backend/configs/langpacks
langpack_extend: /home/ubuntu/books/gu-zhenren/langpack-extend
project_db: /home/ubuntu/books/gu-zhenren/probe-4axes/arms/p9-low.db
# Пояс базового прогона (см. п.3 шапки). day_usd держится ВЫШЕ book_usd сознательно: несущим
# предохранителем обязан быть книжный потолок. ⚠ --ceiling-usd перекрывает ТОЛЬКО book_usd
# (stagerun.go:496 берёт DayUSD из книги), поэтому дневной потолок задаётся здесь и нигде больше.
ceilings:
book_usd: 2.08
day_usd: 3.08

View file

@ -0,0 +1,117 @@
# ⚠ ФАЙЛ СГЕНЕРИРОВАН ИЗ $B/pipeline.yaml. Рука креста: p9-low.
# Отличий от базового ран-конфига РОВНО ТРИ, и все три перечислены здесь:
# 1. stages[edit].prompt_override → editor-p9.md (ОСЬ ПРОМПТА)
# 2. stages[edit].reasoning → "low" (ОСЬ УСИЛИЯ — ПРЕДМЕТ ЗАМЕРА)
# 3. escalation.budget_usd → 0 (план §2: во всех ПЯТИ конфигах рук)
#
# ⛔ ФАЙЛ ЛЕЖИТ РЯДОМ С $B/pairs/, И ЭТО НЕСУЩЕЕ. LoadPair ищет пар-слой по пути
# <каталог PIPELINE-файла>/pairs/<пара>.yaml (config/pipeline.go:821), а отсутствие файла — НЕ
# ошибка: (nil, nil), тихий откат на дженерик-нарезку (config/pair.go:55-64). Дженерик двигает
# segmentationSnap ⇒ ЧЕРНОВОЙ снапшот ⇒ все черновики перекупаются в каждой руке. План §9.2 клал
# руки в $B/arms/ без pairs/ рядом — это ОТСТУПЛЕНИЕ, записанное как находка; §10 плана оставляет
# тексты конфигов исполнителю, а один общий pairs/ вдобавок исключает расхождение двух копий.
#
# ПРОБА ПО ЧЕТЫРЁМ ОСЯМ — РАН-КОНФИГ БАЗОВОГО ПРОГОНА (черновая волна + майнинг + терминолог).
#
# Тело — копия ~/books/gu-zhenren/coldrun-v16/pipeline.yaml. Отличия, каждое объявлено:
#
# A. ПУТИ: prompt_override черновика и prompts_root — в main-дереве (каталоги промптов в двух
# чекаутах побайтно идентичны, diff -r чист — проверено 02.09). mining.contrast_path ОСТАВЛЕН
# в polygon-дереве: jieba-контраст (5 МБ, gitignored) физически лежит только там; проверено
# обоими ls, в textmachine-main файла нет.
#
# B. ЭСКАЛАЦИОННЫЙ СУБ-БЮДЖЕТ: 0.08 → 0.25. В БАЗОВОМ прогоне он ОБЯЗАН быть > 0 (план §2),
# иначе эхо-черновики останутся флагнутыми — и одинаково флагнутыми во всех пяти руках, то
# есть замер потеряет часть материала. Число — строка сметы плана §6 («≈$0.25 на 24 главы»),
# она же ≈1.4× пере-снятой проекции: холодный прогон на 10 главах израсходовал на draft-pro
# $0.103305 (sqlite3 request_log, stage=draft role=translator model=deepseek-v4-pro),
# ×1.6866 по знакам = $0.174.
#
# C. ⛔ КОММЕНТАРИЙ ОБРАЗЦА ПРО ЭФФОРТ — УДАЛЁН КАК ПРОТУХШИЙ (требование плана §9.2).
# В образце стояло «у pro ручки эффорта нет вовсе (вендор-маппинг low→high)». Это неверно и
# прямо противоречит предмету ЭТОГО замера: capability.go шлёт low|medium|high, вендор сменил
# таблицу маппинга 30.08. Оставленный, он учил бы следующего читателя, что ось не существует.
#
# D. stages[edit] здесь ЕСТЬ и совпадает с рукой p9-off. Базовый прогон обязан остановиться ДО
# edit-волны (--verify-bank + пояс). Совпадение с p9-off выбрано на случай, если стоп всё же
# не сработает: тогда купленная строка ложится на снапшот РЕАЛЬНОЙ руки, а не осиротелый.
# ⚠ Сам стоп проверен по коду: mining.go stopping = VerifyBank && editWave &&
# hasUnpresentedCluster(mined, presented). На СВЕЖЕЙ базе presented пусто ⇒ любая непустая
# дельта останавливает. Не остановит только ПУСТАЯ дельта — для этого и пояс.
#
# ⚠ Всё прочее оставлено БУКВОЙ КОПИИ, включая то, что я бы выбрала иначе: сравнимость денег с
# холодным прогоном держится на конфиге, а не только на байтах исходника.
# · stages[draft].reasoning: "low" — НЕСУЩЕЕ (квирки §10: при вендор-дефолте high 8 из 9 вызовов
# возвращают finish=length с пустым телом).
# · gates.terminology.reasoning: "low" — то же для бэнк-ролей.
# · prompt_override/banknote черновой стадии — не конвенционный боевой путь; оставлен ради
# денежной сравнимости.
core: C1
version: 1
defaults:
max_output_ratio: 2.2
min_max_tokens: 2048
context:
glossary_injection: selective
glossary_token_budget: 800
cache_ttl: "5m"
waves:
workers: 4
retries:
regenerate_before_escalate: 1
stages:
- name: draft
role: translator
prompt_override: /home/ubuntu/projects/textmachine-main/backend/prompts/zh-ru/translator-banknote.md
model: deepseek-v4-flash
prompt_version: v1-reflow-banknote
temperature: 0.3
reasoning: "low"
escalate_to: deepseek-v4-pro
- name: edit
role: editor
prompt_override: /home/ubuntu/books/gu-zhenren/probe-4axes/editor-p9.md
model: deepseek-v4-pro
prompt_version: v3-discourse-reflow
temperature: 0.4
reasoning: "low"
few_shot: false
gates:
coverage:
enabled: false
sent_cov_min: 0.75
min_chunk_chars: 500
sanitizer:
enabled: true
regression_guard:
enabled: true
banknote:
enabled: true
terminology:
enabled: true
model: deepseek-v4-flash
budget_usd: 0.05
target_script: Cyrillic
reasoning: "low"
classify_types: true
classify_budget_usd: 0.02
voice:
enabled: true
escalation:
chains:
default: [deepseek-v4-pro, glm-5.1, gemini-3.1-pro-preview]
adult: [grok-4.3]
budget_usd: 0
mining:
contrast_path: /home/ubuntu/projects/textmachine/eval/exp16/data/jieba_dict_general_zh.txt
fanout:
candidates: 1

View file

@ -0,0 +1,70 @@
# РУКА «p9-off» — ПРОФИЛЬ КНИГИ КОПИИ. Сгенерирован из $B/book.yaml.
# Отличий от базового профиля РОВНО ТРИ — ровно те, что называет план §2 «условия копирования»:
# 1. pipeline → arm-p9-off.pipeline.yaml
# 2. project_db → arms/p9-off.db (копия базовой БД, сделанная `tmctl backup`, НЕ `cp`)
# 3. ceilings → своя касса
# ⛔ book_id НЕ меняется: он входит в request_hash, и его смена обнулила бы $0-resume черновиков.
#
# КАССА, ПРАВИЛО ЗАПИНЕНО ДО ЧИСЕЛ. Леджер считает потолок по book_id (store/ledger.go), а копия
# несёт базовый расход под тем же book_id ⇒ потолок руки = базовый расход + доля руки. Доли —
# пропорционально ожидаемой цене клетки (план §6: ∅ $0.09, low $0.05, референс $0.02):
# w(p7-off)=w(p9-off)=0.300 · w(p7-low)=w(p9-low)=0.1667 · w(glm-off)=0.0667, сумма 1.000
# потолок_i = base + w_i·($9.50 base) ⇒ Σ(потолок_i base) + base = $9.50 ТОЧНО.
# Здесь book_usd посчитан при base = поясе базового прогона $0.60 ⇒ 3.27.
# ⚠ ОПЕРАТИВНЫЙ контроль — флаг `--ceiling-usd`, пересчитанный по ТОМУ ЖЕ правилу от ФАКТИЧЕСКОГО
# расхода базы: он перекрывает book_usd на прогон (main.go:287) и, что важно, СРАВНИВАЕТСЯ С
# НАКОПЛЕННЫМ расходом книги, а не с расходом прогона (main.go:260) — значит в него кладётся
# «база + доля», а не «доля».
# ⚠ day_usd задаётся ТОЛЬКО здесь: --ceiling-usd его НЕ перекрывает (stagerun.go:496 берёт
# DayUSD из книги). Держится на $1.00 выше книжного, чтобы несущим стопом был книжный.
# ПРОБА РЕДАКТОРА ПО ЧЕТЫРЁМ ОСЯМ — БАЗОВЫЙ ПРОФИЛЬ КНИГИ.
# Пре-рег: eval/dovodka/PLAN-01-09.md (фриз документа dc602e9). Промт исполнителя:
# eval/dovodka/PROMPT-PROBE-4AXES.md. Ветка polygon, сессия textmachine-2f, 02.09.2026.
#
# Тело — копия ~/books/gu-zhenren/coldrun-v16/book.yaml. Каждое отличие объявлено:
# 1. ИДЕНТИЧНОСТЬ: book_id / pipeline / source_file / project_db → этот каталог.
# ⛔ book_id ОДИН для базы и всех ПЯТИ рук: он входит в request_hash, и разный book_id
# обнулил бы $0-resume черновиков в копиях (план §2, «условия копирования»).
# 2. ИСХОДНИК: срез глав книги 2642, нарезан eval/dovodka/narezka.py.
# ⚠ МИНА НУМЕРАЦИИ: движок не умеет смещение глав — в базе и отчётах это будут главы 117.
# Соответствие держится ТОЛЬКО пре-регом.
# 3. ПОТОЛКИ: пояс базового прогона. ⚠ План §9.3 давал --ceiling-usd 1.20; это ОТСТУПЛЕНИЕ,
# записанное как находка: правило §9.0 п.1 — «чуть выше ожидания ЧЕРНОВОЙ волны», а ожидание,
# пере-снятое с леджера холодного прогона (не-edit = $0.436110 $0.214865 = $0.221245 на
# 28 174 знака ⇒ ×1.6866 = $0.373 на 47 519 знаков) плюс эскалация ⇒ ≈$0.45. При поясе 1.20
# зазор до покупки редактуры ≈ 10 юнитов по резервации — а купленная в базе редактура
# необратима: четыре из пяти копий упрутся в stagerun.go:52 и потребуют запрещённого
# --resnapshot. Пояс 0.60 несимметричен в правильную сторону: перелёт по деньгам стоит
# перекупки, недолёт — бесплатного resume (код 4, черновики сохранены).
# 4. models / langpack_root — как в образце, В MAIN-ДЕРЕВЕ: в polygon нет langpacks/ru, а
# LoadReaderWords на отсутствующий каталог молча возвращает умолчания.
# 5. Сид НЕ подключается — банк рождается майнером + банкнотой, как в холодном прогоне.
book_id: guzhenren-probe4
title: 蛊真人
source_lang: zh
target_lang: ru
genre: вебновелла
audience: взрослые читатели вебновелл
adult: false
venuti: 0.6
honorifics: keep
transcription: palladius
footnotes: minimal
register_blocklist: [терем, терема, тереме, теремом, терему, теремах]
pipeline: /home/ubuntu/books/gu-zhenren/probe-4axes/arm-p9-off.pipeline.yaml
models: /home/ubuntu/projects/textmachine-main/backend/configs/models.yaml
source_file: /home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-ch26-42.gb18030.txt
encoding: gb18030
langpack_root: /home/ubuntu/projects/textmachine-main/backend/configs/langpacks
langpack_extend: /home/ubuntu/books/gu-zhenren/langpack-extend
project_db: /home/ubuntu/books/gu-zhenren/probe-4axes/arms/p9-off.db
# Пояс базового прогона (см. п.3 шапки). day_usd держится ВЫШЕ book_usd сознательно: несущим
# предохранителем обязан быть книжный потолок. ⚠ --ceiling-usd перекрывает ТОЛЬКО book_usd
# (stagerun.go:496 берёт DayUSD из книги), поэтому дневной потолок задаётся здесь и нигде больше.
ceilings:
book_usd: 3.27
day_usd: 4.27

View file

@ -0,0 +1,117 @@
# ⚠ ФАЙЛ СГЕНЕРИРОВАН ИЗ $B/pipeline.yaml. Рука креста: p9-off.
# Отличий от базового ран-конфига РОВНО ТРИ, и все три перечислены здесь:
# 1. stages[edit].prompt_override → editor-p9.md (ОСЬ ПРОМПТА)
# 2. stages[edit].reasoning → "off" (ОСЬ УСИЛИЯ — ПРЕДМЕТ ЗАМЕРА)
# 3. escalation.budget_usd → 0 (план §2: во всех ПЯТИ конфигах рук)
#
# ⛔ ФАЙЛ ЛЕЖИТ РЯДОМ С $B/pairs/, И ЭТО НЕСУЩЕЕ. LoadPair ищет пар-слой по пути
# <каталог PIPELINE-файла>/pairs/<пара>.yaml (config/pipeline.go:821), а отсутствие файла — НЕ
# ошибка: (nil, nil), тихий откат на дженерик-нарезку (config/pair.go:55-64). Дженерик двигает
# segmentationSnap ⇒ ЧЕРНОВОЙ снапшот ⇒ все черновики перекупаются в каждой руке. План §9.2 клал
# руки в $B/arms/ без pairs/ рядом — это ОТСТУПЛЕНИЕ, записанное как находка; §10 плана оставляет
# тексты конфигов исполнителю, а один общий pairs/ вдобавок исключает расхождение двух копий.
#
# ПРОБА ПО ЧЕТЫРЁМ ОСЯМ — РАН-КОНФИГ БАЗОВОГО ПРОГОНА (черновая волна + майнинг + терминолог).
#
# Тело — копия ~/books/gu-zhenren/coldrun-v16/pipeline.yaml. Отличия, каждое объявлено:
#
# A. ПУТИ: prompt_override черновика и prompts_root — в main-дереве (каталоги промптов в двух
# чекаутах побайтно идентичны, diff -r чист — проверено 02.09). mining.contrast_path ОСТАВЛЕН
# в polygon-дереве: jieba-контраст (5 МБ, gitignored) физически лежит только там; проверено
# обоими ls, в textmachine-main файла нет.
#
# B. ЭСКАЛАЦИОННЫЙ СУБ-БЮДЖЕТ: 0.08 → 0.25. В БАЗОВОМ прогоне он ОБЯЗАН быть > 0 (план §2),
# иначе эхо-черновики останутся флагнутыми — и одинаково флагнутыми во всех пяти руках, то
# есть замер потеряет часть материала. Число — строка сметы плана §6 («≈$0.25 на 24 главы»),
# она же ≈1.4× пере-снятой проекции: холодный прогон на 10 главах израсходовал на draft-pro
# $0.103305 (sqlite3 request_log, stage=draft role=translator model=deepseek-v4-pro),
# ×1.6866 по знакам = $0.174.
#
# C. ⛔ КОММЕНТАРИЙ ОБРАЗЦА ПРО ЭФФОРТ — УДАЛЁН КАК ПРОТУХШИЙ (требование плана §9.2).
# В образце стояло «у pro ручки эффорта нет вовсе (вендор-маппинг low→high)». Это неверно и
# прямо противоречит предмету ЭТОГО замера: capability.go шлёт low|medium|high, вендор сменил
# таблицу маппинга 30.08. Оставленный, он учил бы следующего читателя, что ось не существует.
#
# D. stages[edit] здесь ЕСТЬ и совпадает с рукой p9-off. Базовый прогон обязан остановиться ДО
# edit-волны (--verify-bank + пояс). Совпадение с p9-off выбрано на случай, если стоп всё же
# не сработает: тогда купленная строка ложится на снапшот РЕАЛЬНОЙ руки, а не осиротелый.
# ⚠ Сам стоп проверен по коду: mining.go stopping = VerifyBank && editWave &&
# hasUnpresentedCluster(mined, presented). На СВЕЖЕЙ базе presented пусто ⇒ любая непустая
# дельта останавливает. Не остановит только ПУСТАЯ дельта — для этого и пояс.
#
# ⚠ Всё прочее оставлено БУКВОЙ КОПИИ, включая то, что я бы выбрала иначе: сравнимость денег с
# холодным прогоном держится на конфиге, а не только на байтах исходника.
# · stages[draft].reasoning: "low" — НЕСУЩЕЕ (квирки §10: при вендор-дефолте high 8 из 9 вызовов
# возвращают finish=length с пустым телом).
# · gates.terminology.reasoning: "low" — то же для бэнк-ролей.
# · prompt_override/banknote черновой стадии — не конвенционный боевой путь; оставлен ради
# денежной сравнимости.
core: C1
version: 1
defaults:
max_output_ratio: 2.2
min_max_tokens: 2048
context:
glossary_injection: selective
glossary_token_budget: 800
cache_ttl: "5m"
waves:
workers: 4
retries:
regenerate_before_escalate: 1
stages:
- name: draft
role: translator
prompt_override: /home/ubuntu/projects/textmachine-main/backend/prompts/zh-ru/translator-banknote.md
model: deepseek-v4-flash
prompt_version: v1-reflow-banknote
temperature: 0.3
reasoning: "low"
escalate_to: deepseek-v4-pro
- name: edit
role: editor
prompt_override: /home/ubuntu/books/gu-zhenren/probe-4axes/editor-p9.md
model: deepseek-v4-pro
prompt_version: v3-discourse-reflow
temperature: 0.4
reasoning: "off"
few_shot: false
gates:
coverage:
enabled: false
sent_cov_min: 0.75
min_chunk_chars: 500
sanitizer:
enabled: true
regression_guard:
enabled: true
banknote:
enabled: true
terminology:
enabled: true
model: deepseek-v4-flash
budget_usd: 0.05
target_script: Cyrillic
reasoning: "low"
classify_types: true
classify_budget_usd: 0.02
voice:
enabled: true
escalation:
chains:
default: [deepseek-v4-pro, glm-5.1, gemini-3.1-pro-preview]
adult: [grok-4.3]
budget_usd: 0
mining:
contrast_path: /home/ubuntu/projects/textmachine/eval/exp16/data/jieba_dict_general_zh.txt
fanout:
candidates: 1

View file

@ -0,0 +1,51 @@
# ПРОБА РЕДАКТОРА ПО ЧЕТЫРЁМ ОСЯМ — БАЗОВЫЙ ПРОФИЛЬ КНИГИ.
# Пре-рег: eval/dovodka/PLAN-01-09.md (фриз документа dc602e9). Промт исполнителя:
# eval/dovodka/PROMPT-PROBE-4AXES.md. Ветка polygon, сессия textmachine-2f, 02.09.2026.
#
# Тело — копия ~/books/gu-zhenren/coldrun-v16/book.yaml. Каждое отличие объявлено:
# 1. ИДЕНТИЧНОСТЬ: book_id / pipeline / source_file / project_db → этот каталог.
# ⛔ book_id ОДИН для базы и всех ПЯТИ рук: он входит в request_hash, и разный book_id
# обнулил бы $0-resume черновиков в копиях (план §2, «условия копирования»).
# 2. ИСХОДНИК: срез глав книги 2642, нарезан eval/dovodka/narezka.py.
# ⚠ МИНА НУМЕРАЦИИ: движок не умеет смещение глав — в базе и отчётах это будут главы 117.
# Соответствие держится ТОЛЬКО пре-регом.
# 3. ПОТОЛКИ: пояс базового прогона. ⚠ План §9.3 давал --ceiling-usd 1.20; это ОТСТУПЛЕНИЕ,
# записанное как находка: правило §9.0 п.1 — «чуть выше ожидания ЧЕРНОВОЙ волны», а ожидание,
# пере-снятое с леджера холодного прогона (не-edit = $0.436110 $0.214865 = $0.221245 на
# 28 174 знака ⇒ ×1.6866 = $0.373 на 47 519 знаков) плюс эскалация ⇒ ≈$0.45. При поясе 1.20
# зазор до покупки редактуры ≈ 10 юнитов по резервации — а купленная в базе редактура
# необратима: четыре из пяти копий упрутся в stagerun.go:52 и потребуют запрещённого
# --resnapshot. Пояс 0.60 несимметричен в правильную сторону: перелёт по деньгам стоит
# перекупки, недолёт — бесплатного resume (код 4, черновики сохранены).
# 4. models / langpack_root — как в образце, В MAIN-ДЕРЕВЕ: в polygon нет langpacks/ru, а
# LoadReaderWords на отсутствующий каталог молча возвращает умолчания.
# 5. Сид НЕ подключается — банк рождается майнером + банкнотой, как в холодном прогоне.
book_id: guzhenren-probe4
title: 蛊真人
source_lang: zh
target_lang: ru
genre: вебновелла
audience: взрослые читатели вебновелл
adult: false
venuti: 0.6
honorifics: keep
transcription: palladius
footnotes: minimal
register_blocklist: [терем, терема, тереме, теремом, терему, теремах]
pipeline: /home/ubuntu/books/gu-zhenren/probe-4axes/pipeline.yaml
models: /home/ubuntu/projects/textmachine-main/backend/configs/models.yaml
source_file: /home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-ch26-42.gb18030.txt
encoding: gb18030
langpack_root: /home/ubuntu/projects/textmachine-main/backend/configs/langpacks
langpack_extend: /home/ubuntu/books/gu-zhenren/langpack-extend
project_db: /home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-probe4.db
# Пояс базового прогона (см. п.3 шапки). day_usd держится ВЫШЕ book_usd сознательно: несущим
# предохранителем обязан быть книжный потолок. ⚠ --ceiling-usd перекрывает ТОЛЬКО book_usd
# (stagerun.go:496 берёт DayUSD из книги), поэтому дневной потолок задаётся здесь и нигде больше.
ceilings:
book_usd: 0.60
day_usd: 2.00

View file

@ -0,0 +1,41 @@
Ты — литературный редактор художественного перевода на русский язык. Тебе даны ИСХОДНЫЙ текст и его черновой перевод.
Жанр книги: {{genre}}. Аудитория: {{audience}}. Книга: «{{title}}».
Твой мандат — художественная редактура черновика со сверкой по исходнику:
- Сверяй смысл черновика с исходным текстом (язык «{{source_lang}}»): исправляй искажения смысла, неверно понятые реалии, кальки жестов и идиом — переводчик мог передать выражение буквально (напр. поклон как «ударил головой»). Опирайся на исходник, но пиши по-русски, а не подстрочником.
- Правь стиль: убирай кальки с языка «{{source_lang}}», канцелярит, повторы, неестественные конструкции; пиши живым литературным русским.
- Имена собственные, термины и реалии передавай СТРОГО по приведённому глоссарию (если он приложен): приводи любые расхождения к каноническим формам, склоняя по контексту; не придумывай иных вариантов их передачи.
- Вёрстка: собирай повествование в естественные русские абзацы, НЕ копируя построчную разбивку исходника (в оригинале часто одно предложение — одна строка). Реплики прямой речи начинай с нового абзаца через тире «—». Максимума длины абзаца нет.
- Сохраняй ПОЛНОТУ черновика: не выбрасывай и не добавляй предложения; не пересочиняй сцены.
- Единицы и приёмы (общие для zh→ru): МЕРЫ приводи к привычным читателю единицам (时辰 = 2 часа, 三个时辰 ≈ шесть часов; доля 成 — десятые: 六成六 = 66%, а не «6,6»); СТИХИ и названия классики — по смыслу, не транслитерацией. Род и форму имён и терминов сверяй по глоссарию.
- Хонорифики: {{honorifics}}. Баланс форенизация/доместикация: {{venuti}}.
Выведи ТОЛЬКО отредактированный текст перевода — без служебных преамбул, комментариев, пояснений, заметок о правках и markdown-заголовков.
ДИСКУРС-ПЕРЕВЁРСТКА (обязательная операция передачи китайского паратаксиса в русскую гипотактическую прозу — это КОНВЕНЦИЯ русского языка, а не вольность):
1. Молча определи смысловые ходы фрагмента: единый фокал/наблюдатель; действие и его непосредственная реакция; одна тема рассуждения; границы — смена говорящего, времени, места или фокала.
2. Один повествовательный ход оформляй ОДНИМ русским абзацем, ДАЖЕ если китайский исходник разбит на однофразовые строки. Инструмент слияния — причастные и деепричастные обороты, подчинительные союзы и связки (построй иерархию, сегментируй по смыслу, промаркируй вид/время предикатов, варьируй лексику, добавляй связки). Не сливай ТОЛЬКО ради длины и не теряй смысловые атомы.
3. Каждую новую реплику начинай с нового абзаца через тире «—»; слова автора при той же реплике оставляй в её абзаце.
4. Разрешено объединять и делить русские предложения; число предложений и абзацев совпадать с исходником НЕ обязано. Запрещено терять смысловые атомы (участников, действия, числа, полярность, заголовки, а также идиомы и чэнъюй: их передавай ОБОИМИ смысловыми компонентами, не сворачивая в общий смысл — 物是人非 = «вещи те же, люди не те», а не «всё изменилось»).
---FEWSHOT---
Примеры требуемой ПЕРЕВЁРСТКИ (показана структура вывода, не стиль):
[narrative N:1] Исходник построчно — «Он вышел за дверь.» / «Небо было серым.» / «Он вздохнул.» / «Дорога уходила вдаль.» — становится ОДНИМ абзацем:
Он вышел за дверь. Небо было серым; вздохнув, он смотрел, как дорога уходит вдаль.
[dialogue 1:N] Исходник — «Дядя нахмурился и сказал: „Время летит. Садись.“ Племянник ответил: „Спасибо.“» — становится:
Дядя нахмурился.
— Время летит. Садись, — сказал он.
— Спасибо, — ответил племянник.
[focal-shift] При смене наблюдателя/места/времени — новый абзац, даже если в исходнике это продолжение той же строки.
---USER---
Исходный текст (язык «{{source_lang}}»):
{{text}}
Черновик перевода для редактуры:
{{draft}}

View file

@ -0,0 +1,41 @@
Ты — литературный редактор художественного перевода на русский язык. Тебе даны ИСХОДНЫЙ текст и его черновой перевод.
Жанр книги: {{genre}}. Аудитория: {{audience}}. Книга: «{{title}}».
Твой мандат — художественная редактура черновика со сверкой по исходнику:
- Сверяй смысл черновика с исходным текстом (язык «{{source_lang}}»): исправляй искажения смысла, неверно понятые реалии, кальки жестов и идиом — переводчик мог передать выражение буквально (напр. поклон как «ударил головой»). Опирайся на исходник, но пиши по-русски, а не подстрочником.
- Правь стиль: убирай кальки с языка «{{source_lang}}», канцелярит, лишние повторы, неестественные конструкции; пиши живым литературным русским. НО повтор, который стоит в тех же (параллельных) позициях самого исходника, — это авторский рефрен: сохраняй его, не «разнообразь» лексику там, где автор намеренно повторяется.
- Имена собственные, термины и реалии передавай СТРОГО по приведённому глоссарию (если он приложен): приводи любые расхождения к каноническим формам, склоняя по контексту; не придумывай иных вариантов их передачи.
- Вёрстка: собирай повествование в естественные русские абзацы, НЕ копируя построчную разбивку исходника (в оригинале часто одно предложение — одна строка). Реплики прямой речи начинай с нового абзаца через тире «—». Максимума длины абзаца нет.
- Сохраняй ПОЛНОТУ черновика: не выбрасывай и не добавляй предложения; не пересочиняй сцены.
- Единицы и приёмы (общие для zh→ru): МЕРЫ приводи к привычным читателю единицам (时辰 = 2 часа, 三个时辰 ≈ шесть часов; доля 成 — десятые: 六成六 = 66%, а не «6,6»); СТИХИ и названия классики — по смыслу, не транслитерацией. Род и форму имён и терминов сверяй по глоссарию.
- Хонорифики: {{honorifics}}. Баланс форенизация/доместикация: {{venuti}}.
Выведи ТОЛЬКО отредактированный текст перевода — без служебных преамбул, комментариев, пояснений, заметок о правках и markdown-заголовков.
ДИСКУРС-ПЕРЕВЁРСТКА (обязательная операция передачи китайского паратаксиса в русскую гипотактическую прозу — это КОНВЕНЦИЯ русского языка, а не вольность):
1. Молча определи смысловые ходы фрагмента: единый фокал/наблюдатель; действие и его непосредственная реакция; одна тема рассуждения; границы — смена говорящего, времени, места или фокала.
2. Один повествовательный ход оформляй ОДНИМ русским абзацем, ДАЖЕ если китайский исходник разбит на однофразовые строки. Инструмент слияния — причастные и деепричастные обороты, подчинительные союзы и связки (построй иерархию, сегментируй по смыслу, промаркируй вид/время предикатов, варьируй лексику, добавляй связки). Не сливай ТОЛЬКО ради длины и не теряй смысловые атомы.
3. Каждую новую реплику начинай с нового абзаца через тире «—»; слова автора при той же реплике оставляй в её абзаце.
4. Разрешено объединять и делить русские предложения; число предложений и абзацев совпадать с исходником НЕ обязано. Запрещено терять смысловые атомы (участников, действия, числа, полярность, заголовки, а также идиомы и чэнъюй: их передавай ОБОИМИ смысловыми компонентами, не сворачивая в общий смысл — 物是人非 = «вещи те же, люди не те», а не «всё изменилось»).
---FEWSHOT---
Примеры требуемой ПЕРЕВЁРСТКИ (показана структура вывода, не стиль):
[narrative N:1] Исходник построчно — «Он вышел за дверь.» / «Небо было серым.» / «Он вздохнул.» / «Дорога уходила вдаль.» — становится ОДНИМ абзацем:
Он вышел за дверь. Небо было серым; вздохнув, он смотрел, как дорога уходит вдаль.
[dialogue 1:N] Исходник — «Дядя нахмурился и сказал: „Время летит. Садись.“ Племянник ответил: „Спасибо.“» — становится:
Дядя нахмурился.
— Время летит. Садись, — сказал он.
— Спасибо, — ответил племянник.
[focal-shift] При смене наблюдателя/места/времени — новый абзац, даже если в исходнике это продолжение той же строки.
---USER---
Исходный текст (язык «{{source_lang}}»):
{{text}}
Черновик перевода для редактуры:
{{draft}}

View file

@ -0,0 +1,299 @@
{
"manifest_version": "tm-manifest-v2",
"book_id": "guzhenren-probe4",
"key": "e13819afb848c31335b56f8dd88e73925e9157ecdef584aa2d6671444620b132",
"chunker_version": "chunker-v6-generic-heading+srcabbrev+u15.0.0",
"source_sha256": "1ebcc9efb0fa5006548eba8599b58d4d5a44a201562271d0ff9f1eb9b803fccf",
"source_bytes": 97408,
"source_lang": "zh",
"target_lang": "ru",
"encoding": "gb18030",
"chapters": [
{
"id": "8be5f95970fd40a4",
"number": 1,
"heading": "Глава 26",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "8be5f95970fd40a4:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 0
}
]
},
{
"id": "d0b40a25621e5484",
"number": 2,
"heading": "Глава 27",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "d0b40a25621e5484:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 1
}
]
},
{
"id": "fede81819e4ebb22",
"number": 3,
"heading": "Глава 28",
"units_total": 2,
"chunks_total": 2,
"units": [
{
"id": "fede81819e4ebb22:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 1,
"edit_unit_id": 2
},
{
"id": "fede81819e4ebb22:cb440937:1",
"first_chunk_idx": 1,
"chunk_count": 1,
"edit_unit_id": 3
}
]
},
{
"id": "7d0d4599eb685c54",
"number": 4,
"heading": "Глава 29",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "7d0d4599eb685c54:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 4
}
]
},
{
"id": "45c2dd67d529cc3e",
"number": 5,
"heading": "Глава 30",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "45c2dd67d529cc3e:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 5
}
]
},
{
"id": "b8da8e295ddf729c",
"number": 6,
"heading": "Глава 31",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "b8da8e295ddf729c:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 6
}
]
},
{
"id": "bbd490d5308f1978",
"number": 7,
"heading": "Глава 32",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "bbd490d5308f1978:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 7
}
]
},
{
"id": "e9030b3642306fa9",
"number": 8,
"heading": "Глава 33",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "e9030b3642306fa9:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 8
}
]
},
{
"id": "ecf09ad607ef810d",
"number": 9,
"heading": "Глава 34",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "ecf09ad607ef810d:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 9
}
]
},
{
"id": "cf2f4b9cf73d855b",
"number": 10,
"heading": "Глава 35",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "cf2f4b9cf73d855b:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 10
}
]
},
{
"id": "136eeb23c4648c74",
"number": 11,
"heading": "Глава 36",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "136eeb23c4648c74:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 11
}
]
},
{
"id": "8cc71f1fad5e6c09",
"number": 12,
"heading": "Глава 37",
"units_total": 2,
"chunks_total": 2,
"units": [
{
"id": "8cc71f1fad5e6c09:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 1,
"edit_unit_id": 12
},
{
"id": "8cc71f1fad5e6c09:cb440937:1",
"first_chunk_idx": 1,
"chunk_count": 1,
"edit_unit_id": 13
}
]
},
{
"id": "bb55abe10d580cfb",
"number": 13,
"heading": "Глава 38",
"units_total": 2,
"chunks_total": 3,
"units": [
{
"id": "bb55abe10d580cfb:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 1,
"edit_unit_id": 14
},
{
"id": "bb55abe10d580cfb:cb440937:1",
"first_chunk_idx": 1,
"chunk_count": 2,
"edit_unit_id": 15
}
]
},
{
"id": "b6736de839672773",
"number": 14,
"heading": "Глава 39",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "b6736de839672773:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 16
}
]
},
{
"id": "9c8c98b9661ae3bf",
"number": 15,
"heading": "Глава 40",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "9c8c98b9661ae3bf:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 17
}
]
},
{
"id": "67723ac2644ccd11",
"number": 16,
"heading": "Глава 41",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "67723ac2644ccd11:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 18
}
]
},
{
"id": "174100117135b951",
"number": 17,
"heading": "Глава 42",
"units_total": 1,
"chunks_total": 2,
"units": [
{
"id": "174100117135b951:cb440937:0",
"first_chunk_idx": 0,
"chunk_count": 2,
"edit_unit_id": 19
}
]
}
],
"chapters_total": 17,
"units_total": 20,
"chunks_total": 35,
"artifacts": {
"project_db": "/home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-probe4.db",
"bank_export": "/home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-probe4.db.bank.json",
"mined_delta": "/home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-probe4.mined-delta.yaml",
"mined_rejects": "/home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-probe4.mined-rejects.yaml",
"book_files": {
"epub": "/home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-probe4.db.book.epub",
"txt": "/home/ubuntu/books/gu-zhenren/probe-4axes/guzhenren-probe4.db.book.txt"
}
}
}

View file

@ -0,0 +1,104 @@
# ПРОБА ПО ЧЕТЫРЁМ ОСЯМ — РАН-КОНФИГ БАЗОВОГО ПРОГОНА (черновая волна + майнинг + терминолог).
#
# Тело — копия ~/books/gu-zhenren/coldrun-v16/pipeline.yaml. Отличия, каждое объявлено:
#
# A. ПУТИ: prompt_override черновика и prompts_root — в main-дереве (каталоги промптов в двух
# чекаутах побайтно идентичны, diff -r чист — проверено 02.09). mining.contrast_path ОСТАВЛЕН
# в polygon-дереве: jieba-контраст (5 МБ, gitignored) физически лежит только там; проверено
# обоими ls, в textmachine-main файла нет.
#
# B. ЭСКАЛАЦИОННЫЙ СУБ-БЮДЖЕТ: 0.08 → 0.25. В БАЗОВОМ прогоне он ОБЯЗАН быть > 0 (план §2),
# иначе эхо-черновики останутся флагнутыми — и одинаково флагнутыми во всех пяти руках, то
# есть замер потеряет часть материала. Число — строка сметы плана §6 («≈$0.25 на 24 главы»),
# она же ≈1.4× пере-снятой проекции: холодный прогон на 10 главах израсходовал на draft-pro
# $0.103305 (sqlite3 request_log, stage=draft role=translator model=deepseek-v4-pro),
# ×1.6866 по знакам = $0.174.
#
# C. ⛔ КОММЕНТАРИЙ ОБРАЗЦА ПРО ЭФФОРТ — УДАЛЁН КАК ПРОТУХШИЙ (требование плана §9.2).
# В образце стояло «у pro ручки эффорта нет вовсе (вендор-маппинг low→high)». Это неверно и
# прямо противоречит предмету ЭТОГО замера: capability.go шлёт low|medium|high, вендор сменил
# таблицу маппинга 30.08. Оставленный, он учил бы следующего читателя, что ось не существует.
#
# D. stages[edit] здесь ЕСТЬ и совпадает с рукой p9-off. Базовый прогон обязан остановиться ДО
# edit-волны (--verify-bank + пояс). Совпадение с p9-off выбрано на случай, если стоп всё же
# не сработает: тогда купленная строка ложится на снапшот РЕАЛЬНОЙ руки, а не осиротелый.
# ⚠ Сам стоп проверен по коду: mining.go stopping = VerifyBank && editWave &&
# hasUnpresentedCluster(mined, presented). На СВЕЖЕЙ базе presented пусто ⇒ любая непустая
# дельта останавливает. Не остановит только ПУСТАЯ дельта — для этого и пояс.
#
# ⚠ Всё прочее оставлено БУКВОЙ КОПИИ, включая то, что я бы выбрала иначе: сравнимость денег с
# холодным прогоном держится на конфиге, а не только на байтах исходника.
# · stages[draft].reasoning: "low" — НЕСУЩЕЕ (квирки §10: при вендор-дефолте high 8 из 9 вызовов
# возвращают finish=length с пустым телом).
# · gates.terminology.reasoning: "low" — то же для бэнк-ролей.
# · prompt_override/banknote черновой стадии — не конвенционный боевой путь; оставлен ради
# денежной сравнимости.
core: C1
version: 1
defaults:
max_output_ratio: 2.2
min_max_tokens: 2048
context:
glossary_injection: selective
glossary_token_budget: 800
cache_ttl: "5m"
waves:
workers: 4
retries:
regenerate_before_escalate: 1
stages:
- name: draft
role: translator
prompt_override: /home/ubuntu/projects/textmachine-main/backend/prompts/zh-ru/translator-banknote.md
model: deepseek-v4-flash
prompt_version: v1-reflow-banknote
temperature: 0.3
reasoning: "low"
escalate_to: deepseek-v4-pro
- name: edit
role: editor
prompt_override: /home/ubuntu/books/gu-zhenren/probe-4axes/editor-p9.md
model: deepseek-v4-pro
prompt_version: v3-discourse-reflow
temperature: 0.4
reasoning: "off"
few_shot: false
gates:
coverage:
enabled: false
sent_cov_min: 0.75
min_chunk_chars: 500
sanitizer:
enabled: true
regression_guard:
enabled: true
banknote:
enabled: true
terminology:
enabled: true
model: deepseek-v4-flash
budget_usd: 0.05
target_script: Cyrillic
reasoning: "low"
classify_types: true
classify_budget_usd: 0.02
voice:
enabled: true
escalation:
chains:
default: [deepseek-v4-pro, glm-5.1, gemini-3.1-pro-preview]
adult: [grok-4.3]
budget_usd: 0.25
mining:
contrast_path: /home/ubuntu/projects/textmachine/eval/exp16/data/jieba_dict_general_zh.txt
fanout:
candidates: 1

View file

@ -0,0 +1,351 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""proverka_pribora.py — ПОЛОЖИТЕЛЬНЫЙ КОНТРОЛЬ для chetyre.py.
Зачем существует. Правило проекта: «отрицательный результат предъявляется с ПОЛОЖИТЕЛЬНЫМ
КОНТРОЛЕМ» лекарство от одиннадцати ошибок предыдущей сессии, каждая из которых была
«показания инструмента приняты за факт о мире». Если прибор напечатает «ниже разрешения замера»,
читатель обязан иметь улику, что он вообще СПОСОБЕН напечатать «эффект есть».
Что делает: строит СИНТЕТИЧЕСКИЕ проектные базы точно той же схемы, что у движка, с ЗАРАНЕЕ
ИЗВЕСТНЫМ ответом, и прогоняет по ним настоящий chetyre.py. Ни одного платного вызова.
Это НЕ подгонка теста под зелень: сценарии написаны так, чтобы прибор мог их ПРОВАЛИТЬ, и
два из шести ожидают от него именно ОТКАЗА.
"""
import json
import math
import os
import random
import shutil
import sqlite3
import subprocess
import sys
import tempfile
from pathlib import Path
HERE = Path(__file__).resolve().parent
CHETYRE = HERE / "chetyre.py"
PY = HERE.parent / ".venv/bin/python"
ARMS = ["p7-off", "p7-low", "p9-off", "p9-low", "glm-off"]
SHA_P7 = "2b84dee34b0861637a055fee60372d2553f85ffa4c1b535c1e781f5167e8c3a1"
SHA_P9 = "1ad4544e564a9b4049231e79e961a9cbb85bc39de07c5ce72cdbc8d92cc68999"
SCHEMA = """
create table request_log (
id integer primary key, ts text default '', trace_id text default '', book_id text not null,
chapter integer default 0, chunk_idx integer default 0, stage text default '', role text default '',
model_requested text default '', model_actual text default '', request_hash text default '',
prompt_tokens integer default 0, cached_tokens integer default 0, cache_creation_tokens integer default 0,
completion_tokens integer default 0, reasoning_tokens integer default 0, cost_usd real default 0,
latency_ms integer default 0, finish_reason text default '', tm_hit integer default 0,
degraded text default '', err text default '', ok integer default 1,
estimated integer default 0, est_tokens integer default 0);
create table checkpoints (
request_hash text primary key, job_id integer not null, chunk_idx integer not null,
attempt integer default 0, stage text not null, role text not null, model_requested text not null,
model_actual text not null, response_text text not null, usage_json text not null,
cost_usd real not null, finish_reason text default '', provider_request_id text default '',
created_at text default '', escalation integer default 0);
create table chunk_status (
book_id text not null, chapter integer not null, chunk_idx integer not null, stage text not null,
snapshot_id text not null, content_hash text default '', disposition text not null,
flag_reason text default '', attempts integer default 0, final_hash text default '',
cost_usd real default 0, detail text default '', updated_at text default '',
escalated integer default 0, escalation_model text default '', first_flag_reason text default '',
primary key (book_id, chapter, chunk_idx, stage));
create table snapshots (snapshot_id text primary key, brief_hash text not null, payload text not null, created_at text default '');
create table jobs (id integer primary key, book_id text not null, chapter integer not null, stage text not null,
status text default 'done', snapshot_id text not null, created_at text default '', updated_at text default '');
create table spend (book_id text not null, date text not null, committed_usd real default 0, reserved_usd real default 0);
"""
RU = "Мальчик поднял голову и посмотрел на далёкие горы, где клубился утренний туман. "
DRAFT = "Черновой русский текст главы, достаточно длинный чтобы служить ковариатой. " * 20
def snap_payload(sha, reasoning, model, memver="m1"):
return json.dumps({
"brief_hash": "b", "chunker_version": "c", "estimator_version": "e",
"max_tokens_policy": "p", "classifier_version": "cl", "pipeline_core": "C1",
"max_output_ratio": 2.2, "min_max_tokens": 2048, "memory_version": memver,
"stages": [{"name": "edit", "role": "editor", "model": model, "prompt_version": "v3-discourse-reflow",
"prompt_sha256": sha, "temperature": 0.4, "reasoning": reasoning, "few_shot": False}],
}, sort_keys=True)
def draft_snap_payload():
return json.dumps({"brief_hash": "b", "memory_version": "base",
"stages": [{"name": "draft", "role": "translator", "model": "deepseek-v4-flash",
"prompt_version": "v1", "prompt_sha256": "d" * 64,
"temperature": 0.3, "reasoning": "low"}]}, sort_keys=True)
def build(stand, n_units, effect, *, censor_units=(), empty_arms=(), collapse=False, seed=7,
replays=0, sig_break=(), sanitizer_units=()):
"""Строит стенд. effect — во сколько раз low режет размышление (1.0 = эффекта нет)."""
rnd = random.Random(seed)
stand.mkdir(parents=True, exist_ok=True)
(stand / "arms").mkdir(exist_ok=True)
base_spend = 0.42
# база
bcon = sqlite3.connect(stand / "guzhenren-probe4.db")
bcon.executescript(SCHEMA)
bcon.execute("insert into spend values ('guzhenren-probe4','2026-09-02',?,0)", (base_spend,))
bcon.commit(); bcon.close()
draft_sid = "d" * 64
# ⚠ ВСЕ РУКИ ДЕЛЯТ ОДИН черновой снапшот — так и должно быть в настоящем прогоне
for arm in ARMS:
sha = SHA_P9 if (collapse or arm.startswith("p9") or arm == "glm-off") else SHA_P7
reasoning = "low" if arm.endswith("low") else "off"
model = "glm-5" if arm == "glm-off" else "deepseek-v4-pro"
edit_sid = f"{arm:<64}".replace(" ", "x")[:64]
con = sqlite3.connect(stand / "arms" / f"{arm}.db")
con.executescript(SCHEMA)
con.execute("insert into snapshots values (?,?,?,'')", (draft_sid, "b", draft_snap_payload()))
con.execute("insert into snapshots values (?,?,?,'')", (edit_sid, "b", snap_payload(sha, reasoning, model)))
spend = base_spend
rid = 0
for u in range(1, n_units + 1):
con.execute("insert into jobs values (?,?,?,?,?,?,'','')", (u, "guzhenren-probe4", u, "draft", "done", draft_sid))
con.execute("insert into jobs values (?,?,?,?,?,?,'','')", (1000 + u, "guzhenren-probe4", u, "edit", "done", edit_sid))
# черновик — ОДИН И ТОТ ЖЕ во всех руках (блокировка трудности)
dl = 400 + (u * 137) % 3000
dtext = DRAFT[:dl] if dl <= len(DRAFT) else DRAFT * (dl // len(DRAFT) + 1)
dtext = dtext[:dl]
dh = f"draft-{u}"
con.execute("insert into checkpoints values (?,?,?,?,?,?,?,?,?,?,?,?,'','',0)",
(dh, u, 0, 0, "draft", "translator", "deepseek-v4-flash", "deepseek-v4-flash",
dtext, "{}", 0.001, "stop", ))
con.execute("insert into chunk_status values (?,?,?,?,?,'',?,'',1,?,?,'','',0,'','')",
("guzhenren-probe4", u, 0, "draft", draft_sid, "ok", dh, 0.001))
dh2 = f"draft-{u}-b"
con.execute("insert into checkpoints values (?,?,?,?,?,?,?,?,?,?,?,?,'','',0)",
(dh2, u, 1, 0, "draft", "translator", "deepseek-v4-flash", "deepseek-v4-flash",
dtext[: max(50, dl // 2)], "{}", 0.001, "stop"))
con.execute("insert into chunk_status values (?,?,?,?,?,'',?,'',1,?,?,'','',0,'','')",
("guzhenren-probe4", u, 1, "draft", draft_sid, "ok", dh2, 0.001))
# размышление: базовый уровень юнита (общий для рук — блокировка) + шум руки
base_r = math.exp(rnd.gauss(math.log(9000), 0.55))
arm_noise = math.exp(rnd.gauss(0, 0.30))
r = base_r * arm_noise
if arm.endswith("low"):
r /= effect
if arm == "glm-off":
r = 0.0
vis_chars = 4000 + (u * 311) % 3000
vis_tok = 0.3644 * vis_chars
cmpl = int(round(r + vis_tok))
fin = "stop"
if u in censor_units and arm in ("p7-off", "p9-off"):
cmpl, fin = 16000, "length"
pt = 4400 + (u * 53) % 2000
if arm.endswith("low"):
# сигнатура: ∅ low = 79; у юнитов из sig_break она НАРУШЕНА (допуск ±2)
pt -= 79 if u not in sig_break else 40
price = {"in": 1.0, "out": 3.2} if arm == "glm-off" else {"in": 1.32, "out": 3.96}
cost = pt / 1e6 * price["in"] + cmpl / 1e6 * price["out"]
rid += 1
rh = f"{arm}-{u}-a0"
text = "" if arm in empty_arms else (RU * (vis_chars // len(RU) + 1))[:vis_chars]
con.execute("""insert into request_log
(id,book_id,chapter,chunk_idx,stage,role,model_requested,model_actual,request_hash,
prompt_tokens,cached_tokens,completion_tokens,reasoning_tokens,cost_usd,finish_reason,tm_hit,ok)
values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(rid, "guzhenren-probe4", u, 0, "edit", "editor",
"glm-5" if arm == "glm-off" else "deepseek-v4-pro",
"glm-5" if arm == "glm-off" else "deepseek-v4-pro",
rh, pt, 0, cmpl, 0, cost, fin, 0, 1))
con.execute("insert into checkpoints values (?,?,?,?,?,?,?,?,?,?,?,?,'','',0)",
(rh, 1000 + u, 0, 0, "edit", "editor",
"glm-5" if arm == "glm-off" else "deepseek-v4-pro",
"glm-5" if arm == "glm-off" else "deepseek-v4-pro",
text, "{}", cost, fin))
# ⚠ СТРОКИ-ПОВТОРЫ: интерливинг гоняет руку N раз с --max-units 1, и каждый прогон
# дописывает уже оплаченную клетку с tm_hit=1 и НУЛЯМИ во всех токенах, но с тем же
# request_hash ⇒ тем же attempt=0. Прибор обязан взять НАСТОЯЩИЙ вызов, а не повтор.
for k in range(replays):
rid += 1
con.execute("""insert into request_log
(id,book_id,chapter,chunk_idx,stage,role,model_requested,model_actual,request_hash,
prompt_tokens,cached_tokens,completion_tokens,reasoning_tokens,cost_usd,finish_reason,tm_hit,ok)
values (?,?,?,?,?,?,?,?,?,0,0,0,0,0,?,1,1)""",
(rid, "guzhenren-probe4", u, 0, "edit", "editor",
"glm-5" if arm == "glm-off" else "deepseek-v4-pro",
"glm-5" if arm == "glm-off" else "deepseek-v4-pro", rh, fin))
spend += cost
disp = "ok" if text else "flagged"
flag = "" if text else "empty_output"
final = rh
if u in sanitizer_units and text:
# СЫРОЙ ответ несёт эхо-иероглифы, а ОТГРУЖАЕМЫЙ — уже вычищен санитайзером.
# Именно эта клетка делала E5 слепым: по отгруженному CJK = 0.
con.execute("update checkpoints set response_text=? where request_hash=?",
(text + "即便身亡魔心依旧不悔", rh))
final = f"tm-sanitized-v1:{arm}:{u}"
con.execute("insert into checkpoints values (?,?,?,?,?,?,?,?,?,?,?,?,'','',0)",
(final, 1000 + u, 0, 0, "edit", "editor",
"glm-5" if arm == "glm-off" else "deepseek-v4-pro",
"glm-5" if arm == "glm-off" else "deepseek-v4-pro",
text, "{}", 0.0, fin))
disp, flag = "flagged", "sanitizer_stripped"
con.execute("insert into chunk_status values (?,?,?,?,?,'',?,?,1,?,?,'','',0,'','')",
("guzhenren-probe4", u, 0, "edit", edit_sid, disp, flag, final, cost))
con.execute("insert into spend values ('guzhenren-probe4','2026-09-02',?,0)", (spend,))
con.commit(); con.close()
# МАНИФЕСТ ДВИЖКА: у каждого юнита ДВА чанка черновика — как в настоящем стенде (15 из 20),
# чтобы прибор реально суммировал их, а не брал первый.
(stand / "guzhenren-probe4.db.manifest.json").write_text(json.dumps({
"manifest_version": "tm-manifest-v2", "book_id": "guzhenren-probe4",
"chapters_total": n_units, "units_total": n_units, "chunks_total": n_units * 2,
"chapters": [{"number": u, "heading": f"Глава {u}", "units_total": 1, "chunks_total": 2,
"units": [{"id": f"u{u}", "first_chunk_idx": 0, "chunk_count": 2,
"edit_unit_id": u - 1}]} for u in range(1, n_units + 1)],
}, ensure_ascii=False))
(stand / "manifest.json").write_text(json.dumps({
"prompt_sha256": {"P7": SHA_P7, "P9": SHA_P9},
"signature": {"P7": 79, "P9": 79},
}, ensure_ascii=False))
return stand
def run(stand):
p = subprocess.run([str(PY), str(CHETYRE), "--stand", str(stand),
"--manifest", str(stand / "manifest.json")],
capture_output=True, text=True)
return p.returncode, p.stdout + p.stderr
def main():
tmp = Path(tempfile.mkdtemp(prefix="pribor-"))
ok = True # ⚠ меняется и в case(), и напрямую в сценарии 8
def case(name, stand, want_code, want_in=(), want_not_in=()):
nonlocal ok
code, txt = run(stand)
good = code == want_code
for w in want_in:
if w not in txt:
good = False
print(f" ⛔ не найдено в выводе: {w!r}")
for w in want_not_in:
if w in txt:
good = False
print(f" ⛔ найдено лишнее: {w!r}")
print(("" if good else "") + f"{name} (код {code}, ждали {want_code})")
if not good:
print("\n".join(" | " + l for l in txt.splitlines()[-30:]))
ok = ok and good
print("ПОЛОЖИТЕЛЬНЫЙ КОНТРОЛЬ ПРИБОРА chetyre.py\n")
# 0. ПРЯМОЙ ТЕСТ СБОРКИ ДЛИНЫ ВХОДА ЮНИТА ИЗ ЧАНКОВ.
# Дефект, который он ловит, ТИХИЙ: ключ (глава, индекс) у юнита редактуры и у чанка черновика
# СОВПАДАЕТ, поэтому «взять первый чанк вместо суммы» не даёт ни ошибки, ни пустоты — только
# заниженную ковариату. В настоящем стенде так занижены 15 юнитов из 20.
print("0. СБОРКА ДЛИНЫ ВХОДА ЮНИТА ИЗ ЧАНКОВ (прямой тест функции)")
sys.path.insert(0, str(HERE))
import chetyre
man = tmp / "m.json"
man.write_text(json.dumps({"chapters": [
{"number": 1, "units": [{"first_chunk_idx": 0, "chunk_count": 2}]}, # 100+250
{"number": 2, "units": [{"first_chunk_idx": 0, "chunk_count": 1},
{"first_chunk_idx": 1, "chunk_count": 2}]}, # 7 | 11+13
{"number": 3, "units": [{"first_chunk_idx": 0, "chunk_count": 2}]}, # чанка нет
]}))
got = chetyre.unit_draft_lengths(man, {(1, 0): 100, (1, 1): 250,
(2, 0): 7, (2, 1): 11, (2, 2): 13,
(3, 0): 5})
want = {(1, 0): 350, (2, 0): 7, (2, 1): 24, (3, 0): None}
for k in want:
mark = "" if got.get(k) == want[k] else ""
print(f" {mark} юнит {k}: получено {got.get(k)}, ждали {want[k]}"
+ (" ← неполный юнит обязан стать None, а не полусуммой" if want[k] is None else ""))
ok = ok and got.get(k) == want[k]
print(f" (наивная реализация «первый чанк» дала бы юниту (1,0) 100 вместо 350 — молча)")
print("1. ЭФФЕКТ ЕСТЬ И ОН ИЗВЕСТЕН: low режет размышление ровно ×2 на 24 юнитах")
s = build(tmp / "effect", 24, effect=2.0)
case("прибор НАХОДИТ известный эффект и называет величину", s, 0,
want_in=["ВЫВОД: эффект ЕСТЬ", "E1 — ГЛАВНЫЙ"])
code, txt = run(s)
for line in txt.splitlines():
if "Ходжес-Леман" in line and "отношение" in line:
print("", line.strip(), " (истина: ×0.500)")
break
print("\n2. ЭФФЕКТА НЕТ: те же данные, effect=1.0")
s = build(tmp / "noeffect", 24, effect=1.0, seed=11)
case("прибор НЕ находит эффекта и говорит «ниже разрешения», а не «эффекта нет»", s, 0,
want_in=["НИЖЕ РАЗРЕШЕНИЯ ЗАМЕРА"], want_not_in=["эффекта нет."])
print("\n3. ⛔ ЗЕЛЕНЬ НА ПУСТОТЕ: в руке p9-low все клетки пустые")
s = build(tmp / "empty", 24, effect=2.0, empty_arms=("p9-low",), seed=3)
case("прибор ОТКАЗЫВАЕТ кодом 2, а не печатает «в пределах»", s, 2,
want_in=["ПРИБОР ОТКАЗЫВАЕТ", "ПРИБОР ОТКАЗЫВАЕТ"])
print("\n4. ⛔ ОСЬ ПРОМПТА СХЛОПНУЛАСЬ: у P7-рук уехал промпт P9")
s = build(tmp / "collapse", 24, effect=2.0, collapse=True, seed=5)
case("прибор ловит схлопывание оси и отказывает", s, 2,
want_in=["prompt_sha256", "ПРИБОР ОТКАЗЫВАЕТ"])
print("\n5. ЦЕНЗУРА: у трети юнитов ∅ обрывается по длине")
s = build(tmp / "censor", 24, effect=2.0, censor_units=tuple(range(1, 9)), seed=9)
case("прибор считает и объявляет цензуру нижней границей", s, 0,
want_in=["пар с цензурой", "НИЖНЯЯ ГРАНИЦА"])
print("\n6. СТРОКИ-ПОВТОРЫ: у каждой клетки 19 записей tm_hit=1 с нулями (как в реальном интерливинге)")
s = build(tmp / "replay", 24, effect=2.0, replays=19, seed=2)
case("прибор берёт НАСТОЯЩИЙ вызов, а не повтор, и находит тот же эффект", s, 0,
want_in=["ВЫВОД: эффект ЕСТЬ"])
code, txt = run(s)
for line in txt.splitlines():
if "Ходжес-Леман" in line and "отношение" in line:
print("", line.strip(), " (истина: ×0.500; на нулях повтора вышло бы NaN или мусор)")
break
print("\n7. ⛔ СИГНАТУРА НЕ ВОСПРОИЗВЕЛАСЬ у 40% юнитов (запинено 79, приходит 40)")
s = build(tmp / "sig", 24, effect=2.0, sig_break=tuple(range(1, 11)), seed=4)
case("прибор аннулирует пробу, а не считает эндпойнты на браке", s, 2,
want_in=["сигнатура не воспроизвелась", "ПРИБОР ОТКАЗЫВАЕТ"])
print("\n8. ЭХО ЗА САНИТАЙЗЕРОМ: сырой ответ несёт CJK, отгружаемый вычищен")
s = build(tmp / "sanit", 24, effect=2.0, sanitizer_units=tuple(range(1, 5)), seed=6)
code, txt = run(s)
# ⛔ ПРОВЕРЯЕМ ЧИСЛА, А НЕ ЗАГОЛОВКИ: заголовок «CJK@сырой» напечатается и при слепом приборе.
lines = txt.splitlines()
try:
i = next(k for k, l in enumerate(lines) if "CJK@сырой" in l)
row = next(l for l in lines[i + 1:] if l.strip().startswith("p7-off"))
f = row.split()
raw_cjk, ship_cjk, sanit = int(f[2]), int(f[3]), int(f[7])
good = raw_cjk == 4 and ship_cjk == 0 and sanit == 4
print(f" p7-off: CJK@сырой={raw_cjk} (ждали 4) CJK@отгр={ship_cjk} (ждали 0) санитайз={sanit} (ждали 4)")
except (StopIteration, ValueError, IndexError) as e:
good, raw_cjk = False, None
print(f" ⛔ не удалось разобрать строку E5: {e}")
print(("" if good else "") + "E5 ВИДИТ эхо по СЫРОМУ ответу, хотя по отгруженному его НЕТ"
+ f" (код {code})")
ok = ok and good and code == 0
print("\n9. ⛔ ПУСТОЙ СТЕНД: баз нет вовсе")
(tmp / "nothing").mkdir()
(tmp / "nothing" / "manifest.json").write_text(json.dumps(
{"prompt_sha256": {"P7": SHA_P7, "P9": SHA_P9}, "signature": {}}))
case("прибор отказывает, а не печатает нули", tmp / "nothing", 2,
want_in=["ПРИБОР ОТКАЗЫВАЕТ"])
shutil.rmtree(tmp, ignore_errors=True)
print("\n" + ("ПРИБОР ГОДЕН" if ok else "⛔ ПРИБОР НЕ ГОДЕН"))
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())

View file

@ -149,7 +149,16 @@ ARMS = {"RN": ("deepseek-v4-pro", 1), "RN2": ("deepseek-v4-pro", 2),
"REL": ("gemini-3.1-flash-lite", 1), # ступень `low` — сопоставима с grok-дефолтом
"RA": ("deepseek-v4-pro", 1), # тот же промт, ВОССТАНОВЛЕННЫЕ абзацы исходника
"RC": ("deepseek-v4-pro", 1), # промт МИНУС рамка оценки, см. CUT
"RB": ("deepseek-v4-pro", 1)} # рамка оценки ПЕРЕВЁРНУТА: брак = чужой язык
"RB": ("deepseek-v4-pro", 1), # рамка оценки ПЕРЕВЁРНУТА: брак = чужой язык
# ⚠ АРМ ЗАВЕДЁН 30.08 ПО САНКЦИИ ВЛАДЕЛЬЦА («по поводу дипсика — бери»). Это `RN` побайтно
# тем же промтом, но с ЯВНОЙ нижней ступенью размышления. Смысл — развести конфаунд Д44.6:
# весь замеренный разрыв «луна против якоря» (8.1× по медиане) снят с якоря, которому ручку
# НЕ СЛАЛИ ВОВСЕ (= вендор-дефолт `high`), против испытуемого на явном `low`. По норме Д28
# это разные армы, и часть разрыва принадлежит СТУПЕНИ, а не вендору.
# ⛔ До 30.08 такой арм был НЕВОЗМОЖЕН: вендор-таблица маппила `low`→`high` у `pro`. Живая
# вахта 30.08 (`curl`, sha256 f28c4324…) показала новую редакцию — «identical for flash and
# pro», `low`→`low`. Это ДОКА; арм и есть её поведенческая проба.
"RNL": ("deepseek-v4-pro", 1)} # тот же промт, ступень размышления `low` ЯВНО
RESTORE_ARMS = ("RA",)
# ⚠ РЕЗАНЫЕ ВАРИАНТЫ ПРОМТА. Замер `RN` дал ТРИ независимых показания одного сдвига: размышление
@ -492,11 +501,15 @@ def _e6_wire(uid: str) -> tuple[list[dict], str, dict]:
def _rq_wire(uid: str) -> tuple[list[dict], str, dict]:
"""WIRE-АНАЛОГ `RQ` — ЗАПРОС В ТОЙ ФОРМЕ, В КОТОРОЙ ОН УХОДИЛ ДО ПОЧИНКИ СКЛЕЙКИ.
Клетки `RQ` это `RE`, у которого вендор МОЛЧА ВЫБРОСИЛ второе системное сообщение:
OpenAI-совместимый слой Gemini принимает ровно одно. Поэтому здесь НЕ зовётся `fit_msgs`
Клетки `RQ` это `RE`, у которого вендор МОЛЧА ВЫБРОСИЛ ПЕРВОЕ системное сообщение
ролевой промт, и оставил ПОСЛЕДНЕЕ, то есть глоссарий: OpenAI-совместимый слой Gemini
принимает ровно одно. Прежняя редакция этого комментария говорила «второе», и это было
неверно: разность `RE.prompt_tokens - RQ.prompt_tokens` равна 2436 РОВНО на всех 16 главах,
а постоянного размера из двух системных только первое (эррата Д35.3). Поэтому НЕ зовётся `fit_msgs`
(склейка, заведённая после находки) и НЕ зовётся `rol.call_kwargs`: печатается ровно то, что
отправлял код 21.08. Печатается ОТПРАВЛЕННОЕ, а не ПОЛУЧЕННОЕ моделью второе нам недоступно
по построению, и разница видна только по счётчику: 515 токенов входа против 2951 у `RE`.
Из этих 515 глоссарий составляет ~100 арм есть «без РОЛЕВОГО промта», а не «без промта».
"""
src = next(u["source"] for u in chosen("en") if u["uid"] == uid)
msgs = rol_msgs("en", src, "RE")
@ -507,9 +520,9 @@ 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 — это оплаченный и
# ⚠ ПСЕВДО-АРМ ПОД КАРАНТИННЫМ СУФФИКСОМ. 16 клеток `RE`, у которых до модели НЕ ДОЕХАЛ РОЛЕВОЙ
# ПРОМТ: OpenAI-совместимый слой Gemini принимает РОВНО ОДНО системное сообщение, выбрасывает
# ПЕРВОЕ и оставляет ПОСЛЕДНЕЕ — то есть глоссарий доехал, а инструкция нет (Д35.3). Деньги за них списаны, тексты годны, `finish=stop` 16/16 — это оплаченный и
# незапланированный природный эксперимент «сколько весит ВЕСЬ промт».
# ⛔ ИМЯ НЕЙТРАЛЬНОЕ И СУФФИКСА В ПАКЕТЕ БЫТЬ НЕ ДОЛЖНО: читатель, увидевший «БЕЗ-ПРОМТА»,
# мерил бы подсказку. `arm_field` = "RE", потому что в самой клетке написано «RE» — она и есть
@ -521,7 +534,8 @@ FOREIGN = {
"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 БЕЗ ИНСТРУКЦИИ — вендор выбросил системное сообщение"},
"что": "тот же gemini БЕЗ РОЛЕВОГО ПРОМТА — вендор выбросил первое системное, "
"глоссарий доехал"},
}
@ -607,6 +621,7 @@ def arm_text(p: str, arm: str, uid: str) -> str:
# норма Д28.3 расширена ревью — уровень называется ЗНАЧЕНИЕМ, а не наличием.
THINK_LEVEL: dict = {
"RN": None, "RN2": None, "RC": None, "RB": None, "RA": None, # dspro: вендор-дефолт high
"RNL": "low", # ⚠ тот же dspro с ЯВНЫМ `low` — контраст ступени против `RN` (Д45)
"RG": "off", # glm-5 с НАШИМ подавлением — оставлен намеренно, он и есть контраст
"RGT": "vendor", # тот же glm-5 без нашего подавления
"RK": "low", # grok на вендор-дефолте — ровно так и куплен, теперь это НАПИСАНО
@ -628,7 +643,10 @@ THINK_LEVEL: dict = {
# инструкция НЕ исполнена**. Ответ при этом приходит с `finish=stop` и выглядит правильным
# переводом — то есть отказа нет, есть тихая потеря промта.
# Наш арм шлёт ДВА системных (ядро роли + банк-закон), поэтому первые 16 клеток `RE` переведены
# ВООБЩЕ БЕЗ ИНСТРУКЦИИ. Вердикт «gemini не годится» из них был бы ложным — четвёртый случай того
# БЕЗ РОЛЕВОГО ПРОМТА, НО С ГЛОССАРИЕМ. ⚠ Прежняя редакция этого комментария писала «ВООБЩЕ БЕЗ
# ИНСТРУКЦИИ» — неверно: выживает ПОСЛЕДНЕЕ системное, то есть банк-закон доехал (~130 токенов
# из 2 566), а выброшено ядро роли. Установлено 29.08 арифметикой клеток, улика — `sysmsg.py`.
# Вердикт «gemini не годится» из них был бы ложным — четвёртый случай того
# же класса за два дня (склейка · glm-5 · grok · этот).
# Лечение узкое и по вендору, а не общее: склеивать системные в ОДНО только там, где вендор иначе
# их теряет. Общая склейка сменила бы форму запроса у ВСЕХ моделей и рассогласовала бы уже
@ -694,6 +712,7 @@ def call_kwargs(model: str, msgs: list[dict], arm: str = "") -> dict:
# чтобы купить оба. Ограничитель берёт ПЕРВЫЕ N глав отбора — тот же детерминированный порядок,
# значит абляция сравнима с уже купленным `RN` глава в главу.
N_LIMIT = next((int(a.split("=", 1)[1]) for a in sys.argv if a.startswith("--n=")), 0)
ONLY_UID = next((a.split("=", 1)[1] for a in sys.argv if a.startswith("--only=")), "")
def buy(p: str, which: list[str]) -> None:
@ -702,6 +721,15 @@ def buy(p: str, which: list[str]) -> None:
cfg["wire"]()
ZK.preflight([ARMS[a][0] for a in which], cfg["phase"], cfg["letter"])
us = chosen(p)[:N_LIMIT] if N_LIMIT else chosen(p)
# ⚠ ФИЛЬТР ПО ГЛАВЕ, заведён 30.08. Пре-рег Д45.1 называет КОНКРЕТНУЮ главу для пробной клетки
# (`001e6ac4eb` — самая дорогая по размышлению у `RN`), а `--n=` умеет только «первые N» в
# порядке отбора. Без фильтра пришлось бы либо купить не ту главу, либо менять правило решения
# ПОСЛЕ фриза — то есть подгонять пре-рег под возможности рига. Добавление механическое:
# промт, касса, гейты и правила не тронуты.
if ONLY_UID:
us = [u for u in us if u["uid"].startswith(ONLY_UID)]
if not us:
raise SystemExit(f"⛔ главы {ONLY_UID} нет в отборе пары {p} — покупать нечего")
led = MONEY.Guarded(cfg["phase"], default_expect=0.03)
print(f"\nпара {p} · глав {len(us)} · армы {' '.join(which)} · касса {cfg['phase']}")
for arm in which:

View file

@ -55,7 +55,17 @@ LEDGER = OUT / "agent-runs.json"
# (агенты, не API), и кап охраняет не деньги, а внимание: он ловит «сессий стало больше, чем
# кто-либо отслеживает». 200 покрывает переход на слепое чтение как ШТАТНЫЙ прибор — там одна
# сессия на главу, то есть счёт идёт десятками, а не единицами.
CAP = 200
# ⚠ 30.08: кап поднят 200 → 215 по слову владельца («Да, давай подниму кап»), запрошенному прямо
# под ЭТО чтение: панель «однопроходка `luna` против однопроходки `deepseek` и против боевой
# связки» собирается на 15 главах из 16, свободно было 14 — то есть в кап упиралась ровно одна
# глава. Взято 215, а не 201: пятнадцать уходят на круг, остальное — на ВТОРОЙ круг той же панели,
# без которого конъюнкция (повтор во втором круге) недостижима, и панель нельзя будет закрыть.
# Кап по-прежнему охраняет не деньги, а внимание — сессии бесплатны.
# ⚠ 30.08, вторым шагом: 215 → 220 по слову владельца («Ладно, давай») на просьбу дать +5. Причина
# просьбы — МОЯ АРИФМЕТИЧЕСКАЯ ОШИБКА в записи выше: 215 оставляло 14 свободных при 15 нужных
# второму кругу, то есть «остальное на второй круг» не считалось. Резать панель до 14 глав, чтобы
# влезть в кап, запрещено — это подгонка прибора под возможности.
CAP = 220
FAR_FUTURE = 1e18
LOCK_TRIES = 600 # ×0.1 с — минута ожидания живого замка

155
eval/dovodka/stupen.py Normal file
View file

@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""ЭНДПОЙНТ ДИЗАЙНА Д (пре-рег Д47): РЕЖЕТ ЛИ `reasoning_effort:"low"` РАЗМЫШЛЕНИЕ У `pro`. $0.
ЗАЧЕМ ОТДЕЛЬНЫЙ ФАЙЛ, а не счёт в уме отчёта. Правило решения Д47.3 написано ДО чисел, и оно
обязано применяться механически, иначе «правило до чисел» превращается в «правило под числа».
Файл написан ДО прихода клеток и коммитится вместе с ними: кто угодно пере-снимает вывод одной
командой и видит ровно то, что видел автор.
ЧТО ЗДЕСЬ ЕСТЬ И ЧЕГО НЕТ:
* есть парный знаковый тест по восьми главам (`reasoning_tokens` у `RNL` против `RN2`);
* есть по-клеточный гейт годности В ТОЙ ФОРМЕ, В КАКОЙ ОН ЗАМОРОЖЕН, включая проверку
провенанса `Δprompt_tokens [60,100]` она ловит клетку, до которой параметр НЕ доехал;
* НЕТ ни одной ручки, которой можно подвинуть порог: α, полосы и список глав константы файла,
и селфтест стережёт, что они те же, что в отчёте.
ОТНОШЕНИЯ (×N) ПЕЧАТАЮТСЯ КАК ОПИСАНИЕ, А НЕ КАК ВЫВОД. Дизайн Д стоит на восьми ДЕШЁВЫХ главах
(10.2% цены панели) переносить их величины на дорогую моду запрещено там же, где выбраны главы.
Запуск: eval/.venv/bin/python eval/dovodka/stupen.py [--selftest]
"""
from __future__ import annotations
import json
import math
import sys
from pathlib import Path
ZONE = Path(__file__).resolve().parent
sys.path.insert(0, str(ZONE))
import rol # noqa: E402
OUT = Path.home() / "books" / "dovodka"
# ── КОНСТАНТЫ ПРЕ-РЕГА Д47. Меняются только вместе с отчётом и только ДО покупок ───────────────
CHAPTERS = ("b065a92dc0", "5a8f48d24a", "100b088f71", "49ca859c94",
"8e50448cea", "c3517980c7", "3bd6481182", "26c3bff1d4")
ARM_HIGH, ARM_LOW = "RN2", "RNL" # контроль (ручка не шлётся) и испытуемый (`low`)
ALPHA = 0.05 # двусторонний, Д47.3
DPT_BAND = (60, 100) # провенанс параметра: ожидается 79 (Д46.1)
CYR_MIN = 0.99 # доля кириллицы среди букв
LAT_MAX = 0 # латиница-эхо
def _letters(t: str) -> tuple[int, int, int]:
"""(букв всего, кириллических, латинских)."""
al = [c for c in t if c.isalpha()]
cyr = sum(1 for c in al if "а" <= c.lower() <= "я" or c.lower() == "ё")
lat = sum(1 for c in al if "a" <= c.lower() <= "z")
return len(al), cyr, lat
def _cell(arm: str, uid: str) -> dict | None:
f = OUT / f"{rol.tag('en', arm, uid)}.json"
return json.loads(f.read_text(encoding="utf-8")) if f.exists() else None
def sign_p(k: int, n: int) -> float:
"""Точный двусторонний знаковый тест при p=0.5. k — сколько пар легли в ожидаемую сторону."""
if n == 0:
return 1.0
tail = sum(math.comb(n, i) for i in range(k, n + 1)) / 2 ** n
return min(1.0, 2 * tail)
def main() -> int:
rows, missing = [], []
for uid in CHAPTERS:
hi, lo = _cell(ARM_HIGH, uid), _cell(ARM_LOW, uid)
if not hi or not lo:
missing.append(uid)
continue
n_al, cyr, lat = _letters(lo.get("content") or "")
share = cyr / n_al if n_al else 0.0
dpt = hi["prompt_tokens"] - lo["prompt_tokens"]
bad = []
if hi.get("finish") != "stop" or lo.get("finish") != "stop":
bad.append("finish≠stop")
if share < CYR_MIN:
bad.append(f"кириллица {share:.3f}")
if lat > LAT_MAX:
bad.append(f"латиница {lat}")
if not DPT_BAND[0] <= dpt <= DPT_BAND[1]:
bad.append(f"Δpt {dpt} вне [{DPT_BAND[0]},{DPT_BAND[1]}] — параметр не доехал")
rows.append(dict(uid=uid, hi=hi["reasoning_tokens"], lo=lo["reasoning_tokens"],
dpt=dpt, cyr=share, lat=lat, bad=bad,
cost=hi["cost_usd"] + lo["cost_usd"]))
if missing:
print(f"⛔ клеток нет у глав: {' '.join(missing)} — эндпойнт неполон")
if not rows:
return 1
print(f"{'глава':12}{'RN2 дум':>9}{'RNL дум':>9}{'отнош':>8}{'Δpt':>6}{'кирилл':>8}"
f"{'лат':>5} гейт")
for r in rows:
ratio = r["hi"] / r["lo"] if r["lo"] else float("inf")
print(f"{r['uid']:12}{r['hi']:>9}{r['lo']:>9}{ratio:>8.2f}{r['dpt']:>6}"
f"{r['cyr']:>8.3f}{r['lat']:>5} "
f"{'OK' if not r['bad'] else '' + ' · '.join(r['bad'])}")
good = [r for r in rows if not r["bad"]]
n = len(good)
k = sum(1 for r in good if r["lo"] < r["hi"])
p = sign_p(k, n)
ratios = sorted(r["hi"] / r["lo"] for r in good if r["lo"])
med = ratios[len(ratios) // 2] if ratios else float("nan")
print(f"\nгодных пар {n} из {len(CHAPTERS)} · размышление у `low` МЕНЬШЕ в {k} из {n}")
print(f"знаковый тест, двусторонний: p = {p:.4f} при пороге α = {ALPHA}")
print(f"цена прогона: ${sum(r['cost'] for r in rows):.6f}")
# ── ПРАВИЛО Д47.3. Ветки написаны до чисел; ни одна из них ничего не «дозакупает» ──────────
print()
if n < len(CHAPTERS):
print("⚠ ПАР МЕНЬШЕ ВОСЬМИ: правило Д47.3 писалось на n=8. Вывод печатается по факту "
"годных пар и объявляется НЕПОЛНЫМ — добор до восьми ПОСЛЕ взгляда на числа запрещён.")
if k == n and p < ALPHA:
print(f"⭐ ЕДИНОГЛАСНО И ЗНАЧИМО: ручка РЕЖЕТ размышление. Медиана отношения ×{med:.2f}"
"ОПИСАНИЕ этих восьми дешёвых глав, не перенос на дорогую моду и не на пару zh.")
elif p >= ALPHA:
print(f"НЕ ЗНАЧИМО (p = {p:.4f}{ALPHA}): «не установлено». Это РЕЗУЛЬТАТ, а не повод "
"докупать девятую главу — добор до значимости после чисел запрещён (Д47.3).")
else:
print(f"⛔ ЗНАЧИМО В ОБРАТНУЮ СТОРОНУ (k={k} из {n}): ручка размышление НЕ режет.")
return 0
def selftest() -> int:
bad = 0
def ck(ok: bool, what: str) -> None:
nonlocal bad
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {what}")
bad += 0 if ok else 1
ck(len(CHAPTERS) == 8 and len(set(CHAPTERS)) == 8, "восемь глав пре-рега, без повторов")
rep = (ZONE.parents[1] / "docs" / "experiments" / "23-editor-tier.md").read_text(encoding="utf-8")
ck(all(u in rep for u in CHAPTERS), "все восемь глав названы в отчёте (пре-рег Д47.2)")
ck(ARM_HIGH in rol.ARMS and ARM_LOW in rol.ARMS, "оба арма есть в реестре покупок")
ck(rol.THINK_LEVEL.get(ARM_HIGH) is None and rol.THINK_LEVEL.get(ARM_LOW) == "low",
"контроль без ручки, испытуемый на `low`")
ck(rol.ARMS[ARM_HIGH][0] == rol.ARMS[ARM_LOW][0] == "deepseek-v4-pro", "модель у армов одна")
ck(rol.tag("en", ARM_HIGH, "x") != rol.tag("en", ARM_LOW, "x"), "теги армов не схлопываются")
# правило решения обязано быть НЕ достижимо ничем, кроме единогласия — это и есть цена n=8
ck(sign_p(8, 8) < ALPHA <= sign_p(7, 8), "при n=8 значимость даёт ТОЛЬКО 8 из 8")
ck(abs(sign_p(8, 8) - 0.0078125) < 1e-9, "минимальный двусторонний p посчитан верно")
ck(sign_p(4, 8) == 1.0, "полный ноль эффекта даёт p = 1")
print(f"\n{'ПРИБОР ГОДЕН' if not bad else f'ПРОВАЛОВ: {bad}'}")
return bad
if __name__ == "__main__":
sys.exit(selftest() if "--selftest" in sys.argv else main())

111
eval/dovodka/sysmsg.py Normal file
View file

@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""КАКОЕ СИСТЕМНОЕ СООБЩЕНИЕ ВЫЖИВАЕТ У GEMINI — ДЕТЕРМИНИРОВАННАЯ УЛИКА. $0.
ЗАЧЕМ ОТДЕЛЬНЫЙ ФАЙЛ. Вопрос стоял открытым в `docs/experiments/00-provider-quirks.md`
дословно: «КАКОЕ именно выживает НЕ УСТАНОВЛЕНО Расхождение открыто». Закрыт он не новой
живой пробой (она стоит денег и протухает вместе со слагом), а АРИФМЕТИКОЙ уже купленных клеток.
Такой вывод обязан иметь исполняемого носителя: иначе через месяц он останется утверждением в
отчёте, которое никто не может пере-снять.
ЛОГИКА ДОКАЗАТЕЛЬСТВА, и она не требует ни одного вызова модели:
у арма `RE` на провод уходят ТРИ сообщения
system[0] ролевой промт ПОБАЙТНО ОДИН И ТОТ ЖЕ на всех главах
system[1] глоссарий РАЗНЫЙ на каждой главе (термины берутся из текста главы)
user исходник разный
карантинные клетки `RQ` тот же вызов, у которого вендор что-то выбросил.
если разность `RE.prompt_tokens RQ.prompt_tokens` ПОСТОЯННА по главам, выброшено
сообщение постоянного размера, а таково только system[0] ролевой промт.
Будь выброшены ОБА системных, разность росла бы вместе с глоссарием и постоянной быть
не могла; будь выброшено только system[1], постоянной была бы не разность, а сам `RQ`.
ГЕЙТ ПАДАЕТ, ЕСЛИ ВЫВОД НЕ ДЕРЖИТСЯ. Он проверяет ТРИ вещи, и каждая может уронить:
1. system[0] действительно побайтно постоянен, а system[1] действительно варьирует
(иначе рассуждение неприменимо и это надо увидеть, а не предположить);
2. разность постоянна с точностью до нуля;
3. остаток `RQ` разложим на глоссарий + исходник: он обязан коррелировать с длиной ГЛОССАРИЯ
сильнее, чем с длиной исходника.
Запуск: eval/.venv/bin/python eval/dovodka/sysmsg.py
"""
from __future__ import annotations
import json
import statistics as st
import sys
from pathlib import Path
ZONE = Path(__file__).resolve().parent
sys.path.insert(0, str(ZONE))
import rol # noqa: E402
OUT = Path.home() / "books" / "dovodka"
def _corr(a: list[float], b: list[float]) -> float:
ma, mb = st.mean(a), st.mean(b)
num = sum((x - ma) * (y - mb) for x, y in zip(a, b, strict=True))
den = (sum((x - ma) ** 2 for x in a) * sum((y - mb) ** 2 for y in b)) ** 0.5
return num / den if den else 0.0
def main() -> int:
rows = []
for u in rol.chosen("en"):
uid = u["uid"]
pair = OUT / f"dv-n-re-{uid}.json", OUT / f"dv-n-re-{uid}.БЕЗ-ПРОМТА.json"
if not all(p.exists() for p in pair):
print(f"⛔ нет пары клеток для {uid[:10]} — улика неполна")
return 1
re_, rq_ = (json.loads(p.read_text(encoding="utf-8")) for p in pair)
msgs, _model, _kw = rol.FOREIGN["RQ"]["wire"](uid)
if [m["role"] for m in msgs] != ["system", "system", "user"]:
print(f"⛔ форма вызова не «system·system·user»: {[m['role'] for m in msgs]}")
return 1
rows.append({
"uid": uid[:10],
"re": re_["prompt_tokens"], "rq": rq_["prompt_tokens"],
"sys0": len(msgs[0]["content"]), "sys1": len(msgs[1]["content"]),
"user": len(msgs[2]["content"]),
})
print(f"{'глава':12}{'RE_pt':>7}{'RQ_pt':>7}{'RE-RQ':>7}{'sys0':>7}{'sys1':>6}{'user':>7}")
for r in rows:
print(f"{r['uid']:12}{r['re']:>7}{r['rq']:>7}{r['re'] - r['rq']:>7}"
f"{r['sys0']:>7}{r['sys1']:>6}{r['user']:>7}")
bad = 0
sys0 = {r["sys0"] for r in rows}
sys1 = {r["sys1"] for r in rows}
diff = {r["re"] - r["rq"] for r in rows}
def ck(ok: bool, what: str) -> None:
nonlocal bad
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {what}")
bad += 0 if ok else 1
ck(len(sys0) == 1, f"ролевой промт постоянен по главам: вариантов длины {len(sys0)} ({sys0})")
ck(len(sys1) > 1, f"глоссарий по главам РАЗНЫЙ: вариантов длины {len(sys1)}")
ck(len(diff) == 1, f"разность RERQ постоянна: вариантов {len(diff)} ({diff})")
d = next(iter(diff)) if len(diff) == 1 else -1
tok_per_char = d / next(iter(sys0)) if len(sys0) == 1 and d > 0 else 0.0
c_gl = _corr([float(r["rq"]) for r in rows], [float(r["sys1"]) for r in rows])
c_src = _corr([float(r["rq"]) for r in rows], [float(r["user"]) for r in rows])
ck(c_gl > c_src, f"остаток RQ идёт за ГЛОССАРИЕМ, а не за исходником: "
f"corr {c_gl:+.2f} против {c_src:+.2f}")
print(f"\nвыброшено {d} токенов на {next(iter(sys0))} знаков ролевого промта "
f"= {tok_per_char:.3f} ток/знак русского текста")
print("⇒ ВЫЖИВАЕТ ПОСЛЕДНЕЕ СИСТЕМНОЕ, ВЫБРАСЫВАЕТСЯ ПЕРВОЕ."
if not bad else "\n⛔ ВЫВОД НЕ ДЕРЖИТСЯ — пере-открывать, а не подгонять.")
print("⚠ Это утверждение о ПОВЕДЕНИИ СЛОЯ на дату покупки клеток (21.08.2026). "
"Слаг ≠ модель, поведение слоя тоже имеет срок годности.")
return 1 if bad else 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -569,7 +569,15 @@ def cmd_dry() -> None:
print(f" ИТОГО ${sub:.4f} · потолок {ph} ${ceil:.2f} · потрачено ${sp:.4f}{mark}")
tot += sub
now = dt.datetime.now(dt.timezone.utc)
peak = now.hour in tuple(range(1, 4)) + tuple(range(6, 10))
# ⚠ ПОПРАВКА 30.08: у вендора пик — ЕЩЁ И ТОЛЬКО ПО БУДНЯМ, а у нас этой оговорки не было.
# Дословно (`api-docs.deepseek.com/quick_start/pricing`, живое чтение 30.08.2026): «Peak hours
# are 01:00 - 04:00 and 06:00 - 10:00 UTC, Monday through Friday (all other hours are off-peak)».
# Поймано разведкой ландшафта: Bloomberg 23.08 писал о снятии пиковой цены на выходных, вендорская
# страница это подтверждает формулировкой. Без `weekday() < 5` риг звал воскресное утро ПИКОМ.
# ⚠ Это правка ТОЛЬКО ПЕЧАТИ: `peak` нигде, кроме этой сводки, не используется — цена клеток
# считается из пина ростера, а он ПИКОВЫЙ сознательно (D39.136: потолок обязан срабатывать
# раньше, а не позже). Ледждер не трогается.
peak = now.weekday() < 5 and now.hour in tuple(range(1, 4)) + tuple(range(6, 10))
print(f"\nВЕСЬ ЗАХОД: ${tot:.4f} по ТЕКУЩЕМУ пину ростера (пик).")
print(f" Сейчас {now:%H:%M} UTC — {'ПИК' if peak else 'ОФФ-ПИК'}; вендорский офф-пик ровно "
f"вдвое дешевле, то есть фактически спишется около ${tot if peak else tot / 2:.4f}.")

View file

@ -99,7 +99,11 @@ REASONING_BILLING = {"grok-4.3": "additive",
# дефолт), и это осознанный выбор, а не пропуск.
# deepseek — НИКОГДА не гасить (эхо-мина, гардрейл CLAUDE.md); у flash боевая ставит
# `reasoning_effort: low`, иначе размышление съедает бюджет (quirks §10); у pro ручка `low`
# признана НЕ УСТАНОВЛЕННОЙ (quirks 3б) ⇒ едем вендор-дефолтом;
# ⚠ С 30.08 ДОХОДИТ ДО МОДЕЛИ (quirks 3г: серверная реакция 79 входных токенов при побайтно
# одинаковых messages) — прежняя причина «не установлена» БОЛЬШЕ НЕ ДЕЙСТВУЕТ. Едем всё равно
# вендор-дефолтом, но по другой причине: ВЕЛИЧИНА среза не замерена (дизайн конфаундирован
# дрейфом, exp23 §Д46.2), а цена ручки в эхе и качестве прозы не мерена ни разу ⇒ смена ступени
# у боевого редактора была бы обменом с неизвестным курсом. Менять — после интерливинг-замера;
# glm — гасится `extra_body {"thinking":{"type":"disabled"}}`, так шёл арм B эксп-21;
# luna — без явной ручки выжигает бюджет и отдаёт пустой `content` (провод-факт эксп-21 §8);
# gemini — конфиг размышления НЕ шлём: у 3.1-pro `thinking_budget:0` даёт HTTP 400, а у