Цикл одной спеки · теория
Spec · Delta · R_eff
Как написать спецификацию, которая переживёт шесть месяцев и три рефакторинга. Spec отвечает на «как система должна себя вести» - поведение, которое можно протестировать. Delta + DAG сохраняют историю требований и связи между спеками. R_eff с правилом WLNK честно показывает, насколько техническое решение внутри спеки надёжно, и через Evidence Decay сигнализирует, когда пора пересматривать.
01 · Spec против PRD - разница в способе проверки
PRD говорит «что делать». Spec говорит «как система должна себя вести»
Разница маленькая на вид, огромная по последствиям. PRD отвечает на вопрос «что делать». Spec отвечает на вопрос «как система должна себя вести».
Аналогия - рецепт против контрольного вкуса. Рецепт может звучать вкусно и быть при этом совершенно невыполнимым; финальный вкус блюда проверяется только едой. PRD - рецепт. Spec - контрольный вкус.
- POST /notes/:id/tags с валидным массивом → 200 + список тегов
- POST с дубликатом существующего тега → 200, идемпотентно
- POST с пустым массивом → 400 {"error": "empty_tags"}
- Тег: 1-32 ASCII символа, нормализация - lowercase
- Максимум 10 тегов на заметку
- Изменение лимита тегов → review search-spec (фильтрация)
- Смена нормализации → review migration-spec
- Иерархия тегов (покрывается tag-hierarchy-spec в будущем)
- Цвета и иконки тегов
В PRD эту секцию часто пропускают, в spec - никогда. Одна спека отвечает за одну границу поведения, всё остальное - соседние спеки. Вместо одного монолита у вас набор маленьких независимых файлов, каждый проверяется и меняется отдельно.
02 · Delta-spec и DAG зависимостей
Спека не переписывается - она меняется через delta
Допустим, у вас уже есть notes-spec.md. Теперь вы добавляете теги.
Плохой путь - переписать всю спеку в v2 и закоммитить поверх. Через три
месяца никто не поймёт, что именно изменилось.
Хороший путь - написать delta-spec: явный diff с тремя секциями.
POST /notes/:id/tags с массивом 1-10 строк, дубликаты идемпотентны.GET /notes получил опциональный параметр ?tag=<name> для фильтрации./tag-search, он бы попал сюда с причиной.Каскад явно записывается в секции Триггеры или в отдельном файле зависимостей. Когда вы готовите delta, чарт показывает, что ещё нужно проверить - это уберегает от ситуации «поменяли одну спеку, через месяц всплыло в трёх соседних». Длина бара - грубая оценка усилий ревью, не точное число.
Без delta каждая правка спеки стирает историю. С ним вы можете через 6 месяцев открыть
git log openspec/changes/ и пройти по каждой ADDED / MODIFIED / REMOVED
Requirement в обратном порядке - как git blame для требований. Это не
формальность, это страховка от «почему у нас в GET /notes появился
параметр ?tag».
03 · R_eff - координаты качества + Evidence Decay
«Годен» - это не среднее, а способность определить опасную систему
Аналогия - техосмотр автомобиля. Инспектор не ставит одну общую оценку «машина хорошая». Он проверяет несколько независимых систем: тормоза, подвеска, движок, электрика, кузов. Общий вердикт «годен / не годен» - это не среднее, а способность ответить: «есть ли хоть одна система, которая сделает поездку опасной». Если тормоза 2 из 9 - какие бы хорошие ни были подвеска и движок, машина на дорогу не идёт. Через 6 месяцев - повторный техосмотр, старый результат недействителен.
R_eff работает ровно так же. Каждое evidence (доказательство, которое
поддерживает или опровергает решение) имеет два измерения - Verdict и CL - и итоговый
score, по которому считается общий R_eff = min(scores).
- Verdict · отношение evidence к решению. supports = 1.0 / weakens = 0.5 / refutes = 0.0.
- CL · Congruence Level · откуда взято. CL3 ваш замер на этом же проекте (penalty 0.0) · CL2 похожий проект или коллега (penalty 0.1) · CL1 документация / Stack Overflow / блог (penalty 0.4) · CL0 статья про противоположный контекст (penalty 0.9).
-
Оценка ·
max(0, verdict − CL_penalty)для одного evidence. -
R_eff ·
min(scores)по всем evidence решения. Не среднее.
| # | Evidence | Verdict | CL | Оценка | |
|---|---|---|---|---|---|
| E1 | Свой замер на 1000 заметок: JOIN по note_tags(tag_id) p95 < 5 ms; LIKE '%ddd%' p95 ~50 ms |
supports | CL3 | 1.0 | |
| E2 | SQLite docs: junction table рекомендован для many-to-many | supports | CL1 | 0.6 | |
| E3 | Рассылка: деградация JSON1 на 100k записей | weakens | CL1 | 0.1 | ← WLNK |
Среднее обманывает, минимум - нет. Одно опровергающее evidence (даже слабое, CL1) утаскивает доверие вниз. Это и есть смысл формулы «trust = weakest link».
valid_until: 2026-10-01 и
refresh_trigger: notes_count > 100_000. Когда valid_until
истекает - оценка evidence падает до 0.1 (stale), не absent. Решение было,
контекст мог измениться, требуется reaffirm или supersede.
R_eff = 0.1 не значит «всё плохо, разваливай». Это сигнал «решение держится на одном слабом звене - обратите внимание». Стратегия: либо собрать ещё evidence (CL3-замер, который опровергнет E3 или подтвердит, что ситуация из рассылки не наша), либо принять риск явно с пометкой в DDR «опираемся на E1, E3 слабее, пересмотр через 30 дней или после 100k заметок».
Цикл на одной спеке · Vault · добавление тегов
Как все три концепции складываются в один проход - от пустого файла до решения с честным R_eff.
Пишете notes-tagging-spec.md с четырьмя секциями
Поведение (3 контракта поведения), Invariants (ASCII 1-32, max 10 тегов), Триггеры (ссылки на search и migration), Out-of-scope (иерархия, цвета). Spec проверяема - каждое поведение можно превратить в тест.
Пишете delta-notes-tagging.md поверх notes-spec
ADDED: новый endpoint /notes/:id/tags. MODIFIED: GET /notes с опциональным ?tag. REMOVED: пусто. DAG показывает: search-spec и migration-spec требуют review. api-spec добавляет новый endpoint.
Считаете для решения «junction table vs JSON»
Три evidence: свой бенчмарк (CL3, supports, 1.0), SQLite docs (CL1, supports, 0.6), рассылка про деградацию JSON1 (CL1, weakens, 0.1). R_eff = min = 0.1. Среднее 0.57 обмануло бы - решение at risk.
Frontmatter с valid_until и refresh_trigger
В спеку добавляется valid_until: 2026-10-01 и refresh_trigger: notes_count > 100_000. Решение либо подкрепляется новым evidence (CL3-замер на 100k), либо пересматривается. Через 6 месяцев цикл стартует с шага 02 - delta с MODIFIED.