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

IncidentRelay — Change Intelligence

Версия v2.5.0 · Обновлено

IncidentRelay — Change Intelligence#

Архитектурный RFC v1.2 — реализованная первая итерация и план второй

Исходная кодовая база: release/2.5.0, d1dc817 (сверено 2026-11-07)

Статус: первая итерация функционально завершена. Вторая итерация согласована в составе шести этапов 2.1–2.6 (§15), реализация запланирована. Выпускные проверки относятся к конкретной сборке (§12.10).

Согласованные решения — 28–30 октября 2026#

  • Отдельная история AlertGroupEpisode начинается с новых групп и настоящих resolved → active переходов. Миграции и обычные обновления старых групп не создают эпизоды; прошлые циклы не восстанавливаются. Публичный приём Change Events включается после приёмки полного пути от события до связи с алертом.
  • Постоянная интеграция, зарегистрированные source/Team/Service grants и сменяемые машинные токены.
  • Отзыв токена запрещает новые запросы; принятая работа использует текущие grants интеграции.
  • Source выбирает timestamp или sequence. Timestamp — occurred_at отправителя, никогда received_at.
  • Побеждает последнее финальное событие в выбранном порядке; разные итоги с равным временем дают явный конфликт до более позднего финального события.
  • Автоматика предлагает только возможные причины; mitigation в v1 ручная.
  • Автокорреляция включается явно на Service, по умолчанию выключена. Исключения по source/labels применяются к отдельным алертам; группа участвует, если в выбранном эпизоде есть хотя бы один неисключённый алерт. Эпизоды сохраняются для всех групп; отбор выполняется до постановки поисковой работы (§9.1, этап 7).

Реализованы приём Change Events, очередь, Changes Inbox, автоматические предложения причин, ручной review, связи Incident, Related Changes и Service Timeline. Два переключателя и 12 числовых параметров хранятся в системных настройках БД и действуют без перезапуска, включая интервал планировщика. На новой установке приём выключен, обработка очереди включена. Глобальный администратор управляет ими в разделе «Администрирование → Системные настройки». Подробности приведены в конфигурации.

1. Назначение и границы#

Change Intelligence — самостоятельная подсистема, которая принимает унифицированные события изменений инфраструктуры и приложений, хранит историю операций, отображает их на странице Operations → Changes и находит объяснимые потенциальные связи с техническими AlertGroups и операционными Incidents.

GitLab, Jenkins, Terraform, Kubernetes, Ansible, CMDB и пользовательские скрипты отправляют один и тот же стандартизированный JSON на один HTTP endpoint. Ядро не обязано содержать адаптер для каждого продукта: преобразование специфического payload выполняется отправителем или необязательным внешним адаптером.

CI/CD-система может не знать внутренних ID IncidentRelay. Она вправе использовать стабильные slug команд и сервисов; backend разрешает их в числовые ID существующей инсталляции. Сервисы и их зависимости могут поступать из CMDB независимо от Change Events.

Вне v1: ITIL Change Requests, approval workflow, автоматический rollback, автоматическое создание AlertGroup/Incident из Change, AI Root Cause Analysis, реализация CMDB-синхронизатора и обязательная Plugin Platform. Change — не AlertGroup и не Incident. Успешный deployment тоже может стать причиной проблемы; статус изменения и причинная связь независимы.

2. Архитектурные инварианты#

  • Один Change принадлежит ответственной Team (owner_team_id в БД), но непосредственно изменённые и затронутые сервисы могут принадлежать любым Teams, в том числе из других Groups.
  • В публичном API ответственная Team задаётся ровно одним полем: owner_team_id либо owner_team_slug. Slug разрешается в существующую Team перед проверкой полномочий.
  • Каждая ссылка на технический Service передаётся через service_id либо service_slug. Для slug используется Team namespace: team_id или team_slug. Если namespace опущен, берётся owner Team, а не выполняется глобальный поиск.
  • В актуальной модели Team.slug уникален глобально, а Service.slug уникален только внутри Team (UNIQUE(team, slug)). Поэтому один service_slug без Team допустим только как сокращение для owner Team.
  • После успешного разрешения ссылки канонические связи, корреляция и Service Timeline работают с service_id. Исходная ссылка по slug сохраняется отдельно для истории и диагностики; переименование slug не должно перепривязывать старое изменение к другому сервису.
  • Неизвестный slug сервиса в известной Team может быть сохранён как pending_resolution до синхронизации CMDB. Пока он не разрешён и не авторизован, корреляция и cross-team публикация по нему запрещены. Неизвестный service_id не ставится в ожидание.
  • group_id не передаётся в payload и не обязателен в модели Change. Существующий Group RBAC продолжает работать внутри IR через Team/Service.
  • Одна операция — один Change и множество неизменяемых ChangeEvent. Повторы идентичных событий идемпотентны; события могут приходить не по порядку.
  • Разделяются changed (непосредственное изменение), reported_affected (заявленное отправителем влияние) и inferred_affected (вывод по Dependencies). Автоматический вывод не становится подтверждённым фактом.
  • Отправитель не может получить доступ к чужой Team или создать доверенное влияние на её сервис одной лишь подстановкой slug либо ID.
  • Корреляция асинхронна; её сбой не отменяет сохранение ChangeEvent. Автоматический пересчёт не стирает ручное подтверждение или отклонение связи.
  • Change Events не меняют статусы Service/AlertGroup/Incident, не запускают уведомления, эскалацию, создание Incident или rollback.
  • Существующие ServiceEvent, ServiceDependency, Incident, AlertGroup, токены и RBAC используются повторно без нарушения действующих API.
  • Корреляция и ручной review привязаны к постоянному AlertGroupEpisode, а не только к повторно используемой AlertGroup.
  • Pending-ссылка закрепляет числовой namespace_team_id; переименование Team и ротация токена не меняют её владельца.
  • Worker публикует только с актуальными lease, requested generation и ревизиями входов; ручные решения отделены от вычисляемых полей.

3. Схема компонентов#

GitLab / Jenkins / Terraform / Kubernetes / Ansible / Custom
                         |
                         v
             POST /api/change-events
      Auth -> Pydantic -> registered source/team grants
                         |
                         v
              Service Identity Resolver
       service_id OR (team namespace + service_slug)
                         |
                         v
                atomic DB transaction
        Change + ChangeEvent + ChangeServiceReference
           + ChangeService (if already resolved)
           + durable outbox/job record
                         |
                    HTTP 201/200
                         |
                         v
                   Scheduler worker
                /                   \
               v                     v
    Pending reference resolver    Impact / correlation
           |                        |
           |                        v
           |            ChangeImpact / ChangeAlertGroupCorrelation
           |                        |
           +-------> resolved ------+
           ^                        |
           |                        v
    CMDB creates/updates      Changes Inbox / Timeline /
      Service/Dependency      AlertGroup / Incident UI

HTTP 201 означает событие сохранено, а не «все slug разрешены» или «корреляция завершена». Разрешение ожидающих ссылок и корреляция выполняются повторяемыми фоновыми заданиями.

4. Модель данных#

4.1. Change — каноническая операция#

  • id, uid (UUID), integration_id, source_id (FK ChangeSource);
  • owner_team_id, external_change_id (change_id API); имя отправителя хранится в ChangeSource.source; владелец и идентичность source неизменяемы после первого Change;
  • kind: deployment, configuration, infrastructure, database, feature_flag, other;
  • kind, environment — неизменяемая идентичность операции; для другой операции/повторного запуска нужен новый change_id;
  • ограниченные title, description, external_url;
  • status: unknown, running, succeeded, failed, rolled_back, cancelled;
  • lifecycle_conflict и ограниченные диагностические коды; при конфликте финальных исходов status=unknown;
  • mapping_status: mapped, partially_mapped, unmapped, mapping_failed;
  • ordering_mode (timestamp / sequence), скопированный из зарегистрированного source при создании и неизменяемый для этого Change;
  • started_at, completed_at, first_occurred_at, last_occurred_at, created_at, updated_at;
  • revision, увеличиваемая при новом принятом событии или изменении проекции сопоставления/полномочий; точный повтор и чтение её не меняют.

Уникальность: (source_id, external_change_id). Source однозначно связывает (owner_team_id, source) с одной интеграцией (§4.10), сохраняя публичное пространство имён. Payload не может переносить операцию другому владельцу или интеграции.

Change — восстанавливаемая проекция. Порядок событий, конфликты итогов и обновление полей определены в §8. Успешное выполнение не означает отсутствия вредного влияния.

4.2. ChangeEvent — неизменяемая история#

  • id, uid, change_id, source_id, external_event_id (event_id API), event_type;
  • occurred_at (время события у отправителя), received_at (время приёма IR), source_sequence по §8;
  • ограниченные actor_type, actor_label, details, а также явный snapshot присланных полей проекции с различением отсутствующего поля и null;
  • нормализованные полученные ссылки на сервисы и ограниченный диагностический snapshot запроса с удалёнными секретами;
  • request_digest, integration_id, nullable ingest_token_id для аудита использованного секрета, schema_version.

Уникальные ключи: (source_id, external_event_id) и (change_id, source_sequence) для непустого sequence. Event ID относится ровно к одной операции. Использование его с другим change_id даёт 409, даже если остальные поля совпали. При retention нужно сохранять компактную запись идентичности/digest на документированный срок допустимых повторов, чтобы старое событие не создавало новую работу.

Для идемпотентности хешируется валидированное присланное содержимое, включая change_id, без токена, received_at и последующих результатов сопоставления. Используются канонический порядок JSON-ключей, UTC-время с микросекундной точностью, нормализация сокращённых полей и отсортированные множества ссылок без дублей. ID и slug остаются разными представлениями. Избыточная точность времени отклоняется, а не округляется молча. Digest вычисляется до удаления секретов; исходные секреты и вторая полная неочищенная копия payload не сохраняются.

Точный авторизованный повтор возвращает прежнее событие (200), не увеличивает revision и не создаёт задания; другой digest даёт 409. Даже для повтора проверяются текущие права на владельца/source. Устаревший неразрешимый owner slug даёт ошибку идентичности, а не обход авторизации через глобальный поиск event ID. История сохраняет канонические ID; отправителям, которым нужны повторы независимо от переименований, следует изначально использовать ID владельца.

Типы: change.started, change.updated, change.succeeded, change.failed, change.rolled_back, change.cancelled. Последующие изменения полномочий или сопоставления не меняют события и исходные ссылки.

4.3. ChangeServiceReference — полученные идентификаторы и отложенное разрешение#

  • change_id, first_event_id, relation_type (changed / reported_affected);
  • ref_type (id / slug), исходные requested_service_id / requested_service_slug и requested_team_id / requested_team_slug;
  • namespace_team_id (обязательный FK Team), разрешённый и авторизованный при приёме, даже если Service пока неизвестен;
  • normalized_ref_key: id:12 или slug:team-id:9/postgresql-prod; namespace определяется каноническим ID Team, а не изменяемым slug;
  • nullable resolved_service_id, resolution_status: resolved, pending_resolution, denied, invalid, expired;
  • verification_state: trusted, unverified, rejected; evidence/версия проверки, resolved_at, last_attempt_at, attempts, expires_at, last_error_code.

Уникальность: (change_id, relation_type, normalized_ref_key). Разные полученные формы ссылки сохраняются, но сходятся в одной канонической связи ChangeService. Уже разрешённая ссылка автоматически не перепривязывается.

Поиск pending выполняется строго по (namespace_team_id, requested_service_slug) при активной Team и неудалённом Service. Переименование/повторное использование Team slug не меняет ID; удаление или перенос namespace не запускает поиск другой Team. Перенос Service из закреплённого namespace требует явной проверки, а не перепривязки. Переименование ожидаемого Service slug требует нового события/ссылки; угадывания alias в v1 нет. Истечение срока/отказ видны владельцу; после исправления grants возможен явный повтор с аудитом и новой генерацией задания.

Создание Team/Service/Dependency остаётся вне подсистемы. Ограниченное повторное сопоставление описано в §6, одинаковые ответы для неавторизованных ссылок — в §7.

4.4. ChangeService — каноническая связь#

  • change_id, service_id (FK на технический Service);
  • relation_type: changed или reported_affected;
  • first_reference_id, verification_state, evidence проверки и input_stamp, created_at, updated_at;
  • опциональный snapshot исходных идентификаторов для диагностики смены slug.

Уникальность: (change_id, service_id, relation_type). Владелец Service не обязан совпадать с owner_team_id Change. Все алгоритмы влияния и корреляции опираются на эту таблицу, не на текст из payload.

4.5. ChangeImpact — вычисляемая проекция#

Поля: change_id, service_id, origin_service_id, ограниченный dependency_path_snapshot, depth, reason, computed_at, engine_version, input_stamp, active. Уникальность: (change_id, origin_service_id, service_id); несколько путей — ограниченный evidence, а не дубли строк влияния.

