Методология

Документ Версия: 0.1 draft

Этот документ описывает, как организована спецификация сервиса ЭНЗС, где что лежит и как с ней работать — разработчику, ИИ-ассистенту и любому, кто читает спеку впервые.

Спека — источник правды для устройства системы. Цель — не подробное руководство, а такой набор фактов, который не противоречит коду, не устаревает быстро и собирается в одно место для понимания.

⟨/⟩ Исходник

1. Spec first

Спецификация — источник правды. Изменение поведения системы начинается с изменения спеки, не с правки кода. Реализация догоняет спеку, не наоборот.

2. AI first + optimized

ИИ-агент — первоклассный читатель спеки наравне с человеком. Формат, размеры порций, способ ссылаться — выбираются так, чтобы агент мог их потреблять без потерь. Оптимизация под агента — это compile-safe идентификаторы, плоские ссылки nameof(), отсутствие неявных соглашений «из контекста».

3. C# compilable

Спецификация оформлена как компилируемый C#-проект (sdaid/Rugrid.Sdaid.Epas/). Это даёт линтер: ссылка на несуществующий артефакт ломает сборку, переименование через F2 правит все упоминания, find-references показывает обратные связи без отдельных индексов.

4. Human readable

C#-формат не отнимает у человека читаемости. Генератор превращает классы в навигируемый HTML-сайт с прозой, таблицами, диаграммами и кликабельными ссылками между артефактами. Любой раздел доступен по стабильному URL и пригоден для копи-пейста в чат, обсуждение, ревью.

5. Материализация прозы

Любой кусок прозы, на который команда уже ссылается («FR-301», «модель потребления», «энергопрофиль», «отчёт MVER-R01») — кандидат на материализацию в отдельный класс-артефакт. До материализации это текст; после — адресуемая сущность с собственной страницей, секциями, обратными ссылками. Vision-таблицы — пример прогрессивной материализации: текст «FR-301» в ячейке становится ссылкой на IFunctionalRequirementDocument-класс ровно тогда, когда требование берётся в проработку.

6. Правило одного факта

Каждый факт описан ровно в одном месте. Если он нужен в двух — второе место это ссылка (typed-reference, не копия).

Из этого правила следует много частностей:

🔍 Признак нарушения: если поиск по точной фразе в проекте даёт ≥2 result-а в разных артефактах — это дрейф. Нужно: оставить ровно один источник правды, остальные превратить в typed-ссылку (nameof, SdaidAnchors.RefTo<T>(text), GlossaryAnchors.RefTo<T>(text)).

7. Что куда писать / структура папок

Где живёт vision, глоссарий, требования, эпики, доменные сущности, архитектурные решения — фиксируется явно. Цель: и человек, и ИИ-агент однозначно знают, куда положить новый артефакт и где искать существующий — без догадок.

7.1. Принципы (зачем именно так)

Один домен — ntr. Вся предметная область сервиса ЭНЗС — единый домен. Чтобы группировать сущности, домен поделён на доменные модули (50_Domain/<Module>/). Это группировка по смыслу, а не отдельные Bounded Contexts. Поэтому контракт API и UI-спека живут на корне спеки (45_Api, 65_Ui), а не внутри доменных модулей.

Слои — горизонтальные, на корне. Каждый слой живёт в своём top-level разделе: контракт API — 45_Api, домен — 50_Domain, поведение бэка — 55_Backend, эпики — 60_Epics, UI-спека — 65_Ui. Внутри раздела — группировка по области/эпику (Endpoints/<Area>/, Handlers/<Area>/, Pages/<Area>/).

Эпик — тонкая нить. 60_Epics/<Epic>/ собирает сквозной контекст (use-case-ы, локальные решения, AC, state-machine-ы) и ссылается на артефакты слоёв через nameof(). Реализационные артефакты use-case-а (endpoint, handler, страница) лежат в своих слоях, не в подпапках use-case-а.

Реализация — 80_Subsystems, по необходимости. Особенности конкретной реализации деплой-единицы (ADR подсистемы, нюансы стека) — в 80_Subsystems/. Если особенностей нет — раздел не заводится. Инфраструктурные контракты (file/MQ/интеграции — например, приём данных из АИС) — в 70_Infrastructure/, тоже по необходимости.

