Документация

К списку статей
16. Граница интеграции: что реализуем мы, что даёт заказчик

Аудитория: суперадминистратор, интегратор, технический заказчик. Закрывает вопрос: «где проходит граница между нашим продуктом и инфраструктурой заказчика при переходе с фикстуры на боевой сбор и исполнение?». Сценарий: готовим боевой пилот — нужна чёткая, проверяемая граница без переписывания ядра.

Концепция сбора — статья 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)  # тот же контракт

Гейт прогоняется штатно вместе с остальными тестами бэкенда (контракт-набор границы интеграции).


Чек-лист боевого пилота

Сбор:

  1. Реализовать адаптер источника (один класс нужной формы), смаппить поля в TelemetryRecord.
  2. Зарегистрировать адаптер в реестре под типом источника (ksc_sql / odata_1c / syslog_evtx).
  3. Прогнать контракт-гейт границы — адаптер должен пройти тот же тест формы, что фикстура.
  4. Выдать коллектору строки подключения/секреты заказчика (через окружение, не в код/логи).
  5. Запустить прогон и наблюдать его в мониторинге сборщиков; данные пойдут в факты активности.

Исполнение:

  1. Выбрать движок и реализовать provision() боевого исполнителя.
  2. Включить двумя явными флагами: PROVISIONER=… и ALLOW_REAL_PROVISIONER=1.
  3. Прогнать контракт-гейт исполнителя (та же сигнатура, что симулятор).
  4. Прогнать на тестовом АРМ; наблюдать статус задач провижининга в выдаче.

Во всех шагах ядро, приём и статусная машина не правятся — меняется только «голова» адаптера.