# Таймлайн действий агента: сравнивайте временные метки без самообмана

Таймлайн действий агента способен убедительно лгать, даже если каждая система записала честную временную метку. Ложь появляется, когда расследующий принимает отображение местных часов, время получения API-сервером и запись аудита за взаимозаменяемые доказательства одного момента.

Храните временные метки такими, какими их увидел источник, вместе с числовым смещением и информацией об источнике. Затем сравнивайте их как отдельные часы с разным смыслом. Данных потребуется немного больше, чем для одного поля `created_at`, зато вы избежите привычного отчета об инциденте, где агент якобы действовал до получения одобрения или API-запрос завершился раньше, чем начался.

## Одной временной меткой действие не описать

У действия бывает несколько значимых моментов. Агент может принять решение вызвать API в один момент, отправить байты позже, еще позже достичь сервера и получить результат после завершения работы сервером. Каждый из этих моментов важен, когда нужно понять, вышел ли агент за пределы своих полномочий.

Обычно запись содержит как минимум такие утверждения:

- Процесс агента сообщает, когда начал действие.
- Сервер-получатель сообщает, когда принял запрос.
- Сервер-получатель может сообщить, когда зафиксировал или завершил работу.
- Шлюз действий сообщает, когда получил и выпустил запрос.
- Человек может видеть местное время в пользовательском интерфейсе.

Это не разные версии одного поля. Они описывают разные точки причинной последовательности. Если при сборе свести их к одной нормализованной временной метке, вы потеряете различия, которые объясняют очереди, сетевые задержки, повторы, ожидание одобрения и долгие вызовы.

Я видел, как команды называли запись аудита API временем, когда агент «сделал дело», хотя запись показывала только принятие запроса. Ошибка становится дорогостоящей, если конечная точка поставила работу в очередь и выполнила изменение намного позже. Агент мог остановиться до изменения, но именно его запрос все равно его вызвал.

Используйте названия, описывающие событие. `agent_action_started_at`, `gateway_received_at`, `server_received_at` и `server_completed_at` заставляют читателя спросить, что произошло в каждой точке. Расплывчатое поле `timestamp` побуждает людей придумывать ответ задним числом.

## Смещение сохраняет момент, часовой пояс объясняет отображение

Числовое смещение UTC превращает показание часов в конкретный момент. Названный часовой пояс объясняет гражданские правила, по которым это показание появилось. Часто нужны оба значения, но задачи у них разные.

Сравним два значения:

```text
2025-11-02T01:30:00-04:00
2025-11-02T01:30:00-05:00
```

Циферблат в обоих случаях показывает 1:30 ночи. Но моменты разделяет час. При осеннем переходе на стандартное время в Северной Америке местный час повторяется, и значение без смещения, такое как `2025-11-02 01:30:00`, не позволяет понять, какой именно момент наступил.

RFC 3339 решает эту проблему напрямую. Формат временной метки содержит полную дату и время, а также `Z` для UTC или числовое смещение. RFC допускает и `-00:00`, означающее, что источник знает время, но не знает местное смещение. Это полезное свидетельство. Не переписывайте молча `-00:00` в `Z`: UTC утверждает факт, которого источник не утверждал.

Идентификатор зоны вроде `America/Los_Angeles` тоже стоит хранить, если одобрение человека, тикет поддержки или запись экрана ссылаются на местное офисное время. Он позволяет воспроизвести календарные правила, действовавшие в этом месте. Но он не заменяет смещение. Правила зон могут меняться, а одна и та же зона имеет разные смещения в течение года.

Сохраняйте полученную временную метку как строку, не меняйте исходное смещение и выводите момент UTC для сортировки. Не храните только отрисованную строку местного времени. Отображение должно происходить на границе системы, когда человек выбирает зону показа.

## Местные, серверные и аудиторские часы отвечают на разные вопросы

Местное время показывает, каким его считали оператор или хост агента. Серверное время показывает, когда удаленная служба увидела или выполнила работу. Время аудита показывает, когда система учета приняла событие. При расследовании нужно сравнивать все три значения, а не объявлять одно универсальной истиной.

Начните с границы события. Если агент отправляет `POST /deployments`, его местное время действия может подтверждать намерение. Время получения сервером показывает, когда удаленная служба стала отвечать за запрос. Время завершения сервером может показать, когда изменилось состояние развертывания. Запись аудита показывает, когда ваша контрольная точка заметила попытку и одобрила или отклонила ее.

