openspec/changes/slice-0-skeleton/open-questions-proposal.md — носитель
15 предложенных вопросов, правок статусов OQ-46/OQ-47 и кандидатов в DBML
v5.14 для docs-PR (открывает владелец ../docs); архивируется вместе с
change. Добавлены пункты финального ревью: аудит LOGIN/LOGIN_FAILED,
CORS * и доверенные прокси, открытые /docs и /openapi.json, статус
stored_file из inbox, срок ответа tabular, append-only на уровне БД,
последовательный callback NATS и длинный маппер, проверка ключа
results/{file_id}/{job_id}/ до чтения результата.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
20 KiB
20 KiB
Назначение: предложения для
docs/context/07-open-questions.mdи DBML v5.14; docs-PR открывает владелец../docs. Дата: 2026-10-04 (срез 0, веткаfeat/1-slice-0-skeleton; файл архивируется вместе с changeslice-0-skeleton, рулинг R-26). НомераOQ-??присваиваются там, в реестре07-open-questions.md(подряд с OQ-48), а не здесь.
Предложения для ../docs/context/07-open-questions.md (Срез 0, arm-core) — для PR в docs (R-8)
Формат строк — как в таблице 07-open-questions.md: | ID | Вопрос | Откуда | Влияет на | Статус / допущение |.
Занятые номера: OQ-01…OQ-47 (последний — OQ-47). Новые строки ниже помечены OQ-?? (предл. N); при переносе —
нумеровать подряд с OQ-48 в порядке предложений. Ничего в ../docs не правилось.
A. Правки существующих строк
| ID | Вопрос | Откуда | Влияет на | Статус / допущение |
|---|---|---|---|---|
| OQ-46 (правка статуса) | Обязательны ли все ОСВ расширенного подхода (02, 03, 08, 44, 26, 91, 51, 52, 59, 80, 83, 86, 97, 001, 008, 009, 011) при approach = EXTENDED, или часть — «при наличии»? |
ТЗ 5.1; required_document_rule.is_mandatory |
срез 0 (seed), срез 3 (чек-лист) | допущение принято и применено: все 27 строк EXTENDED в seed (api/seed/core/required_document_rules.yaml, маркер ASSUMPTION(OQ-46)) — is_mandatory = true; смена — правка одного yaml; BASIC — 10 строк, все обязательные |
| OQ-47 (дополнение) | Источник полного плана счетов 1С для seed chart_of_account: в фикстурах файла нет, «план счетов из бандла макета» не передан. Дополнение: названия нестандартных субсчетов (66/67 .06/.22/.24/.26, 76.07.x, 76.08/.28/.38) в seed выведены по их роли в §4 правил, а не взяты из справочника 1С |
06-test-data.md §6, fixtures/README.md; api/seed/core/chart_of_accounts.csv |
срез 0 (seed), срез 1 (справочник счетов) | допущение: План счетов 94н (01–99, 001–011) + типовые субсчета 1С БП 3.0, упомянутые в §4/§6 (122 строки: 62 синтетических 94н, 11 забалансовых, 49 субсчетов/подсубсчетов); названия-заглушки заменить по справочнику 1С клиента; остальное добавляет администратор (ARCH-004) |
B. Новые вопросы
| ID | Вопрос | Откуда | Влияет на | Статус / допущение |
|---|---|---|---|---|
| OQ-?? (предл. 1) | Правило элиминации № 1 (§4), сторона Б «76.07.1 − 76.07.5 − 76.07.9»: 76.07.5 и 76.07.9 не потомки 76.07.1, поэтому «вычитание» чисто информативно. Так и задумано? | 05-domain-rules.md §4 (правило 1); seed elimination_rules_v1.yaml (T14.1) |
срез 5 (пары ВГО) | допущение: исключения 76.07.5/76.07.9 на стороне Б хранятся как is_excluded и не влияют на сумму (76.07.1 их не содержит); алгоритм на них не опирается; подтвердить с методологами |
| OQ-?? (предл. 2) | Правило № 1, сторона А: резервный счёт 01 лежит в одном списке с 03 и 76.07.9 — приоритета нет. Как выбирать «03, а если у А нет ОСВ 03 — 01»? Что значит «результат в строку ППА» (см. OQ-11)? | §4 (правило 1: «01 — резервный»); seed (T14.2) | срез 5–6 | допущение: в seed accounts: ["03", "76.07.9", "01"] без порядка; fallback «берём 01, если нет ОСВ 03» — в comment правила; логику приоритета реализует срез 5, до решения — не реализуется |
| OQ-?? (предл. 3) | Составные коды статей КФО 1450/1550 (правило 1), 1310/1350 (правило 7, гудвилл) хранятся в elimination_rule.article_2_line одной строкой с «/». Как их потреблять: делить по «/» и подбирать ту строку, что есть в ББ Б, или хранить две строки? |
§4 (правила 1, 7); seed (T14.3) | срез 5–6; DBML (note колонки) | допущение: хранится как в §4, потребитель делит по «/»; предложение — зафиксировать это в note article_2_line в DBML v5.14 |
| OQ-?? (предл. 4) | required_document_rule: уникальный индекс (approach, document_type, account_id) при nullable account_id допускает дубли строк для не-ОСВ документов (NULL в PostgreSQL различны). Добавить NULLS NOT DISTINCT? |
DBML v5.13 (строка Indexes { (approach, document_type, account_id) [unique] }); ORM следует DBML буквально (T8.3) |
DBML v5.14, срез 3 (чек-лист) | допущение: ORM/миграция 1:1 с DBML (без NULLS NOT DISTINCT); предложение v5.14: [unique, note: 'NULLS NOT DISTINCT'], как у document |
| OQ-?? (предл. 5) | Контракт v1.0, recognition-result-message: schema_version принимает только 1.x (^1\.\d+$), а result_schema_version — любой MAJOR (^\d+\.\d+$). Намеренно (файл результата версионируется отдельно) или небрежность? |
docs/contracts/recognition-result-message.schema.json (T20.7) |
контракт v1.x, срез 2 (inbox/маппинг) | допущение: ядро повторяет схему буквально (любой MAJOR проходит валидацию); проверку совместимости MAJOR результата делает маппинг Среза 2; уточнить у владельца контракта |
| OQ-?? (предл. 6) | Контракт v1.0: схемы нигде не задают additionalProperties: false (кроме columns ОСВ). Считать ли неизвестные поля допустимыми расширениями? |
docs/contracts/*.schema.json (T20) |
контракт v1.x, все три сервиса | допущение: модели ядра extra = "ignore" (строже схемы быть нельзя — иначе ядро отвергнет совместимого пира); изменение — через MINOR контракта |
| OQ-?? (предл. 7) | Параметры JWT РАИСА неизвестны: JWKS URL / публичный ключ, iss, aud, имя claim с email. Что в продуктивном контуре? Сделать проверку aud/iss обязательной в проде? |
ТЗ 8.1, 03-architecture.md (SSO РАИСА); core/auth.py (T16.2) |
срез 1+ (прод-конфигурация) | допущение: настраиваются AUTH_JWKS_URL/AUTH_PUBLIC_KEY (взаимоисключающие), AUTH_ISSUER, AUTH_AUDIENCE, AUTH_EMAIL_CLAIM (по умолчанию email); при пустых AUTH_AUDIENCE/AUTH_ISSUER aud/iss не проверяются; предложение: в проде сделать aud обязательным и предупреждать при старте, если пусто |
| OQ-?? (предл. 8) | В enum audit_action нет действия для смены статуса задания распознавания (recognition_job). Добавлять ли (DBML v5.14) и нужен ли аудит обмена вообще? |
DBML v5.13 audit_action; решение R-7 (design #20) |
DBML v5.14, срез 2 (обмен) | допущение: Срез 0 не пишет audit_event из обмена recognition_job; сервис аудита (services/audit.py) готов и покрыт тестами; если нужно — действие в v5.14 и вызовы в recognition_exchange.py |
| OQ-?? (предл. 9) | Append-only аудит обеспечен только на уровне кода (единственная точка записи, статический тест на UPDATE/DELETE по AuditEvent). Нужна ли защита на уровне БД (триггер или REVOKE UPDATE, DELETE для роли приложения)? |
инвариант «Append-only аудит» (../CLAUDE.md); services/audit.py (задача 21) |
DBML/миграции, платформа (права БД) | допущение: только код + тесты; решение по триггеру/правам — с командой платформы до прода |
| OQ-?? (предл. 10) | Счётчик повторных публикаций запроса (ACK_TIMEOUT_SECONDS × OUTBOX_MAX_REPUBLISH) хранится в recognition_job.metrics.republish_count (jsonb), колонки в DBML нет. Нужна ли колонка (v5.14)? |
services/recognition_exchange.py (design #6, T23) |
DBML v5.14, срез replay | допущение: в metrics.republish_count; attempt не растёт при повторе; при будущем replay счётчик и reply_deadline надо сбрасывать (T23.1) |
| OQ-?? (предл. 11) | Семантика сроков и ошибок обмена: (а) reply_deadline по умолчанию = now + 5 мин для обоих pipeline (tabular, image), если у задания NULL; (б) при невозможности собрать запрос по контракту задание уходит в error / INVALID_REQUEST; (в) при ACK_TIMEOUT × (MAX_REPUBLISH+1) ≥ 5 мин статус dlq недостижим (раньше сработает timeout). Верно ли? |
DBML reply_deadline note («OCR — 5 мин»); recognition_exchange.py (T23.4) |
срез 2–3, настройка таймаутов | допущение: как описано (дефолты 30 с × 3 укладываются в 5 мин); сроки по pipeline уточнить с командами парсера и шлюза |
| OQ-?? (предл. 12) | Email пользователя сопоставляется с app_user.email по lower(email); при двух записях, различающихся регистром, — 403 (fail-closed). Добавить CHECK/уникальный индекс по lower(email) в DBML? |
core/auth.py, api/dependencies/auth.py (R-17, design #29) |
DBML v5.14, срез 1 (админ-экран) | допущение: уникальность email — регистрозависимая (как в DBML); регистронезависимая уникальность — на v5.14 |
| OQ-?? (предл. 13) | Seed --dev не защищён флагом DEBUG: создаёт dev-пользователей (в т.ч. ADMIN) на любой БД, на которую указывает .env, и перезаписывает существующего app_user с совпадающим email. Нужна ли защита? |
scripts/seed.py (T15.2, T15.4) |
срез 0 (процесс), прод-развёртывание | допущение: спека не требует защиты, --dev помечен «только для разработки»; предложение — отказ при DEBUG=false или явный флаг подтверждения |
| OQ-?? (предл. 14) | Неверный метод на существующий API-маршрут (например POST /api/v1/me) отдаёт 404 вместо 405: SPA-монтирование на / перехватывает частичное совпадение, а /api/* в нём принудительно 404. Приемлемо? |
app.py (SPAStaticFiles, T18.2) |
API-контракт (клиенты, фронт) | допущение: оставлено как есть (честный 404 для несуществующих путей важнее 405); при необходимости — отдельная правка |
| OQ-?? (предл. 15) | Решение по web/package-lock.json и уязвимостям: шаблон lock не содержит, raisa-* из Nexus — lock вне банка отличался бы; в Срезе 0 lock в .gitignore (R-23, design #35). npm install сообщает 6 high (npm audit) в зависимостях шаблона web. Коммитить ли lock; кто обновляет зависимости? |
web/ (T25.1, task-26-report.md C1) |
платформа/фронт, до прода | допущение: lock не в репозитории, зависимости не менялись; решение — с платформенной командой (Nexus, политика audit) |
B2. Новые вопросы из финального ревью ветки (2026-10-04)
| ID | Вопрос | Откуда | Влияет на | Статус / допущение |
|---|---|---|---|---|
| OQ-?? (предл. 16) | Аудит LOGIN / LOGIN_FAILED (действия есть в audit_action) для JWT-аутентификации: в каком срезе и что считать «входом» — первый запрос с токеном, каждый запрос, отказ проверки токена? |
DBML audit_action; core/auth.py, api/dependencies/auth.py (финальное ревью) |
срез 1 (вход, админ-экран), аудит | допущение: в Срезе 0 вход не аудируется; сервис аудита готов |
| OQ-?? (предл. 17) | Шаблонные CORS allow_origins=["*"] + allow_credentials=True и ProxyHeadersMiddleware(trusted_hosts=["*"]): в проде любой клиент может подставить X-Forwarded-For, и audit_event.ip_address недостоверен. Какие origins и доверенные прокси на платформе? |
app.py (шаблон команды); финальное ревью |
прод-конфигурация, достоверность аудита | допущение: оставлено как в шаблоне; перечни origins и доверенных прокси — с платформенной командой до прода |
| OQ-?? (предл. 18) | /docs и /openapi.json FastAPI открыты без аутентификации, в том числе в проде. Закрывать их (выключать при DEBUG=false) или оставить? |
app.py (шаблон); финальное ревью |
прод-конфигурация, безопасность | допущение: открыты, как в шаблоне; решение — с платформой/ИБ |
| OQ-?? (предл. 19) | Inbox не обновляет stored_file.status (UPLOADED → PROCESSING → PROCESSED / ERROR) и stored_file.current_recognition_job_id по результату задания. Кто и когда это делает в Срезе 2 — inbox или маппер, в той же транзакции? |
DBML stored_file, file_status; recognition_exchange.py (финальное ревью) |
срез 2 (загрузка и маппинг) | допущение: в Срезе 0 файл остаётся UPLOADED; решение — при проектировании маппера Среза 2 |
| OQ-?? (предл. 11, дополнение) | Срок ответа по умолчанию 5 мин для tabular взят по аналогии с OCR: в DBML значение есть только для OCR («OCR — 5 мин»), для парсера — нет. Какой срок у tabular-parser? |
DBML recognition_job.reply_deadline note; DEFAULT_REPLY_TIMEOUT в recognition_exchange.py (финальное ревью) |
срез 2, настройка таймаутов | допущение: 5 мин для обоих pipeline (см. предл. 11) |
| OQ-?? (предл. 9, дополнение) | Append-only аудита на уровне БД — финальное ревью повторно подняло вопрос; см. предл. 9 (триггер или REVOKE UPDATE, DELETE для роли приложения) |
инвариант «Append-only аудит»; финальное ревью | миграции, права БД | допущение: как в предл. 9 — только код + тесты |
| OQ-?? (предл. 20) | Обработчик recognition.result выполняется последовательно в callback подписки NATS; длинный маппер Среза 2 задержит следующие сообщения, ack сервиса не успеет за ACK_TIMEOUT_SECONDS (30 с) — ложный republish, затем dlq. Предложение: развести быстрый inbox (только фиксация сообщения и статуса) и отдельный цикл маппинга результата |
core/nats.py (subscribe_results), recognition_exchange.py (handle_message, ResultMapper); финальное ревью |
срез 2 (маппинг), архитектура обмена (D-6) | допущение: в Срезе 0 NoopMapper — проблема не проявляется; решение — до реализации маппера Среза 2 |
| OQ-?? (предл. 21) | Перед чтением файла результата маппер должен проверять, что ключ начинается с results/{file_id}/{job_id}/, а бакет — бакет роли results (иначе сервис может «подсунуть» чужой объект). Закрепить в контракте и 03-architecture.md? |
контракт recognition-result-message (result_s3); _register_result_object в recognition_exchange.py; финальное ревью |
срез 2 (маппинг), контракт v1.x | допущение: в Срезе 0 результат только регистрируется в s3_object, не читается; проверку реализует маппер Среза 2 |
C. Кандидаты в DBML v5.14 (сводка из вопросов выше)
required_document_rule: уникальный индекс(approach, document_type, account_id)—NULLS NOT DISTINCT(предл. 4).audit_action: действие для смены статусаrecognition_job(предл. 8).recognition_job: колонка счётчика повторных публикаций вместоmetrics.republish_count(предл. 10).app_user: уникальность поlower(email)(предл. 12).elimination_rule.article_2_line: note о составных кодах «1450/1550», «1310/1350» (предл. 3).- OQ-45 (уже в реестре):
NUвosv_data_type— Срез 0 не затрагивает.
D. Заметки для спек/контрактов (не OQ)
contracts/recognition-result-message.schema.json:result_schema_versionvsschema_version(предл. 5), нетadditionalProperties: false(предл. 6).03-architecture.md/ контракт: ключ файла результата — только подresults/{file_id}/{job_id}/в бакете ролиresults, проверка до чтения (предл. 21).docs/dev/(compose): тома общие для всех worktree, имя проектаarm-ka.