Обратный обход начинается с авторизованных канонических changed-сервисов и доверенных reported-affected сервисов. Доверие через граф должно опираться на авторизованный changed-сервис; присланные утверждения не подтверждают друг друга. Сохраняются snapshot использованного пути и ревизия графа; более поздний граф — новое evidence, а не доказательство существования этой топологии во время события. Текущие проекции заменяемы, но snapshots ручных решений сохраняются. Ревизии и правила обхода определены в §4.11 и §9.

4.6. ChangeAlertGroupCorrelation#

  • change_id, episode_id (FK AlertGroupEpisode; родитель эпизода определяет AlertGroup);
  • score (релевантность, не вероятность), reason, ограниченный evidence, input_stamp, engine_version, computed_at, active;
  • review_status: suggested, confirmed, rejected;
  • relation_type: potential_cause, confirmed_cause, mitigation, context;
  • reviewed_by, reviewed_at, неизменяемый snapshot evidence ручного решения, row_version, updated_at.

Уникальность: (change_id, episode_id). Автоматика обновляет только вычисляемые поля и предлагает potential_cause; поля ручного решения не меняет. Review использует row_version, проверяет право реагировать на AlertGroup и читать хотя бы безопасную проекцию Change, атомарно пишет аудит. Устаревший review получает 409. Изменение evidence может сделать предложение неактивным, но не удалить решение и не перенести его в другой эпизод. mitigation и context в v1 создаются вручную, в том числе без автоматического предложения.

  • change_id, incident_id, relation_type: context, potential_cause, confirmed_cause, mitigation;
  • linked_by, linked_at, removed_by, removed_at, row_version.

Одна активная связь на (change_id, incident_id); удаление мягкое, повторное добавление восстанавливает связь с записью истории в аудит. POST требует права реагировать на Incident и читать контекст Change; изменение роли/удаление проверяет row_version. Используется действующий контракт транзакции/CAS Incident: событие Incident, связь и аудит записываются атомарно. Повторное добавление той же роли — 200, новая/восстановленная связь — 201, устаревшее или конфликтующее изменение — 409. Статус и уведомления Incident не меняются; AlertGroup не обязателен.

4.8. ChangeJob — durable outbox / очередь работы#

  • job_type, entity_id, state (pending, running, done, dead);
  • requested_generation, claimed_generation, completed_generation;
  • target_revision, graph_revision, auth_revision, engine_version (набор input_stamp);
  • lease_owner, новый при каждом захвате lease_token, lease_expires_at;
  • attempts, next_attempt_at, last_error_class, ограниченный discovery cursor, created_at, updated_at.

Уникальность: (job_type, entity_id). Повторное планирование атомарно увеличивает requested_generation, а не создаёт неограниченные дубли. У выполняющегося задания lease сохраняется, новая генерация остаётся ожидающей до завершения/перехвата. Новую работу запрашивает изменение ревизии, а не точный повтор ingestion. Типы: resolve_references, refresh_impacts, refresh_correlations, sync_service_timeline, а также ограниченные discovery-задания для нового эпизода или ревизии каталога.

Протокол фиксации:

  • Ingestion записывает ChangeEvent, ссылки, ревизию агрегата и запрос задания одной транзакцией. Изменения эпизода/каталога сохраняют необходимое discovery-задание в своей транзакции с учётом отбора §9.1; discovery надёжно создаёт задания затронутых Changes до подтверждения собственного завершения.
  • Claim атомарно выбирает готовую/просроченную работу, выдаёт новый lease token и фиксирует генерацию. PostgreSQL использует короткую транзакцию/row lock (SKIP LOCKED при конкуренции workers), SQLite — BEGIN IMMEDIATE с ограниченным повтором при busy. Вычисления выполняются вне write-транзакции.
  • Input stamp читается до загрузки зависимостей/сопоставлений. Изменения графа и прав увеличивают свои ревизии в транзакции изменения. Нельзя пометить старый snapshot новой ревизией, прочитанной после вычисления.
  • Публикация — одна короткая транзакция: проверить тот же непросроченный lease token, неизменность requested generation и актуальность input stamp, затем записать проекции/timeline и завершить генерацию. Сравниваемые строки ревизий блокируются/перепроверяются до commit; проверки только в памяти приложения недостаточно.
  • При несовпадении результат отбрасывается. Потерявший lease worker не может публиковать, подтверждать завершение, продлевать lease нового владельца или менять его retry-состояние. Ещё владеющий заданием worker планирует актуальную генерацию; recovery/discovery обеспечивает сохранность работы. Истечение lease не разрешает перезаписывать чужой результат.
  • Timeline использует уникальный (service_id, source='change_intelligence', dedup_key) из UUID события и роли связи. Проекции и подтверждение задания записываются одной DB-транзакцией: сбой не оставляет неподтверждённого неидемпотентного эффекта. Внешние уведомления не отправляются.
  • Повторяются только временные ошибки, с ограниченным экспоненциальным backoff/jitter. Ошибки валидации/прав остаются видимыми у ссылок, исчерпанные задания — в dead; ручной повтор аудируется и создаёт новую генерацию. Метрики показывают backlog, возраст старейшей работы и класс ошибки без payload/секретов.

Единый порядок блокировок: строка состояния графа → строка интеграции/прав → Changes по ID → эпизоды по ID при публикации корреляций → задания. Claim/reclaim блокирует только задания. Действующий lifecycle сохраняет порядок Incident → AlertGroup → эпизод; эти транзакции лишь добавляют discovery и не берут graph/Change locks. Discovery записывает задания Changes до блокировки строки собственного подтверждения, исключая обратный порядок job → Change. Сетевых вызовов и обхода графа под этими блокировками нет.

4.9. AlertGroupEpisode — постоянная история технических циклов#

Хранение добавлено на этапе 1: id, uid, alert_group_id, ordinal, nullable started_at, nullable ended_at, state (open, resolved, merged), start_quality (exact, unknown), nullable start_event_id, end_event_id, created_at. Уникальность (alert_group_id, ordinal) и не более одного открытого эпизода группы (частичное уникальное ограничение PostgreSQL/SQLite). Добавляется nullable AlertGroup.current_episode_id; у отслеживаемой активной группы он указывает на её открытый эпизод; у старой группы без отслеживаемого цикла остаётся NULL до настоящего reopen. Этап 1 оставляет указатели существующих групп NULL. ID эпизода не меняется при acknowledge, shelving и повторном intake.

  • Создание новой активной группы создаёт первый эпизод атомарно с первым дочерним Alert и событием lifecycle. Настоящее повторное использование resolved → active создаёт следующий эпизод и нового ребёнка в той же транзакции. Повтор reopen не создаёт второй эпизод или второго ребёнка.
  • Acknowledged → firing, unshelve, изменение severity/assignee остаются в прежнем эпизоде. Reopen операционного Incident сам по себе не открывает технический эпизод. Для группы, созданной сразу из resolved-входа, открытый эпизод не выдумывается.
  • Resolve/merge закрывает текущий эпизод в транзакции закрытия группы, current_episode_id очищается. Повторный resolve уже закрытой группы — no-op и не может закрывать ещё не зафиксированный новый эпизод. Merge закрывает эпизод исходной группы, не превращая его в эпизод целевой.
  • Контракт обязателен для всех технических записывающих путей: intake/recovery, ручных действий/callbacks, heartbeat, orchestration, merge и удаления ресурсов. Ошибка БД откатывает группу, ребёнка, эпизод, lifecycle event и запрошенное discovery-задание вместе. Действующее поведение AlertGroup/Incident API и уведомлений сохраняется.
  • Миграции создают только хранение: существующие группы не получают эпизоды и discovery-задания. Инициализация старых активных групп на этапе 2 удалена. Обычный intake, новый ребёнок внутри ещё активной группы, acknowledge, пересчёт, silence/maintenance и shelve/unshelve не создают отсутствующий эпизод. Resolve/recovery/удаление ресурса закрывают только уже существующий эпизод; при NULL ничего не создают. Первое настоящее resolved → active после перехода writers создаёт первый отслеживаемый эпизод с известным временем начала и ordinal=1, если история эпизодов отсутствует. Merge в старую активную группу не начинает цикл; merge, действительно возобновивший resolved-группу, начинает новый. Прошлые циклы и первое срабатывание старой группы не восстанавливаются, новые эпизоды с неизвестным началом не создаются. Уже сохранённые эпизоды сохраняются; автоматическое удаление данных не выполняется.
  • Evidence ручного review и история, связанная с Incident, защищают соответствующие Changes/events/эпизоды от retention. Очистка сначала удаляет только допустимые непроверенные производные строки, затем родителей; каскад не уничтожает сохраняемые доказательства расследования. Новые FK и правила retention входят в миграцию эпизодов.

Эпизоды сохраняются независимо от настройки автокорреляции Service и исключений алертов. Отбор §9.1 управляет поисковой работой и автоматическими предложениями, не созданием/закрытием эпизода. Он не сокращает число эпизодов и не отменяет правила retention.

4.10. Постоянная интеграция, sources и grants#

Модели хранения добавлены на этапе 1; администрирование и машинная авторизация реализованы на этапе 3:

  • ChangeIntegration: id, uid, имя, active, auth_revision, даты аудита. Машинный принципал не зависит от персональной учётной записи и отдельного секрета.
  • ChangeSource: id, integration_id, owner_team_id, source, active, стандартный ordering_mode. Уникальность (owner_team_id, source) закрепляет namespace за одним отправителем. Отзыв grant/токена не освобождает namespace другой интеграции. Имя source и owner Team можно редактировать до первого Change. После первого Change изменение этих полей возвращает 409; для другого namespace создаётся новый источник. Проверка и запись сериализуются с приёмом через блокировку интеграции. В UI поля заблокированы с пояснением причины; статус и порядок новых Changes остаются редактируемыми. Изменение режима порядка действует только на новые Changes.
  • ChangeGrant: source_id, capability (reference, changed, reported_affected), target_team_id, опциональный target_service_id, нормализованный target_key (team:9 или service:12), active. Уникальность (source_id, capability, target_key) не зависит от особенностей NULL в разных БД. reference разрешает непроверенное утверждение и поиск идентичности в своём scope, но не доверенную публикацию; остальные capabilities также разрешают поиск своей цели. Grant на Service действует только пока тот принадлежит закреплённой Team. Grant на Team явно охватывает будущие Services этой Team и допускает pending-ссылки своей capability; grant только на Service — нет.
  • ApiToken расширяется nullable change_integration_id. Токены интеграции имеют только changes:ingest, не получают персональные полномочия user/group/team, не создают токены и не обращаются к другим API. Переиспользуются генерация секрета, хранение хеша, срок действия и отзыв; второе хранилище секретов не вводится. Действующее поведение персональных токенов сохраняется.

Публичное создание интеграции требует owner_team_id: в одной транзакции создаются интеграция и один Source с именем на основе имени интеграции, уникальным внутри Team. При совпадении добавляется суффикс. Режим по умолчанию — timestamp; grants и токены автоматически не выдаются. Группа и Team должны соответствовать текущим правам. Старые записи не дополняются автоматически. В списке интеграций показываются source_count и permission_count по всем настроенным записям, включая отключённые.

Администратор может явно редактировать capability, source и цель разрешения внутри интеграции. Цель повторно проверяется по текущим правам; конфликт уникальности даёт 409 без частичного изменения. Запись сохраняет ID, предыдущие и новые настройки записываются в аудит, auth_revision и invalidation меняются той же транзакцией. Перемещение Service в каталоге по-прежнему не переносит его grant автоматически. Действия источников, разрешений и токенов доступны через единое меню с тремя точками и иконками.

Активный зарегистрированный source разрешает интеграции присылать операции для этого владельца/source, но не заявлять любые изменения всех его сервисов. Доверенные утверждения задаются явными ChangeGrants, в том числе для сервисов owner Team. Интеграциями, sources, grants и машинными токенами управляют глобальные администраторы и активные user_admin группы-владельца; ingest-токен не выдаёт себе права. Каждое изменение аудируется. Изменения grants/активности source и влияющего на полномочия состояния Team/Service транзакционно увеличивают auth revision затронутых интеграций.

Ротация/отзыв токена запрещает новые запросы с этим секретом; принятая работа принадлежит интеграции и продолжается по её текущим grants. Отключение интеграции/source или отзыв нужного grant блокирует новое сопоставление/публикацию. Удаление/ротация секрета не удаляет неизменяемые события. Права чтения отделены от ingestion в §7.

4.11. Ревизия графа и входы проекций#

Добавляется ChangeGraphState с одной монотонной строкой revision для v1. Создание/изменение/удаление Dependency и существенные изменения Service (владелец, environment, enabled/deleted, политика автокорреляции §9.1) увеличивают её и ставят ограниченную invalidation/discovery-работу той же транзакцией. Глобальная ревизия выбрана как простое решение; ревизии отдельных компонент — возможная будущая оптимизация.