Сетевая задержка создает нормальные разрывы между этими значениями. Очереди создают еще большие разрывы. Повторы усложняют картину: клиент может использовать один идентификатор действия для нескольких попыток, а сервер записывает каждую отдельно. Таймлайн, в котором показывается только первое местное время, все это скрывает.

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

Практическое сравнение можно вести в трех колонках рабочего листа расследования:

| Источник доказательств | Что сохранить | На какой вопрос отвечает |
|---|---|---|
| Хост агента | Исходная местная временная метка, смещение, зона, идентификатор процесса | Когда этот процесс утверждает, что начал действие или получил результат? |
| Удаленная служба | ID запроса, время получения, время завершения, статус ответа | Когда служба приняла запрос и выполнила работу? |
| Система аудита | ID события, записанное время, доказательство целостности, результат авторизации | Когда контрольная точка увидела вызов и разрешила или запретила его? |

У каждой строки должны оставаться собственные времена, даже после добавления расчетной колонки UTC для сортировки. Аккуратная таблица с одной колонкой времени выглядит удобно, но скрывает цепочку доказательств.

## Переходы на летнее время создают повторяющиеся и пропускаемые часы

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

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

Такая запись пригодна для работы, потому что содержит и точный момент, и гражданский контекст, из которого он получен:

```json
{
  "event_id": "act_8f3c",
  "event": "authorization_granted",
  "observed_at": "2025-11-02T01:14:22-04:00",
  "zone": "America/New_York",
  "instant_utc": "2025-11-02T05:14:22Z",
  "clock_source": "agent_host"
}
```

Поле `instant_utc` вычисляется, поэтому сохраняйте и исходную строку `observed_at`. Если позже изменится парсер или ошибка повлияет на преобразование, вы сможете повторить расчет и объяснить разницу. Производные значения нужно считать результатом анализа, а не заменой исходным доказательствам.

Сокращение зоны вроде `EST` только усложняет ситуацию. Сокращения неоднозначны в разных регионах и ненадежно показывают, действовало ли летнее время. Используйте идентификатор зоны IANA для гражданского контекста и числовое смещение для момента. Если источник выдает только сокращение, запишите его как есть и отметьте, что его смысл пока не установлен.

Для повторяющихся расписаний нужно отдельное правило. Храните расписание как местное время плюс зону IANA, затем рассчитывайте каждое вхождение по правилам этой зоны. Фактически выполненное действие храните как временную метку со смещением. Расписание говорит, когда задание должно было запуститься, а запись события - когда оно запустилось.

## Дрейф часов превращает точный на вид порядок в ложную уверенность

Точность до миллисекунд не означает точность до миллисекунд. Несинхронизированный ноутбук может выдавать временные метки с шестью знаками после запятой, отставая от сервера на несколько минут. Сон устройства, потеря сети, виртуальные машины и плохая синхронизация создают такую проблему.

Разделяйте точность записи и неопределенность в модели данных. Точность - это число записанных цифр. Неопределенность - это интервал, в котором, по вашему мнению, находится настоящий момент. Временная метка хоста `10:00:00.123Z` с неопределенностью в две секунды не должна разрешать спор о порядке событий, разделенных одной секундой, с удаленным API.

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

Например, сборщик отправляет запрос в местное `10:00:00.000`, получает доверенный ответ в местное `10:00:00.200`, а в ответе указано `10:00:00.150Z`. Серверное время произошло где-то в пределах обмена. Середина местного интервала - `10:00:00.100`, поэтому при обычном предположении о симметричной передаче хост отстает примерно на 50 миллисекунд. Это предположение может быть неверным, поэтому полезный результат - диапазон, а не утверждение истины.

Монотонные часы решают более узкую задачу. Они измеряют прошедшее время внутри одного работающего процесса и не скачут при изменении календарного времени. Когда нужно доказать, что действие B последовало за действием A в пределах одного процесса, записывайте значение монотонных часов при старте и длительность вместе с обычным временем. Не преобразуйте монотонные значения в UTC и не сравнивайте их между хостами.

Документация NTP проводит похожее практическое различие: синхронизация оценивает смещение и рассеяние, а не дарит идеальное время. Если порядок событий важен, считайте эти оценки частью доказательств.

## Храните исходные данные и нормализованное время в одной записи

Защищаемая схема событий сохраняет то, что фактически сообщил каждый участник, и делает анализ воспроизводимым. Следующая форма JSON подходит для действия агента и не притворяется, будто все поля пришли от одних часов.

