DarkRiDDeR12 мин

Через полгода: ADR не заменяет проверку решения

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

Через полгода после Accepted ADR команда видит в code знакомую границу: export когда-то был synchronous и рассчитан на небольшой request. Теперь рядом появился status endpoint, а в discussion звучит «старый документ уже не нужен». Самое рискованное действие — отредактировать прошлый ADR так, будто он всегда описывал новый путь. Симптом — code, record и current operational question показывают разные формы системы. Цена ошибки — потерять доказательство, почему old boundary был разумен, и одновременно не создать проверяемое описание нового compromise.

Другой соблазн — считать ADR самоисполняющимся. В нём может быть написано «проверить после изменения request shape», но это не означает, что check произошёл. Record хранит hypothesis и expected consequence; compliance требует отдельного evidence с scope, owner и методом. Если data, code link или requirement изменились, ADR помогает сформулировать reassessment, но не заменяет test, security review, metric query или controlled rollout.

Симптом → причина → проверка → действие

  1. Симптом. Existing ADR говорит об одной boundary, а новый code, contract или requirement заставляет reviewer сомневаться, что assumption всё ещё выполняется.
  2. Причина. Record приняли как вечное правило либо link на implementation не имеет validation path, owner и trigger для возврата к решению.
  3. Проверка. Сверьте four items: original context, stated assumptions, current code or contract link, allowed evidence. Затем назовите, какой signal опровергает assumption, а какой лишь требует наблюдения.
  4. Действие. Создайте successor ADR в Proposed, не меняя old record. После human acceptance проставьте связь Superseded; implementation и rollback проведите отдельным change plan с собственными проверками.

Три сигнала drift и разный ответ на каждый

Drift — не синоним «что-то поменялось». Он появляется, когда изменение затрагивает assumption или constraint, на котором стоял decision. New line of code может быть совместима со старым ADR, а ровно такой же line в другом boundary — нет. Поэтому сначала нужно выписать initial contract. Если record говорил «small synchronous export only», а requirement теперь требует visible status для неизвестного completion time, вопрос не в том, насколько много строк поменялось. Вопрос в том, отменён ли основной boundary.

Сигналы устаревания ADR и безопасный первый шаг
СигналЧто может изменитьсяПроверкаПервый безопасный шаг
Code or contract link driftimplementation больше не совпадает с declared decision boundaryпрочитать diff и linked contract; отделить фактический change от interpretationсоздать proposed reassessment note, не редактировать old ADR
Assumption driftsource shape, access rule, data class или request bound перестали быть теми, что были записаныназвать отменённую assumption и owner, который может подтвердить фактсформулировать successor question и evidence plan
Evidence gapdecision требует test, query или review, но результата нет или scope не определёнпроверить method, period, environment and stop conditionоставить status Proposed либо открыть отдельную validation task
Consequence driftrollback, support cost или dependency ownership стали другимисверить owner и actual boundary, не подменяя это одной metricобновить successor consequences и change plan
Calendar review без сигналадата наступила, но нет нового фактапроверить assumptions, links и evidence gapreconfirm or schedule evidence; не объявлять supersede автоматически
Цикл reassessment: зафиксированное assumption встречает signal, затем команда сверяет context, code link и evidence boundary. Если assumption выдерживает проверку, record подтверждается; если нет, создаётся proposed successor, который только после acceptance помечает прежний ADR как Superseded.
Схема разделяет проверку решения, новый ADR и implementation change. Она не показывает реальный incident, status конкретной команды, Git history или production metric.

Учебный разбор: старый export record и новый status contract

В учебной модели есть fixed case fixed-report-export-reassessment-v1. В нём старый synthetic ADR называется synthetic-adr-0012-synchronous-export. Его initial boundary — small synchronous path. Новый synthetic context говорит лишь одно: declared request bound больше не считается выполненным, а caller должен видеть status. Это не incident report и не evidence реального export. Но этого достаточно для учебного вопроса: old record нельзя переписывать, потому что он описывал другой context.

Alternatives тоже не обязаны скрываться под словом «обновим». Можно оставить synchronous path, предложить queued export with visible status или уйти в unbounded background work. Matrix выбирает второй synthetic вариант не потому, что он всегда лучше, а потому что fixed criteria требуют status contract и отделяют initial request от completion. Цена видна сразу: нужен owner status, recovery boundary и отдельная validation. Если эти вещи не удаётся назвать, successor должен остаться Proposed, даже если implementation уже кажется очевидным.

import {
  createFixedSyntheticAdrInput,
  inspectSyntheticAdrDecision,
  planSyntheticAdrRecord,
  reassessSyntheticAdrRecord,
} from './upgrade-2024-09.mjs';

const report = inspectSyntheticAdrDecision(
  createFixedSyntheticAdrInput('fixed-report-export-reassessment-v1'),
);
const successor = planSyntheticAdrRecord(report);
const review = reassessSyntheticAdrRecord(successor);

console.log(successor.status);       // proposed
console.log(review.previousStatus);  // accepted-until-a-human-accepts-the-successor
console.log(review.successorStatus); // proposed-only

// Никакой ADR file, Git record, code, CI, metric или production system не меняется.

Важна отрицательная ветка: reassessment result не меняет old status на Superseded. Он возвращает accepted-until-a-human-accepts-the-successor. Это не излишняя строгость. Пока successor не прошёл review, old record остаётся единственным Accepted объяснением. Если change надо остановить или откатить, команда знает, к какому document decision вернуться. Только после acceptance появляется связь «superseded by», а затем implementation может отдельно пройти delivery и validation steps.