Поскольку stamp глобальный, каждая ревизия графа запускает постраничный обход всех Changes в автоматическом горизонте и всех running Changes, а не только предположительно затронутых: иначе несвязанные строки навсегда останутся устаревшими. Обход объединяется в одно discovery-задание по ID состояния графа с requested generation и durable cursor. Фиксируется верхняя граница Change ID, обход идёт по ID; каждый ограниченный batch атомарно ставит задания Changes и сдвигает cursor с проверкой lease/generation. Новая ревизия перезапускает генерацию/cursor; приём нового Change имеет собственное задание. Обход прав охватывает все ещё публикуемые Changes затронутой интеграции. Повторы отдельных ссылок остаются индексируемыми по namespace/slug.

Полный набор входов вычисления: (Change.revision, ChangeGraphState.revision, ChangeIntegration.auth_revision, engine_version) и requested generation задания. Сопоставление/верификация увеличивает Change.revision; новые эпизоды имеют собственный durable discovery-триггер. Новый запрос, пришедший во время вычисления, не теряется при подтверждении прежней генерации. Ручной review использует собственную row_version и неизменяемый snapshot evidence; автопересчёт эти поля не записывает.

Для корреляции конкретного эпизода input stamp дополнительно включает ревизию его контекста отбора (§9.1). На этапе 7 изменения состава/source/labels/Service эпизода увеличивают эту ревизию в транзакции изменения; закрытие фиксирует контекст для исторического поиска. Worker перепроверяет её при публикации. Этап 7 сохраняет этот контекст и проверяет его ревизию; этапы 1–2 его не восстанавливали.

5. Универсальный контракт API#

POST /api/change-events
Authorization: Bearer <changes:ingest token>
Content-Type: application/json

5.1. Рекомендуемый payload — только slugs, без внутренних ID IncidentRelay#

{
  "schema_version": 1,
  "event_id": "postgres-config-1842-applied",
  "change_id": "postgres-config-1842",
  "source": "terraform-production",
  "owner_team_slug": "infrastructure",
  "kind": "configuration",
  "event_type": "change.succeeded",
  "occurred_at": "2026-10-10T14:00:00Z",
  "title": "PostgreSQL connection settings updated",
  "environment": "production",
  "changed_services": [
    {"service_slug": "postgresql-prod"}
  ],
  "affected_services": [
    {"team_slug": "devops", "service_slug": "payments-api"},
    {"team_slug": "payments", "service_slug": "checkout-api"}
  ],
  "actor": {"type": "automation", "name": "terraform-ci"},
  "external_url": "https://ci.example/jobs/1842",
  "details": {"parameter": "max_connections", "commit": "abc123"}
}

changed_services[0].service_slug разрешается только внутри owner_team_slug=infrastructure. Cross-team затронутые сервисы указываются с явным team_slug. Pipeline не знает ни одного числового ID IR.

5.2. Альтернатива — числовые ID и смешанные ссылки#

{
  "schema_version": 1,
  "event_id": "postgres-config-1843-applied",
  "change_id": "postgres-config-1843",
  "source": "terraform-production",
  "owner_team_id": 2,
  "kind": "configuration",
  "event_type": "change.succeeded",
  "occurred_at": "2026-10-10T15:00:00Z",
  "title": "PostgreSQL settings updated",
  "changed_services": [
    {"service_id": 12}
  ],
  "affected_services": [
    {"service_id": 34},
    {"team_id": 9, "service_slug": "checkout-api"}
  ]
}

changed_services и affected_services — предпочтительные поля, принимающие список ссылок двух видов. Для простых числовых клиентов допускаются сокращения changed_service_ids: [12] и affected_service_ids: [34,56]. Нельзя одновременно указывать сокращённое и развёрнутое поле одной роли в одном событии — 422.

5.3. Правила разрешения#

ВходРазрешение
owner_team_idTeam по числовому ID
owner_team_slugTeam по уникальному Team.slug
{ "service_id": 12 }Service по ID; если дополнительно передан Team namespace, проверяется совпадение владельца
{ "service_slug": "postgresql-prod" }Service по (owner_team_id, Service.slug)
{ "team_slug": "devops", "service_slug": "payments-api" }Сначала Team по slug, затем Service по (team_id, slug)
{ "team_id": 9, "service_slug": "checkout-api" }Service по (9, "checkout-api")
  • В одном payload задаётся ровно одно из owner_team_id / owner_team_slug. В ссылке на сервис — ровно одно из service_id / service_slug. В Team namespace — не более одного из team_id / team_slug.
  • Slug валидации должен соответствовать существующему API Service (строчные символы, цифры и дефис); Team slug проверяется по действующей модели Team. Глобального поиска одного service_slug по всем командам нет.
  • Если service_id найден, но переданный Team namespace противоречит его владельцу, запрос отклоняется. Если slug указывает на удалённый/недоступный ресурс, доступ и публикация проверяются так же строго, как при ID.
  • owner_team_* должен однозначно разрешаться уже при ingestion: иначе невозможно безопасно проверить владельца Change и токен. Несуществующая owner Team — ошибка; она не создаётся автоматически.
  • Неизвестный service_id — ошибка валидации ссылки. Неизвестный service_slug в существующей Team — допустимое pending_resolution при выполнении проверок области полномочий. Неизвестная team_slug в ссылке на сервис — ошибка, а не повод угадать Team по глобальному Service slug.
  • Для changed нужен grant changed; для каждой ссылки reported_affected требуется хотя бы право reference в её области. До подтверждения через граф или явный grant reported-affected утверждение остаётся непроверенным (§7.2). Само принятие pending-ссылки не предоставляет полномочий на сервис.
  • При успешном сопоставлении сохраняем service_id; используем его в ChangeService, ChangeImpact и корреляции. Полученную slug-ссылку сохраняем в ChangeServiceReference.
  • Удаление, перенос или переименование Service не перепривязывает исторически разрешённый Change к новому Service с тем же slug. Повторное использование slug должно контролироваться CMDB; при переименовании отправитель исправляет будущие ссылки; автоматический alias mapping не входит в v1 и не меняет историю молча.
  • event_id повторно используется только с тем же нормализованным содержимым запроса. Изменение представления с ID на slug под тем же event_id может дать 409, даже если обе ссылки ведут на один Service. Для новой информации нужен новый event_id.

Ответ 201 включает безопасные счётчики ссылок и correlation_status=queued при включённом engine, иначе not_enabled. Счётчики охватывают только доступные интеграции категории; непроверенные чужие утверждения не раскрывают существование сервисов. 200 возвращается при точном повторе; 400/422 — некорректная схема/неразрешимая идентичность, 401 — нет авторизации, 403 — запрещённая область, 409 — конфликт внешнего ID, 429 — rate limit.

5.4. Read/review API#

GET    /api/changes
GET    /api/changes/{id}
GET    /api/changes/{id}/events
GET    /api/changes/{id}/services
GET    /api/changes/{id}/correlations
GET    /api/changes/{id}/correlation-targets
GET    /api/changes/{id}/incidents
POST   /api/changes/{id}/correlations
POST   /api/changes/{id}/correlations/{correlation_id}/review
GET    /api/services/{service_id}/changes
GET    /api/alert-groups/{alert_group_id}/related-changes
GET    /api/incidents/{incident_id}/changes
POST   /api/incidents/{incident_id}/changes
PATCH  /api/incidents/{incident_id}/changes/{link_id}
DELETE /api/incidents/{incident_id}/changes/{link_id}

Чтение Changes поддерживает фильтр service_id либо service_slug с Team namespace, используя тот же resolver. Для каждого endpoint проверяются RBAC и явные scopes §7, SQL-фильтр применяется до пагинации. Ручное создание корреляции передаёт episode_id и relation_type; review/смена роли/удаление используют row_version. Выбор Change для вложенной связи возвращает только разрешённую полную или безопасную проекцию, без чужих исходных данных.

6. Интеграция с CMDB и позднее разрешение Service#

CMDB независимо создаёт Team, Service и ServiceDependency через действующие API каталога; Change Intelligence ничего из этого не создаёт. Новый продуктовый CMDB-протокол не требуется. Source/grant должен разрешать namespace ещё до принятия ссылки.

6.1. Service уже существует#

Разрешаются owner Team и зарегистрированный source, закрепляется target namespace_team_id, затем Service ищется по ID или (namespace_team_id, service_slug). Сохраняются канонический ID и исходная ссылка. Grant changed разрешает прямое утверждение; reported с reference-only остаётся непроверенным до явного grant или проверенного пути зависимостей. ID и slug подчиняются одним полномочиям.

6.2. ChangeEvent пришёл раньше Service#

  • Владелец/source и target namespace известны и разрешены, Service slug отсутствует. Событие/ссылка сохраняются с resolution_status=pending_resolution, закреплённым namespace_team_id, first_event_id и сроком ожидания; владелец видит pending в Inbox.
  • Каноническая связь с Service, корреляция и чужая публикация пока не создаются. Поиск идентичности никогда не создаёт ресурс.
  • Создание/изменение каталога ставит индексируемую работу разрешения своей транзакцией; периодический обход готовых pending-ссылок — ограниченный fallback. Полного сканирования истории Change нет.
  • Поиск только по (namespace_team_id, requested_service_slug) с текущими интеграцией/source, grants и состоянием ресурсов. Переименование Team безопасно; повторно искать Team по исходному slug запрещено. Сопоставление/evidence, увеличение Change.revision и downstream-задания записываются атомарно.
  • Ротация/отзыв токена сами по себе не останавливают принятое. Нет текущих grants — denied, namespace недействителен/удалён — invalid, срок истёк — expired. Перехода к новой Team/Service со старым slug нет. Доступны диагностика владельцу и аудируемый разрешённый повтор.
  • После позднего сопоставления ограниченный discovery учитывает и подходящие существующие эпизоды. Более старая история за автоматическим горизонтом доступна для ручных связей. При выключенной фоновой обработке ответ приёма содержит correlation_status=not_enabled; после её включения принятые задания продолжают выполняться.

TTL pending, число попыток и размер batch хранятся в системных настройках БД. Явный повтор не меняет присланную ссылку; исправленный Service slug присылается новым событием/ссылкой. Отозванное основание, expiry и dead jobs остаются видимыми в диагностике.

6.3. Изменения каталога/grants и историческое evidence#

Изменение графа увеличивает ChangeGraphState.revision, изменение полномочий — auth_revision интеграции. Владелец/environment/active/deleted Service участвуют в нужных ревизиях той же транзакцией. Change — восстанавливаемая проекция событий: правка каталога не переписывает принятые события и не переназначает их канонические Service ID. Она инвалидирует соответствующие производные mapping/trust/impact и надёжно запрашивает ограниченный пересчёт.

Сохраняются snapshot пути и ревизия расчёта. Текущий граф может предложить связь со старым событием, но не доказывает существование этой топологии в прошлом; ограничение показывается в evidence. Ручной review сохраняет исходный snapshot даже при исчезновении текущего вычисленного основания. Автоматического переноса на alias или новый Service нет. External_service_ref/Service.uid как входной контракт — отдельная будущая работа.

7. Authentication, токены и RBAC#

7.1. Машинный приём и администрирование#

Интеграция имеет неизменяемый owner_group_id: NULL означает центральную интеграцию, доступную только глобальному администратору. Новые интеграции группы доступны её активным администраторам (user_admin); источники и grants ограничены командами и сервисами этой группы, в том числе после перемещения команды. Роль editor не даёт доступа. Проверяются страница, API и сервисные операции, а изменение членства сериализуется с записью. Выбор активной группы в UI не расширяет права. Миграция сохраняет существующие интеграции центральными без автоматической передачи полномочий. На входе показана таблица; меню действий открывает модальный редактор. Секрет очищается при закрытии, навигации и смене раздела редактора.

POST /api/change-events требует активный привязанный к интеграции ApiToken со scope changes:ingest, даже при выключенной глобальной обязательности API auth. JWT, персонального токена или wildcard без идентичности ChangeIntegration недостаточно. После аутентификации разрешается owner Team и проверяется принадлежность активного (owner_team_id, source) этой интеграции, затем grants. Payload не выбирает другого принципала.

Машинные токены выдаются/ротируются в новом admin-only UI/API Change Integrations с действующим механизмом секретов ApiToken. Profile → API Tokens остаётся для персональных токенов чтения/review и не выдаёт идентичность интеграции. Административные маршруты под /api/admin/change-integrations управляют sources, grants и токенами, требуют JWT аутентифицированного администратора и аудируют каждую запись. В v1 ни ingest-токен, ни персональный API-токен не администрируют этот namespace.

Scopes: changes:ingest для машинного POST; changes:read для чтения; changes:write для ручного review/связей. Нет неявного changes:ingest через resources:write или персональный *. Токены интеграции принимаются только endpoint приёма. JWT человека использует текущий RBAC; персональный API-токен дополнительно проверяет scopes и существующее ограничение Group. Вложенные read endpoints Service/AlertGroup/Incident требуют одновременно changes:read и read scope родительского ресурса; review/запись связи — changes:write и write scope целевого ресурса. В api_token_scopes.py нужны явные правила method/path, без случайной выдачи целого домена через общий prefix.