```json
{
  "action_id": "a91c2d7e",
  "attempt": 2,
  "agent": {
    "process_id": "p_4b71",
    "started_at": "2025-04-18T14:07:12.481-07:00",
    "zone": "America/Los_Angeles",
    "monotonic_start_ms": 9184421,
    "clock_uncertainty_ms": 750
  },
  "gateway": {
    "received_at": "2025-04-18T21:07:12.661Z",
    "authorized_at": "2025-04-18T21:07:14.034Z",
    "result_released_at": "2025-04-18T21:07:14.882Z",
    "audit_event_id": "aud_3e90"
  },
  "server": {
    "request_id": "req_7c19",
    "received_at": "2025-04-18T21:07:14.301Z",
    "completed_at": "2025-04-18T21:07:14.649Z",
    "status": 201
  },
  "normalization": {
    "sort_instant_utc": "2025-04-18T21:07:12.481Z",
    "method": "RFC3339 offset conversion"
  }
}
```

Эта схема проводит границу, которую команды часто размывают: идентификатор действия связывает записи, а временная метка упорядочивает одно событие внутри действия. Использовать один идентификатор после повтора разумно. Использовать одну временную метку для всех этапов нельзя.

Записывайте ID запроса сервера, даже если у вас уже есть внутренний ID действия. Во время расследования собственный идентификатор сервера часто оказывается единственным надежным способом отличить запрос, завершившийся по тайм-ауту, от запроса, который так и не покинул клиент.

Не храните только миллисекунды эпохи, если производитель не гарантирует UTC и не документирует источник часов. Значения эпохи легко сортировать, но они теряют исходное смещение, контекст отображения, а иногда и единицу измерения. Если принимаете их от сторонних систем, явно записывайте единицу и сохраняйте полученное представление.

## Таймлайну нужны интервалы, когда доказательства перекрываются

Если у двух источников есть неопределенность, рассчитывайте интервалы, а не навязывайте порядок. Это предотвращает распространенную ошибку: расследующий видит `10:03:01.010` и `10:03:01.400`, сортирует их и утверждает, что первое событие вызвало второе, хотя смещение часов двух машин неизвестно.

Допустим, агент сообщает о начале действия в `21:07:12.481Z` с неопределенностью 750 миллисекунд. Возможный интервал идет от `21:07:11.731Z` до `21:07:13.231Z`. Шлюз сообщает о получении в `21:07:12.661Z` с неопределенностью 20 миллисекунд. Интервалы перекрываются, поэтому одни временные метки не доказывают, что шлюз получил вызов после заявленного начала. Протокол может поддерживать такой вывод, но календарные часы сами по себе нет.

Указывайте основание каждого утверждения о порядке. Это разные выводы:

- «Шлюз записал получение после отправки запроса агентом» следует из протокольных доказательств или связанной трассировки запроса.
- «Временная метка получения шлюзом позже» следует только из отображаемых показаний часов.
- «Записи устанавливают порядок с учетом заявленной неопределенности» можно утверждать, когда интервалы не перекрываются.
- «Записи не позволяют установить порядок» - правильный результат, если интервалы перекрываются и причинные доказательства не заполняют пробел.

Людям не нравится последний вывод, потому что в отчете об инциденте хочется получить аккуратную историю. Выдуманная точность историю не улучшает. Она дает следующему проверяющему повод усомниться во всех выводах рядом с ней.

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

## Одобрение и выполнение должны иметь разные временные метки

Одобрение человека доказывает, что кто-то разрешил возможность в конкретный момент. Оно не доказывает, что агент отправил запрос в ту же секунду, и тем более не доказывает, что удаленная система тогда же завершила работу.

Храните события одобрения отдельно от вызовов. Запись одобрения должна содержать участника, предоставленный объем полномочий, процесс или сессию, к которым оно относилось, время наблюдения и ID события системы авторизации. Запись вызова должна, если это применимо, ссылаться на эту авторизацию и сохранять собственные времена получения и выпуска.

Различие особенно важно для авторизации сессии. Одно одобрение может охватывать несколько действий в течение жизни процесса. Если позднее действие причинит ущерб, расследующему нужно ответить на два отдельных вопроса: когда произошло одобрение и оставался ли процесс в рамках одобренной сессии, когда выполнял этот вызов? Одно поле `approved_at` не отвечает на оба вопроса.

