# Проверка членства в СРО: эксплуатация Источник `sro_membership_check`, группа/тип `sro_membership`, карточка `sro-membership-check`. Upstream — сайт `reestr-sro.ru`; внутренний справочник организаций используется для точной привязки UID и ОКПО. Другие внешние реестры загрузчик не опрашивает. ## Доступ и пределы запросов По умолчанию сбор закрыт. Для production необходимы одновременно `SRO_UPSTREAM_ACCESS_APPROVED=true` и непустой `SRO_UPSTREAM_APPROVAL_REFERENCE` — ссылка/номер документированного разрешения владельца сайта на автоматизированные query/search запросы. Согласование разработки внутри проекта не заменяет разрешение владельца upstream. Условия разрешения должны быть совместимы с настройками загрузчика; более строгие пределы задаются перед включением. Секреты в approval reference хранить нельзя. 15.09.2026 пользователь явно изменил требование: «Изменить требование и включить сбор на dev». Только для dev предусмотрен отдельный opt-in `SRO_DEV_COLLECTION_ENABLED=true`. Он разрешает сбор без изменения `SRO_UPSTREAM_ACCESS_APPROVED` и `SRO_UPSTREAM_APPROVAL_REFERENCE` и не обозначает разрешение владельца upstream. По умолчанию флаг `false`; на production он должен оставаться выключенным. Код разрешает запуск при включённом dev-флаге **или** при наличии обоих параметров согласованного доступа. Для отключения dev-исключения верните флаг в `false` и примените конфигурацию web/worker. Исходный [source-first контракт](source-integration/sources/sro-membership-check/backend-api.md) с ограничением production refresh и [документ замечаний](source-integration/source-records-backend-improvements.md) с исходным ответом `409` сохранены как происхождение требования. Настоящее изменение относится только к включению сбора на dev; ограничения запросов, привязки организаций и публикации данных сохраняются. Оба ручных entrypoint проверяют этот gate до enqueue; task и HTTP клиент повторяют проверку перед внешним IO. Закрытый gate возвращает typed `409 upstream_access_not_approved`. Если gate закрыт после постановки, job завершается с ошибкой, без успешной пустой публикации. | Настройка | По умолчанию | Значение | | --- | --- | --- | | `SRO_DEV_COLLECTION_ENABLED` | `false` | Явное включение сбора только на dev, отдельно от подтверждения разрешения upstream. | | `SRO_REQUEST_INTERVAL_SECONDS` | `3` | Минимум 3 секунды между запросами, включая redirects/retries; можно увеличить. | | `SRO_HTTP_MAX_RESPONSE_BYTES` | `2097152` | Предел распакованного тела одного ответа. | | `SRO_HTTP_TIMEOUT_SECONDS` | `30` | Read timeout; connect timeout 10 секунд. | | `SRO_MAX_REQUESTS_PER_RUN` | `10000` | Общий предел запросов одного запуска, включая redirects/retries/sitemaps. | HTTP клиент делает не более трёх попыток при транспортной ошибке и ответах 429/500/502/503/504. `Retry-After` учитывается как секунды или HTTP date, ожидание свыше 300 секунд завершает запуск ошибкой. Цепочка redirects ограничена пятью ответами; каждый адрес проверяется до обращения. Разрешён только HTTPS без credentials/нестандартного порта, на `reestr-sro.ru` и его поддоменах. Переменные окружения HTTP proxy автоматически не используются. HTML/query с идентификаторами не выводится в application logs. User-Agent — нейтральный `Mostovik SRO integration`, без утверждения о разрешении upstream. Наблюдаемый 15.09.2026 ответ lookup `404` учитывается как отсутствие членства только для `/proverka_dopuska/` с одним `q` из 10 или 13 цифр, при точном title «Проверка членства в реестре СРО, проверить допуск организации в СРО по ИНН», единственном h1 «Запрашиваемая страница на сайте отсутствует.» и отсутствии `table.sro-members`. Конечный `q` после redirects должен совпадать с запрошенным идентификатором организации; иначе запуск отклоняется с `sro_lookup_identifier_mismatch`. Другой HTML при lookup `404` отклоняется с `sro_lookup_response_unrecognized`. На остальных путях `404` сохраняет прежнее значение `sro_page_not_found`, в том числе на страницах даты допуска. Этот шаблон `404` неоднозначен: он сам по себе не доказывает отсутствие членства. Поэтому если **все** кандидаты получили такой ответ и ни один lookup не вернул успешно разобранный `200`, весь запуск отклоняется с `sro_ambiguous_empty_scan` до публикации и изменения checkpoints; прежние записи сохраняются. Смешанный запуск с корректным `200` может учитывать распознанные отрицательные ответы. Приватный raw manifest сохраняет `status_code`; artifact metadata содержит `recognized_lookup_404_count` и `successful_lookup_200_count`. Предел размера ответа действует и для lookup `404`. Дата реестра из error-шаблона не извлекается. ## Публикация и обновление Celery task: `parsers.sro_membership_check.refresh`; Python entrypoint: `apps.parsers.tasks_registry_snapshots.parse_sro_membership`, keyword args `requested_by_id=None`, `mode="incremental"|"full"`. Проверяются только организации текущего канонического справочника. Инкрементальный запуск выбирает новые организации и те, у которых изменились name/full_name, ИНН/ОГРН/ОКПО. Изменение определяется fingerprint; у модели Organization нет универсального `updated_at`. Полный запуск проверяет весь справочник. Lookup использует точный ОГРН, при его отсутствии/неподходящем формате — ИНН с проверкой нормализованного имени. Если нет пригодного идентификатора, запуск отклоняется с `sro_candidate_identifier_missing`, без удаления прежних записей. Ответ дополнительно сопоставляется по ИНН+ОГРН через внутренний индекс. Отсутствие ОКПО, конфликт идентификаторов, неоднозначная организация, неизвестный статус, неполные обязательные поля или missing/unsafe SRO URL отправляют строку в quarantine. Новые организации из внешнего ответа не создаются. Одна запись соответствует паре canonical organization UID + SRO ID. Record UID стабилен между загрузками, включая переход `active` → `excluded`. SRO ID/URL берётся из same-site ссылки или точного ID, разрешённого по same-site sitemap; по одному похожему названию URL не угадывается. Для даты допуска читается same-site страница members по ИНН. Неизвестная дата остаётся `null` с причиной `not_found`, `sro_page_unresolved` или `parse_error`. Наблюдаемая дата реестра не подменяется датой загрузки. Raw ответы сохраняются в приватном ZIP artifact с manifest URL→файл; отдельные staged rows содержат source fields, нормализованный payload и причину quarantine. Для SRO используется отдельный `FileSystemStorage`: `PARSER_PRIVATE_ARTIFACT_ROOT` по умолчанию равен `/private-parser-artifacts`, вне `MEDIA_ROOT`. Путь обязан быть абсолютным и не находиться внутри `MEDIA_ROOT`, в том числе после разрешения symlink. Ошибка конфигурации запрещает файловую операцию. Приватный storage не выдаёт публичный URL; файлы создаются с правами `0600`, каталоги `0700`. Остальные источники сохраняют прежний `default_storage`. В контейнерном окружении задайте `PARSER_PRIVATE_ARTIFACT_ROOT=/app/private-parser-artifacts` и подключите к web/worker один постоянный volume по этому пути. Каталог должен быть доступен пользователю приложения для записи и не должен монтироваться в public media, static или reverse-proxy document root. Существующая retention-команда удаляет private файлы через тот же `artifact.file.delete()`; удаление/пересоздание контейнера не должно удалять volume. Миграция `0037` меняет storage в состоянии модели без перемещения данных. До этой версии живой SRO сбор не запускался. Если оператор ранее создал SRO artifacts в старом MEDIA_ROOT вручную, их требуется отдельно перенести в private root с сохранением относительных имён и убрать публичные копии до открытия источника; автоматического fallback на public raw нет. Публикация записей, checkpoint организаций, load log и terminal job выполняется в одной транзакции после завершения сбора всех кандидатов. Transport/schema failure, нарушение целостности или отменённая job откатывает публикацию и checkpoints целиком. Незавершённый batch остаётся диагностическим и не становится current snapshot. Завершённый сбор может закончиться `success` с quarantine, как в примере истории исходного контракта. Валидные строки публикуются, включая валидные членства организации, у которой другая строка оказалась некорректной. Неизвестный статус, отсутствующий SRO ID или URL, конфликт идентификаторов не исправляются догадками: исходная строка остаётся в quarantine с причиной. У таких организаций прежние членства, не обновлённые валидной строкой этого batch, сохраняются с прежними UID, payload и load batch. Ошибка в строке не доказывает прекращение других членств. Для полностью проверенных организаций исчезнувшие членства удаляются; допустимый пустой ответ удаляет их все. При incremental неизменённые, не опрошенные организации также сохраняются. Checkpoint полностью проверенной организации обновляется. У организации с quarantine прежний checkpoint удаляется в той же транзакции, чтобы следующий incremental повторил lookup даже при неизменившемся fingerprint. Raw artifact и staged audit сохраняются. Если нет ни одной валидной строки и ни одного подтверждённого пустого lookup `200`, quarantine отклоняет запуск с `sro_incomplete_membership_scan`; смесь invalid rows и неоднозначных `404` не считается подтверждённым отсутствием. Для полностью состоящего из `404` запуска сохраняется отдельный `sro_ambiguous_empty_scan`. В обоих случаях прежние записи и checkpoints остаются без изменений. Если incremental не нашёл изменившихся организаций, batch имеет нулевую дельту и наследует source registry date/version последней успешной публикации; ссылка на неё сохраняется в `metadata.base_snapshot_artifact_id`. Данные не переписываются. После commit безопасно ставится существующая задача обновления общих export artifacts. Ошибка брокера после commit не превращает успешную публикацию в failure; готовность выгрузки отражает собственный контракт export service. ## Счётчики и диагностика `artifact.published_count`, `result.published_records_count` и `load_log.records_count` содержат размер текущего опубликованного набора источника. Для incremental это общий результат, включая неизменённые организации. `metadata.snapshot_records_count` и `snapshot_organizations_count` дают такие же общие record/distinct organization counts; `batch_published_records_count` и `updated_records_count` — число опубликованных строк текущей дельты. При full с quarantine общий count также включает сохранённые старые членства. `retained_records_count` — число старых строк организаций с quarantine, которые не были обновлены валидным upsert и сохранены от удаления; `quarantined_organizations_count` — число организаций хотя бы с одной строкой quarantine; `completed_organizations_count` — число полностью проверенных организаций без quarantine, включая допустимые пустые результаты. Эти три поля доступны в metadata API. В приватном artifact `confirmed_empty_lookup_200_count` отдельно фиксирует подтверждённые пустые `200` для проверки допустимости публикации. `parsed_count`, quarantine и candidate/queried counts относятся к текущей проверке; складывать их с общим published count нельзя. Новая дата публикации не изменяет прежнюю lineage сохранённых записей. Источник публикует весь собственный справочник; карточка/list/dashboard для трёх новых источников используют весь published scope. Coverage ОПК показывается отдельно. МСП и бюджет продолжают полную замену: новый scoped аргумент публикации по умолчанию `None` сохраняет их поведение. Для расследования используют job status/error, artifact status, `metadata.rejection_reason`, `rejection_reasons` и staged disposition/reason. Они содержат ограниченные коды ошибок, не raw HTML. Partial raw ZIP сохраняется при ошибке уже начатого сбора; предыдущая публикация остаётся доступна. Приватные raw artifacts удерживаются минимум 90 дней и минимум 10 последних успешных SRO batches; неуспешные batches не вытесняют эти десять успешных. ## Расписания и граница проверки Миграция `parsers.0036_sro_disabled_schedules` создаёт **выключенные** schedules: daily incremental в 04:00 Europe/Moscow; monthly full 16-го числа в 04:00 Europe/Moscow — после наблюдаемого обновления реестра 15-го. Перед включением нужно подтвердить фактическую периодичность/условия разрешения. Расписание само не открывает gate; оба механизма по умолчанию выключены. На 14.09.2026 query/search и живой SRO snapshot не запускались. Основная страница без query доступна через read-only web; свежий robots.txt получить не удалось. Указанные в source-first спецификации `Crawl-delay: 3` и query/search disallow учтены консервативно. DOM selector/mapping проверены синтетическими fixtures формы из `docs/source-integration/sources/sro-membership-check/backend-api.md`, а не полным live scan. После разрешения владельца обязательна ограниченная проверка фактической разметки перед первой полной загрузкой.