7.2. Право ссылки и доверие#

  • Владение source разрешает только приём операции. Каждый changed Service требует grant changed; grant на Team разрешает pending slug, grant на Service — только конкретный существующий Service.
  • Reported affected требует как минимум reference для namespace/Service. Это разрешение непроверенного утверждения, а не межкомандной публикации. Доверие даёт grant reported_affected либо проверенный серверный путь зависимостей от авторизованного changed Service. Основание и input stamp сохраняются. Потеря пути/grant отменяет вычисленное доверие, не переписывая полученное событие.
  • Разрешённый namespace проверяется до раскрытия существования Service. Внутренний lookup числового ID для определения владельца допустим, но за разрешённым scope существующие, отсутствующие и soft-deleted ID/slug получают одинаковый безопасный отказ. В явно разрешённом namespace неизвестный Team/ID — ошибка валидации, неизвестный Service slug может стать pending. Так ожидание CMDB не превращается в проверку существования чужих ресурсов.
  • Разные формы канонической ссылки объединяются детерминированно: доверие есть, пока хотя бы одна текущая авторизованная ссылка или проверенный путь поддерживает эту роль. Непроверенный дубль не понижает доверенное утверждение, отозванное основание не сохраняет доверие. Исходные ссылки и основания сохраняются все.
  • Grant разрешает утверждение, а не чтение исходного Change или чужого Incident. Права человека проверяются отдельно при запросе, включая счётчики, пагинацию, поиск, Explain и безопасные списки выбора связей.

7.3. Безопасная межкомандная проекция и Service Timeline#

Читатель owner Team видит поля операции, но детали чужих Service/участков пути всё равно требуют прав на эти ресурсы. Читатель только затронутого Service получает проекцию по белому списку: непрозрачный UUID Change, доступный ему Service, время события, безопасные kind и роль связи. Заголовок константный, например «Связанное изменение»; без присланных title/description, имени source, внешних ID, actor, URL, raw details и закрытых участков пути. Нельзя сначала сериализовать полный объект и затем удалять несколько известных ключей.

Service Timeline следует той же политике. Сериализация прежних ServiceEvent возвращает actor, URL и payload без проверки владельца Change; события Change используют отдельную безопасную ветку. Поэтому новые ServiceEvent изменения содержат только безопасную проекцию даже у owner Services; полные данные читаются отдельным авторизованным endpoint Change. Используются постоянный source=change_intelligence, безопасные title/summary, пустые actor/URL и payload по белому списку. На этапе 9 добавлены nullable индексируемый change_event_id для происхождения и change_relation_type; поведение прежних событий других типов не меняется. Одного URL в source_ref или копии raw JSON недостаточно.

До пагинации/подсчёта SQL timeline/related changes проверяет текущий доступ к Service и видимость соответствующей канонической связи/влияния Change. После изменения grants, графа или владельца Service устаревшая производная видимость скрывается до перепроверки; задержавшийся worker не продлевает старую авторизацию. История владельца и evidence ручного review сохраняются без раскрытия через чужую проекцию. При отсутствии доступа к полным данным ручной review использует тот же безопасный evidence; связывание с Incident не выдаёт участникам права владельца Change.

7.4. Отзыв и принятая работа#

Отзыв/истечение секрета немедленно запрещает новые запросы, включая повторы; ротация не меняет source и владельца события. Уже принятая работа перепроверяет актуальные интеграцию/source/grants, а не существование прежнего токена. Отключение интеграции/source или отзыв grant увеличивает auth_revision и блокирует зависимое сопоставление/новую публикацию. Задания и события остаются в аудите; после разрешённого включения явная reconciliation может их продолжить. Старый секрет в заданиях не хранится.

8. Lifecycle и события не по порядку#

8.1. Контракт порядка source#

Source явно выбирает один режим; он копируется в новый Change. Изменение настройки source влияет только на будущие Changes. Существующие операции и события не переключают порядок задним числом.

РежимОбязательный вход и порядокОтклонение
timestamp (стандартный для простых отправителей)occurred_at с часовым поясом, нормализованный в UTC; время события, не приёмаЛюбой source_sequence даёт 422
sequenceнеотрицательный целочисленный source_sequence в каждом событии, уникальный внутри Change; пропуски и поздний приход меньших номеров допустимыНет/неверный sequence — 422; другое событие с тем же sequence — 409

occurred_at обязателен в обоих режимах и задаёт время для корреляции, даже если lifecycle упорядочен sequence. received_at — только аудит, никогда tie-breaker. Время без пояса и превышение допустимого ухода часов в будущее (по умолчанию пять минут) отклоняются с 422; серверное время молча не подставляется. Старые события остаются историей; глубина автоматической исторической корреляции ограничена отдельно (§9). Противоречие sequence и времени диагностируется без выдумывания другого occurred_at.

8.2. Детерминированная проекция статуса#

Финальные типы: succeeded, failed, rolled_back, cancelled. Обрабатывается всё принятое множество, а не порядок доставки. Ключ порядка — sequence в соответствующем режиме или occurred_at в timestamp. Итог задаёт наибольший ключ финальных событий; started/updated не отменяют финальный исход.

Принятые события lifecycleИтоговый статус
Нет started и финальных событий, только updatesunknown, без lifecycle conflict
Есть started, финальных событий нетrunning
Один исход у событий с наибольшим финальным ключомЭтот исход, независимо от поздней доставки started/updated
Разные финальные исходы с одним наибольшим occurred_at без sequenceunknown, lifecycle_conflict=true; сохраняются все ID конфликтующих событий
После конфликта финальное событие с большим ключомЕго исход; текущий конфликт снимается, прежняя диагностика сохраняется

В timestamp одинаковые финальные исходы с одним временем не конфликтуют. Event ID может упорядочить отображение и метаданные с равным временем, но не выбирает между успехом и ошибкой. Финальный исход с большим ключом заменяет прежний иной исход (включая failed → succeeded и succeeded → rolled_back); история сохраняется. Для нового исполнения всё равно нужен новый change_id, а не started, стирающий прежний итог.

Примеры используют время события у отправителя:

События в любом порядке доставкиРезультат
failed в 10:05; succeeded в 10:10succeeded, даже если failed принят последним
succeeded в 10:10; started в 10:00 принят последнимsucceeded
succeeded и failed ровно в 10:10unknown + conflict
Этот конфликт; rolled_back в 10:20rolled_back
sequence 20 failed в 10:10; sequence 21 succeeded в 10:09succeeded по sequence; инверсия времени диагностируется, исходные времена сохраняются

8.3. Остальные поля и атомарный приём#

kind и нормализованный environment (включая исходное отсутствие) не меняются внутри операции (409). Title непустой; description/URL можно явно очистить через null. Для изменяемого поля берётся событие с наибольшим ключом, в котором это поле передано; при равном времени метаданные выбираются лексикографически по external_event_id. Отсутствие сохраняет прежнее значение; received_at и автоинкремент БД не выбирают победителя. change.updated меняет эти поля/ссылки, но не статус lifecycle; произвольного свойства status у него нет. Actor/details принадлежат событию, а не разрушающему объединению несвязанных словарей.

started_at — минимальный occurred_at started-событий; без started это NULL. completed_at — occurred_at победившего финального события либо NULL при отсутствии итога/конфликте. first/last_occurred_at — min/max всех принятых событий. Отрицательная/противоречивая длительность диагностируется и не отображается как корректная. Для корреляции интервал воздействия начинается с started_at, если он известен, иначе с времени самого раннего финального события; updated-only операции и неразрешённый конфликт итогов не дают автоматических предложений причины. Обновление метаданных не является новым воздействием.

Ссылки на сервисы накапливаются; отсутствующий/пустой список не удаляет прежние связи. Pending/denied не переписывают статус операции. В одной write-транзакции сериализуются source/Change, вставляется неизменяемое событие, обновляются ссылки и детерминированная проекция, увеличивается revision и запрашивается outbox-работа. Ошибка любого шага откатывает всё. Уникальность PostgreSQL защищает конкурентную первую вставку; SQLite резервирует запись до чтения. Повтор после rollback даёт одно событие и одну фактическую генерацию.

9. Change Intelligence / Correlation#

  • Вход — канонические авторизованные changed Services и доверенные reported affected. Неразрешённые, запрещённые, непроверенные и просроченные утверждения не запускают корреляцию.
  • Обход активных неудалённых ServiceDependency идёт обратно (в БД consumer → provider, для влияния provider → consumers), с учётом enabled, correlation_enabled, типа зависимости, environment и evidence задержки распространения. Ограничиваются глубина (по умолчанию 5), узлы, кандидаты и время; циклы и выбор пути обрабатываются детерминированно. Прямое совпадение и несколько путей не создают дубли связей. Разные известные environment исключают автокандидата; отсутствие environment означает неизвестность, а не совпадение.
  • Сохраняются snapshot пути и полный input stamp. Пересчёт по сегодняшнему графу не объявляет его историческим: показываются время/ревизия вычисления, evidence ручного review хранится отдельно. Ingestion не вызывает записывающие статусы эффекты действующего операционного Service Impact engine.
  • Автоматика использует только известное начало эпизода. Для интервала воздействия операции [start, end] подходят эпизоды с началом в [start, end + W], W по умолчанию два часа. start определён в §8.3; end — время победившего финального события или момент расчёта для running. Противоречивый интервал исключается с диагностикой. Running проверяется при новых эпизодах, а не только ChangeEvents; AlertGroup.first_seen_at не подменяет начало повторного эпизода. Ретроспективный discovery завершённых операций/старых начал эпизодов ограничен горизонтом (по умолчанию семь дней); всё ещё running-операция не исключается только из-за давнего начала. Более старая история доступна для ручных связей.
  • Автоматика предлагает только potential_cause. Change с началом воздействия после начала эпизода не объявляется его автоматической причиной. Mitigation в v1 только вручную: оператор связывает позднее изменение с конкретным эпизодом или напрямую с Incident, в том числе без автоматического кандидата. Ручная связь за автоматическим окном допустима с аудируемым пояснением; вымышленного score для mitigation нет.
  • Score — версионированная детерминированная релевантность, не вероятность. Отбор/равенство кандидатов, веса, неизвестные данные и пределы входят в engine_version и проверяются фиксированными примерами ранжирования. Порядок SQL/обхода графа не меняет рейтинг. Ручная роль/review не перезаписывается, закрытые участки пути не попадают в Explain.
  • Новые ChangeEvents, создание/закрытие эпизода, изменение его контекста отбора, сопоставление и изменения каталога/grants запрашивают durable ограниченную discovery/recompute-работу с учётом §9.1. Повторы и перезапуски workers не теряют триггер и не дублируют проекции (§4.8).

Из корреляции не следуют автоматические rollback, paging/escalation, создание/смена статуса Incident или технического статуса. Ручной confirmed_cause фиксирует суждение оператора, а не объявляет совпадение времени доказательством причины.

9.1. Отбор алертов для автокорреляции — этап 7#

Контракт отбора реализован на этапе 7. Эпизоды создаются для новых групп и настоящего reopen; поиск ставится после проверки политики Service. Старые группы без эпизода и эпизоды без достоверного контекста при обычных обновлениях не восстанавливаются.

  • Явное включение на Service. На этапе 7 добавить change_correlation_enabled с default false для новых и существующих Services и список change_correlation_exclusions с default []. Настройки изменяются через API/UI Service с действующими правами его редактирования и аудитом. Нет Service, Service выключен/удалён или флаг выключен — группа не участвует автоматически. Флаг относится к Service алертов: выключенный для поиска алертов промежуточный Service не обрывает путь к разрешённым потребителям. Он не заменяет ServiceDependency.correlation_enabled, grants или RBAC и не запрещает приём Change Events/сопоставление сервисов.
  • Исключения отдельных алертов. В v1 правило содержит source алерта и/или labels с точным сравнением значений; это не source отправителя Change. Все условия одного правила должны совпасть у одного алерта; достаточно любого правила, чтобы исключить его. Пустой список ничего не исключает, пустое правило не допускается, отсутствующий label не совпадает с заданным значением. Переиспользуется применимая часть существующего matcher, без нового языка условий. Автоматических исключений по heartbeat, severity или типу алерта нет. Пример правила: {"source":"alertmanager","labels":{"alertname":"CertificateExpiresSoon"}}.
  • Смешанная группа. AlertGroup участвует, если у выбранного эпизода есть хотя бы один неисключённый дочерний Alert; один исключённый ребёнок не исключает остальных. Не объединять условия из labels разных детей и не использовать только common_labels. Уже разрешившиеся дети этого эпизода учитываются при поиске его причин. Для закрытого эпизода используются сохранённые состав/source/labels и Service этого эпизода, а не дети следующего reopen или результат последующего merge. Надёжный контекст и его ревизия добавляются на этапе 7; старый эпизод без такого контекста пропускается автоматически, без реконструкции по timeline. Изменение допуска не меняет started_at/ended_at и не создаёт новый эпизод.
  • Отбор до очереди. Общая проверка Service и исключений применяется перед постановкой поиска из lifecycle и при выборе эпизодов со стороны Change, повторов и reconciliation. Для недопущенного эпизода новые задания поиска корреляций не создаются; он не запускает обход зависимостей. Проверки не отменяют необходимую работу самого Change, mapping и расчёт влияния на другие Services. Для допущенного эпизода затем действуют grants, environment, известное начало, временное окно и лимиты §9. Отбор уменьшает поисковую работу, но в примере с 1000 новыми группами и 500 reopen останется 1500 эпизодов.
  • Повторная проверка и изменение настроек. Worker проверяет допуск перед вычислением и перед публикацией под проверкой ревизий §4.8–4.11. Настройки и их invalidation/discovery сохраняются одной транзакцией. Выключение/новое исключение немедленно скрывает утратившие допуск автоматические предложения; уже выполняющийся worker не публикует старый результат. Ранее поставленная, включая задания этапа 2, поисковая работа для недопущенных эпизодов завершается без бесконечных retry. Ограниченная работа по деактивации прежних предложений допустима и не запускает новый поиск.
  • Повторное включение. Включение Service, удаление исключения, появление допустимого ребёнка или привязка Service запрашивают ограниченный discovery для подходящих эпизодов в горизонте §9, сохраняя правила running-операций и неизвестного начала. Периодическая ограниченная сверка подхватывает пропущенный из-за конкурентного изменения настроек триггер. Повтор не создаёт дубли; вся старая история не пересчитывается без ограничений.
  • Ручные решения и диагностика. Настройка управляет автоматическими предложениями; ручные связи, confirm/reject и их evidence сохраняются. Создание ручной связи остаётся доступным при действующих правах §7, даже когда автоматика выключена или алерт исключён. UI различает выключение на Service, отсутствие Service, исключение всех детей, неизвестный контекст/начало, отсутствие кандидатов и ошибку обработки; отсутствие задания не показывается как вечный queued.

