From 68b50a563f7627fb22c0aa946df11604d9d89cb4 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Mon, 5 Oct 2026 17:06:40 +0400 Subject: [PATCH] =?UTF-8?q?docs:=20R-25=20=E2=80=94=20=D1=81=D0=BB=D0=B5?= =?UTF-8?q?=D0=BF=D0=B0=D1=8F=20=D0=B7=D0=BE=D0=BD=D0=B0=20R-21=20(=D1=81?= =?UTF-8?q?=D0=B2=D1=91=D1=80=D0=BD=D1=83=D1=82=D0=BE=D0=B3=D0=BE=20=D0=BF?= =?UTF-8?q?=D1=80=D1=8F=D0=BC=D0=BE=D0=B3=D0=BE=20=D1=80=D0=BE=D0=B4=D0=B8?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D1=8F=20=D0=B4=D0=BE=D1=81=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=BE),=20=D0=BB=D0=B8=D0=BC=D0=B8=D1=82?= =?UTF-8?q?=D1=8B=20=D1=87=D1=82=D0=B5=D0=BD=D0=B8=D1=8F,=20=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D1=82=D0=BE=D1=87=D0=BD=D1=8B=D0=B5=20=D1=80=D0=B8?= =?UTF-8?q?=D1=81=D0=BA=D0=B8=20OQ-108?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 05-domain-rules §1.4: слепая зона R-21 переформулирована — достаточно свёрнутого непосредственного родителя (N2); §1 «Лимиты»: expat-скан всех XML-частей, после-чтения счёт только индексируемых диапазонов, что не ограничено. 07-open-questions OQ-108: остаточные риски (объём ячеек, sharedStrings/стили, entity expansion) и рекомендации (лимит памяти контейнера, предпроверка dimension/счётчик ячеек, подпроцесс с RLIMIT). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2 --- context/05-domain-rules.md | 12 +++++++----- context/07-open-questions.md | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/context/05-domain-rules.md b/context/05-domain-rules.md index ceabe58..e3a8431 100644 --- a/context/05-domain-rules.md +++ b/context/05-domain-rules.md @@ -147,8 +147,9 @@ r11 Договоры ← договорами под свёрнутым контрагентом не ловится; - RR-4 — тождество строки сверяет только нетто строки, «Итого = корень» — только корень: ошибка, сохраняющая нетто строки, ловится лишь сверкой с развёрнутым родителем по колонкам; - - слепая зона: обе стороны сальдо развёрнутого ребёнка увеличены на одно X, а все предки до корня свёрнуты — не - ловится ничем; + - слепая зона: обе стороны сальдо развёрнутого ребёнка увеличены на одно X, а его непосредственный родитель свёрнут — + не ловится ничем: достаточно свёрнутого прямого родителя (сверка с ним — по нетто, нетто ребёнка не изменилось, + значения родителя и выше не затронуты); - родитель (0, 0) при детях (X Дт, X Кт) принимается (нетто 0) — осознанное поведение, закреплено тестом; - вне R-21: текст вида «12.ООО» принимается за счёт (§1.3, R-16). - Строка «Итого» (TOTAL) == корневая строка ACCOUNT (первая строка уровня 0). Если корня нет (**пустая ОСВ**: заголовок и @@ -208,9 +209,10 @@ result-osv-needs-check.json`. Лимиты: `PARSE_MAX_BYTES` (по умолчанию 50 МиБ) и `PARSE_TIMEOUT_SECONDS` (120 с; кооперативный — проверка между листами, в циклах по листу и между стратегиями; чтение книги библиотекой он не прерывает). Поэтому до библиотеки чтения действуют -лимиты чтения (R-23) → `UNSUPPORTED_FORMAT`: распакованный zip (xlsx/xlsb) ≤ 10 × `PARSE_MAX_BYTES`, суммарная площадь -объединённых ячеек книги ≤ 200 000 (реальный максимум 10 090). Защиты от XML-бомб (`defusedxml`) нет — **[открыто]** -OQ-108. Любой реальный файл разбирается быстрее 5 с (максимум ≈ 0,6 с). +лимиты чтения (R-23, R-25) → `UNSUPPORTED_FORMAT`: распакованный zip (xlsx/xlsb) ≤ 10 × `PARSE_MAX_BYTES`, суммарная +площадь объединённых ячеек книги ≤ 200 000 (реальный максимум 10 090; xlsx — потоковый expat-скан всех XML-частей до +openpyxl, после чтения — только диапазоны, которые индекс листа раскладывает по ячейкам). Объём самих ячеек, +`sharedStrings`/стили и XML-бомбы (`defusedxml`) не ограничены — **[открыто]** OQ-108. Любой реальный файл разбирается быстрее 5 с (максимум ≈ 0,6 с). Словари: `osv_headers.yaml` (признаки листов, группы сумм, «Дебет»/«Кредит», колонка ИНН, «Итого», метки шапки), `osv_groups.yaml` (группировка → `group_type`), `periods.yaml` (период → квартал и нарастающий итог), `data_types.yaml` diff --git a/context/07-open-questions.md b/context/07-open-questions.md index 41b0927..b56091a 100644 --- a/context/07-open-questions.md +++ b/context/07-open-questions.md @@ -113,7 +113,7 @@ | OQ-105 | Сверка «сумма детей = родитель» по нетто (R-21): у родителя сальдо свёрнуто (счёт — одна сторона), у детей — развёрнуто. Подтвердить аналитиками, что нетто-свёртка родителя — штатное поведение 1С для **всех** версий выгрузки, а не артефакт двух организаций; остаточные риски RR-1…RR-4 и слепые зоны (перестановка сторон и одинаковое увеличение обеих сторон сальдо ребёнка под свёрнутыми предками не ловятся ничем) перечислены в §1.4 | tabular-parser: R-21 (design №31), `05-domain-rules.md` §1.4 | срез 2a (контрольные суммы), срез 2c | допущение принято: нетто допустимо, если у родителя не более одной ненулевой стороны сальдо; 32 из 66 реальных файлов иначе не сходились | | OQ-106 | «Итого = корень» сверяется с **первой** корневой строкой ACCOUNT (уровень 0). Лист с несколькими корнями уровня 0 (несколько счетов в одной ОСВ; ОСВ по субконто без строк-счетов) получает `needs_check`, хотя «Итого» может быть равна Σ корней. Сверять «Итого» с суммой корней? | tabular-parser: финальное ревью Среза 2a, M-4 | срез 2a+ (контрольные суммы `checks.py`), чек-лист документов | допущение принято: сверка с первым корнем; многокорневой лист → `needs_check` (аналитик проверяет вручную); в фикстурах таких листов нет | | OQ-107 | При `hierarchy=sums` (дерево восстановлено по равенству «сумма детей = родитель») сверка «сумма детей = родитель» тавтологична — она не может не сойтись. Считать ли такой разбор `needs_check` (или отдельное предупреждение достаточно)? | tabular-parser: финальное ревью Среза 2a, M-5 | срез 2a+ (статус результата), срез 2c (UI проверки) | допущение принято: статус — по контрольным суммам, стратегия видна в `metrics.warnings` (`hierarchy=sums`); на реальных файлах стратегия не выбирается (все — `outline`/`trivial`) | -| OQ-108 | Защита фазы чтения xlsx от XML-бомб (entity expansion) — `defusedxml` как новая зависимость сервиса — и иные лимиты памяти чтения. Решение платформы | tabular-parser: финальное ревью Среза 2a, I-1 (хвост), R-23 | tabular-parser (readers), платформа (зависимости образа) | допущение принято: действуют лимиты R-23 — распакованный zip ≤ 10 × `PARSE_MAX_BYTES`, площадь объединённых ячеек книги ≤ 200 000; XML-бомбы не отсекаются (README «Известные ограничения») | +| OQ-108 | Защита фазы чтения xlsx от XML-бомб (entity expansion) — `defusedxml` как новая зависимость сервиса — и иные лимиты памяти чтения. Остаточные риски после R-23/R-25: (1) бомба по объёму самих ячеек в пределах лимита распакованного zip — xlsx в 5 МБ с 2 млн обычных ячеек читается openpyxl 13 с и занимает 1,5 ГБ (замер ревьюера); (2) объём `sharedStrings` и стилей в тех же пределах; (3) entity expansion (от экспоненты «billion laughs» защищает только сам expat ≥ 2.4). Рекомендация: жёсткий лимит памяти контейнера сервиса; и/или предпроверка объявленного `` и потоковый счётчик ячеек ``/строк `sharedStrings` (тем же expat-сканом, что объединения) с лимитом; и/или разбор книги в подпроцессе с `RLIMIT_AS`/таймаутом, который можно убить. Решение платформы | tabular-parser: финальное ревью Среза 2a, I-1 (хвост), R-23, R-25 | tabular-parser (readers), платформа (зависимости и лимиты образа/пода) | допущение принято: действуют лимиты R-23/R-25 — распакованный zip ≤ 10 × `PARSE_MAX_BYTES`, площадь объединённых ячеек книги ≤ 200 000 (потоковый expat-скан всех XML-частей до openpyxl); объём ячеек, `sharedStrings`/стили и entity expansion не ограничены (README «Известные ограничения») | | OQ-109 | Воркер tabular-parser скачивает объект S3 целиком и только потом проверяет `PARSE_MAX_BYTES`; проверять размер по `stat` до скачивания | tabular-parser: финальное ревью Среза 2a, M-7 | tabular-parser (`core/worker.py`) — вне Среза 2a | открыт: правка воркера — follow-up; лимит размера сейчас срабатывает после скачивания (память под файл выделяется) | Служебные идентификаторы в DBML/аналитике: `ANL-nnn` — замечания аналитика к макету; `ARCH-nnn` — архитектурные решения;