Прикладная часть 1. Восстановление спецификаций из legacy
Статус: Рекомендация. Сбор доказательств, нормализация временной шкалы и разделение требований и memory bank — устоявшиеся инженерные приёмы. Трёхсторонний файловый арбитраж в конце главы — фронтир.
Для учебного прохождения достаточно собрать один genealogy.md и отделить утверждённое требование от гипотезы. Файловый арбитраж, нормализаторы и replay исторических данных нужны только полному production-треку.
Эта глава продолжает часть 13 первого тома: там мы восстанавливали конституцию существующего проекта, здесь восстанавливаем одно production-требование из следов инцидента. Держите фокус узким: один claim, два источника, один открытый вопрос. Всё, что требует нормализаторов, исторического replay или файлового арбитража, относится к полному треку.
Перед чтением
- Опора из первого тома: часть 13 учит восстанавливать конституцию существующего проекта; здесь вы восстанавливаете одно production-требование.
- Локальный учебный кейс:
node_not_ready, потому что по нему легко показать провенанс и неопределённость.
- След для
capstone/: одна записьgenealogy.mdдля основногоhigh_memory_usageс двумяevidence_refи одним открытым вопросом. - Главные термины первого прохода:
evidence_refиmemory bank(граница между требованием и фоновым контекстом). Остальные термины главы —Verifier/Implementor/Safety, Координатор-протоколист, нормализатор, файловый арбитраж — справочные, разбираются подробно в части 8. - Что отложить: нормализаторы логов, исторический replay и файловый арбитраж.
В первом томе AgentClinic был учебным проектом на TypeScript, Hono, серверном JSX, SQLite и Vitest. Во втором томе мы используем учебную модель AgentClinic-production. Тот же проект мысленно развёрнут в Kubernetes. Grafana и PagerDuty шлют вебхуки (webhook) в его триаж-контур, а долго работающие реплики накопили операционную историю. Python во втором томе используется только для маленьких runnable-скриптов в examples/, а не как стек основного приложения.
Реальный кластер поднимать не нужно. Legacy-следы, с которыми работают главы 1–11, — это учебные пост-мортемы, дашборды и логи production-сценария. Конкретные инциденты дальше (node_not_ready, appointment_latency / appointment_latency_spike, autoscale_200pct, cdn_error_budget_burn, high_memory_usage) — события из этой модели, а не абстрактные сценарии.
Инженерное название этого приёма — восстановление спецификаций из наблюдаемых артефактов: логов, метрик, чатов, пост-мортемов и проверяемых следов решений. Если встречаете образную формулировку «Spec-некромантия», считайте её только коротким ярлыком для этой реконструкции, а не отдельной техникой.
Цель
После оттока команды SRE в проекте автоматического управления инцидентами остались фрагменты: 47 страниц неструктурированных логов, несколько Slack-тредов, скриншоты дашбордов и пост-мортемы без формального SDD. Цель главы — показать, как по таким следам восстановить инженерно пригодную спецификацию для триаж-пайплайна на основе Qwen Code. Альтернатива — набор правдоподобных догадок — нам не подходит.
После раздела вы сможете:
- отделять требования от фоновой модели
memory bank(полное определение — в «Ключевых идеях» ниже); - собирать доказательства в единую цепочку событий;
- извлекать неявные правила и превращать их в проверяемые пользовательские истории;
- закреплять происхождение каждого пункта так, чтобы спорные решения можно было аудировать и переобосновывать позже (рамка SDD «спецификация как исполняемый артефакт» из GitHub Spec Kit).
Минимальный учебный сценарий
Учебный кейс
Production-инцидент node_not_ready: по логу метрик, эскалации в PagerDuty и одному пост-мортему нужно восстановить одно требование — когда событие NodeNotReady становится P1 и когда его нельзя закрывать автоматически.
Подготовка
book2/examples/templates/genealogy.md— шаблон провенанса.- Учебная выписка ниже — минимальный заменитель логов, пост-мортема и Slack-треда.
- Один спорный факт: окно планового деплоя, canary namespace или ручная отмена эскалации.
Логичный вопрос: чем genealogy.md отличается от git log или git blame. Коротко: тем, что в git нет полей, которые здесь несущие. git log показывает, какой файл изменился и кто это сделал. genealogy.md показывает, откуда взялось само требование, насколько мы в нём уверены (uncertainty), какие источники его подтверждают (evidence_ref) и какие открытые вопросы остались без ответа. Коммит «added requirement» в git-истории не отличает «мы это твёрдо знаем из двух пост-мортемов» от «мы это предположили в чате». В genealogy.md эта разница обязательна.
Минимальная учебная выписка:
grafana:NR-2026-05-17-01 cluster=prod-k8s node=worker-07 event=NodeNotReady count=3 window=10m
pagerduty:NR-2026-05-17-01 escalation=created owner=platform_oncall severity=P1
postmortem:node-not-ready-2026-05 note="auto-resolve was rejected until two stable OK windows"
open_questions:
- "canary namespace исключает P1 или только снижает уверенность?"
Если у вас нет своих логов, используйте эту выписку. Если есть реальные материалы, замените строки на свои, но сохраните тот же минимум: два источника, один claim, один открытый вопрос.
Шаги
- Скопируйте шаблон
genealogy.mdв рабочий каталог. Ожидание: появился файл с разделами для источника, статуса, уверенности и открытых вопросов. - Запишите одно утверждение-кандидат: например,
>=3 NodeNotReady за 10 минут создают P1. - Добавьте минимум две пометки-доказательства (
evidence_ref) и один недостающий контекст. Ожидание: утверждение нельзя прочитать как «просто мнение автора». - Отделите требование от
memory bank: топология кластера и имена дежурных не должны становиться контрактом. - Перепишите утверждение в Given/When/Then и рядом укажите, какое поле будущей JSON Schema проверит порог, severity и условие закрытия.
- Поставьте статус
approved,needs_clarityилиrejected. Ожидание: спорный факт не маскируется под утверждённое требование.
Контрольный факт
В genealogy.md есть одна запись, где одновременно видны утверждение, источники, уровень уверенности, недостающий контекст и связь с проверяемым поведением. Если порог или SLA нельзя защитить ссылкой на источник, требование остаётся гипотезой.
Как это попадает в capstone/
Перенесите в capstone/genealogy.md только одну защищённую запись: утверждение, два evidence_ref, уровень уверенности и открытый вопрос. Не переносите всю временную шкалу, выписки логов и Slack-цитаты, если они не стали доказательством конкретного требования.
Минимальный фрагмент для high_memory_usage:
- claim: "При memory_percent >= 90% за 10m для appointments-api создаётся P1."
status: needs_clarity
evidence_ref: ["grafana:HM-2026-05-17-01", "postmortem:api-memory-2026-05"]
uncertainty: medium
open_questions:
- "Подтверждён ли запрет auto-resolve без двух стабильных окон?"
Ревьюируемый след
В учебном пакете сохраняйте только заполненный genealogy.md или его фрагмент. Черновые выписки логов и временные таблицы не нужны в репозитории, если они не стали проверяемым доказательством.
Ключевые идеи
Первая дисциплина восстановления спецификаций — жёстко разделяйте фактические требования и фоновую модель memory bank. Под memory bank мы понимаем отдельный слой инфраструктурного контекста: всё, что помогает интерпретировать факты, но само контрактом не является.
Если этот термин кажется новым, посмотрите на него через первый том. То, что там жило в tech-stack.md (на чём пишем) и в QWEN.md (постоянный контекст агента), во втором томе называется одним общим словом memory bank. Это тот же фоновый слой, только теперь он явно отделён от требований, потому что в production-сценариях разница «контракт vs контекст» становится критичной.
Требование, в отличие от memory bank, описывает поведение фичи. Что считается триггером. Когда создаётся инцидент. Какой SLA применяется. Кто получает эскалацию. При каких условиях событие закрывается.
memory bank хранит другое: топологию кластера, список команд, исторические договорённости, ограничения API, привычные каналы связи и операционную лексику. Почему это важно разделять. Если смешать уровни, в SDD легко появится ложное правило вроде «canary всегда неэскалируемый». На самом деле это может быть только контекст тестового namespace, а не универсальное поведение продукта.
Вводите разделение уже на этапе инвентаризации артефактов. В SDD относите утверждения, которые можно проверить наблюдаемым сценарием: >=3 NodeNotReady за 10 минут создают P1, NOC получает уведомление через 15 минут, закрытие требует 2 последовательных OK.
В memory bank отправляйте всё, что помогает интерпретировать факты, но контрактом не является:
- кто дежурил в ночь инцидента;
- почему в Slack использовали старое имя сервиса;
- какие команды имеют доступ к Grafana.
Такой фильтр снижает риск, что Qwen Code примет инфраструктурный фон за бизнес-правило и начнёт проектировать поведение на основании случайной детали.
Вторая идея — собрать и нормализовать доказательства в единую временную цепочку событий. Источники у каждого свой профиль:
- логи дают наблюдаемые состояния и порядок наступления событий;
- Slack показывает намерения операторов и ручные обходы;
- пост-мортем фиксирует причины и последствия;
- метрики позволяют оценить масштаб деградации.
Перед анализом приведите источники к общему времени (UTC). Уберите дубли, выделите коды событий и свяжите записи единым идентификатором инцидента, кластера, узла (node) или развёртывания (deployment). Без этого восстановление SDD превращается в спор о воспоминаниях, а не в реконструкцию поведения системы.
Нормализованная цепочка строится как последовательность ts → source → event_code → actor → affected_scope → evidence_ref, где последнее поле — это пометка-доказательство (evidence_ref), ссылка на конкретное место в исходном артефакте. В кейсе node_not_ready каркас может показать, что три события NodeNotReady за 10 минут почти всегда предшествовали созданию P1. Затем через 15 минут шла эскалация в NOC. Закрытие происходило только после пары устойчивых OK.
Отдельно фиксируйте исключения: окно планового деплоя, canary namespace, временная потеря метрик или ручная отмена эскалации. Не удаляйте такие исключения как шум — именно они часто указывают на скрытые условия будущей спецификации.
> [conceptual interface] — эти команды показывают ожидаемый интерфейс локальных нормализаторов. Готовых timeline_builder.py и evidence_matrix.py в репозитории учебника нет; реализуйте их в своём проекте, если перейдёте от учебного минимума к полному треку.
rg -n "NotReady|NodeNotReady|ALERT|deploy" evidence/raw/* > evidence/index.txt
python3 tools/timeline_builder.py --input evidence/raw --out evidence/timeline.ndjson
python3 tools/evidence_matrix.py \
--timeline evidence/timeline.ndjson \
--slack evidence/slack_export.json \
--metrics evidence/metrics.csv \
--out evidence/matrix.csv
Контроль: каждая строка в evidence/timeline.ndjson содержит ts, source, event_code, cluster, namespace, actor и evidence_ref; пустые поля блокируют переход к выводу требований.
Дальше схема показывает, как из legacy получают восстановленный SDD. На правой стороне появляется блок «Арбитраж» с тремя ролями и координатором: это полный трек, который подробно разбирается в части 8. На первом проходе считайте блок «Арбитраж» одним шагом «спорные требования проверяет независимая роль» — детальный состав ролей здесь читать не нужно.
flowchart TD
subgraph Вход["Вход: legacy"]
L[Логи, пост-мортем, Slack, метрики]
end
subgraph Обработка["Обработка"]
P[Парсинг и временная цепочка]
R[Гипотезы требований и пользовательские истории]
end
subgraph Арбитраж["Арбитраж (полный трек, ч.8)"]
TBR[Независимая роль проверяет спорные требования]
end
subgraph Результат["Результат"]
S[Восстановленный SDD и genealogy.md]
end
L --> P --> R --> TBR --> SТретья идея — извлекать неявные требования через Qwen Code, но оценивать каждое утверждение по источнику и контексту. Qwen Code здесь работает не как автор бизнес-логики, а как посредник для извлечения. Ему передают факты, ограничения среды и строгий формат ответа, где запрещены утверждения без ссылки на доказательство.
Хороший запрос просит не «придумать SDD», а сделать другое:
- найти повторяющиеся правила в цепочке событий;
- указать подтверждающие источники;
- назвать контрпримеры;
- присвоить уровень уверенности.
Так модель усиливает анализ, но не получает права превращать догадки в требования. Ожидайте от Qwen Code список утверждений-кандидатов (claims), а не финальную спецификацию.
Плохо:
> REQ-NR-01: при частых NodeNotReady на node создаётся P1.
Проблема: нет ни порога, ни окна, ни пометки-доказательства. Правило невозможно ни проверить, ни оспорить.
Хорошо:
> REQ-NR-01: при >=3 NodeNotReady за 10 минут на одном node и коррелированном росте 5xx создаётся P1. evidence: logs/node-2026-05-12.parquet#row_4123, slack/thread_11#msg_7, grafana/node_5xx#segment_11:00. confidence: medium. missing_context: planned deploy window.
Что это даёт на практике. Такая запись полезнее гладкого текста пользовательской истории: она сразу показывает, где требование устойчиво, а где требует проверки у владельца сервиса. Если правило подтверждено только одним пост-мортемом и не совпадает с метриками, оно остаётся гипотезой, даже если звучит убедительно.
> [project script] — qwen -p сам по себе runnable, но входной @evidence/matrix.csv нужно сначала собрать в вашем проекте. Формат итогового JSON стабилизируйте отдельным парсером-нормализатором.
qwen -p "Прочитай @evidence/matrix.csv. Найди повторяющиеся правила
инцидента node_not_ready. Верни claims с evidence, counterexample,
missing_context и confidence. Не утверждай факты без evidence." \
--approval-mode plan \
--output-format json \
> sdd/drafts/nr-claims.qwen.json
qwen -p "Прочитай @sdd/drafts/nr-claims.qwen.json и проведи cross-examine:
для каждого claim проверь source, counterexample и missing_context.
Пометь claim как approved, needs_clarity или rejected." \
--approval-mode plan \
--output-format json \
> sdd/drafts/nr-claims-cross.qwen.json
Контроль: Qwen здесь работает в безголовом Plan Mode. Итоговый JSON Qwen Code —
отчёт с сообщениями сессии; если проекту нужен строгий claims.json,
добавьте отдельный парсер-нормализатор и проверяйте его тестами.
Четвёртая идея — кодировать требования одновременно в Given/When/Then и машинно-читаемый контракт, например JSON Schema. Given/When/Then удерживает требование на языке поведения: исходное состояние, событие, ожидаемый результат.
JSON Schema фиксирует обязательные поля, допустимые значения, числовые границы и структуру данных. Контракт можно валидировать в CI или локальном валидаторном пайплайне. Двойная запись устраняет разрыв между «понятно человеку» и «проверяемо машиной».
Для node_not_ready поведенческая история выглядит так:
- Given кластер
prod-k8sнаходится в активной смене и за 10 минут фиксируется>=3 NodeNotReadyдля одного узла; - When событие коррелировано с развёртыванием или ростом 5xx в связанных метриках;
- Then создаётся инцидент
severity=P1, первичная реакция ожидается за 8 минут, автоэскалация вNOCпроисходит через 15 минут, а закрытие разрешено только после 2 последовательныхOKв течение 10 минут.
Оформите исключение для canary namespace как отдельное условие, а не как примечание в конце. Иначе валидатор не сможет отличить стандартный путь от ослабленного порога. Такой формат переводит разговор о «быстрой реакции» в конкретные числа, события и статусы.
Минимальная JSON Schema того же контракта (полная форма с triggers и регулярным выражением для auto_resolve_window — в полном треке):
{
"$id": "urn:spec:node-not-ready:v1",
"type": "object",
"required": ["rule_id", "severity", "sla_minutes", "conditions"],
"properties": {
"rule_id": {"type": "string"},
"severity": {"type": "string", "enum": ["P0", "P1", "P2", "P3"]},
"sla_minutes": {"type": "integer", "minimum": 1, "maximum": 120},
"conditions": {
"type": "object",
"required": ["event_code", "count", "window_minutes", "namespace_rule"],
"properties": {
"count": {"type": "integer", "minimum": 3},
"window_minutes": {"type": "integer", "minimum": 1},
"namespace_rule": {"type": "string", "enum": ["standard", "canary"]}
}
}
}
}
Пятая идея относится только к полному треку: спорные восстановленные требования можно отдавать в файловый арбитраж. Голосуют три роли — Верификатор, Имплементор, Safety; Координатор ведёт журнал, не голосуя. Верификатор проверяет непротиворечивость чисел и статусов, Имплементор — реализуемость в текущем триаж-пайплайне, Safety — границы безопасного действия и право veto при critical_risk. Подробно роли, вердикты и прецеденты разбираются в части 8; запускаемый учебный аналог — [examples/tribunal/](examples/tribunal/). Для учебного минимума этот шаг не нужен: достаточно genealogy.md с источниками, уровнем уверенности и открытым вопросом.
Шестая идея — вести genealogy.md, отдельный реестр происхождения каждого требования. Зачем он нужен. Восстановленный SDD быстро теряет ценность, если через месяц невозможно объяснить:
- почему выбран порог 3 события за 10 минут;
- кто подтвердил SLA 8 минут;
- почему canary получил отдельный режим.
genealogy.md связывает утверждение с логами, Slack, метриками, пост-мортемом, решением файлового арбитража и текущим уровнем неопределённости. Так спецификация становится цепочкой доказательств, а не текстовым снимком коллективной памяти.
- req_id: NR-01
statement: "При >=3 NodeNotReady за 10m для одного node и росте 5xx создаётся P1."
source:
- logs: evidence/normalized_node_logs.parquet#row_4123
- slack: export/slack_thread_11.json#msg_7
- metrics: grafana/node_5xx_timeseries.csv#segment_2026-05-12T11:00
status: approved
adjudicated_by: [Verifier, Implementor, Safety]
uncertainty: low
open_questions: []
Если пункт остаётся спорным, не маскируйте его под утверждённый контракт. Поставьте uncertainty: medium или uncertainty: high, укажите причину сомнения и добавьте план проверки:
- запросите владельца сервиса;
- прогоните replay по историческим данным;
- сравните с соседним кластером;
- соберите недостающую метрику.
Такой реестр провенанса особенно важен для будущей Конституции проекта. В неё должны переходить только правила с понятным происхождением, областью действия и механизмом пересмотра.
Примеры и применение
Учебная выписка из 4 строк в «Минимальном учебном сценарии» — это уже отфильтрованный итог нормализации. В исходном наборе встречается:
- 9 часов наблюдений;
- 11 релевантных Slack-сообщений;
- 47 страниц неочищенных логов;
- 1 248 событий
NodeNotReady; - 63 алерта;
- 8 ранее закрытых инцидентов.
После нормализации видно, что резкий рост NodeNotReady совпал с развёртыванием, часть событий ушла в canary-сегмент с другой логикой автоэскалации, и появляются две ветки поведения: стандартный P1 и canary-путь с ослабленными порогами.
> [conceptual interface] — псевдокод нормализатора. Runnable-примеры второго тома остаются на Python stdlib и лежат в book2/examples/.
read evidence/normalized_node_logs
sort events by ts
filter event_code == "NodeNotReady"
group by cluster,node in 10m windows
mark windows where count >= 3
link marked windows to alerts and Slack messages in [-15m,+5m]
Окно [-15m,+5m] нужно потому, что оператор мог обсудить проблему до формальной записи инцидента или уже после автоматического алерта. Если событие относится к canary namespace без деградации SLO — ставьте отдельную метку, а не удаляйте как шум. Если окно планового деплоя объясняет часть NodeNotReady, прямо укажите в требовании, блокирует ли это создание P1 или только снижает уверенность.
Восстановленный SDD становится рабочим артефактом только после реплея: прогоните исторические инциденты через новый JSON-контракт и проверьте, совпадают ли созданные severity, SLA и эскалации с подтверждёнными исходами. Несовпадения не всегда означают ошибку контракта — иногда они показывают, что старая практика была противоречивой или зависела от конкретного дежурного. Что менять в этом случае — спецификацию, memory bank или статус гипотезы в genealogy.md — решает файловый арбитраж из части 8.
Итог
Восстановление спецификаций из legacy восстанавливает SDD не из интуиции, а из проверяемой цепочки доказательств. Маршрут такой:
- legacy-артефакты нормализуются во временную шкалу;
- Qwen Code извлекает кандидатов-утверждений с уровнем уверенности;
- требования отделяются от
memory bank; - затем кодируются в Given/When/Then и JSON Schema;
- для полного трека проходят файловый арбитраж Координатор/Имплементор/Верификатор;
- получают провенанс в
genealogy.md.
Такой процесс превращает хаос логов, чатов и пост-мортемов в контракт. Контракт можно валидировать, оспаривать, переигрывать на исторических данных и переносить в более строгую систему правил. В следующей главе мы намеренно отравим спецификации противоречиями и изучим, где Qwen Code начинает застревать.
Артефакты и критерии готовности
| Артефакт | Готов, когда |
|---|---|
genealogy.md с одним требованием или гипотезой | требование отделено от memory bank, спорные факты помечены как гипотезы |
Минимум два evidence_ref и один недостающий контекст | утверждение нельзя прочитать как «мнение автора», порог/SLA защищается ссылкой на источник либо явно помечен как пока неутверждаемый |
| Given/When/Then-формулировка | проверяемые поля связаны с тем, что покрывает JSON Schema |
Полный трек добавляет evidence/timeline.ndjson, evidence/matrix.csv со ссылками на логи, Slack, метрики и пост-мортемы, sdd/drafts/nr-claims.qwen.json с утверждениями-кандидатами, contracts/node_not_ready.schema.json и запись файлового арбитража для требований, которые нельзя утвердить вручную. Считайте полный трек готовым, если Given/When/Then и JSON Schema описывают один и тот же контракт, нормализатор даёт воспроизводимую временную цепочку, а валидатор или файловый арбитраж выносит проверяемый verdict.
Практика
- Скопируйте [
examples/templates/genealogy.md](examples/templates/genealogy.md) вcapstone/genealogy.mdи заполните одну запись по основному кейсуhigh_memory_usage: утверждение, минимум дваevidence_ref, уровень уверенности и один открытый вопрос. Учебную выписку из «Минимального учебного сценария» можно использовать как заменитель реальных логов. - Перепишите своё утверждение в Given/When/Then и рядом укажите, какие три поля JSON Schema проверяют порог, severity и условие закрытия. Поле, которое нельзя защитить ссылкой на источник, оставьте как
uncertainty: medium, а не как утверждённый контракт.
- Откройте [
appendix-a-bridges-to-book.md](appendix-a-bridges-to-book.md) и отметьте, какая глава первого тома была опорой для вашегоgenealogy.md. Если опоры нет — это сигнал, что требование пока не привязано к учебной модели.
Контрольные вопросы
- Почему доказательство важнее уверенной формулировки требования?
- Чем
memory bankотличается от SDD-контракта и почему опасно их смешивать? - Когда гипотезу нельзя переводить в approved-требование?
- Вы восстановили правило по двум пост-мортемам, но владелец сервиса уволился полгода назад. Что вы сделаете с этим правилом до того, как добавите его в
requirements.md?