9.2. Реализованные правила correlation-v1#

  • Связь важнее времени: базовая оценка changed — 90, доверенного reported_affected — 80, зависимости глубины d — 70 - 5 × (d - 1). Утверждения affected не запускают обход графа.
  • Gap — секунды от конца операции до начала эпизода, либо ноль внутри running-операции. Близость по времени добавляет max(0, 4 - floor(4 × gap / 7200)); совпавший известный environment добавляет 1. Неизвестный даёт ноль и обозначается как неизвестный. Разные известные environment исключают путь или кандидата.
  • Фиксированные примеры: прямое совпадение с известным environment внутри интервала — 95; на границе двух часов — 91; прямое с неизвестным environment — 94; reported внутри интервала — 85; глубина 1 внутри интервала — 75; глубина 2 через час — 68. Это релевантность, не вероятность. Равные оценки сортируются по точному gap, затем стабильному Change UID.
  • Обходятся hard, soft и external; informational, выключенные и удалённые связи пропускаются. Выбирается кратчайший путь, при равенстве — лексикографический путь ID потребителей. Ромб даёт одну строку origin/target. Лимиты: глубина 5, 200 канонических связей/строк влияния, 1000 рёбер, пять секунд на расчёт; публикация эпизодов порциями change_worker_batch_size. Превышение лимита графа даёт limit_exceeded, не публикуя произвольный обрезанный граф. Задержка распространения сохраняется в evidence и не расширяет окно двух часов.
  • Новые эпизоды сохраняют correlation_context: Service/Team/environment и наблюдавшихся детей с source/labels; отдельно индексируются correlation_service_id и context_revision. Наблюдавшиеся в цикле resolved-дети остаются в снимке. Reopen начинает новый состав; он не восстанавливается по временным меткам. Закрытые снимки не меняются. Миграция оставляет старый контекст NULL; обычные обновления его не заполняют.
  • Завершение resolver ставит автоматическую работу. Создание/закрытие допустимого эпизода и изменение контекста запускают постраничный discovery недавних и всё ещё running Changes. Обходы графа/прав и периодическая сверка восстанавливают пропущенные триггеры. Публикация блокирует граф → интеграцию → Change → эпизоды → задание и проверяет входы, lease и generation.
  • Чтение использует current_suggestions, одного active недостаточно: текущая политика и stamp немедленно скрывают устаревшие предложения. Ручные review, роль, evidence и row_version не меняются. Показ Related Changes, безопасных объяснений и диагностики эпизода относится к этапу 9; этап 7 не добавляет сырые пути в существующие API чтения.

Очистка алертов удаляет автоматические предложения перед родительскими эпизодами под блокировками группы/эпизодов. Review, ручная роль или evidence удерживают группу; очистка Change/events — отдельная политика.

10. UI#

Настройки технического Service включают явное включение автокорреляции и список исключений source/labels (§9.1), доступные с правом редактировать Service. В AlertGroup показываются результат отбора и причина пропуска; это не фильтр отображения Inbox.

Operations → Changes — самостоятельный Inbox, одна строка на Change.

  • Поиск по ID, названию, source, внешнему ID, доступным сервисам и их service_slug.
  • Реализованы фильтры Team (owner или доступная затронутая), Service (ID или slug с Team namespace), kind, status, source, environment и дат. Фильтры по связям Incident, проблемным привязкам и состоянию обработки запланированы в §15.6.
  • Одна строка на Change при нескольких затронутых Teams; RBAC-фильтрация и DISTINCT выполняются сервером до пагинации и подсчёта.
  • Change Details: история с occurred_at и received_at, режим порядка, явный конфликт итогов, Changed, Reported affected, Inferred impact, mapping/expiry/job state и разрешённое evidence. Чужой читатель получает только безопасную проекцию, а не страницу исходных данных владельца.
  • Для pending_resolution — исходный slug/Team namespace, причина ожидания и момент последней попытки (в доступном владельцу scope); без кнопки «создать Service» автоматически.
  • AlertGroup Details выбирает текущий или исторический эпизод и показывает только его Related Changes/review. Неизвестные границы и ручная mitigation явные. Incident Details поддерживает context/potential_cause/confirmed_cause/mitigation без обязательной AlertGroup.
  • Service Timeline использует ServiceEvent после разрешения ссылки: стабильная дедупликация, индексируемое происхождение, безопасно сохранённая проекция и SQL-видимость §7.3. Полные данные читаются отдельным авторизованным endpoint Change; inferred impact не объявляется прямым изменением.
  • Redacted view для чужих Teams: собственный сервис, безопасные тип/время/тип связи, без закрытой топологии, полного payload, actor и URL.

11. Технические требования и эксплуатация#

  • Pydantic v2: строгие альтернативные ID/slug, время с поясом, целочисленный sequence, пределы строк/ссылок/body/details. Размер body ограничивается до разбора; задаются тайм-ауты запросов и rate limits каждой интеграции. Глобального поиска Service slug нет.
  • Auth применяется независимо от глобальной настройки обязательности. Source/grants повторно проверяются под блокировкой auth revision транзакции приёма; проверки только до конкурентного отзыва недостаточно.
  • Ограничения БД и короткие write-транзакции PostgreSQL/SQLite; явный rollback и ограниченные повторы временных ошибок. Сетевых вызовов/обхода графа под lifecycle/Change locks нет. Порядок блокировок — §4.8, несколько интеграций блокируются по ID.
  • Durable worker поставляется в фазе 1 на существующих scheduler и БД; Redis/Celery не обязателен, очереди только в памяти Flask недостаточно. Claim, heartbeat lease, expiry, retry/dead и commit проекции проверяют lease_token/generation. Общий scheduler lock дополнителен и не доказывает исключительное владение после истечения срока.
  • Индексы Change(owner_team_id,last_occurred_at), уникальные source/external IDs, ChangeService(service_id,change_id), ChangeServiceReference(resolution_status,namespace_team_id,requested_service_slug), ChangeImpact(service_id,active), correlation(episode_id,review_status), ChangeJob(state,next_attempt_at), происхождение timeline и episode(group,ordinal). До discovery проверяются планы запросов на ограниченном репрезентативном наборе.
  • Текущие RBAC/валидность проекции применяются в SQL до счётчиков, поиска и пагинации. Полные owner payload/actor/URL не попадают в межкомандный timeline или клиентский кеш, доступный после смены прав.
  • Время отправителя и приёма различаются; UI показывает UTC offsets и явные conflict/unknown-boundary/stale/mapping состояния. Неизвестные длительность/причина не угадываются молча.
  • Реализованы метрики состояний/возраста очереди, результатов worker, состояний ссылок и причин dead-заданий, а также панели Grafana. Метрики приёма, длительности обработки и конфликтов lifecycle дополняются в §15.1. Prometheus использует ограниченные категории без ID/slug и произвольных имён источников.
  • Секреты удаляются до сохранения диагностических данных и логирования; URL-схемы ограничены, контент экранируется. Документируются payload limits, future skew, pending TTL, retry budget, discovery horizon, retention и срок идемпотентных повторов. Ручное/Incident evidence защищено от очистки (§4.9).
  • Старые ServiceEvents без достоверного внешнего Change ID сохраняются как есть; Change/эпизоды не синтезируются догадкой. Документируются аддитивная миграция, порядок writer/worker rollout и операционный rollback без удаления evidence.
  • UI EN/RU/DE/FR/ES/ZH, OpenAPI, все сценарии §13 и поддерживаемый Python CI. Изменение архитектуры не означает готовность реализации или пройденные выпускные проверки.

12. Порядок реализации#

Первая итерация выполнена по этапам 1–9 ниже и дополнена системными настройками в БД. Их перечни работ и тестов сохраняются как контракт реализованного поведения. Этап 10 задаёт выпускную проверку конкретной сборки. Объём второй итерации находится в §15.

Область первой итерацииЭтапыТекущий статус
Хранение и эпизоды AlertGroup1–2Реализовано
Интеграции, приём, очередь и Changes Inbox3–6Реализовано
Корреляция, review и связи Incident7–8Реализовано
Related Changes и Service Timeline9Реализовано
Общие системные настройки и смена интервала без перезапускаДополнение d1dc817Реализовано
Выпускные проверки10Повторяются для выпускаемой сборки; локальные результаты приведены в §14

Схема, lifecycle writers, worker и политика чтения развёртываются до включения приёма. Администратор включает приём после проверки сквозного пути на своей установке. На новой установке приём выключен. Эпизоды создаются только для новых групп и настоящего reopen, включая первое переоткрытие старой группы. Исторические циклы сохраняют ранее согласованный контракт §4.9.

12.1. Этап 1 — Модели, ограничения и миграции#

  • Работа: модели Change, ChangeEvent, ChangeServiceReference, ChangeService, ChangeIntegration, ChangeSource, ChangeGrant, ChangeJob, ChangeGraphState и AlertGroupEpisode; поля ApiToken/AlertGroup; уникальность, FK и индексы. Добавить запись запроса задания в транзакции вызывающего кода. Создать только схему и начальную ревизию графа, без реконструкции эпизодов и постановки заданий для старых алертов. Модели корреляции, ручных связей и поля timeline добавляются на этапах 7–9 вместе с их использованием.
  • Тесты: чистая БД и обновление существующей; повтор миграции; старые алерты/события/токены не меняются, эпизоды/Changes/задания не создаются; ошибка откатывает схему и инициализацию; один открытый эпизод; ограничения уникальности и FK в PostgreSQL/SQLite. Запрос задания откатывается вместе с транзакцией.
  • Готово, когда: схема создаётся и обновляется без изменения существующих данных и включения записи эпизодов или приёма; определён порядок обновления процессов и восстановления после неудачной миграции.

12.2. Этап 2 — Эпизоды во всех путях AlertGroup#

  • Работа: атомарное создание/закрытие эпизода при intake, настоящем reopen, resolve, recovery, callbacks, heartbeat, orchestration, merge и удалении ресурса. Старые группы без эпизодов сохраняют NULL до настоящего reopen; миграции и обычные обновления не инициализируют их историю. В той же транзакции сохранять discovery-задание; обработка появится на этапе 5, отбор поисковых заданий по §9.1 — на этапе 7. Сохранить порядок блокировок Incident → AlertGroup → episode и действующие уведомления.
  • Тесты: миграции оставляют старые группы всех статусов без эпизодов/заданий; обычные обновления, ack, silence/maintenance и resolve не создают эпизод; первый настоящий reopen старой группы даёт ordinal=1 с известным началом; два reopen; resolve одновременно с reopen; повторный resolve; ошибки записи ребёнка, события и задания. Ack → firing, unshelve и reopen только Incident не создают новый эпизод.
  • Готово, когда: все записывающие пути соблюдают один контракт, незавершённая транзакция не оставляет частичного эпизода, прежние lifecycle-тесты проходят на обеих БД.

12.3. Этап 3 — Интеграции, grants и токены#

