38 KiB
Малая пачка: ingest грязных епабов (14а) + комментарии промпт-файлов (14б)
База: записка оркестратора №8 от 30.07, санкции владельца 26.07 (бэклог docs/PROGRESS.md, строки 14а/14б).
Зона: backend/. Деньги сессии: $0 — ни одного вызова провайдера, вся приёмка на локальном сырье.
Статус: построено и проверено исполнением. Сессия НЕ коммитит, лендинг — оркестратора.
Ревью-шапка (оркестратор №9, 30.07): ПРИНЯТО И ЗАЛЕНДЕНО, D39.54. Приёмка исполнением на неподвижных копиях (старая = HEAD, новая = деливерабл): собственный дампер old-vs-new по трём епабам стенда — 205 документов, дифф глав и ruby ПУСТ (3.29 МБ на сторону); полный сьют
-race -count=114/14 зелёный, gofmt/vet чисто; четыре спот-мутации вне порядка сессии (D — SHA по сырому, M8 — нейтрализация вырезания, M7 — raw-text везде, M10 — flushRuby в no-op) — все КРАСНЫЕ, восстановление зелёное. Финальная сверка сессии (§2.8, несбалансированный ruby — третий улов ревью) вошла в приёмку: копии сняты ПОСЛЕ фикса, мутационная таблица сессии 17/17. Посылки перемерены независимо: 126 itemref (не 958) · 8/8 книг стенда txt · ровно 2 сущности из 252 расходятся (кросс-чек через WHATWG-таблицу python и GOROOT-таблицуxml.HTMLEntity) · цитатаsnapshot.go:138дословна · grepterminologпоsnapshot.goпуст. Диспозиции: CDATA (§7.4) — В СКОУПЕ (без неё переезд = регрессия на валидном XHTML); chunkerVersion НЕ поднят — ратифицировано D39.54 (экспозиция — пустое множество); mimetype — верность формату, не фикс поведения (§7.5 принят, в D-ноте так и записано). Фикс-лист (1): комментарий поляSHA256вrender.go:89остался «hash of the raw file» — одна строка, на ближайшее касание. Кандидат §8 (<meta charset>без XML-объявления) — бэклог 34а.
Две поправки к посылкам записки — читать до §1. Обе найдены замером, обе меняют формулировки приёмки, ни одна не меняет решение строить.
- «Empire of the Dawn, 958 документов spine» — неверно. В книге 126
<itemref>(288 записей zip, 283<item>манифеста, из них 154.jpg). Число 958 не соответствует ничему в файле. Сравнение проведено на всех трёх епабах стенда — 205 документов суммарно.- «Ingest выполняется при создании книги — существующие БД и снапшоты не трогаются» — обоснование неверно, вывод верен.
TranslateBookвызываетchunk.IngestEncodedиSplitChunksна КАЖДОМ прогоне (bookrun.go:112,140; то же вstatus.go:190,194) — источник переизвлекается и перерезается каждый раз. Существующие БД защищены другим фактом: все книги стенда — txt/gb18030 (book.yamlвосьми проектовgu-zhenren/*), аextractXHTMLдостижим только изingestEPUB. Ни одна существующая книга епаб-путь не проходит. Подробно — §6.
§1 Что построено
| № | Пункт | Где |
|---|---|---|
| 14а-1 | extractXHTML на токенайзере x/net/html вместо encoding/xml |
chunk/ingest.go |
| 14а-1а | Паритетные помощники: voidTags, rawTextTags, tokenTagName, checkXHTMLCharset, unwrapCDATA, flushRuby |
там же |
| 14а-2 | Chapter.EntryName + два теста (percent-href, #fragment) |
chunk/chunktest/epub.go, chunk/ingest_test.go |
| 14а-3 | mimetype фикстуры через zip.Store + тест-пин OCF |
chunk/chunktest/epub.go, chunk/ingest_test.go |
| 14б-1 | Вырезание <!-- --> ДО сплита по ---USER--- |
pipeline/render.go (stripPromptComments) |
| 14б-2 | SHA по канонической (очищенной) форме | там же, LoadPromptTemplate |
| 14б-3 | Шапка terminologist.md ужата 12 строк → 2 |
prompts/zh-ru/terminologist.md |
| 14б-4 | Тест-пины + мутации | pipeline/promptcomments_test.go |
Диффа: 5 отслеживаемых файлов +651/−97 (из них ingest.go +216/−70, ingest_test.go +374) плюс новый
promptcomments_test.go (161 строка).
§2 14а — переезд на токенайзер
2.1 Четыре класса: КРАСНЫЕ до, ЗЕЛЁНЫЕ после
Приёмка «красное-до» снята не рассуждением, а исполнением оригинальной функции: старый
extractXHTML извлечён дословно из git show HEAD:backend/internal/chunk/ingest.go и прогнан по тем же
фикстурам рядом с новым. Старый на всех четырёх — жёсткая ошибка и пустой текст, то есть в
ingestEPUB это return nil, err: один грязный документ убивал импорт всей книги.
| Класс | Старый encoding/xml |
Новый токенайзер |
|---|---|---|
голый < в прозе |
XML syntax error on line 3: expected element name after < |
"Если a < b, то дальше.\n\n\n\nВторой абзац." |
| перехлёст тегов | XML syntax error on line 3: unexpected end element </i> |
"жирный оба курсив хвост." |
содержимое <script> |
XML syntax error on line 3: expected attribute name in element |
"Настоящий текст." |
-- в комментарии |
XML syntax error on line 3: invalid sequence "--" not allowed in comments |
"Настоящий текст." |
Пин — TestIngestEPUBDirtyXHTMLImports (4 подтеста): каждый проверяет и что проза выжила, и что
шум (document.write, текст комментария, теги) в неё НЕ попал.
2.2 Пять решений, которых требует токенайзер (и почему именно так)
Токенайзер — лексер, а не парсер: он не достраивает дерево и не синтезирует закрывающие теги. Пять мест, где наивный перенос состояния ломается (пп. а–в найдены при проектировании, пп. г–д — ревью уже после сборки):
(а) Пропуск поддерева — ПО ИМЕНИ, а не по глубине. Старый код на encoding/xml считал
skipDepth++ на каждом StartElement. У токенайзера <meta/>/<link/> в <head> — это
SelfClosingTagToken без парного EndTagToken, а <meta> без слэша — StartTagToken без пары.
Слепой счётчик никогда не вернулся бы в ноль и съел бы всю главу. Пропуск отслеживается по имени
корня + глубина одноимённых; плюс правило «<body> закрывает незакрытый <head>», чтобы битая
разметка не проглатывала текст.
(б) Void-элементы закрываются сами. xml.HTMLAutoClose синтезировал End для br/hr/img/meta/link
и др., поэтому старый код для блочного <hr> писал разрыв ДВАЖДЫ ("\n\n\n\n"). Новый код на
SelfClosingTagToken или элементе из voidTags вызывает end() сам — это и даёт байтовый паритет.
Замерено: <p>A</p><hr/><p>B</p> → "\n\nA\n\n\n\n\n\n\n\nB\n\n" в обеих реализациях, обе записи
<hr/> и <hr> совпадают.
(в) Префикс пространства имён срезается. encoding/xml отдавал Name.Local (<epub:p> → p),
токенайзер отдаёт epub:p. Без среза префиксованная епаб3-глава схлопнулась бы в один абзац.
(г) Сырой текст — только там, где он нужен (§2.7) и (д) ruby как РЕЖИМ, а не глубина (§2.8) — оба найдены адверсариальной проверкой уже ПОСЛЕ первой сборки, оба критические, обоих сравнение по чистому корпусу не увидело бы вовсе (вместе с CDATA §2.4 — три регрессии).
Плюс сохранённый инвариант: экран объявленной кодировки (checkXHTMLCharset — не-UTF-8
объявление остаётся ГРОМКОЙ ошибкой, как делал CharsetReader).
2.3 Побайтовое сравнение на реальных епабах: расхождений НЕТ
Метод: дампер прогоняет extractXHTML по каждому документу spine ДО правки (снят с чистого дерева
до первого редактирования) и ПОСЛЕ, и сравнивает по двум осям — сырые байты извлечения и
«проводная» ось (text.NormalizeSource → splitParagraphs, то есть ровно то, что склеивается в
ch.Text: chunker.go:318 собирает чанк из TrimSpace-абзацев через \n\n).
| Епаб | Документов | Расхождений сырых байт | Расхождений на проводе | Расхождений ruby |
|---|---|---|---|---|
| Empire of the Dawn | 126 | 0 | 0 | 0 |
| isekai_majutsushi_jp | 45 | 0 | 0 | 0 |
| fifty_shades_1_en | 34 | 0 | 0 | 0 |
| Итого | 205 | 0 | 0 | 0 |
Поимённо перечислять нечего — список расхождений пуст. Это не «примерно совпало»: совпали сырые
байты, включая 161 <hr/>, 1149 <br/> и 2200 в корпусе. Ошибок извлечения ноль в обеих
реализациях (корпус стенда чистый — классы (iii)/(iv) в нём не встречаются вовсе: <script> 0,
комментариев 0).
2.4 Улов В ХОДЕ стройки: CDATA была РЕГРЕССИЕЙ — починена
Сравнение только по корпусу дало бы ложное «всё чисто». Отдельный дифференциальный прогон по 24 конструкциям, которые старый ридер ПРИНИМАЛ (только такие и могут лежать в существующей БД), показал три расхождения, из них одно — настоящая регрессия:
*** CDATA in body: old="До.\n\nсырой <текст>\n\nПосле." new="До.\n\n]]>\n\nПосле."
*** CDATA wrapping markup: old="<p>не разметка</p>..." new="не разметка\n\n]]>..."
У HTML-токенайзера CDATA нет — он лексит секцию как «bogus comment», теряя часть содержимого и
вываливая ]]> в прозу. Добавлена распаковка unwrapCDATA до токенизации (содержимое CDATA —
литеральный текст, экранируется и отдаётся токенайзеру обратно). После неё:
SUMMARY: 23 identical on the wire | 1 DIVERGED | 0 the old reader rejected outright
Реальная запись CDATA в корпусе (/*<![CDATA[*/ внутри <style>, 2 штуки в fifty_shades) остаётся
отброшенной вместе с поддеревом — сравнение 205 документов после правки повторено, снова 0/0/0.
2.5 Оставшиеся расхождения — ДВА класса, оба про сущности
Свип по 24 конструкциям дал один вход ("A & G B"). Он оказался частным случаем более широкого
класса: отдельная сверка всех 252 имён xml.HTMLEntity против токенайзера и сверка форм без
точки с запятой (перемер третьим способом, независимо от свипа) показала ровно два класса:
(1) Legacy-сущности без точки с запятой. HTML5 разрешает &/©/  без ;;
encoding/xml оставлял их буквой.
x& y → old "x& y" | new "x& y" x  y → old "x  y" | new "x y"
x© y → old "x© y" | new "x© y" x& y → old "x& y" | new "x& y"
AT&T → old "AT&T" | new "AT&T" ← СОВПАДАЮТ R&D → совпадают
Обычная проза не задета: после & должно стоять начало известного имени, а T/D таковым не
являются. Закреплено тестом TestExtractXHTMLBareAmpersandInProseSurvives.
(2) Ровно две именованные сущности из 252 расходятся значением:
⟨ xml=U+2329 (〈) tok=U+27E8 (⟨) ⟩ xml=U+232A (〉) tok=U+27E9 (⟩)
NFC их НЕ сводит (NFC(U+2329)=U+3008, NFC(U+27E8)=U+27E8), то есть text.NormalizeSource разницу
не поглощает. Значение токенайзера — актуальное по HTML5; значение xml.HTMLEntity — устаревшая
CJK-эквивалентная форма.
Ни один из двух классов не сработал ни на одном из 205 реальных документов. Оценка риска — §6.
2.7 Улов ПОСЛЕ сборки: сырой текст — КРИТИЧЕСКАЯ регрессия, починена
Найдено адверсариальным ревью (author≠reviewer), перепроверено мной прогоном старого ридера рядом с
новым. Раз-мод токенайзера — то, что делает безобидным разметко-подобный JS (класс iii), — включается
на целом семействе тегов, а не только на script/style. Два следствия, оба реальные:
(1) Раз-текстовые теги ВНЕ skipRoots (noscript, textarea, iframe, xmp, noembed,
noframes, plaintext) отдавали содержимое одним куском с тегами внутри — литеральная разметка
уезжала в ch.Text и на провод:
noscript: old "\n\nВключите JS\n\n\n\nПроза.\n\n" new "<p>Включите JS</p>\n\nПроза.\n\n"
(2) САМОЗАКРЫТЫЙ раз-текстовый тег — потеря остатка главы. <script src="x.js"/> — обычное
написание в XHTML. Закрывающего тега не будет никогда, поэтому раз-мод шёл до конца файла:
self-closed script: old "\n\nДо.\n\n\n\nПосле.\n\n" new "\n\nДо.\n\n<p>После.</p></body></html>"
То есть весь остаток главы уходил модели сырой разметкой. Ни один из 205 документов корпуса этого не содержал — сравнение по корпусу дало бы чистое «0 расхождений» и пропустило бы дефект.
Правка: набор rawTextTags + z.NextIsNotRawText() для всех случаев, кроме единственного, где
раз-мод нужен, — непустого <script>/<style>, поддерево которого мы и так выбрасываем. После
правки все 11 форм байт-в-байт совпали со старым ридером, класс iii продолжает работать (оба свойства
проверяются одним тестом TestExtractXHTMLRawTextElementsByteParity), сравнение 205 документов
повторено — снова 0/0/0.
2.8 Второй улов ревью: несбалансированный <ruby> — КРИТИЧЕСКАЯ потеря текста, починена
Найдено вторым адверсариальным заходом, перепроверено мной прогоном старого ридера. HTML5 делает
</rt>, </rp> и </rb> необязательными — их закрывает соседний элемент или </ruby>, — а
токенайзер, в отличие от нестрогого encoding/xml, подразумеваемые закрывающие теги НЕ достраивает.
Счётчики rtDepth/rpDepth из старого кода при первом же пропуске текли навсегда:
пропущен </rt>: old "漢текст\n\n\n\n字ещё" ruby=[漢/かん, 字/じ]
new "漢текст\n\n\n\nещё" ruby=[漢/かん] ← база 字 УДАЛЕНА из прозы
То есть у КАЖДОГО последующего ruby в главе база уходила в буфер чтения вместо тела — текст пропадал
с провода. Второе следствие: незакрытый <ruby> держал rubyDepth > 0 до конца документа, поэтому
blockTags[name] && rubyDepth == 0 не срабатывал ни разу — все разрывы абзацев подавлялись, глава
схлопывалась в один слипшийся блок:
незакрытый <ruby>: old "漢\n\n\n\nВторой абзац.\n\n\n\nТретий.\n\n" (3 абзаца)
new "漢Второй абзац.Третий." (1 абзац, слова слиты)
Правка: под-элемент ruby стал РЕЖИМОМ (rubyBase|rubyRT|rubyRP), а не глубиной — режим сбрасывает
любой сосед и любой </ruby>, поэтому течь ему некуда; плюс flushRuby() на границе блока (ruby —
строчный элемент и не может пережить свой абзац). После правки обе формы совпадают со старым ридером
байт-в-байт, вложенный ruby — тоже. Побочно: при пропущенном </rp> тело совпадает со старым, а
чтение 東/とう, которое старый ридер ТЕРЯЛ, теперь захватывается — улучшение, и оно не трогает провод
(ruby питает только сид глоссария, не ch.Text).
Оба улова (§2.7 и §2.8) были невидимы для сравнения по корпусу — 205 документов давали чистый ноль, пока код содержал обе регрессии. Это главный методический вывод пачки: приёмка «побайтово на реальной книге» показывает лишь то, что в книге есть; классы, которых в ней нет, надо строить руками и сверять со СТАРОЙ реализацией.
2.6 EntryName, ссылки и mimetype
resolveHref имел две ветки, которые ни один тест не мог достать: фикстура писала запись zip ровно по
Href, поэтому ни percent-кодирование, ни #fragment не воспроизводились. Добавлено поле
Chapter.EntryName (пусто → прежнее поведение; все 14 мест конструирования используют имена полей,
позиционных литералов нет — ни один вызов не тронут). Ветка выбора «xhtml или ассет» переведена с
Href на имя записи: у href с фрагментом расширение .xhtml#part2 не совпало бы ни с чем.
Два теста: percent-href (в т.ч. реальный случай — CJK-имя файла, %E7%AC%AC%E4%B8%80%E7%AB%A0.xhtml
→ 第一章.xhtml) и ch1.xhtml#part2. Оба убивают удаление своей ветки (§4).
mimetype пишется через zip.CreateHeader(… Method: zip.Store) — первым и несжатым, как требует OCF.
Честно: ingest файл mimetype не читает вовсе (grep пуст) — это верность фикстуры формату, а не
починка поведения; покупает она только то, что фикстура остаётся настоящим епабом по мере роста
ридера. Подтверждение уместности: все три епаба стенда хранят mimetype именно так (compress_type=0,
первая запись).
§3 14б — комментарии промпт-файлов
3.1 Утечка подтверждена и закрыта
LoadPromptTemplate не вырезал <!-- -->, и 12-строчная шапка terminologist.md уезжала модели в
system каждого батча. Правка: вырезание до сплита (закомментированный ---USER--- не должен
резать файл) и SHA по канонической форме.
3.2 Почему SHA именно по очищенной форме — обосновано доком самого снапшота
snapshot.go:138 формулирует назначение PromptSHA256 прямым текстом: «без этого правка промпта
молча переиспользовала бы чекпойнты». То есть хеш отвечает на вопрос «изменился ли вход на
проводе», а не «изменился ли файл». Комментарий после правки на провод не идёт — значит канонический
хеш и есть буквальное исполнение контракта. Побочный эффект — ровно тот, что просил владелец: правка
комментария перестаёт стоить денег.
3.3 Ось замерена: двигается ровно один файл
Пересчёт SHA всех промпт-файлов старым способом (sha256 сырых байт) против нового:
prompts/zh-ru/editor-mono.md UNCHANGED prompts/zh-ru/repair/dc1_time_units.md UNCHANGED
prompts/zh-ru/editor.md UNCHANGED prompts/zh-ru/repair/latin_residue.md UNCHANGED
prompts/zh-ru/judge-selector.md UNCHANGED prompts/zh-ru/translator-banknote.md UNCHANGED
prompts/zh-ru/repair/broken_word.md UNCHANGED prompts/zh-ru/translator.md UNCHANGED
prompts/zh-ru/repair/dc1_fractional.md UNCHANGED prompts/zh-ru/terminologist.md *** MOVED ***
9 из 10 — байт-в-байт прежние. Двигается только terminologist.md (единственный файл с комментарием).
Что это стоит. SHA терминолога в снапшот НЕ фолдится (grep terminolog по snapshot.go пуст —
ни stageSnap, ни repairSnap его не видят), поэтому snapshot_id ни одной книги не меняется:
волны draft/edit резюмируются за $0. Адрес батча терминолога — RequestHash{… Messages: msgs}
(terminologist.go:395), а msgs несут отрендеренный system, поэтому батчи терминолога стендовой
пробы перекупятся один раз (центы). Ровно та ось, что заявлена в записке; проверена двумя путями.
3.4 Незакрытый комментарий — громкая ошибка
Решение не в записке, обосную. Незакрытый <!-- — битый шаблон. Молча вырезать «до конца файла»
значило бы съесть ---USER--- и выдать невнятную ошибку про отсутствующий разделитель (мутация F
показала ровно это). Отказ на ЗАГРУЗКЕ, до любого биллинга — та же идиома, что уже применяет
loadTerminologyTemplate («гейт, который не может отработать, отвергается на загрузке»).
3.5 Шапка ужата
12 строк → 2, обоснования оставлены ссылками на D39.46/47/52 и отчёты полигона (норма владельца: улики — в отчётах, не в файле).
§4 Мутации: все КРАСНЫЕ
Каждая правка восстанавливалась побайтно после прогона.
| # | Мутация | Убита тестом |
|---|---|---|
| A | убрать url.PathUnescape в resolveHref |
TestIngestEPUBPercentEncodedHref |
| B | убрать срез #fragment |
TestIngestEPUBHrefFragmentIgnored |
| C | mimetype обратно в Deflate |
TestEPUBFixtureMimetypeIsOCFConformant |
| D | SHA по СЫРОМУ файлу | TestPromptSHAIsOverTheCanonicalForm |
| E | вырезать комментарии ПОСЛЕ сплита (только system) | 4 теста, вкл. утечку в user-сообщение |
| F | незакрытый комментарий глотает молча | TestPromptUnterminatedCommentFailsLoud |
| M1 | void/self-closing не закрывать самим | TestExtractXHTMLVoidTagByteParity (hr) |
| M2 | пропуск по слепой глубине вместо имени | TestExtractXHTMLVoidTagByteParity (head без body) |
| M3 | убрать unwrapCDATA |
TestExtractXHTMLUnwrapsCDATA |
| M4 | убрать экран кодировки | TestExtractXHTMLRejectsNonUTF8Charset |
| M5 | убрать выход из <head> по <body> |
TestIngestEPUBVoidTagsInHeadDoNotSwallowChapter |
| M6 | не срезать префикс пространства имён | TestExtractXHTMLStripsNamespacePrefix |
| M7 | убрать NextIsNotRawText (раз-текст везде) |
TestExtractXHTMLRawTextElementsByteParity |
| M8 | нейтрализовать вырезание комментариев на боевых промптах | TestShippedPromptsCarryNoCommentsOnTheWire (ловит и repair/, и terminologist.md) |
| M9 | rt/rp обратно на текущие счётчики (без сброса соседом) | TestIngestEPUBRubyCaptureAndBaseInBody |
| M10 | убрать flushRuby() на границе блока |
TestExtractXHTMLUnbalancedRuby/unclosed <ruby> |
| M11 | </ruby> не сбрасывает под-режим |
TestExtractXHTMLUnbalancedRuby/nested ruby |
M1 и M2 сначала ВЫЖИЛИ — это улов самого гейта, а не формальность. M1 выжила потому, что разница
<hr> ("\n\n\n\n" против "\n\n") поглощается splitParagraphs, и ни один тест не смотрел на сырые
байты. M2 выжила потому, что правило «<body> закрывает <head>» маскирует поломку счётчика в любом
документе с <body>. Добавлены: тест байтового паритета по 10 формам (эталон снят замером со СТАРОГО
ридера) и случай «<head> без <body>», где маскировки нет. После этого обе красные.
Два теста 14б были почти вакуумны — тоже улов ревью, тоже настоящий:
- пин «комментарий не в проводе» рендерил
tpl.System, тогда как боевой путь сначала сплющивает few-shot (runner.go:315), поэтому канарейка из блока---FEWSHOT---ни во что не попадала. Теперь тест сплющивает черезSystemFor(true)и проверяет посылку — что канарейки вообще есть в исходной фикстуре; - охранник боевых промптов ходил
os.ReadDirпоprompts/zh-ru/и не виделrepair/— 6 файлов из 10. Теперьfilepath.Walk+ полlen(files) >= 10. Невакуумность доказана исполнением: внедрение комментария вrepair/latin_residue.mdпри нейтрализованном вырезании — тест КРАСНЫЙ и называет оба файла.
§5 Гейты
| Гейт | Итог |
|---|---|
go build ./... |
чисто |
go vet ./... |
чисто |
gofmt -l по моим файлам |
чисто (internal/llm/llm.go — до-паковый, в дереве не изменён, не трогал) |
go test ./... -count=1 |
14/14 пакетов зелёные |
go test ./... -count=1 -race |
14/14 пакетов зелёные |
golden (pipeline) |
зелёный в составе пакета |
| мутации | 17/17 красные (§4); три из них выжили с первого раза и потребовали новых тестов |
| «перемерь другим способом» | сравнение 205 документов по ДВУМ осям (перепрогнано после каждой правки); дифференциальный свип по 24 конструкциям; сверка ВСЕХ 252 имён xml.HTMLEntity против токенайзера; round-trip CDATA по 8 payload'ам; ось SHA пересчитана независимо от тестов |
| адверсариальная проверка (author≠reviewer) | ТРИ дефекта найдены и починены ПОСЛЕ первой сборки: §2.7 раз-текст (критический), §2.8 несбалансированный ruby (критический), §4 два почти-вакуумных теста 14б |
Замечание по гигиене: субагенты-ревьюеры, вопреки выданной им инструкции «репозиторий только на
чтение», оставили в internal/chunk/ восемь временных zz*_test.go (часть не компилировалась и
роняла сборку). Все удалены, дерево проверено git status — в диффе только мои файлы.
Временные харнессы (дампер, red-before, свип) жили в internal/chunk/ только на время прогона и
удалены; git status чист от них.
§6 Что висит на владельце: chunkerVersion НЕ поднят
Факт. chunkerVersion (render.go:52) документирован как версия «правил сегментации/ингеста
(включая… слой ingest txt/epub)», и его смысл — сделать изменение поведения ингеста громким
--resnapshot, а не молчаливой расходящейся переоплатой. Прецедент в самом доке: v3→v4 подняли в том
числе за «<br> внутри <ruby> больше не течёт переводом строки в тело» — правку уже той же породы.
Я его не поднял. Обоснование и остаточный риск:
- Ингест действительно переисполняется на каждом прогоне (
bookrun.go:112) — то есть механизм риска реален, посылка записки в этой части неверна (см. шапку). - Но
extractXHTMLдостижим только изingestEPUB, а все восемь книг стенда — txt/gb18030. Ни одна существующая БД епаб-путь не проходит, поэтому двигать нечего. - Для епаб-книг, которых ещё нет, поднятие версии не даёт ничего: они будут созданы уже на новом коде.
- Остаточная экспозиция — два класса сущностей (§2.5): книга, в чьей прозе стоит legacy-сущность без
точки с запятой (
&/©/ …) либо⟨/⟩. Такая книга — и только епаб-книга, уже имеющая БД, — при следующем прогоне перерезалась бы молча. На стенде таких книг нет (п.2). - Цена поднятия несимметрична: новый
snapshot_idменяетrequest_hashкаждого чанка, то есть полная переоплата всех книг стенда — за изменение, которое их байтов не касается. Гейт согласия на перепокупку (checkRebillConsent) остановил бы это суммой, но платить всё равно не за что.
Рекомендация: не поднимать. Если владелец предпочитает строгое правило («любая правка ингеста — громкий resnapshot, без рассуждений о достижимости») — это одна строка, и гейт согласия делает подъём безопасным. Решение денежное, поэтому оставлено подписи, а не принято сессией.
§7 Пинги оркестратору (расхождения с записями)
- «958 документов spine» в записке — в файле 126. Число ни на что в епабе не отображается; строку бэклога 14а стоит поправить, чтобы будущая сессия не искала недостающие 832 документа.
- Ось «ingest при создании книги» сформулирована неверно (переисполняется каждый прогон); вывод «существующие БД не трогаются» верен по другой причине (все книги txt). См. §6.
- Заявка «4 класса падений» подтвердилась полностью — все четыре были жёсткими ошибками, теряющими главу целиком (а с ней — импорт книги).
- CDATA не была в скоупе записки, но без неё переезд был бы регрессией на валидном XHTML (§2.4).
Считаю это частью «переносится с пинами», а не расширением скоупа; если оркестратор считает иначе —
правка изолирована в
unwrapCDATA+ один тест и снимается отдельно. mimetypeчерезzip.Storeне чинит поведение —ingestего не читает. Записано, чтобы пункт не пошёл в D-лог как исправление импорта.
§8 Чего сессия НЕ делала
- Не коммитила (по записке).
- Не трогала
docs/BACKEND_PACK19_VOICE_STATE_SESSION_PROMPT.mdиSTART_PROMT.MD— в дереве были чужие незакоммиченные правки (на момент старта; pack-19 позже стал чистым). - Не форматировала
internal/llm/llm.go(до-паковое состояние, чужая зона диффа). - Не расширяла экран кодировки на
<meta charset>: старый ридер смотрел только XML-объявление, сохранён паритет. Книга с<meta charset="gb18030">и без XML-объявления по-прежнему прочлась бы как UTF-8 — это до-паковая дыра, не регрессия; кандидат в бэклог, если владелец захочет.