Перейти к содержанию

agate-audit

Ограниченный контекст аудита: журнал прозрачности с возможностью только добавления, смоделированный как дерево Меркла в стиле RFC 6962.

agate-audit записывает каждую пару (событие, вердикт), произведённую прокси, в защищённый от подделки журнал с возможностью только добавления. Вместо наивной хеш-цепочки он использует дерево Меркла RFC 6962, которое поддерживает эффективные доказательства включения и согласованности.

Ответственность

  • Добавлять записи и поддерживать дерево Меркла над ними.
  • Производить подписанную вершину дерева (корень, размер и эпоху алгоритма, подписанные).
  • Отвечать на доказательства включения (запись i находится в дереве с вершиной H) и согласованности (вершина H₂ является добавочным расширением H₁).
  • Оставаться проверяемым через крипто-эпохи (см. agate-crypto).

Журнал прозрачности на дереве Меркла

flowchart TD
    root["корневой хеш<br/>(подписанная вершина дерева)"]
    n01["hash(0,1)"]
    n23["hash(2,3)"]
    l0["лист 0<br/>(событие,вердикт)"]
    l1["лист 1"]
    l2["лист 2"]
    l3["лист 3"]
    root --> n01
    root --> n23
    n01 --> l0
    n01 --> l1
    n23 --> l2
    n23 --> l3

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

Язык домена

  • TransparencyLogкорень агрегата (встраивает коллекцию доменных событий).
  • Меркловы значения, сущности, сервисы (хеширование) и фабрики под domain/merkle/.
  • Доменные порты: Clock, IdGenerator.

Слои

Слой Содержимое
domain Чистые сущности, объекты-значения и доменные сервисы (хеширование Меркла, агрегат TransparencyLog, доказательства). Без I/O.
application Сценарии CQRS (командные/запросные обработчики) над конвейером-медиатором; конвейерные поведения (TransactionBehavior, MetricsBehavior); исходящие порты (KeyStore, CheckpointAnchor, EventOutbox, TransactionManager, AuditMetrics и шлюзы журнала по CQRS).
infrastructure Конкретные адаптеры: SystemClock, UuidLogIdGenerator, PostgresLog{Command,Query}Gateway, управление транзакциями, миграции, Ed25519KeyStore, PostgresCheckpointAnchor.
presentation HTTP-обработчики (health, версионированные маршруты) и отображение AuditError → HTTP.
setup Корень композиции: типизированная конфигурация из окружения, IoC-контейнер froodi, бутстрап HTTP.

Персистентность разделена по CQRS: командный шлюз загружает/сохраняет агрегат (сторона записи); запросный шлюз возвращает модели чтения/DTO (сторона чтения). Крейт зависит внутрь от agate-crypto ради хеширования и подписи.

Checkpoint'ы (подписанные tree head)

Checkpoint — это подписанный tree head: корень Меркла и размер на момент времени, подписанные так, чтобы любой мог проверить состояние журнала и выявить подделку. POST /logs/{id}/checkpoint с телом {"key_id": "…"} запускает команду IssueCheckpoint: снимает head, подписывает его настроенным ключом Ed25519, анкорит (публикует вовне), сохраняет агрегат и возвращает подписанный tree head (размер, корень, метка времени, id ключа, алгоритм, подпись — бинарное в hex).

Ключ подписи берётся из окружения — 32-байтовый seed AUDIT_CHECKPOINT_SEED (64 hex-символа) под AUDIT_CHECKPOINT_KEY_ID (по умолчанию checkpoint-ed25519). Без настроенного seed запросы checkpoint падают с понятной ошибкой, а не подписываются эфемерным ключом, которому никакой верификатор не сможет доверять между перезапусками. Порт CheckpointAnchor — это шов для независимого свидетеля (защита от split-view / двусмысленности). Адаптер по умолчанию (PostgresCheckpointAnchor) долговечно сохраняет каждый подписанный tree head в таблицу audit_checkpoint — на той же request-транзакции, что и выпуск, поэтому checkpoint и снимок коммитятся атомарно — и логирует его; клиент внешнего свидетеля позже подключается за тем же портом.

Наблюдаемость

Метрики добавления — это прикладная логика, скрытая за портом, а не вызовы counter!, разбросанные по коду. Порт AuditMetrics вызывается из MetricsBehavior в конвейере-медиаторе, зарегистрированного самым внешним на AppendRecord, чтобы учитывать исход после того, как поведение транзакции зафиксировало или откатило её: один agate_audit_records_appended_total при успехе, один agate_audit_records_dropped_total при ошибке. Инфраструктурный адаптер пишет через фасад metrics; модульные тесты гоняют поведение с фейком. Записи, отброшенные до конвейера (сбой открытия scope в outbox, backpressure в sink), учитываются через тот же порт на стороне сервера.

Инварианты и тестирование

Round-trip доказательств Меркла и отклонение подделки покрываются proptest. Шлюзы на базе БД тестируются с testcontainers в слое infrastructure.