Реализованы /admin/change-integrations и /api/admin/change-integrations, доступные глобальным администраторам и user_admin группы-владельца по JWT: интеграции, owner/source с блокировкой после первого Change, явно редактируемые grants и токены. Изменения конфигурации требуют expected_revision; устаревшая ревизия возвращает 409. Изменения прав и связанного состояния каталога блокируют graph state, затем затронутые интеграции по ID, атомарно повышают auth_revision и ставят discover_auth; обработка реализована на этапе 5. Ротация атомарно отзывает старый токен, сохраняя интеграцию и ревизию прав. Секрет возвращается один раз с Cache-Control: no-store; списки и аудит не содержат секретов или хешей. authorized_source удерживает блокировку прав до завершения записи вызывающего обработчика; принятая работа использует постоянную идентичность, новый приём дополнительно проверяет токен. Маршрут /api/change-events реализован на этапе 4 и по умолчанию выключен.

  • Работа: административные API/UI интеграций, sources, grants и ротации; машинный ApiToken и явные scopes/разрешённый маршрут; журнал изменений и auth_revision. Реализовать проверку владельца/source и области Service, включая reference-only grants. Персональные токены сохраняют действующее поведение.
  • Тесты: подмена source/Team; выход за grant; одинаковый безопасный отказ для недоступных ID; запрет чтения и администрирования ingest-токеном; ротация, отзыв и конкурентное изменение прав. Персональный wildcard не заменяет идентичность интеграции.
  • Готово, когда: администратор может создать интеграцию и заменить секрет, не меняя владельца её событий; изменения прав атомарно обновляют ревизию. Продолжение уже принятой работы проверяется с worker на этапе 5.

12.4. Этап 4 — Атомарный приём и проекция Change#

Реализован POST /api/change-events с переключателем Приём Change Events в базе данных (выключен на новой установке). Глобальный администратор включает его после сквозной проверки развёртывания. Проверка токена, поиск изменяемого owner slug, проверка grants и запись удерживают блокировку интеграции; PostgreSQL затем блокирует Change, SQLite резервирует запись до чтений. Новое событие, исходные ссылки, канонические связи, ревизия Change и запрос resolve_references фиксируются вместе. Worker и задания корреляции здесь не запускаются. Точный повтор возвращает исходный UID события и текущее безопасное состояние Change без увеличения revision/generation.

Время отправителя нормализуется в UTC с микросекундами; разные финальные исходы с одинаковым временем дают конфликт. Режим sequence закреплён за Change. Отсутствующие поля не затирают метаданные; до первого title используется change_id. Environment очищается от пробелов по краям, чувствителен к регистру; отсутствие и null означают отсутствие environment. Namespace и разрешённый Service не меняются после переименований. Новое событие может разрешить pending-ссылку, но не перепривязать уже разрешённую. Неизвестный ID в явно разрешённой Team даёт 422; неизвестный ID без namespace и недоступные ссылки получают одинаковый 403.

Лимиты: тело 256 KiB до разбора JSON, суммарно 100 ссылок в запросе, details 64 KiB/10 уровней/2000 узлов, время с часовым поясом и максимум шестью знаками долей секунды. Повторные JSON-ключи, NaN/Infinity, NUL и некорректный Unicode отклоняются. Значения по умолчанию: 300 секунд допустимого будущего времени, 600 новых принятых событий в минуту на интеграцию, 1000 событий на Change (настраивается до 10000); точные повторы не расходуют эти лимиты. Digest вычисляется до удаления секретов; сохраняется только очищенный payload и метаданные. На PostgreSQL ожидание блокировки ограничено 2 секундами, SQL-запроса — 5 секундами на попытку; временные ошибки БД повторяются максимум три раза, затем возвращается 503 с Retry-After. Для передачи тела продолжают действовать таймауты WSGI/reverse proxy. Очистки истории пока нет: сохранённые идентификаторы остаются доступны для повторов. TTL pending и повторы worker реализованы на этапе 5; retention/tombstones запланированы в этапе 2.2 (§15.2). Настройки описаны в конфигурации, API — в OpenAPI.

  • Работа: валидация общего payload, resolver ID/slug с закреплённым namespace_team_id, digest и идемпотентность; режимы timestamp/sequence и детерминированная проекция. Event, ссылки, состояние Change, revision и запрос задания записываются одной транзакцией с повторной проверкой прав. Endpoint остаётся выключенным в production до сквозной приёмки этапа 10.
  • Тесты: перестановки событий, одинаковое время разных итогов и более поздний итог; поздний started; пропуски/дубли sequence; absent/null; ротация и повтор event_id; конкурентные первые запросы; ошибка на каждом шаге записи.
  • Готово, когда: повтор возвращает 200 без новых эффектов, конфликт — 409; после rollback повтор сохраняется один раз; порядок доставки не меняет результат принятого множества событий.

12.5. Этап 5 — Worker, pending-ссылки и пересчёт#

В существующий scheduler добавлены захват/перехват задания, продление аренды, проверка поколения, обработка ссылок и discovery порциями с сохранением курсора. PostgreSQL использует SKIP LOCKED, SQLite резервирует запись до чтений. Расчёт выполняется вне блокировок записи. Публикация блокирует graph → integration → Change → job, сверяет ревизии входов и аренду, затем фиксирует ссылки, канонические связи, ревизию Change и подтверждение задания одной транзакцией. Устаревший worker не может записать результат, продлить аренду или изменить повторы нового владельца. Новый запрос работы не теряется при завершении или ошибке старого обработчика.

Pending разрешаются по закреплённому Team ID и текущим интеграции/Source/grants; исходный токен и повторный поиск Team по slug не используются. Разрешённый Service ID не перепривязывается. Отсутствие прав, недоступные ресурсы и истечение окна дают видимые состояния denied/invalid/expired. Явный аудируемый повтор или новое принятое событие с той же ссылкой заново проверяет текущие права и закреплённую идентичность, при необходимости открывая новое окно pending. Точный повтор event_id не меняет ссылку и не создаёт работу. Исходные идентификаторы и события сохраняются. Reference-only заявления об affected остаются unverified. Доверие по графу, обход impact, поиск эпизодов, корреляция и timeline в этот этап не входят.

По умолчанию: запуск раз в 5 секунд, аренда 60 секунд, до 20 заданий за запуск с бюджетом начала новых заданий 10 секунд, порции по 100 строк, восемь технических попыток с экспоненциальной задержкой 5–300 секунд и случайным разбросом, TTL pending семь дней с приёма и проверка раз в 60 секунд. Успешная порция сбрасывает бюджет технических ошибок; ожидание создания сервиса ошибкой не считается. Dead-задание оживляет оператор или новое принятое событие, а не периодическая сверка. Изменения каталога/зависимостей атомарно повышают graph revision и запрашивают обход сохранённых Change порциями; auth-discovery ограничен интеграцией. Периодическая сверка читает только индексированные pending-ссылки и не сбрасывает активный курсор. Очистки истории нет, сохранённые digest доступны для повторов. В текущей корреляции действуют окно два часа и горизонт семь дней. Их настройка запланирована в §15.4, очистка Change/events — в §15.2. Ручные доказательства защищены действующими ограничениями и очисткой алертов.

Публикация ссылок может занимать несколько порций. Для актуального полного результата потребитель проверяет input stamp, cursor IS NULL и claimed_generation=requested_generation; завершённый проход может оставаться pending до следующей проверки отсутствующего сервиса. Новая ревизия или запрос работы начинают проход заново. Обработчики эпизодов, impact, корреляции и timeline реализованы на этапах 7–9. Выключение обработки сохраняет задания до возобновления, а изменение допуска Service запрашивает ограниченный обход допустимой истории.

Операционный API: JWT GET /api/admin/change-integrations/jobs для глобального администратора либо /{integration_id}/jobs для администратора группы-владельца; только метаданные, фильтр state и пагинация по курсору. POST на соответствующий /jobs/{job_id}/retry требует expected_generation, даёт 409 при устаревшем запросе и пишет аудит в той же транзакции. Метрики показывают фиксированные категории результатов, состояний и ошибок, а также возраст очереди без идентификаторов отправителя. Настройки и откат описаны в конфигурации.

  • Работа: claim/lease/generation, проверка актуальности входов при записи, retry/backoff, expiry, dead state и аудируемый повтор. Подключить catalog/auth revisions, discovery эпизодов, сохранение прогресса обхода и периодическую сверку pending-ссылок. Выполнять доступные обработчики; до этапа 7 не ставить бесконечно ожидающие задания выключенной корреляции. Зафиксировать TTL, лимиты, сроки повторов и метрики очереди.
  • Тесты: потеря lease и поздняя запись старого worker; новый триггер во время расчёта; сбой/рестарт между записью результата и подтверждением; поздняя CMDB, переименование Team и повторное использование slug; принятая работа после отзыва токена; отзыв grant во время вычисления; возобновление обхода после сбоя.
  • Готово, когда: задания переживают рестарт, не теряют новые запросы и не создают дубли; pending разрешается только в исходной Team по текущим правам; ошибки доступны в диагностике.

12.6. Этап 6 — Read API и минимальный Changes Inbox#

Реализованы /changes, прямые ссылки на карточки по UUID и GET /api/changes, /{change_uid}, /{change_uid}/events, /{change_uid}/services, /api/services/{service_id}/changes. Авторизация обязательна и при отключённой глобальной проверке API. JWT активного пользователя и его личный токен с changes:read используют текущие права Team и ограничения Group/Team токена; вложенный маршрут Service дополнительно требует services:read. Машинным токенам чтение запрещено.

SQL-проверка видимости, фильтры закрытых полей, подсчёт и пагинация используют один снимок чтения (repeatable read на PostgreSQL). Поля владельца сериализуются отдельно; сервисы, исходные ссылки и их счётчики ограничены правами на ресурсы. Без доступа к владельцу возвращаются только непрозрачный UUID, постоянное название, тип, время события и до пяти доступных доверенных связей. Полный доступный набор связей выдаётся постранично через services. История событий доступна только владельцу, без request snapshot, исходного массива ссылок, digest и идентификаторов токена. Недоступный и отсутствующий Change/Service дают одинаковый 404. Чтение не меняет ревизии и не создаёт задания.

Межкомандная видимость требует завершённого прохода resolver: совпадают ревизии Change/auth/graph и версия движка, нет курсора/ошибки, claimed=requested, состояние done/pending. Отзыв grants, изменение каталога или новое поколение работы скрывает устаревшую связь до пагинации, не ожидая worker. Диагностика владельца сохраняется. Поиск и фильтры status/source/environment не сопоставляют закрытые поля безопасных карточек. scope_team_id отбирает контекст владельца или доступного связанного сервиса; owner_team_id — только доступного владельца. ID и slug с namespace имеют одинаковые ограничения.

Inbox использует стандартные таблицы, фильтры и пагинацию. Показывает конфликты итогов, доступные состояния mapping/expiry, ошибки обработки и историю. Браузерные тесты покрывают прямые ссылки, назад/вперёд, ошибку обновления и запоздавшие ответы после навигации. Приём управляется системным переключателем; review, корреляция и публикация Service Timeline реализованы на этапах 7–9. Пример отправителя и API чтения — в конфигурации.

  • Работа: список, детали, события и сервисы; поиск, фильтры, счётчики и пагинация с SQL RBAC; полная проекция владельца и безопасная проекция для доступного затронутого Service. UI показывает конфликт итогов, mapping, pending/expired/dead и состояние обработки. Документировать общий API и пример отправителя.
  • Тесты: один Change с несколькими Team не дублируется; владелец, затронутая команда и посторонний видят только разрешённые поля; фильтры/поиск/счётчики не раскрывают скрытые данные; end-to-end приём → pending → создание Service → отображение.
  • Готово, когда: доступные изменения, состояния ссылок и обработки согласованно отображаются с учётом прав; сквозной сценарий приём → pending → появление Service → отображение пройден. Текущая реализация дополнена корреляцией и связями этапов 7–9.

12.7. Этап 7 — Автоматическая корреляция#

  • Работа: миграции ChangeImpact и ChangeAlertGroupCorrelation; настройки Service с default off, API/UI и исключения source/labels по §9.1; сохранение контекста отбора эпизода и его ревизии; единый отбор до очереди и повторная проверка перед публикацией; ограниченный обратный обход графа и привязка к точному эпизоду. До реализации score зафиксировать версию правил и эталонные примеры ранжирования. Подключить все триггеры пересчёта, включая полный обход допустимой истории при смене общей graph revision; публиковать только при актуальном input_stamp. Автоматика предлагает только potential_cause.
  • Тесты: default off, отсутствие Service, исключения source/labels и смешанные группы, смена допуска при работающем worker, ограниченное повторное включение и сохранение ручных решений; прямые/косвенные связи, циклы, ромбы, выключенные рёбра, environment, границы окна, длительный running, неизвестное начало эпизода; Change до/после AlertGroup; смена графа/grant во время расчёта; повторные задания и стабильность рейтинга.
  • Готово, когда: недопущенные эпизоды не создают поисковую работу, все эпизоды сохраняются, одинаковые входы дают одинаковый результат и объяснение, устаревший расчёт не публикуется, автоматической mitigation и изменений операционных статусов/уведомлений нет.