7.2. Структура (верхнеуровневые разделы)

00_Methodology/        как работать со спекой
00_Migration/          инструкция миграции md→SDAID (временная, пока идёт перенос)
10_Vision/             слабоструктурированный вход от заказчика
20_Glossary/           общий проектный словарь
30_Requirements/       FR — материализуются по мере проработки
35_ChangeRequests/     батчи замечаний и пожеланий
40_Arch/               системные кросс-cutting решения
   ADR/                  общесистемные ADR
   Tech/CSharp/          техно-ADR backend
   Tech/Angular/         техно-ADR frontend
45_Api/                контракт API
   Based/                общие базовые типы (BasedCommand, BasedQuery, …)
   Endpoints/<Area>/     endpoint + Command/Query + Result
50_Domain/             домен ntr, поделён на доменные модули
   <Module>/
      BC_<Module>.cs     overview модуля (IBoundedContextModuleDocument)
      <Entity>.cs        сущности и агрегаты
55_Backend/            поведение бэка
   Handlers/<Area>/      canonical narrative операций (IHandlerDocument)
   Services/<Area>/      доменные / прикладные сервисы
60_Epics/              бизнес-направления (тонкая нить)
   <Epic>/
      EPIC_<Epic>.cs     цель, акторы, scope, открытые вопросы, AC
      UseCases/<UC>/     use-case-документы (Given/When/Then), state-machine-ы
65_Ui/                 UI-спека
   Pages/<Area>/         страницы (IPageDocument)
   Componetns/, Layout/  компоненты и layout-ы
   html/                 общий shared.css для HTML-прототипов
70_Infrastructure/     [при необходимости] инфраструктурные контракты (file/MQ/интеграции)
80_Subsystems/         [при необходимости] особенности реализации деплой-единиц

7.3. Папка → kind-маркер (что внутри какой папки)

Папка Kind-маркер класса Что внутри
00_Methodology/ ISdaidDocument (без подтипа) Документ методологии проекта (этот файл)
00_Migration/ ISdaidDocument (без подтипа) HowTo миграции md→SDAID (временно)
10_Vision/ IVisionDocument Vision-документ
20_Glossary/Terms/ IGlossaryTerm (отдельный контракт, не ISdaidDocument) Термины проекта, по одному классу на термин
30_Requirements/ IFunctionalRequirementDocument FR-документы (материализуются по мере проработки)
35_ChangeRequests/ IChangeRequestDocument Батчи замечаний и пожеланий
40_Arch/ADR/ IAdrDocument Общесистемные ADR
40_Arch/Tech/<Stack>/ IAdrDocument Техно-ADR (CSharp, Angular)
45_Api/Endpoints/<Area>/ IApiContractDocument (+ IFolder<ApiFolder>) Endpoint + Command/Query + Result
45_Api/Based/ ISdaidDocument Общие базовые типы команд/запросов
50_Domain/<Module>/ (overview) IBoundedContextModuleDocument Описание доменного модуля
50_Domain/<Module>/<Entity>.cs IDomainEntityDocument Сущности и агрегаты (агрегаты пока без отдельного маркера)
55_Backend/Handlers/<Area>/ IHandlerDocument (+ IFolder<BackendFolder>) Canonical narrative операции
55_Backend/Services/<Area>/ IApplicationServiceDocument / IDomainServiceDocument (+ IFolder<BackendFolder>) Сервисы реализации
60_Epics/<E>/ (overview) IEpicDocument Цель эпика, акторы, scope, открытые вопросы, AC
60_Epics/<E>/UseCases/<UC>/ IUseCaseDocument (с PrimaryActor) Given/When/Then narrative
60_Epics/<E>/UseCases/<UC>/StateMachines/ IStateMachineDocument State machine с mermaid в Body
65_Ui/Pages/<Area>/ IPageDocument (+ IFolder<UiFolder>) Спека страницы
65_Ui/Componetns/, Layout/ IUiComponentDocument / IUiSpecDocument (+ IFolder<UiFolder>) Компоненты, layout-ы
70_Infrastructure/<Capability>/ IInfrastructureDocument Контракты на file/MQ/интеграции (по необходимости)
80_Subsystems/<S>/ IApplicationDocument, IAdrDocument и др. (+ IFolder<SubsystemsFolder>) Особенности реализации подсистемы (по необходимости)

