Самая дорогая ошибка в сквозной наблюдаемости — принять совпадение имён за общую причинную историю. API log с тем же job name, metric worker и UI span могут относиться к разным попыткам, разным часам и разным пользователям. Цена — ложный incident narrative: команда меняет worker, хотя разрыв был в propagation, или объявляет проблему очередью, хотя metric агрегировала несколько исходов.
В августе 2026 эта статья описывает только будущий сценарий проектирования со срезом источников на 31.07.2026. Никакой trace не собирался, latency не измерялась, sampling не включался. Задача — задать семантику до инструмента: что переносит context, что означает error, где допустимо время, какие labels можно агрегировать и когда ответ обязан быть stop.
Один context не делает сигнал одинаковым
Trace context нужен, чтобы связать позицию операции в графе, а не чтобы сделать span, log и metric тремя форматами одного документа. Span может указывать начало и конец ограниченной операции. Log может пояснить решение в одной ветке. Metric складывает повторяющиеся события. У этих объектов разные единицы анализа и разные риски. Если metric получает trace id как label, она начинает хранить идентификатор в агрегате и теряет назначение; если log требует длительность каждого span, он становится хранилищем timing-модели.
W3C определяет traceparent как переносимый fixed-length format и отдельно различает trace-id и parent-id. В нашей модели это означает только проверяемый инвариант: UI, API и worker должны назвать один fixed context или прекратить построение end-to-end вывода. Не означает, что любая асинхронная очередь автоматически сохраняет parent relation. В реальном протоколе это потребовало бы отдельного контракта message boundary; его здесь нет.
| Объект | Разрешённый вопрос | Нельзя заключать | Fail-closed повод |
|---|---|---|---|
| span | какая named operation следует после другой в fixed model | что пользователь увидел результат | нет context |
| log | какой named outcome-class записан у API | что ошибка уникальна или исчерпана | есть свободный payload |
| metric | сколько synthetic jobs по small class | какая конкретная попытка виновата | label содержит identity |
| sampling policy | какое правило планируется обсуждать | что известны затраты и coverage | нет причины и границы |
| hand-off | материал структурно готов к review | есть выпуск, rollout или production evidence | положительный verb сильнее плана |
Ошибка — это смысл, а не красная точка
У слова error минимум три слоя. Transport failure говорит, что граница вызова не завершилась ожидаемо. Domain rejection говорит, что операция дошла до правила и получила допустимый отрицательный исход. Retryable processing failure говорит, что worker может повторить работу, но не обещает её результат. Если все три записать как error=true, будущий reader увидит ярлык без вопроса: нужно ли искать отсутствие ответа, спор правила или повторный маршрут.
План хранит outcome-class, а не текст исключения. Это не маскировка: классу можно назначить ограниченный словарь и владельца. Текст исключения может содержать input, URL, имя клиента или случайный id; его нельзя делать metric dimension. Если для расследования нужен подробный event, механизм должен быть отделён от этой статьи: другой доступ, другой retention и другой stop, а не новая строка в label map.
Время без часов — сначала модель, затем измерение
Внутри synthetic plan нельзя говорить «операция заняла столько-то» даже условно, если нет определённых clock boundaries. UI время, server receive time и worker processing time не складываются без контекста очереди, ретраев и разных источников времени. Поэтому timing в этой статье обозначает только место будущего вопроса: UI ожидание, API обработка, queue delay и worker execution должны быть разными named segments. Пока таких данных нет, общее число было бы декоративным.
Это важнее, чем кажется. Один длинный root span способен скрыть ожидание доставки и повторную работу; три коротких spans способны не показывать путь через worker, если context потерян. Механика не выбирает «правильную длительность». Она запрещает писать performance conclusion до того, как есть complete boundary, источник времени и отдельный смысл для ожидания. Такая строгость удерживает августовский план от заранее придуманного bottleneck.
Literal проверка семантики
import { createFixedObservabilityPlan, reviewFixedSignalBoundary } from './upgrade-2026-08.mjs';
const incomplete = createFixedObservabilityPlan('missing-worker-context-v1');
const result = reviewFixedSignalBoundary(incomplete);
console.log({ status: result.status, reason: result.reason });
// { status: 'stop-broken-correlation-context', reason: 'ui-api-worker-must-carry-one-named-fixed-context' }
Пример deliberately выбирает неполный literal. Он показывает, что correlation нельзя восстановить похожим именем job или предположением о порядке. Public export работает в памяти, клон возвращаемого объекта заморожен, сеть и часы не читаются. Stop не утверждает, что в какой-либо системе context потерян; он указывает, какой факт обязан появиться в будущем plan before any cross-boundary inference.
Cardinality и sampling решают разные задачи
Cardinality растёт от количества отличающихся комбинаций attributes. Sampling отбирает traces по rule. Сэмплирование не делает безопасным label с email: меньшее число записей не меняет характер поля. И наоборот, low-cardinality outcome-class не требует trace на каждую операцию, чтобы быть пригодной metric dimension. Эти оси часто смешивают потому, что обе связаны с объёмом. Но первая определяет форму данных, а вторая — выбор наблюдений.
OpenTelemetry metrics model допускает удалять attributes и переагрегировать data с меньшим набором attributes. Это техническая возможность, а не оправдание сначала собрать всё. В августовском сценарии safer order обратный: сначала минимальный словарь, затем explicit consumer, затем отдельное решение, можно ли расширять. Если исходное поле уже идентифицирует человека или пытается описать произвольный payload, переагрегация не отменяет сам факт лишнего сбора.
Propagation — контракт между состояниями, а не побочный эффект SDK
UI создаёт client-side intent; API получает network boundary; worker получает message boundary. На каждом шаге context можно продолжить, создать новый relationship или потерять. Эти варианты нельзя спрятать под одно слово «автоматически». В будущей схеме нужно назвать carrier, точку extraction, точку injection и поведение при отсутствующем или некорректном input. Именно поведение при ошибке важнее happy path: если API не может разобрать context, он обязан выдать stop для end-to-end вывода, а не сгенерировать похожий идентификатор и склеить две истории задним числом.
Асинхронность добавляет ещё одну границу: принятие задачи и её выполнение не обязаны быть частью одной операции времени. План не выбирает тип span и не спорит о модели links; он требует не выдавать дальнейшую связь за доказанную, пока её нет в explicit contract. Это сохраняет возможность позже выбрать технический механизм по версии SDK и требованиям команды. Сначала определяется допустимый вывод, затем структура событий. Если перепутать порядок, инструмент начнёт диктовать семантику, а передача через worker окажется «особым случаем», который никто не проверял.
Почему fail-closed полезнее корреляционной эвристики
В реальных потоках имена операций, payload shape и порядок событий часто повторяются. Эвристика может решить, что log API относится к «ближайшему» trace, а metric worker — к той же попытке, потому что похожи job-kind. Для будущего incident review это опаснее честной пустоты: ответ выглядит детальным, но его невозможно оспорить на уровне контракта. Fixed validator выбран намеренно грубым: context либо один named value на трёх границах, либо связи нет. Он не пытается быть умнее данных.
Такой stop ещё и улучшает будущий контракт. Вместо просьбы «добавьте больше логов» он показывает точное место: определить owner propagation, carrier и boundary absent-context. Вместо «сделайте метрики полезнее» — назвать class и запретить identity label. Неопределённость становится очередью конкретных решений. Это формат зрелой технической речи: не обещать, что контекст всё объяснит, а зафиксировать, какой вывод запрещён до появления названного звена.
Порядок семантического review
- Зафиксировать дату сценария и source boundary; убрать формулировки о свершившемся августе.
- Проверить, что каждый UI, API и worker шаг несёт один named fixed context.
- Развести span, log и metric по вопросу, а не по удобству одного SDK.
- Составить закрытый словарь outcome-class и отдельный список запрещённых payload-полей.
- Пометить будущие time boundaries без чисел и не складывать интервалы до появления отдельного evidence.
- Назвать sampling rule и его предел; передавать только synthetic hand-off или точный stop.
Граница доказательства
Даже complete fixed map не доказывает, что telemetry backend примет данные, что headers дойдут через proxy, что worker согласован с API или что consumer найдёт incident быстрее. Она не подтверждает compliance, retention, access control, privacy impact или стоимость. Спецификации дают общие модели контекста и измерений, но не назначают нашу taxonomy и не делают её достаточной для конкретного продукта.
Следующий шаг — написать контракт одной асинхронной границы как вопросник, а не конфигурацию: что именно переносится, кто создаёт новую операцию, какие error classes остаются на стороне API, какие у worker, где заканчивается payload и какой evidence способен опровергнуть связь. До такого контракта любой граф остаётся визуальной гипотезой. Это допустимо для августа как plan; недопустимо как заявленный operational fact.
Проверяемые источники
- OpenTelemetry Specification, trace API and metrics data model — версия: immutable commit c6520a73287040ca16499cba62cea1b3508dc4da, Release 1.29.0, 11 January 2024. Pinned OpenTelemetry source определяет SpanContext и терминологию trace API, а metrics model описывает attribute reduction/re-aggregation. Граница: Он не устанавливает наш словарь outcome-class, retention или фактический telemetry pipeline.
- W3C Trace Context — версия: W3C Recommendation, dated 23 November 2021. W3C Recommendation определяет traceparent, trace-id, parent-id и обсуждает privacy risks. Граница: Документ не доказывает propagation через конкретный browser, API, queue или worker.