12.8. Этап 8 — Ручной review и связи Incident#

  • Работа: ручное создание связи с эпизодом, confirm/reject, context/mitigation; row_version, аудит и сохранённое evidence. Добавить ChangeIncidentLink и API add/change/remove/reactivate через транзакции/CAS Incident. Защитить связанные доказательства от retention одновременно с появлением связей. UI действий в Change Details использует разрешённую полную или безопасную проекцию.
  • Тесты: конкурентные confirm/reject и изменение/удаление связи; отсутствие автоматического предложения; mitigation вне автоматического окна; повторное добавление и реактивация; ошибка записи link/event/audit; пересчёт после ручного решения; новый эпизод; очистка родительских объектов.
  • Готово, когда: устаревшая запись получает 409, повтор соблюдает контракт 200/201, ручное решение и его основания переживают пересчёт/очистку и не переносятся на другой эпизод. Связь не раскрывает закрытые данные Change.

Этап 8 реализован в Change Details. Все ручные записи требуют непустого note (до 2000 символов); права читаемой Change и право responder на целевую AlertGroup/Incident проверяются независимо. Персональные токены требуют одновременно changes:write и alerts:write либо incidents:write; для вложенных списков нужны оба read scope. Group/Team-ограничения действуют и для токена глобального администратора.

Review передаёт row_version, decision (confirmed/rejected), relation_type и note. Для автоматического кандидата обязателен возвращённый evidence_version: изменение вычисленных оснований без изменения ручной версии тоже даёт 409. Отдельное действие «Указать связь вручную» передаёт manual=true и допускается без актуального автоматического основания, в том числе при выключенной политике. Неактуальные кандидаты показываются явно устаревшими, без score; обычное подтверждение для них недоступно. Ручные связи без кандидата имеют score=NULL, а не вымышленную оценку.

Snapshot сохраняет доступную оператору проекцию Change, конкретный эпизод, показанную оценку/тип основания/время расчёта, временную близость и пояснение. Повторное ручное решение сохраняет исходные основания; автоматический пересчёт не меняет snapshot. Чтение снова проверяет текущие права: закрытые поля старого полного snapshot и недоступные Service не выдаются безопасному читателю. Сырые пути графа не публикуются на этом этапе. Общий Incident timeline получает только идентификаторы и роли; введённое человеком пояснение сохраняется в аудите. Приватные title/source/payload Change автоматически не копируются ни в timeline, ни в аудит.

POST связи Incident возвращает 200 для активной связи той же роли и 201 для новой. Изменение роли и удаление используют row_version; удаление мягкое. Для восстановления POST передаёт версию удалённой связи из списка с include_removed=true и возвращает 201; без актуальной версии — 409. Событие Incident, CAS версии Incident, связь и аудит сохраняются атомарно. Повтор не создаёт событие/аудит; конфликт не повторяется автоматически в UI.

Миграция 20261104010000_change_reviews сохраняет прежние корреляции, разрешает NULL score и создаёт ChangeIncidentLink с уникальной парой (Change, Incident). RESTRICT защищает оба родителя даже после удаления связи. Reviewed эпизоды сохраняются действующим retention; исторические группы не получают новые эпизоды. При откате приложения таблицы и решения остаются; правила отключения старого retention из §9.2 сохраняются.

12.9. Этап 9 — Service Timeline и страницы AlertGroup/Incident#

  • Работа: поля происхождения ServiceEvent и безопасная сохранённая проекция; идемпотентная публикация timeline с SQL-проверкой видимости. Добавить выбор эпизода и Related Changes в AlertGroup, отображение/действия со связями в Incident. Подключить API предыдущих этапов, не менять статус сервисов и уведомления.
  • Тесты: повтор и crash/retry публикации не дублируют timeline; один Change через все экраны не раскрывает лишних полей; отзыв доступа, перенос Service, устаревший input_stamp и навигация браузера; прежние ServiceEvents отображаются как раньше.
  • Готово, когда: owner и затронутая команда получают согласованные представления на всех страницах; приватные title/source/actor/URL/payload не попадают в чужой timeline или кеш.

Этап 9 использует общий шаблон Related Changes, один диалог review и общие CSS для рабочих страниц. В AlertGroup выбирается существующий эпизод (по умолчанию последний), показывается допуск к автоматической корреляции и доступны ручные решения для этого эпизода. API списка эпизодов имеет пагинацию и не восстанавливает старую историю. Incident workspace поддерживает добавление, изменение роли, удаление и восстановление с теми же версиями, правами и аудитом, что Change Details.

Миграция 20261104020000_change_service_timeline добавляет nullable происхождение и уникальный индекс (service_id, change_event_id, change_relation_type), не переписывая прежние события. Отложенный FK завершается после исторической миграции Services, поэтому поддерживаются и чистая установка, и обновление. Обновление ставит в очередь ограниченный по batch обход уже принятых Changes для перепроверки; эпизоды AlertGroup и вымышленные деплои не создаются.

Worker sync_service_timeline публикует не больше настроенного размера batch. Каждому неизменяемому ChangeEvent соответствует одно безопасное ServiceEvent на разрешённую пару Service/тип связи. Уже опубликованные записи исключаются SQL-запросом; публикация и продвижение задания сохраняются атомарно с проверкой graph/integration/Change/lease. Повторная обработка неизменившегося mapping не сбрасывает лимит попыток ошибочного задания. Каноническая связь отличается от актуального косвенного влияния. Изменение входных graph/auth/Change немедленно скрывает устаревшую производную видимость до пагинации на всех экранах; история владельца остаётся ограниченной доступными сервисами.

События Change в timeline сохраняют постоянные title/source/event_type, без исходного actor и внешнего URL, с payload {change_uid, kind, relation_type}. Даже owner Services получают только эту проекцию. Персональному токену нужен changes:read вместе с доступом к Service; endpoints AlertGroup требуют одновременно changes:read и alerts:read. Ответы Service details/timeline имеют no-store; браузер заново проверяет доступ при открытии истории и отбрасывает ответы закрытых или сменившихся экранов. Прежние события сохраняют свой сериализатор и пагинацию.

12.10. Этап 10 — Выпускные проверки#

Первая итерация функционально завершена. Ниже сохранён обязательный порядок проверки выпускаемой сборки. Последний локальный регрессионный прогон указан в §14; полная Linux/Python CI-матрица и проверка конкретного развёртывания фиксируются отдельно при выпуске.

  • Работа: сверить OpenAPI, примеры CI/CD и CMDB, шесть локализаций, настройки/лимиты, метрики, инструкции миграции, запуска worker и отката. Описать порядок включения функций и отключение обработки без удаления истории. Проверить планы основных SQL-запросов и ограниченность обходов на репрезентативных данных.
  • Тесты: весь набор §13 на PostgreSQL/SQLite, поддерживаемая матрица Python CI, upgrade с существующими данными, повтор/сбой миграции, перезапуск worker, сквозные роли и регрессии AlertGroup/Incident/уведомлений. До включения публичного приёма проверить путь от события до отображения разрешённой связи с эпизодом, включая повторы и оба порядка прихода Change/Alert. Неотработавшие проверки явно отмечаются как блокеры.
  • Готово, когда: результаты обязательных проверок записаны, блокеров нет, развёртывание и восстановление воспроизводимы. Завершение RFC или одного этапа само по себе не означает готовность всего Change Intelligence к релизу.

13. Критерии приёмки и обязательные тесты#

Все применимые сценарии записи/конкурентности выполняются на PostgreSQL и SQLite. Гонки используют реальные независимые соединения, barriers/events вместо ожиданий только по времени. Проверяются сохранённые данные, результат, число событий/аудитов и задания, а не один HTTP status.

ОбластьОбязательные регрессионные сценарии
ИдентичностьТолько slug и смешанные ID; одинаковые Service slug у разных Teams; конфликт namespace; неизвестный ID против pending slug; разные формы дают одну каноническую связь
Закреплённый namespaceTeam переименована, старый slug получила другая: pending ищется только в исходном namespace_team_id; удаление/перенос namespace или Service не перепривязывает историю
Право ссылкиНеизвестный/существующий/удалённый чужой ID даёт одинаковый отказ без namespace grant; reference-only не публикует непроверенное утверждение; доверие по графу теряется при исчезновении основания
ИдемпотентностьТот же event/digest — 200 без роста revision/jobs; другой digest/change_id с тем же event ID — 409; конкурентные первые события дают одну операцию/событие/логическое задание; ротация сохраняет namespace/dedup
ПорядокВсе перестановки принятых событий дают одинаковые статус и метаданные; occurred_at отправителя важнее received_at; пропуски sequence и поздние меньшие номера допустимы; отсутствующий/смешанный/дублирующий sequence отклоняется по контракту
Конфликт итоговОдинаковое время succeeded/failed в timestamp сохраняет оба и даёт unknown + conflict; одинаковые исходы не конфликтуют; более поздний итог снимает текущий конфликт; поздний started/updated не открывает завершённый Change
Проекция полейОтсутствие/null, offsets/микросекунды времени, порядок метаданных с равным временем, неизменяемые kind/environment, пустые списки сервисов, уход часов в будущее и противоречивая длительность
Атомарность приёмаОшибки после вставки Change, события, объединения ссылок, проекции и outbox: частичных Change/event/job нет; повтор сохраняется один раз
Полномочия интеграцииТокен source A не подставляет source B/другого владельца; ingest не читает, не делает review, не управляет токенами и не редактирует Incident; персональный wildcard/JWT не обходит машинную идентичность
ОтзывПринятый pending переживает ротацию/истечение/отзыв токена; новые запросы старым секретом запрещены; отключение integration/source или отзыв grant запрещает mapping/публикацию, в том числе уже считающему worker
Переход к эпизодамМиграции сохраняют старые группы всех статусов без эпизодов/Changes/заданий; обычные обновления и resolve старой группы не создают эпизод; acknowledged → firing не принимается за resolved reopen; настоящий reopen создаёт первый отслеживаемый цикл с точным началом; merge в старую активную группу не создаёт цикл, возобновление resolved-группы создаёт; уже сохранённая история не удаляется; старые ServiceEvents не выдумываются как Changes
Атомарность эпизодаДва reopen дают один эпизод/ребёнка; ошибка ребёнка/event/job откатывает всё; manual resolve во время незавершённого reopen не закрывает новый эпизод; duplicate resolve закрывает один раз
Все lifecycle-путиRecovery/ручные callbacks, heartbeat, orchestration, merge и удаление ресурса корректно открывают/закрывают эпизоды; ack/unshelve/reopen только Incident не создают эпизод
Защита jobWorker A потерял lease, B записал результат, A вернулся: A не публикует/подтверждает и не меняет retry B. Триггер во время вычисления не теряется. Сбой до/после публикации не теряет работу и не дублирует timeline
Ревизии входовВо время вычисления меняются граф/grant/mapping: старый input stamp не публикуется; устаревшая межкомандная видимость скрывается до пагинации/счётчиков; ручной review сохраняется
Discovery/expiryChange до AlertGroup и наоборот; поздняя CMDB; периодический fallback; пределы batch/depth/horizon; обход после смены графа охватывает и несвязанные Changes, переживает сбой batch и перезапускается на новой ревизии; истечение pending/dead job и аудируемый повтор
КорреляцияПрямой/зависимый Service, циклы/ромбы/несколько путей, выключенные/удалённые рёбра, correlation_enabled=false, разные/неизвестные environment, длительная running-операция, границы окна и неизвестное начало эпизода
Отбор алертовНовый/существующий Service default off; нет Service, выключенный/удалённый Service; пустой список/запрещённое пустое правило, отсутствующий label, AND условий одного ребёнка и OR исключений; один допустимый ребёнок сохраняет участие смешанной группы, все исключены — поиска нет; разрешившийся ребёнок и исторический контекст сохраняются; неизвестный контекст пропускается; выключенный промежуточный Service не отрезает разрешённого потребителя
Изменение допускаВыключение/исключение во время расчёта не допускает старую публикацию; изменение source/labels/Service/состава увеличивает ревизию эпизода; повторное включение ограничено горизонтом и не создаёт дублей; reconciliation закрывает гонку enable/новый эпизод; старые задания этапа 2 не зацикливаются; ручные решения и все эпизоды сохраняются; изменение настроек требует прав и аудита
Ручной reviewКонкурентные confirm/reject дают одну принятую версию и 409 устаревшему; пересчёт сохраняет роль и evidence; новый эпизод не наследует прежнее решение
Ручная mitigationПозднее изменение не получает автоматическую причинную связь, но связывается вручную с более ранним эпизодом/Incident с аудируемым пояснением; без выдуманной вероятности и paging
Связи IncidentIncident без AlertGroup; duplicate add/reactivation/remove/смена роли соблюдают 200/201/409; link/event/audit откатываются вместе; статус и уведомления Incident не меняются
RedactionОдин Change через inbox/details/search/counts/Explain/timeline/Incident links: затронутая команда не видит закрытые title/source/ID/actor/URL/payload/path; перенос Service, отзыв доступа и навигация браузерного кеша не раскрывают старые полные данные
Retention и развёртываниеРеализована защита reviewed/Incident evidence при очистке родителей; upgrade/retry миграций, rollback и развёртывание worker проверяются для выпуска. Очистка Change/events и защита повторов после неё относятся к §15.2

Пределы payload и повторов документированы в конфигурации и OpenAPI; ранжирование проверяется фиксированными примерами. Сроки очистки Change/events и защита повторов после неё определяются в §15.2. Неизвестные исторические границы и скрытые устаревшие проекции имеют явные состояния UI.