7.4. Что куда — детально

Что за артефакт Где живёт
Доменная сущность / агрегат 50_Domain/<Module>/<Entity>.cs
Endpoint + Command/Query + Result 45_Api/Endpoints/<Area>/
Handler (canonical narrative) 55_Backend/Handlers/<Area>/
Доменный / прикладной сервис 55_Backend/Services/<Area>/
Инфраструктурный контракт 70_Infrastructure/<Capability>/ (по необходимости)
Страница / компонент UI 65_Ui/Pages/<Area>/, 65_Ui/Componetns/
Use-case (Given/When/Then) 60_Epics/<E>/UseCases/<UC>/
State machine 60_Epics/<E>/UseCases/<UC>/StateMachines/
Системный ADR 40_Arch/ADR/
Tech ADR (Angular/EF/CSharp) 40_Arch/Tech/<Stack>/
Особенности реализации подсистемы 80_Subsystems/<S>/ (по необходимости)

7.5. Эпик ↔ доменный модуль ↔ слой

Разные сущности, связанные через ссылки:

Один эпик затрагивает ≥1 доменный модуль и артефакты нескольких слоёв; модуль/слой обслуживают ≥1 эпик. Связи трассируются через nameof() в обе стороны.

8. Границы ответственности, контракты и соглашения

Каждый модуль (домен, API, backend, UI) отвечает за свою часть знаний и не лезет в чужую. На границах модулей фиксируются явные контракты (что один модуль обещает другому) и соглашения (как они общаются: имена, формат данных, ошибки, идемпотентность). Изменение контракта — событие, требующее согласования; изменение внутреннего устройства модуля — внутреннее дело модуля.

9. Эпики и слои

Эпик — это бизнес-цель, реализация которой проходит через несколько слоёв (домен, API, backend, UI).

Артефакты слоёв первичны — сущность, страница, endpoint существуют сами по себе и в принципе могут участвовать в нескольких эпиках. Эпик их не «забирает себе», а собирает вместе и добавляет cross-cutting контекст (use-case, state-machine, AC, локальные решения).

Артефакт физически группируется в подпапку с именем области/эпика, если он относится только к нему:

Если артефакт начинает использоваться в нескольких эпиках — он поднимается на верхний уровень слоя (без эпик-подпапки) и его эпик-специфичные ссылки выносятся в epic-уровень.

60_Epics/<Epic>/ — тонкая нить: эпик-документ, use-cases, state-machines, локальные решения, AC. Содержит только то, что характерно для пересечений слоёв; не дублирует их.

9.1. Ссылки артефактов слоя на эпик

Артефакт, физически лежащий в подпапке своего эпика, может ссылаться на локальные документы эпика (OQ-N в эпик-документе, R-N в локальных ADR, AC-*). Это естественный контекст: пока артефакт привязан к эпику, его комментарии живут в эпик-словаре.

Если артефакт перестаёт быть эпик-специфичным — все ссылки на эпик надо вычистить до перемещения наверх. Артефакт без эпика не должен ссылаться на конкретный эпик-документ.

11. Проверки качества документа (обязательны при каждом касании)