Как связать ADR с code и metrics без ложной автоматизации

Link на ADR полезен, когда указывает на конкретный code or contract boundary. Он не доказывает compliance. Для каждого ADR нужен отдельный validation question: test, schema check, human review или permitted metric query с scope и stop condition. Если evidence недоступен, это limitation successor, а не повод рисовать график.

Metric не заменяет assumption. Throughput не доказывает security constraint, а короткое окно без errors не отменяет missing recovery path. Evidence должен отвечать на конкретный вопрос: например, есть ли status contract и различает ли caller states. Capacity требует отдельной workload boundary и owner.

Безопасный supersede — это маршрут, а не редактирование строки

Первый шаг — оставить old ADR неизменным и создать successor с link назад. В context successor нужно написать именно drift, а не «стало лучше»: changed request bound, changed dependency contract, new access requirement, missing owner или invalidated non-functional assumption. Второй шаг — заново сравнить alternatives. Старый choice может снова победить, если change оказался local and reversible; тогда record reconfirms decision. Если winner другой, consequences должны включить migration, rollback and support price.

Третий шаг — провести human review. У accepted decision есть owner и readers who can challenge context, but a status name must not impersonate unanimous approval. После acceptance old record получает Superseded link. Это меняет document lifecycle. Не нужно переписывать его context or consequences, иначе future reader потеряет причинную связь. Четвёртый шаг — вести code change отдельно: scope, test, deployment, monitoring, stop condition and rollback. Documentation status не запускает migration, а migration не переписывает history.

Полевой маршрут для одной устаревшей записи

  1. Откройте old ADR и выпишите invariants. Context, decision, consequences, owner, review date и links должны быть видны до чтения current diff.
  2. Назовите один signal. Не «система изменилась», а конкретно: bound не держится, contract changed, owner disappeared, evidence missing или code link diverged.
  3. Определите evidence boundary. Кто читает data, в какой environment, за какой period, какой result опровергнет assumption и когда нужно остановиться.
  4. Создайте successor Proposed. Добавьте old ADR link, current context, alternatives, price, validation and owner. Не ставьте Superseded заранее.
  5. Примите или отклоните successor. Если Accepted, mark old record Superseded with a link. Если Rejected, сохраните reason and leave old decision readable.
  6. Проведите delivery отдельно. Реальный change имеет own tests, approvals, rollback and operational checks. ADR помогает review, но не заменяет ни один из них.

Что делать, если старого ADR вообще нет

Отсутствие ADR — не повод создавать реконструкцию с выдуманными мотивами. Сначала составьте current decision record: what system does now, what evidence exists, which unknown remain and who owns the next check. Если прошлый rationale известен из accessible source, сослитесь на него как на источник, не переписывая его как уверенный факт. Если unknown критичен, record должен прямо сказать «не восстановлено». Новый ADR может зафиксировать будущее choice without pretending to certify the past.

Такой подход особенно важен после incident or urgent fix. Code может правильно снизить risk, но explanation появится позже. В retrospective ADR отделите observed facts от interpretation, назовите temporary workaround и expiry. Затем либо create successor for the durable decision, либо archive the workaround as rejected. Плохой путь — назвать emergency patch Accepted architecture без вариантов, consequence and owner. Тогда временное решение получает срок жизни системы.

Ограничения и следующий проверяемый шаг

Материал не читает current code, Git history, ticket, service metric, CI, network или production. Synthetic export record не доказывает actual timeout, queue length, user experience, security or cost. Nygard snapshot и immutable templates показывают форму rationale, но не назначают период reassessment и не дают authority supersede record в чужом repository. Любая реальная проверка требует scope, permission, owner and safe evidence collection.

Следующий шаг: возьмите один Accepted ADR старше трёх месяцев. Запишите original assumption, current link, evidence gap и verdict: reconfirm, successor or unknown. Old text не редактируйте. Successor начните с context и alternatives. Так видно, изменили implementation или само инженерное решение.

Историческая граница сентября 2024

Материал опирается на датированный snapshot первичного ADR text Michael Nygard и два immutable Git artifacts: snapshot Nygard template и MADR 3.0.0 template. Все они закреплены состоянием до сентября 2024. Из них следует ограниченный подход: сохранять rationale, alternatives, consequences, status and validation, а при смене decision связывать новый record со старым. Fixed export case, synthetic signals, scores and lifecycle outcomes не являются реальным ADR, source code, Git history, interview, CI output, metric or production evidence.

Проверяемые источники

  • Michael Nygard: Documenting Architecture Decisions, snapshot 22.08.2024 — Датированный снимок первичного текста автора ADR: короткий record с context, decision, status и consequences, а также сохранение старого record при supersede. Snapshot закрепляет историческую версию до сентября 2024; это формат и аргументация, а не policy конкретной команды.
  • Nygard ADR template mirror, immutable commit 93f7e465, 18.10.2023 — Неизменяемый Git snapshot template, который явно ссылается на формат Michael Nygard и фиксирует Title, Status, Context, Decision и Consequences. Это cross-check формы, а не первичный текст и не обязательный process команды.
  • MADR 3.0.0 template, immutable commit 97fb8ed, 09.10.2022 — Неизменяемый первичный артефакт Markdown ADR, доступный до сентября 2024. В template есть status, date, deciders, decision drivers, options, outcome, consequences и validation. Это пример формы, не обязательный набор полей и не измерение качества решения.