Тема
Сквозной контекст события
Единственный источник истины для передачи связанных данных через торговый тракт: от обнаружения события до сигналов, заявок, наливов и позиций. Документ определяет зоны ответственности компонентов и требования к распределению данных. Точные wire-контракты, поля внешних сообщений, пагинация, управление подписками и процедуры восстановления устанавливаются и проверяются контрактными probes в change, который реализует соответствующий источник.
Назначение и граница
Торговый тракт строится вокруг одного логического контекста события. Контекст начинается с полной ревизии Gamma-события и последовательно обогащается фактами каждой стадии. Модуль получает типизированный входной контекст, принимает только своё решение и возвращает новый контекст с добавленным результатом.
Это требование решает четыре задачи:
- ни один модуль не восстанавливает связь рынка, решения и исполнения по времени, слагу или неявному глобальному состоянию;
- все рынки одного события остаются вместе при обработке и шардировании;
- разные стратегии переиспользуют одну фактическую ревизию события без копирования её рынков и без смешивания результатов стратегий;
- новый обработчик подключается к типизированной стадии, а не собирает собственный неполный контекст из SQLite, кэшей и внешних API.
Контекст не является одной изменяемой структурой, которую параллельно переписывают несколько задач. Единый означает одну причинную цепочку и один контракт данных. Физически цепочка состоит из неизменяемых ревизий и независимых ветвей.
Термины
EventRevision— полная атомарная фактическая ревизия Gamma-события с ключомevent_id; определяется в discovery.md.EventContext<Stage>— неизменяемая оболочка одной стадии обработки. Она несёт ссылку на исходнуюEventRevision, идентичность ветви и типизированные результаты уже пройденных стадий.StrategyEventContext<Stage>— ветвьEventContext, принадлежащая одному экземпляру стратегии.StrategyServices— доступ стратегии к портам движка, времени, фактическим представлениям и операциям. Это сервисная поверхность, а не передаваемый контекст события. Целевой контракт не должен называть еёStrategyContext, потому что это смешивает зависимости модуля с данными конкретного решения.
Точные имена Rust-типов могут измениться при реализации. Нормативны границы, неизменяемость, причинные ссылки и направления передачи.
Каноническая модель
Концептуальная форма контекста:
rust
struct EventContext<Stage> {
event: Arc<EventRevision>,
branch: ContextBranch,
lineage: ContextLineage,
stage: Stage,
}
enum ContextBranch {
Canonical,
Strategy {
strategy_instance_id: StrategyInstanceId,
},
}
struct ContextLineage {
event_id: EventId,
event_revision: u64,
parent_stage: Option<StageIdentity>,
}Stage содержит только добавление текущей стадии и типизированные ссылки на предыдущие результаты. В нём не создаётся второй экземпляр EventRevision. Идентификаторы signal_uid, order_uid, fill_id и position_uid добавляются тогда, когда соответствующий факт действительно появился; до этого они отсутствуют, а не заполняются правдоподобным значением.
Контекст является append-only на уровне смысла:
- входная ревизия не изменяется;
- стадия не переписывает результат предыдущей стадии;
- отказ, фильтрация, отсутствие сигнала и ошибка являются типизированным результатом стадии, а не исчезновением контекста;
- асинхронное подтверждение заявки или новый налив создают следующую ревизию контекста с причинной ссылкой, а не меняют ранее опубликованное сообщение.
Zero-copy и владение памятью
Полный контекст доступен каждому модулю логически, но не копируется целиком между модулями. Крупные неизменяемые данные разделяются ссылками:
EventRevisionи упорядоченный массив рынков принадлежатArc;- неизменяемые снимки книги, факты рынка и крупные массивы также передаются через
Arc, когда их время жизни пересекает границу задачи; - клонирование контекста клонирует счётчики ссылок и малые идентификаторы, а не рынки, книги и историю решений;
- запись новой стадии выделяет только данные этой стадии;
- изменяемая общая карта, доступная нескольким стратегиям или шардам, запрещена.
Arc применяется только к неизменяемому снимку. Он не превращает изменяемый объект в безопасную модель и не разрешает внутреннюю мутацию через общий lock. Обновление внешнего факта создаёт новую ревизию, после чего старая остаётся согласованным снимком для уже начатых решений.
Zero-copy является внутрипроцессной гарантией. Между процессами данные неизбежно сериализуются и копируются транспортом; отдельный издатель может сохранить одну каноническую модель и порядок ревизий, но не может обещать общее адресное пространство. Поэтому несколько движков стратегий внутри процесса разделяют Arc, а будущий межпроцессный shared-market-data-publisher передаёт целые типизированные ревизии с проверкой идентичности и порядка.
Источники данных, владельцы и потребители
Внешний клиент, доменная модель и потребитель являются разными границами. Клиент владеет соединением, протоколом, разбором ответа, авторизацией и общим бюджетом запросов. Доменный модуль один раз преобразует внешний факт и публикует неизменяемую ревизию. Потребитель подписывается на нужный доменный факт или ревизию, а не создаёт собственный клиент и не разбирает тот же ответ повторно.
| Источник | Владелец преобразования | Публикуемый доменный результат | Потребители |
|---|---|---|---|
| Gamma API | discovery через единый GammaClient | Полная EventRevision, индексы event_id / condition_id / token_id | Шардирование, профили вселенной стратегий, гидратация последующих источников |
| Data API | Портфельная модель чтения через единый DataClient | Активность кошелька и внешние портфельные представления | Дашборд и аналитика; не стратегия и не авторитет торгового состояния |
| CLOB API | OrderGateway, Reconciliation и поставщик точечных рыночных снимков через общий ClobClient | Результат торговой операции, результат сверки или запрошенный снимок | OrderRouter, lifecycle заявки, восстановление материализованных представлений |
| WS Market | MarketWsClient и типизированный распределитель | Lifecycle рынка отдельно от фактов книги, цены, сделки и tick size | discovery получает lifecycle; market_data получает рыночные факты |
| WS User | UserWsClient и материализатор собственного lifecycle | Факты собственных заявок и наливов с причинной идентичностью | OrderLifecycle, PositionRegistry, сверка и защитные потребители |
| WS RTDS | RtdsClient и материализатор торговых свидетельств | Публичные сделки и типизированное TradeEvidence | EventShard, риск-модуль, защитный обработчик и аналитика |
Единство клиента означает одного владельца соединений, авторизации, повторов и ограничения запросов на источник и совместимую область авторизации, а не один универсальный клиент всех протоколов. Он может владеть пулом соединений или шардами, если этого требует внешний контракт, но потребители не создают параллельные независимые клиенты в обход этого владельца. Авторизованные User WS и CLOB-операции изолируются по кошельку или учётной области.
Клиент отвечает только за внешний протокол: соединение, авторизацию, rate limit, разбор wire-формы и транспортные ошибки. Материализатор источника отвечает за преобразование разобранного сообщения в доменный факт и за состояние, которое строится из последовательности таких фактов. Бизнес-потребитель получает только доменный результат и не знает wire-форму внешнего API.
HTTP-ответы не превращаются в глобальный сырой поток. Владелец операции вызывает общий клиент и публикует результат конкретной операции: discovery публикует EventRevision, сверка — типизированный результат полноты и расхождений, OrderGateway — результат команды. Это не позволяет случайному подписчику повторно интерпретировать внешний ответ и принимать второе бизнес-решение по тем же данным.
Схема распределения по зонам ответственности
text
Gamma API ──▶ GammaClient ──▶ discovery ───────────────▶ EventRevision ─┐
WS Market ─▶ MarketWsClient ─▶ lifecycle ──────────────▶ discovery │
└▶ market facts ─▶ market_data ───────────┤
WS RTDS ───▶ RtdsClient ───────▶ TradeEvidence ─────────────────────────┤
▼
EventShard(event_id)
│
▼
EventLiveRevision
│
┌──────────────────────────┼──────────────┐
▼ ▼ ▼
Strategy Risk Protection
WS User ───▶ UserWsClient ───▶ OrderLifecycle ───▶ OrderRouter / PositionRegistry
CLOB API ──▶ ClobClient ─────▶ commands, snapshots and reconciliation ────────┘
Data API ──▶ DataClient ─────▶ PortfolioReadModel ─▶ Dashboard / AnalyticsСтрелка обозначает передачу типизированного доменного результата, а не прямую зависимость потребителя от внешнего SDK. Один источник может питать несколько доменов, но каждое сообщение разбирается один раз, а каждый материализованный факт имеет одного писателя.
Разветвление WS после единственного разбора
Каждый кадр WS разбирается и нормализуется один раз, после чего подписчики получают одну неизменяемую оболочку по Arc:
rust
struct SourceEnvelope<T> {
source: DataSource,
source_ts: Option<UnixMillis>,
received_at: Instant,
payload: T,
}Точные поля непрерывности соединения будут определены после отдельного решения о разрывах. Нормативно уже сейчас:
- подписка клонирует
Arc, а неpayloadи его коллекции; - клиент публикует типизированные темы, а не заставляет каждого потребителя фильтровать полный сырой поток;
NewMarketиMarketResolvedполучает lifecycle-потребительdiscovery;- book, price change, best bid/ask, last trade и tick size получает
market_data; - User WS получает
OrderLifecycle, а RTDS —TradeEvidence; - добавление нового потребителя не создаёт второе внешнее соединение и второй разбор того же кадра.
market_data является единственным писателем живого состояния рынка, а TradeEvidence — единственным писателем своего представления публичных сделок. После применения входного факта каждый публикует собственную неизменяемую ревизию. Владелец EventShard(event_id) последовательно принимает эти ревизии и составляет контекст события из ссылок на их последние согласованные версии:
rust
struct EventLiveRevision {
event: Arc<EventRevision>,
market_data: Arc<EventMarketData>,
trade_evidence: Option<Arc<TradeEvidenceRevision>>,
revision: u64,
}EventShard не изменяет внутренние представления источников и не становится их вторым писателем: он создаёт новую оболочку только при получении новой ревизии. При изменении одного рынка создаётся новое состояние только этого рынка и новая оболочка события; неизменившиеся рынки, свидетельства и Gamma-метаданные разделяются через Arc. Стратегия поэтому читает полное последнее состояние связанного события, не собирая соседние рынки отдельными запросами.
Разные гарантии доставки
Одна политика очереди для всех потребителей запрещена, потому что снимок состояния и необратимый факт имеют разную семантику:
discoveryполучает редкие lifecycle-факты без объединения;- стратегия получает последнюю целую
Arc<EventLiveRevision>; уведомления о промежуточных состояниях могут объединяться только после того, как каждый входной кадр применён единственным писателем; - риск-модуль и защитный обработчик получают каждый значимый факт в порядке применения и прямую read-only ссылку на ту же текущую ревизию;
- User WS fills, изменения lifecycle заявки и RTDS trade evidence не могут исчезать по правилу latest-wins;
- телеметрия и аналитика получают отдельные проекции и не создают противодавление денежному пути.
Прямой доступ стратегии и риск-модуля означает доступ к опубликованной неизменяемой Arc-ревизии. Доступ к внутренней изменяемой карте производителя или удержание общего lock через границу задачи запрещены: это смешивает владение, связывает задержки потребителей и допускает чтение частично применённого события.
Стадии торгового тракта
text
Gamma /events/keyset + /markets/keyset + Market WS
│
▼
Discovery ──▶ EventContext<Discovered>
│
▼
shard(event_id)
│
▼
fork(strategy_instance_id)
│
▼
Strategy ──▶ StrategyEventContext<Decided>
├── 0..N Signal
└── 0..N OrderIntent, каждый с точной ссылкой на Signal
│
▼
SignalRegistry ──▶ StrategyEventContext<SignalsPersisted>
│
▼
OrderRouter ──▶ StrategyEventContext<OrderLifecycle>
│
▼
Exchange events / fill reconciliation
│
▼
PositionRegistry ──▶ StrategyEventContext<PositionLifecycle>Ответственность стадий
| Компонент | Чем владеет | Что получает | Что публикует | Чего не делает |
|---|---|---|---|---|
discovery | Канонический каталог событий, рынков и индексы идентичности | Gamma и lifecycle рынка | EventContext<Discovered> | Не применяет правила допуска конкретной стратегии и не читает книги |
market_data | Последовательно применённое живое состояние рынков события | Рыночные факты WS и запрошенные CLOB-снимки | Arc<EventMarketData> | Не принимает торговых решений и не владеет заявками |
EventShard | Порядок ревизий одного event_id и композицию ссылок на представления источников | EventRevision, рыночные ревизии и TradeEvidence | Целую EventLiveRevision и независимые ветви стратегий | Не копирует представления источников и не принимает торговых решений |
Strategy | Решение одного экземпляра стратегии | Собственную ветвь контекста и read-only рыночную ревизию | Вердикт, Signal, OrderIntent | Не вызывает внешний торговый клиент и не резервирует деньги |
SignalRegistry | Устойчивую идентичность принятого решения | Контекст с сигналом и намерением | Контекст с зафиксированным signal_uid | Не меняет параметры намерения |
OrderRouter | Единый допуск, бюджет, резервацию и маршрутизацию денежных команд | Зафиксированный OrderIntent с полной причинной цепочкой | Планируемую заявку и наблюдаемый результат команды | Не изобретает intent и не считает отправку исполнением |
OrderLifecycle и Reconciliation | Материализованную фактическую судьбу заявки и полноту её восстановления | User WS, результаты CLOB-команд и сверок | Типизированные ревизии заявки, наливы и явные разрывы полноты | Не выводит исполнение из отсутствия данных и не принимает стратегическое решение |
PositionRegistry | Позиции и идемпотентное применение наливов | Контекст с идентифицированным фактом налива | StrategyEventContext<PositionLifecycle> | Не разбирает wire-сообщения и не восстанавливает связь по времени |
| SQLite / EventBus | Устойчивое состояние и доставляемые доменные факты | Проекции соответствующей стадии | Транзакционно сохранённое состояние и outbox | Не становится вторым владельцем бизнес-решения |
| Prometheus / VictoriaMetrics / ClickHouse | Наблюдаемые и аналитические проекции | Уже принятые типизированные результаты | Метрики и строки аналитики с причинными идентификаторами | Не управляет торговлей и не блокирует денежный путь своим отказом |
Discovery
Discovery создаёт EventRevision, разрешает condition_id и token_id обратно в event_id и публикует EventContext<Discovered>. За границу discovery не может выйти рынок без принадлежности к событию. Локальные фильтры стратегии не изменяют каноническую ревизию.
Шардирование и ветвление стратегий
event_id — единственный ключ выбора шарда. Все рынки события и все последующие ревизии этого события попадают одному владельцу. Внутри шарда ревизии одного события обрабатываются последовательно; разные события могут обрабатываться параллельно.
Одна Arc<EventRevision> разветвляется по активным экземплярам стратегий. Каждая стратегия получает собственный StrategyEventContext; её сигналы, намерения, заявки и позиции не записываются в ветвь другой стратегии. Общая фактическая часть доступна всем ветвям только для чтения.
Параллельная оценка разных стратегий допустима лишь до общей денежной границы. Если ветви используют один кошелёк или общий бюджет, все денежные намерения проходят через единый OrderRouter и единый контроль допуска. Порядок арбитража одновременно готовых намерений разных стратегий должен быть детерминирован и отдельно согласован до реализации нескольких активных стратегий; порядок прихода задач планировщика не может быть бизнес-правилом.
Strategy
Стратегия получает полное событие с рынками и доступным рыночным снимком. Она возвращает результат решения, добавляя к своей ветви:
- типизированный вердикт обработки;
- ноль или несколько
Signal; - ноль или несколько
OrderIntent, каждый из которых ссылается на породивший егоSignal.
Signal — зафиксированный факт торгового решения. Он не отправляется на биржу и не преобразуется OrderRouter в заявку автоматически. Стратегия определяет исполняемое намерение — сторону, размер, цену и execution policy — и создаёт OrderIntent. Один сигнал может не породить заявку либо породить несколько намерений.
SignalRegistry
SignalRegistry сохраняет каждый сигнал и возвращает тот же контекст с его устойчивой идентичностью хранения. Только после успешной фиксации связанный OrderIntent может пересечь денежную границу. Ошибка сохранения не позволяет передать намерение в OrderRouter.
OrderRouter и исполнение
OrderRouter получает OrderIntent вместе с полной ветвью контекста, выполняет единый контроль допуска, резервирует деньги, фиксирует планируемую заявку до внешней отправки и добавляет order_uid и наблюдаемый результат отправки.
Подтверждение, отказ, отмена, истечение и налив асинхронны. Каждый такой факт создаёт следующую ревизию той же ветви. Корреляция выполняется по устойчивым идентификаторам контекста, сигнала и заявки; соединение по condition_id и времени запрещено.
PositionRegistry
PositionRegistry получает контекст исполнения с типизированным фактом налива, применяет его идемпотентно и добавляет созданную или изменённую позицию. Контекст позиции сохраняет ссылки на событие, стратегию, сигнал, заявку и конкретный налив. Частичный налив порождает наблюдаемое состояние позиции и не завершает причинную цепочку до фактического терминального исхода заявки и торговой логики стратегии.
Хранилища и наблюдаемость
SQLite, EventBus, Prometheus/VictoriaMetrics и ClickHouse не являются следующими последовательными бизнес-стадиями. Это порты, получающие проекции контекста на тех стадиях, где возник соответствующий факт.
SQLite
SQLite хранит обязательное состояние продолжения торговли. Запись сигнала, заявки, налива или позиции и соответствующий outbox-факт выполняются в одной транзакции. После записи должна сохраняться точная причинная связь минимум по event_id + event_revision + strategy_instance_id и доступным идентификаторам signal_uid, order_uid, fill_id, position_uid. Физическая схема и индексы определяются в storage.md; восстановление связи по близости времени запрещено.
EventBus
После успешного COMMIT модуль публикует типизированную проекцию новой ревизии контекста. Шина переносит уведомление и причинную идентичность, но не становится вторым владельцем доменного состояния. Медленный потребитель восстанавливается из outbox по курсору.
Prometheus и VictoriaMetrics
Метрики получают агрегатную проекцию стадии. В метки не переносятся высококардинальные event_id, signal_uid, order_uid и position_uid. Prometheus/VictoriaMetrics отвечают на вопрос о числовом состоянии системы, но не хранят полный контекст сделки.
ClickHouse
ClickHouse получает неблокирующие типизированные проекции для анализа решений и исполнения. Каждая строка обязана нести все уже существующие причинные идентификаторы своей стадии. Отсутствующий будущий идентификатор хранится как NULL/отсутствие, а не восстанавливается по condition_id и времени.
Отказ аналитического приёмника не блокирует торговлю, но фиксируется как наблюдаемый разрыв. Поэтому ClickHouse не является источником восстановления торгового состояния; им остаётся SQLite.
WS-обогащение и непрерывность
Любой WS-факт сначала нормализуется владельцем источника, затем разрешается в каноническую идентичность события и только после этого обогащает соответствующий доменный контекст. token_id и condition_id являются индексами разрешения, но не заменяют event_id как корень причинной цепочки.
Материализатор применяет входные сообщения последовательно и публикует уже согласованную неизменяемую ревизию. Потребители не применяют deltas самостоятельно и не читают внутреннее изменяемое состояние материализатора. Благодаря этому стратегия, риск-модуль и защитный обработчик видят одну и ту же применённую ревизию, даже если используют разные очереди уведомлений.
Непрерывность определяется отдельно для каждого материализатора, потому что источники несут разную семантику:
- полное состояние может вытеснять предыдущую ревизию;
- delta применима только к доказанно непрерывной базе;
- необратимый факт заявки, налива или торгового свидетельства не может быть объединён по правилу latest-wins;
- потеря, неоднозначность или неполная сверка становятся явным результатом владельца источника, а не скрываются как пустой набор данных;
- стратегия и защитные потребители получают типизированное разрешение на чтение материализованного представления, а не вычисляют пригодность по общему TTL.
Общий DataHealthMonitor, единый статус Fresh/Stale и единый TTL всех данных в модель не входят. Они смешали бы возраст полного снимка, непрерывность delta-потока и полноту необратимых фактов. Нужное состояние принадлежит конкретному владельцу: market_data, OrderLifecycle, TradeEvidence или Reconciliation.
Граница будущих контрактных решений
Этот SSOT намеренно не фиксирует детали, которые должны быть подтверждены при реализации официальным контрактом, живым безопасным наблюдением и постоянным probe-тестом:
- точные поля, единицы времени и варианты перечислений Market WS, User WS и RTDS;
- начальный охват User WS, изменение подписки внутри соединения и наличие ответного подтверждения;
- пагинация, признак полноты и терминальный cursor CLOB/Data/Gamma API;
- конкретный источник восстановления после разрыва каждого WS-потока;
- переход торгового допуска при неполной или неуспешной сверке;
- поведение защитного SELL при недоступности нужного рыночного представления.
Каждое такое решение оформляется в change реализации соответствующего владельца, а не добавляется сюда как предположение. Probe обязан отдельно доказывать внешний контракт и реакцию нашей системы; mock внешнего сервиса доказывает только наш исходящий wire и внутренний control flow.
Проверяемые требования
event_idи номерEventRevisionсохраняются от discovery до позиции и строки аналитики.- Все рынки одного события имеют один shard owner; fallback на
event_slugилиcondition_idзапрещён. - Две стратегии получают одну
Arc<EventRevision>, но разные ветви результатов. - Модуль не может изменить входной контекст; он возвращает новую типизированную стадию.
OrderRouterне принимает голыйSignal: он принимает связанныйOrderIntentи контекст.- Налив однозначно разрешается в событие, стратегию, сигнал, заявку и позицию без временного соединения.
- Повтор одного внешнего факта идемпотентен и не создаёт вторую заявку, второй налив или вторую позицию.
- Отказ SQLite останавливает соответствующую торговую операцию; отказ метрик или ClickHouse не меняет торгового решения и оставляет наблюдаемый разрыв.
- Тест параллельной обработки подтверждает последовательность ревизий одного
event_idи независимость разныхevent_id. - Тест нескольких стратегий подтверждает отсутствие записи результата одной ветви в другую и отсутствие глубокого копирования общей
EventRevision. - Добавление второго WS-потребителя не создаёт второе соединение и не выполняет повторный разбор кадра; оба получают один
Arc. - Тест объединяемого потребителя подтверждает, что все входные deltas сначала применены к состоянию, а опубликованная последняя ревизия совпадает с последовательным применением исходного потока.
- Тест lossless-потребителя подтверждает, что fill и RTDS evidence не вытесняются следующим событием; переполнение становится явным разрывом, а не молчаливой потерей.
- Каждый внешний источник имеет одного владельца соединений и преобразования; добавление потребителя не создаёт второй независимый клиент в обход него.
- Реализация каждого источника содержит постоянные probes точного внешнего контракта, полноты данных и реакции системы на разрыв; эти детали не выводятся из общей модели контекста.
Текущее состояние относительно цели
Контракт выше является целевой архитектурой и ещё не реализован. На дату фиксации:
- discovery разворачивает Gamma-события в рынки и теряет
event_id; - шардирование использует
event_slugс fallback наcondition_id; - стратегия получает
MarketDataSnapshot, а не событийный контекст; - причинная связь становится устойчивой только с
signal_uid; - SQLite и ClickHouse не хранят
event_idи номер ревизии по всей цепочке; selection_verdictsсвязывает решение с будущим сигналом по рынку и времени;- WS-клиент Market уже разделяет один
Arc<MarketEvent>между глобальным и per-asset потоками, но единый типизированный data plane для всех WS-источников отсутствует; - текущий снимок книги возвращается копированием
Book, а downstream снова собирает одиночныйMarketDataSnapshotпоtoken_id; - RTDS выдаёт одному адаптеру единственный
mpsc::Receiver, а ветвление потребителей выполняется внутри этого адаптера; - контракты непрерывности Market WS, User WS и RTDS ещё не сведены с новым событийным контекстом.
Эти пункты являются разрывом current → target, а не описанием уже работающей системы.
Обоснование решения
- Неизменяемые ревизии выбраны вместо общей изменяемой структуры, потому что разделяемая мутация смешивает результаты стратегий и требует блокировок на горячем пути.
Arcи типизированные добавления выбраны вместо полного копирования: событие и его рынки читаются многими стадиями, но не должны многократно размещаться в памяти.- Ветвление по стратегии происходит после шардирования события: так стратегия видит полную связанную группу рынков, а общая фактическая модель не зависит от торговой политики.
OrderIntentостаётся результатом стратегии, потому что цена, размер и политика исполнения являются частью торгового решения.OrderRouterотвечает за безопасность и исполнение, а не за изобретение намерения из сигнала.- Наблюдаемость подключена как боковая проекция, чтобы отказ ClickHouse или метрик не увеличивал задержку и не останавливал торговый путь.
Связанные документы
- discovery.md — формирование
EventRevision, индексы и шардирование. - engine.md — денежная граница, OrderRouter и lifecycle.
- extension-points.md — контракт стратегии.
- storage.md — обязательное состояние и outbox.
- observability.md — роли SQLite, VictoriaMetrics и ClickHouse.