При создании или правке любого документа спеки проверять пять пунктов — и озвучивать замечание, если что-то не так (не молча):

  1. Структурные связи проставлены. Документ, зависящий от другого, обязан нести связь явно (IHasStructuralLinks, см. раздел «Структурные связи»). Забыли проставить — напомнить и доставить.

  2. Проза, а не структурное описание. Тело документа пишется связной прозой (по материализации прозы из секций), а не «структурным конспектом»/списком ради списка. Встретили документ, написанный структурным описанием вместо прозы — по возможности переписываем в прозу; как минимум — напоминаем.

  3. Нет документов-сирот. У документа должна быть хотя бы одна связь — исходящая или входящая (на него кто-то ссылается, либо он ссылается). Встретили полностью изолированный документ (никто не ссылается и он ни на кого) — напомнить: либо вписать его в граф, либо это сигнал, что документ лишний/недописан.

  4. Vision — точка входа. Vision не просто неструктурированный вход от заказчика (10_Vision/) — вся спецификация разбивается на разделы, достижимые от Vision: от Vision до любого пункта спецификации должен существовать путь по ссылкам (RefTo/nameof, структурные связи), пусть и через несколько документов. Новый документ, до которого от Vision нет пути — не только документ-сирота (п.3) в узком смысле «нет связей вообще»; это пробел именно в навигации Vision — отследить и дописать путь, а не только факт наличия произвольной связи.

  5. Раздел — только по теме заголовка. Текст под заголовком раздела/секции относится только к этому разделу. Факт, принадлежащий другой теме (другой процедуре, ролевой модели внутри описания загрузки, плану правок самого документа внутри описания целевого состояния и т. п.), не смешивается в прозу «заодно» — выносится в свой раздел, даже если по смыслу он рядом.

12. Запросы на изменение (Change Request)

Замечания после тестирования и пожелания пользователей оформляются батчами в документы kind IChangeRequestDocument (папка «Запросы на изменение», 35_ChangeRequests/). Цель — трассируемость: по системе видно, почему принято то или иное решение.

Один батч = один документ. Имя — дата + тема: DOC_CR_<гггг>_<мм>_<дд>_<Тема>. Новые замечания другой даты — новый документ (старый не правим задним числом, кроме раздела «Итог»).

Структура CR-документа:

  1. Источник (как есть) — дословный текст замечаний/пожеланий (verbatim: язык и опечатки сохраняем, как при миграции).
  2. Разбор — что меняем по каждому пункту, детали, открытые вопросы.
  3. Blast radius — на что влияет: спеки (страницы/компоненты/endpoint/use-case), backend, UI, миграции/swagger. Затронутые документы объявляются структурными связями (Rel.Uses<…>) — тогда влияние видно на «Графе связей», а на затронутых страницах появляется обратная ссылка на CR.
  4. План — шаги (спека → реализация), чек-лист.
  5. Итог — что фактически сделано (со ссылками) + статус по каждому пункту: выполнено / отложено / в работе.

Ссылки в прозе. Кроме раздела «Источник (как есть)» (он verbatim), в прозе CR на каждую упоминаемую страницу/компонент/endpoint/use-case/термин ставим навигационную ссылку (SdaidAnchors.RefTo<…>() в interpolated-строке, либо markdown-ссылка), чтобы из CR можно было перейти к затронутому документу. Затронутые документы дополнительно объявляем структурной связью Rel.Uses<…> (blast radius на графе).

UI-изменения — сначала макет. Если CR меняет внешний вид страницы, сперва обновляем (или создаём) её wireframe-мокап, показываем пользователю и получаем «ок», и только затем идём в реализацию. Не начинаем кодить визуальную часть, не показав, что получится.

Процесс: оформили CR из «как есть» → обсудили разбор/blast radius/план → идём по нему (спека + мокап для UI → согласование → реализация) → фиксируем «Итог» и откладываем CR (статус). Так батчи замечаний не перемешиваются и каждое решение прослеживается до источника.

13. Сервисы и алгоритмы

В backend бывают абстракции, которые не являются ни хендлерами, ни сущностями: сервисы (например, оркестрация приёма данных из АИС, расчёт по модели потребления, отправка уведомлений) и алгоритмы (парсеры выгрузок, экстракторы полей, валидаторы энергопаспорта). Их тоже надо специфицировать.

13.1. Расположение

Сервисы и алгоритмы живут в слое 55_Backend (см. M_S6_FolderStructure):

13.2. Что в C#-файле сервиса

13.3. Почему класс, а не интерфейс

Конвенция «комментарий говорит ЧТО, тело говорит КАК» — естественная и ценная. Интерфейс не позволяет описать «КАК должен работать метод», только сигнатуру и контракт. Если запихать «КАК» в XML-doc, получается смешение «что» и «как» в одном комментарии — теряется смысловое разделение.