Одобрение каждого вызова создает более строгую последовательность, но пробел все равно остается. Пользователь может одобрить действие в 14:07:14, шлюз отправить его в 14:07:14.1, а удаленная служба зафиксировать результат в 14:08:02. Тайм-аут удаленной системы после отправки не исключает, что служба завершила действие.

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

## Разбирайте спорное развертывание, не смешивая доказательства

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

Доказательства содержат такие записи:

| Событие | Указанное время | Источник |
|---|---|---|
| Одобрение сессии | `2025-04-18T17:58:40-07:00` | Локальная запись авторизации |
| Агент начинает вызов развертывания | `2025-04-18T17:59:02-07:00` | Хост агента |
| Шлюз получает вызов | `2025-04-19T00:59:02.410Z` | Журнал аудита шлюза |
| Удаленная служба принимает запрос | `2025-04-19T00:59:04Z` | Запись аудита службы |
| Удаленная служба завершает развертывание | `2025-04-19T01:01:18Z` | Запись аудита службы |

Преобразуйте первые две записи, но сохраните их исходные формы. Одобрение произошло в `00:58:40Z`, а агент начал действие в `00:59:02Z`. Записи шлюза и службы следуют правдоподобной причинной последовательности. До одобрения ничего не произошло: кто-то просто прочитал `17:58` рядом с `00:59`, словно оба значения показывали одни часы.

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

Расследование должно сохранить оба вывода. Один надежен, другой ограничен. Это лучше, чем делать весь таймлайн одинаково точным на вид.

## Проверяйте целостность аудита, прежде чем доверять его месту во времени

Аудитный журнал может показать, что доказательства изменяли, переставляли или удаляли, только если вы проверите механизм его целостности. Выполните эту проверку до того, как использовать записи аудита для подтверждения последовательности. Затем отдельно выясните, какие часы проставили время каждой записи.

Sallyport создает журналы сессий и вызовов из одного зашифрованного аудита с цепочкой хешей, а `sp audit verify` позволяет офлайн проверить эту цепочку поверх шифротекста. Такая проверка дает расследующему способ проверить непрерывность журнала, не раскрывая сохраненные секреты.

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

Запускайте проверку по копии доказательств и сохраняйте результат команды вместе с материалами дела. Полезная запись содержит команду, идентификатор входного артефакта, результат проверки и человека или автоматическое задание, выполнившее ее. Не вставляйте в тикет только зеленую строку статуса. Следующему расследующему нужно воспроизвести ту же проверку.

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

## Стройте представление расследования на явно объявленных допущениях

Хорошее представление показывает исходные временные метки, преобразования в UTC, идентификатор источника и неопределенность. Оно не прячет преобразование за подписью панели вроде «время события». Читатель должен видеть, почему отображение ставит два события именно в такой порядок.

Используйте эту последовательность при сборке таймлайна действия:

1. Соберите неизменяемые исходные записи и сохраните исходные строки временных меток, идентификаторы и смещения.
2. Определите, какое событие отмечает каждая временная метка: решение, одобрение, получение шлюзом, принятие сервером, завершение или выпуск результата.
3. Преобразуйте временные метки со смещением в UTC в отдельном поле и запишите использованный парсер или метод.
4. Оцените неопределенность часов каждого источника, который может повлиять на спорный порядок.
5. Отсортируйте события по нормализованным моментам, затем проверьте перекрывающиеся интервалы неопределенности и ID запросов, прежде чем делать причинные выводы.

Не преобразуйте местную временную метку, просто добавляя текущее смещение самого расследующего. Такая ошибка сдвигает исторические события, если расследующий работает в другой зоне или правила перехода на летнее время отличаются. Разбирайте смещение, указанное в записи. Если смещения нет, пометьте запись как нерешенную, пока не установите исходную зону и действовавшие в ней правила.

Команды должны проверять это до инцидента. Создайте действие рядом с переходом на летнее время в непроизводственной среде. В безопасных пределах измените часы клиента. Принудительно вызовите повтор и задержанный ответ сервера. Затем попросите человека, который не создавал систему журналирования, восстановить порядок событий. Если ему нужны устные пояснения, чтобы понять доказательства, формат записи неполон.

В инциденте агента время редко бывает единственным доказательством. Идентификаторы запросов, область авторизации, идентификатор процесса, тела ответов и проверки изменений часто устанавливают факты, которых часы не показывают. Храните эти факты вместе с собственными событиями. Следующее спорное действие будет проще объяснить, потому что вы записали, что знала каждая система, вместо того чтобы заставлять все системы соглашаться с одной вымышленной временной меткой.
