arm-core/openspec/changes/slice-0-skeleton/open-questions-proposal.md
Andrey Epifancev db3c1e5141 docs: предложения открытых вопросов в change slice-0-skeleton (I-5, R-26)
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>
2026-10-04 02:01:41 +04:00

65 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

> Назначение: предложения для `docs/context/07-open-questions.md` и DBML v5.14; docs-PR открывает владелец `../docs`.
> Дата: 2026-10-04 (срез 0, ветка `feat/1-slice-0-skeleton`; файл архивируется вместе с change `slice-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_version` vs `schema_version` (предл. 5), нет `additionalProperties: false` (предл. 6).
- `03-architecture.md` / контракт: ключ файла результата — только под `results/{file_id}/{job_id}/` в бакете роли `results`, проверка до чтения (предл. 21).
- `docs/dev/` (compose): тома общие для всех worktree, имя проекта `arm-ka`.