Класс с пустыми телами решает это естественно: XML-doc метода = «что», тело со spec = «как». Реализация в src/ — отдельная задача и отдельный класс.

Spec-классы:

13.4. Когда всё-таки interface

Если у спеки нет «как» (только контракт типа маркера или dispatch-точки) — допустим интерфейс. Но когда в спеке есть существенный «КАК» — это сигнал использовать класс.

13.5. Что НЕ делать

13.6. Связка сервис ↔ хендлер ↔ state machine

Типичная картина для сервисов с состоянием:

Все три ссылаются друг на друга через nameof(). Хендлер не дублирует содержимое state machine и XML-doc сервиса, но интегрирует их: где сервис делает «что», state machine задаёт «когда какой переход», а хендлер связывает их в «при вызове endpoint происходит вот это».

14. Каноническое описание операции

Когда читатель приходит с вопросом «что произойдёт, если дёрнуть этот endpoint», он должен в одном месте получить полную картину — без необходимости стыковать обрывки из 4-х файлов.

Этим местом является handler-spec — string spec в <Name>Handler.cs.

14.1. Распределение описания

Файл / артефакт Где живёт Что описывает
Endpoint (<Name>Endpoint.cs) 45_Api/Endpoints/<Area>/ Назначение операции в 1-3 предложения + указатель на handler через <see cref>. Не описывает поля — XML-doc каждого поля живёт на самом поле в Query/Command/Result.
Handler (<Name>Handler.cs) 55_Backend/Handlers/<Area>/ Каноническая narrative — проводит сквозь всю цепочку: валидация входа → загрузка данных → ветвление по состоянию → вызовы методов сервиса (с краткими 1-2-предложными summary) → применение результата → сохранение → ответ. Реакции на ошибки и не-happy-path тоже здесь.
Service (<Name>Service.cs) 55_Backend/Services/<Area>/ Что конкретно делает один шаг — без оркестрационного контекста и без ссылок на конкретного вызывающего (сервис может переиспользоваться). Класс, не интерфейс (см. соответствующую секцию).
State machine (<Aggregate>StateMachine.cs) 60_Epics/<E>/UseCases/<UC>/StateMachines/ Какие статусы и переходы разрешены, при каких условиях. Не содержит «как работает конкретный handler». Описывается секциями с mermaid в Body.
Use case 60_Epics/<E>/UseCases/<UC>/ (класс use-case-а как IUseCaseDocument + Given/When/Then секциями) Сквозной сценарий пользователя через несколько endpoint-ов. Если операция ограничена одним endpoint — use-case не нужен.

14.2. Что НЕ делать

14.3. Длина handler-spec

14.4. Ссылки в spec-строках — inline nameof / RefTo

nameof(...) и SdaidAnchors.RefTo<...>() ставятся inline в spec-строках. Выносить их в class-level const-алиасы не требуется: спека читается в сгенерированном HTML (не в исходнике), а алиасы создают лишний шум при чтении кода (в т.ч. для ИИ-агента). При необходимости подключаем namespace через using.

15. Страницы и формы (page / modal / drawer)

Страница описывается прозой — какие возможности от неё ожидаются, а не структурой компонентов. Это IPageDocument (в 65_Ui/Pages/<Area>/) с:

Ссылки — типизированные (SdaidAnchors.RefTo<...>()): на endpoints-источники данных и команды, на доменные сущности, на другие страницы. Клик ведёт на их страницы, переименование ловит компилятор. Голую прозу-имя без ссылки не оставляем.

Список и его карточка/форма (drawer/modal) живут в одном документе страницы, пока близки по смыслу. Если форма вырастает в модалку или отдельную страницу редактирования — выделяется в отдельный IPageDocument.

15.1. Что НЕ пишем

15.2. Доступ

Доступ к странице и действиям — через UI-claims; серверные операции дополнительно проверяются на backend. В прозе указываем ожидаемый доступ.

15.3. HTML-прототип