14. Текущее состояние первой итерации#

Сверено с release/2.5.0, d1dc817, 7 ноября 2026 года. Функциональный объём первой итерации завершён.

  • Эпизоды записываются атомарно в технических lifecycle-путях. Старые группы начинают отслеживаться при настоящем reopen.
  • Интеграции, Source, grants и сменяемые токены задают постоянную идентичность отправителя. Приём и worker проверяют текущие права.
  • Change и неизменяемая история событий поддерживают оба режима порядка, идемпотентные повторы, явные конфликты и отложенное разрешение Service.
  • Действуют очередь с lease/generation, повторы и аудит ручного перезапуска, graph/auth revision и ограниченные обходы истории.
  • Корреляция предлагает причины; оператор подтверждает/отклоняет связи и добавляет context/mitigation. Снимки решений защищены от пересчёта и очистки алертов.
  • Inbox, страницы Change/AlertGroup/Incident и Service Timeline используют текущий RBAC, безопасные проекции и проверку актуальности.
  • Общие системные настройки содержат два переключателя и 12 числовых параметров. Планировщик проверяет изменение своего интервала раз в пять секунд, в том числе при выключенной обработке очереди.

Основные модули находятся в app/services/change_intelligence/, app/modules/db/changes_repo.py, схемах/API Change, app/services/system_settings.py и app/services/scheduler.py. Используются существующие Team/Service, зависимости, эпизоды, Incident, API scopes и аудит.

Последняя локальная регрессия 6 ноября 2026 года для изменений, включённых в d1dc817, дала 466 пройденных тестов SQLite, 92 PostgreSQL и два браузерных теста Chrome. Также прошли Ruff и проверка diff. Это результаты выбранных регрессионных наборов. Полная матрица CI и выпускные проверки §12.10 сопровождают конкретную сборку; общий порядок описан в release notes 2.5.

В §15 вынесены дальнейшие работы по диагностике, очистке истории, объяснениям, политике корреляции, CI/CD и аналитике.

15. Вторая итерация — согласованный объём и этапы#

Объём согласован 7 ноября 2026 года. Во вторую итерацию входят все шесть направлений ниже. Каждый этап поставляется отдельным проверяемым изменением с документацией, OpenAPI и локализациями затронутого интерфейса. Статус всех этапов — запланирован.

ЭтапРезультатЗависимости
2.1. Диагностика интеграций и обработкиСобытия, проблемные задания и повтор в интерфейсеПервая итерация
2.2. Сроки хранения и очисткаУправляемый рост истории с сохранением решений и защиты от дублей2.1 для наблюдения за очисткой
2.3. Объяснение связейОснования предложения и временная шкала Change/эпизодаПервая итерация, контракт сохранения evidence из 2.2
2.4. Политика корреляцииНастраиваемые окно/горизонт, групповое наследование и редактор исключений2.3 для объяснения применённой политики
2.5. Подключение CI/CDПоддерживаемые шаблоны отправителей и проверка подключенияКонтракты повторов 2.2 и диагностика 2.1
2.6. Оценка качества и рабочие фильтрыСтатистика review и поиск изменений, требующих внимания2.2–2.4 для сохранности и интерпретации данных

15.1. Этап 2.1 — Диагностика интеграций и обработки#

Использовать существующие административные API заданий, аудит, очередь и метрики как основу интерфейса.

  • Добавить в интеграцию последние принятые события и задания с фильтрами по состоянию/типу, временем последней попытки, числом попыток, следующей попыткой и понятной причиной остановки. Показывать различие между ожиданием сервиса, выключенной обработкой, утратой прав и исчерпанными попытками.
  • Подключить ручной повтор к действующему контракту expected_generation и аудиту. После 409 показывать текущие данные и действие, доступное пользователю. Просмотр диагностических данных проверяет права глобального администратора либо администратора группы-владельца интеграции.
  • Дополнить существующие метрики и панели Grafana счётчиками accepted/duplicate/rejected, длительностью обработки и конфликтами lifecycle, где этих измерений ещё нет. Использовать ограниченный набор меток. Для отклонённых запросов хранить безопасную категорию ошибки, исключая тело запроса и секреты.

Проверить повтор после отзыва grants, конкурентный повтор, смену группы/прав во время просмотра, отсутствие секретов, восстановление после ошибки API и пагинацию большой очереди.

Этап завершён, когда администратор может определить, на каком шаге остановилось изменение, устранить причину и повторить разрешённую работу из интерфейса.

15.2. Этап 2.2 — Сроки хранения и очистка#

Добавить в системные настройки отдельные сроки для истории событий, завершённых заданий и автоматических результатов. Начать с предварительного расчёта кандидатов, объёма и причин удержания. Удаление включается явно после проверки этой оценки.

  • Для первой реализации очищать события целой завершённой незащищённой операции. Частичное удаление ChangeEvent требует отдельного контракта восстановления проекции, поскольку текущий приём пересчитывает Change по сохранённым событиям. Проверять завершённость, отсутствие конфликта итогов, ожидающих ссылок и незавершённой работы под блокировкой.
  • Ручные подтверждения/отклонения, их evidence и ссылки Incident, включая удалённые связи с возможностью восстановления, удерживают соответствующую историю. Для автоматических строк проверять зависимости публикации и повторного расчёта до удаления.
  • Сохранять компактные записи идентичности операции и событий, digest и режим порядка на согласованный срок повторов. Зафиксировать ответы на точный повтор, изменённое содержимое и новое событие для уже очищенной операции. Очистка не должна позволять повторно использовать source namespace или создать прежний Change заново.
  • Выполнять очистку ограниченными порциями с возобновлением после сбоя, повторной проверкой кандидатов и согласованным порядком блокировок. Удаление завершённых заданий должно учитывать их роль в проверке актуальности проекций.
  • До удаления определить, какие факты review и даты нужны для аналитики этапа 2.6. Сохранять их либо явно ограничивать доступный период отчёта покрытой историей.

Проверить гонки с ingestion, ручным review, связыванием Incident, запросом новой генерации и публикацией worker. Повторить прерванную очистку и старые запросы на PostgreSQL/SQLite. Проверить границы срока хранения и сохранность расследований.

Этап завершён, когда размер незащищённой истории ограничивается заданной политикой, повтор очистки безопасен, а причины удержания и объём удаления видны администратору.

15.3. Этап 2.3 — Объяснение предложений и временная шкала#

Переиспользовать сохранённое evidence расчёта и отдельный снимок ручного review.

  • Показывать прямое изменение сервиса, заявленное влияние или путь зависимостей, известность окружения, временной разрыв и применённое окно. Пояснение должно соответствовать конкретной версии предложения.
  • Добавить общую временную шкалу интервала Change и выбранного эпизода. Неизвестное начало, незавершённая операция и противоречивые времена получают явное представление.
  • Проверять доступ к каждому раскрываемому сервису и переходу пути. Для закрытых участков выдавать разрешённое обобщённое объяснение. После изменения прав заново проверять как текущую проекцию, так и сохранённый снимок review.
  • При устаревании расчёта показывать состояние пересчёта. Основания принятого человеком решения доступны как исторический снимок с временем расчёта и review.

Проверить одинаковые основания на страницах Change/AlertGroup/Incident, скрытый промежуточный сервис, отзыв доступа, смену графа после review, разные часовые пояса, неизвестные границы и узкий экран.

Этап завершён, когда оператор может объяснить предложение по видимым фактам и отличить текущий расчёт от оснований сохранённого решения.

15.4. Этап 2.4 — Политика корреляции и наследование#

Перенести окно корреляции и горизонт автоматического поиска из констант в настройки БД. Начальные значения соответствуют первой итерации — два часа и семь дней.

  • Использовать общую модель системных настроек. Окно корреляции выбрано первым параметром с групповым переопределением по архитектуре наследования. Горизонт поиска первоначально действует глобально как ограничение объёма обработки.
  • Согласовано 7 ноября 2026 года — окно определяется настройками группы сервиса выбранного эпизода алерта. При отсутствии группового значения применяется системное. Каждая целевая группа управляет поиском причин своих алертов при изменениях из любой разрешённой группы.
  • Показывать действующее значение и его источник, дать явный возврат к наследованию. Изменения проверяют полномочия и фиксируют аудит. Поисковая работа получает ревизию/снимок применённой политики, который проверяется при публикации вместе с остальными входами.
  • Смена окна, горизонта или наследования запрашивает ограниченный пересчёт допустимой истории. Уже сохранённый review удерживает свои основания. Окно проверяется для каждого целевого эпизода, даже когда один Change затрагивает несколько групп.
  • Заменить JSON-поле исключений в Service на редактор source/labels. Добавить предварительную проверку правил на доступных алертах с объяснением совпадения. Сохранить правило участия группы при наличии хотя бы одного неисключённого ребёнка выбранного эпизода.

Проверить один Change из третьей группы и эпизоды двух целевых групп с разными окнами, наследование и его сброс, совпадение явного и системного значения, конкурентную смену политики во время расчёта, отозванные права и эквивалентность редактора существующему matcher.

Этап завершён, когда настройки применяются без перезапуска, результат объясняет использованную политику, а устаревший worker не публикует расчёт по прежним значениям.

15.5. Этап 2.5 — Поддерживаемые отправители CI/CD#

Выбрать первую систему по реальным пайплайнам пользователя и поставить поддерживаемый шаблон отправки через существующий POST /api/change-events. Список первых систем фиксируется перед реализацией этапа.

  • Определить идентичность операции для deployment job, окружения и попытки запуска. Сетевой повтор сохраняет event_id и содержимое. Новое исполнение после повторного запуска pipeline получает новую идентичность Change. Отдельно описать parallel/matrix jobs и rollback.
  • Поддержать started, succeeded, failed, cancelled и rolled_back там, где отправитель располагает достоверным событием. Пропущенный финал после принудительного завершения job должен быть виден в диагностике.
  • Учитывать Retry-After, ограничивать повторы и явно различать ошибки авторизации/валидации и временный сбой. Перед реализацией согласовать, влияет ли недоступность IR на результат pipeline.
  • В интеграции формировать пример с её Source, разрешёнными ссылками и переменной секрета. Проверка подключения отправляет событие через обычную машинную авторизацию в выделенный тестовый Source/Service. Тестовые данные должны быть различимы для оператора и аналитики; способ обозначения фиксируется в контракте шаблона.

Проверить сгенерированный пример через настоящий intake, повтор после потерянного ответа, ротацию токена, одинаковое событие от двух попыток доставки, отмену job и независимость параллельных deployment jobs.

Этап завершён, когда выбранная CI/CD-система проходит путь от шаблона до Change, его истории и разрешённой связи с тестовым эпизодом по воспроизводимой инструкции.

15.6. Этап 2.6 — Качество предложений и рабочие фильтры#

Использовать подтверждения и отклонения для оценки результатов корреляции.

  • Показывать число рассмотренных предложений, доли подтверждённых и отклонённых среди них, число ожидающих решения и время до первого review. Долю считать с явным знаменателем и указанным периодом наблюдения.
  • Разделить автоматические предложения и самостоятельно созданные связи, а также роли cause/context/mitigation. Для отчёта по текущим решениям одна пара Change/эпизод учитывается один раз; повторное редактирование не увеличивает число рассмотренных предложений. Время до review требует сохранённого времени первого предложения.
  • Учитывать текущие права в выборке до агрегирования. Данные, удержанные для аналитики после очистки, подчиняются тому же RBAC. Показывать границы покрытия истории и объём исключённых тестовых событий.
  • Добавить фильтры Inbox по доступной связи с Incident, ожидающим/проблемным привязкам сервисов и состоянию обработки. Комбинации фильтров должны согласовывать список, счётчик и пагинацию.
  • Для сравнения правил учитывать версию движка и политики, чтобы изменение настроек было видно рядом с результатами review.

Проверить нулевой знаменатель, повторное и изменённое решение, ручную связь, несколько эпизодов одного Change, границы периода, очистку истории, фильтры чужих связей и изменение прав между запросами.

Этап завершён, когда оператор может найти изменения, требующие внимания, а администратор оценить качество предложений по воспроизводимым определениям.

15.7. Решения перед реализацией и общая приёмка#

РешениеКогда согласовать
Сроки хранения и защита от повторов, поведение новых событий очищенного Change и запросов за пределами срокаПеред 2.2
Разрешённые диапазоны настроек окна и горизонта поискаПеред 2.4
Первая CI/CD-система, обозначение тестовых данных и влияние сбоя IR на pipelineПеред 2.5

Каждый этап проверяется через обычные API и интерфейс с действующим RBAC. Изменения хранения и конкурентных операций проходят PostgreSQL и SQLite, повторы и откаты после ошибок. Перед приёмкой всей итерации выполнить сквозной сценарий CI/CD → приём → объяснение → review → аналитика → очистка → повтор события. Проверить нагрузку на большой истории, размер порций и планы запросов. Выпускные проверки §12.10 применяются к итоговому артефакту второй итерации.