Аудитория: суперадминистратор, интегратор, технический заказчик. Закрывает вопрос: «где проходит граница между нашим продуктом и инфраструктурой заказчика при переходе с фикстуры на боевой сбор и исполнение?». Сценарий: готовим боевой пилот — нужна чёткая, проверяемая граница без переписывания ядра.
Концепция сбора — статья 13; честный статус слоя сбора — статья 15; механизм коннекторов — статья 04. Здесь — граница ответственности и контракт совместимости, на котором держится переход.
Весь тракт (планировщик → приём → обезличивание → распознавание → хранение → журнал прогонов) построен и отлажен на безопасной заглушке, у которой интерфейс ровно тот же, что у боевого источника. Переход «фикстура → бой» = реализовать ОДИН интерфейс и подменить запись реестра. Ядро, приём, статусная машина и дашборды при этом не меняются.
Два контура с одной и той же границей:
| Контур | Что делает | Контракт (боевая сигнатура) | Точка подмены |
|---|---|---|---|
| Сбор телеметрии | KSC SQL / 1С OData / syslog-evtx → факты активности | Collector: source_type + collect(tenant, since) → Iterable[TelemetryRecord] | реестр коннекторов по типу источника |
| Исполнение установки | тихая установка/отзыв ПО на АРМ | ProvisioningConnector: provision(sys_id, user_id) → (status, log) | реестр исполнителей + env PROVISIONER |
| Учётная запись | блокировка SSO при увольнении | IdentityConnector: disable_sso(user_id) → (status, log) | дефолтный симулятор за сигнатурой |
Реализуем мы (один адаптер на источник/исполнитель):
- класс с нужной формой (см. контракт выше), маппинг полей источника в
TelemetryRecord/
результат provision;
- регистрация адаптера в реестре по типу источника/исполнителя.
Первые боевые адаптеры уже реализованы (D4/D5) — не только сигнатура:
- `SyslogFileCollector` (
collectors.py) — разбирает реальный syslog RFC 5424 с локального
спул-файла (SYSLOG_SPOOL_PATH), агрегирует события в дневные факты. Сетевого egress нет (источник пишет журнал на диск, мы читаем) → гардрейл «ноль внешних» не нарушен. Включается при заданном пути, иначе фикстура.
- `LocalScriptConnector` (
connectors.py) — боевой агент на АРМ: локальная установка через
subprocess (PROVISION_CMD, плейсхолдеры {sys_id}/{user}; {user} — opaque hex, не ПДн). Ноль сетевого egress. Под двойным opt-in (PROVISIONER=local + ALLOW_REAL_PROVISIONER=1).
- Это egress-free половина боевого контура. Сетевой пуш (KSC SQL / 1С OData / удалённый
WinRM/SSH) требует исходящего коннекта → остаётся за границей заказчика (ниже).
Даёт заказчик (боевая конфигурация, не код):
- строки подключения и доступы (KSC SQL / 1С OData / коллектор журналов);
- для исполнения — парк АРМ, домен/политики, выбранный движок (WinRM/SSH/Ansible) и его доступы;
- сетевой доступ из контура коллектора к источникам.
Сырьё (табельный номер, имена) обезличивается на приёме Стрибогом — боевой адаптер сырьё не хранит (подробнее — статья 02). Адаптер отдаёт записи, opaque id ставит ядро.
Совместимость адаптера — проверяемое свойство, а не обещание: контракт-гейт признаёт любой класс правильной формы и «кусается» на неправильной. И фикстура, и будущий боевой адаптер проходят ОДИН тест формы.
▸Контракт сбора: что именно проверяется
# Боевая сигнатура источника (runtime_checkable):
class Collector(Protocol):
source_type: str
def collect(self, tenant_id: str, since: str | None = None) -> Iterable[TelemetryRecord]: ...
# TelemetryRecord — поля, которые читает приём:
# tabel_no, sys_code, activity_date, tx_count, session_minutes, raw_product(опц.)
# Контракт-гейт (суть): любой класс нужной формы совместим, неправильный — нет.
assert isinstance(FixtureFileCollector("ksc_sql"), Collector) # фикстура
assert isinstance(FakeRealCollector(), Collector) # боевой адаптер — тот же контракт
assert not isinstance(ClassBezCollect(), Collector) # гейт кусается▸Контракт исполнения: opt-in боевого исполнителя
class ProvisioningConnector(Protocol):
def provision(self, sys_id: int, user_id: str) -> tuple[str, str]: ...
# Дефолт — симулятор (гардрейл «ноль внешних обращений»).
# Боевой исполнитель включается ДВУМЯ явными шагами, иначе старт падает громко:
# PROVISIONER=winrm|ssh|ansible и ALLOW_REAL_PROVISIONER=1
# Без opt-in боевой класс поднимает NotImplementedError, а не ходит наружу молча.
assert isinstance(SimulatedConnector(), ProvisioningConnector)
assert isinstance(FakeRealProvisioner(), ProvisioningConnector) # тот же контрактГейт прогоняется штатно вместе с остальными тестами бэкенда (контракт-набор границы интеграции).
Сбор:
- Реализовать адаптер источника (один класс нужной формы), смаппить поля в
TelemetryRecord. - Зарегистрировать адаптер в реестре под типом источника (
ksc_sql/odata_1c/syslog_evtx). - Прогнать контракт-гейт границы — адаптер должен пройти тот же тест формы, что фикстура.
- Выдать коллектору строки подключения/секреты заказчика (через окружение, не в код/логи).
- Запустить прогон и наблюдать его в мониторинге сборщиков; данные пойдут в факты активности.
Исполнение:
- Выбрать движок и реализовать
provision()боевого исполнителя. - Включить двумя явными флагами:
PROVISIONER=…иALLOW_REAL_PROVISIONER=1. - Прогнать контракт-гейт исполнителя (та же сигнатура, что симулятор).
- Прогнать на тестовом АРМ; наблюдать статус задач провижининга в выдаче.
Во всех шагах ядро, приём и статусная машина не правятся — меняется только «голова» адаптера.