Кликабельный макет кладётся в html/ рядом со страницей и встраивается в спеку через SdaidHtml.IframeOf<TPage>(...) (+ SdaidHtml.LinkTo<TPage>(...) — открыть в новой вкладке). Прототип берёт стили из единого 65_Ui/html/shared.css — не инлайнит свой CSS, чтобы прототипы не «плыли» друг от друга. Разметка прототипа самодостаточна, общий стиль — один на все.

16. Когда нужен use-case, а когда нет

Use-case-документ создаётся только если выполнено хотя бы одно:

Иначе — описание живёт в page-спеке. «Просмотр одного грида + одного query» — не use-case, это функциональность одной страницы.

Use-case — карта пересечений: цель, актор, шаги Given/When/Then на бизнес-уровне (без слов «контроллер», «компонент»), таблица «шаг → page → endpoint → handler → entity», локальные NFR, открытые вопросы. Пересказ page-спеки в use-case — запрещён.

17. State machines

State-machine состоит из трёх частей, и попытка положить всё в один формат теряет одну из них:

  1. Структура (статусы, допустимые переходы) — таблица.
  2. Поведение (что делается на переходе) — проза.
  3. Архитектурная привязка (где исполняется) — короткий блок.

State-machine — это C#-класс с маркером IStateMachineDocument, в Body секций которого:

Файл размещается рядом с use-case-ом, который владеет машиной: 60_Epics/<E>/UseCases/<UC>/StateMachines/<Aggregate>StateMachine.cs. Имена статусов компайл-сейфтятся через nameof() на enum.

18. Именование

19. ADR vs локальные решения эпика

Если решение в эпик-локальном Adr начинает повторяться в других эпиках — поднимаем его в 40_Arch/ADR/ отдельной задачей.

20. Открытые вопросы

Все нерешённые вопросы эпика живут в одном месте — секция «Открытые вопросы» в IEpicDocument-классе эпика. Use-cases, UI-spec-и, handler-spec-и и state-machine-ы на эти вопросы ссылаются через nameof() на конкретные секции (OQ-секции эпика — каждая отдельный класс-секция, тогда auto-linkify работает).

Это запрещает спеке выглядеть «готовой», когда в ней дыры.

21. Acceptance criteria

AC живут на двух уровнях:

Главное представление — сводный список всех AC по проекту со статусами (генерится автоматически по kind-index-у или через faceted view).

Правила:

22. Карта реализации (traceability)

Traceability автоматическая — строится генератором из nameof()-упоминаний в прозе и из структурных связей. Не нужно вручную поддерживать таблицу «Артефакт → Где»; mention-graph её собирает.

Use-case остаётся самостоятельным документом (IUseCaseDocument) потому что:

На странице эпика и use-case-а генератор автоматически рендерит блок «Связанные артефакты» (все классы, упомянутые через nameof()) и «Где используется» (обратные ссылки).

23. Как работать со спекой

23.1. Разработчику

23.2. ИИ-ассистенту

23.3. Сборка спеки

dotnet build sdaid/Rugrid.Sdaid.Epas/Rugrid.Sdaid.Epas.csproj

Проект — это сама SDAID-спека на C#. Компилируется как линтер: переименование сущности через F2 правит все её упоминания, find-references показывает зависимости, ссылки на несуществующие классы (через nameof(), RefTo<T> и т. п.) ломают сборку.

После любого обновления методологии (и спеки в целом) — обязательно перегенерировать HTML-сайт вызовом sdaid\build-html.cmd. Сборка проверяет компайл-сейфти, рендер — что секции выглядят как ожидалось; одно без другого оставляет рассинхрон.

24. Общие антипаттерны

Базовый список того, что не нужно делать при ведении спеки. Не привязан к структуре папок — антипаттерны процесса/мышления.

25. Журнал изменений методологии

Принятые изменения самой методологии — для тех, кто читал её раньше и хочет увидеть, что поменялось, не перечитывая документ целиком. Новые записи — сверху; дата — когда решение принято. Это журнал решений по методике (не путать с CHANGELOG нотации Rugrid.Sdaid.Notation, который описывает API авторской нотации).

25.1. 2026-11-05

25.2. 2026-08-14