Методология

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

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

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

⟨/⟩ Исходник

1. Spec first

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

Раскрывается по мере проработки.

2. AI first + optimized

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

Раскрывается по мере проработки.

3. C# compilable

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

Раскрывается по мере проработки.

4. Human readable

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

Раскрывается по мере проработки.

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

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

Раскрывается по мере проработки.

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

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

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

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

7. Язык прозы: спека читается заказчиком

Спецификация — не только внутренний документ. Её разделы цитируют в переписке, переносят в документы для заказчика, показывают на согласованиях. Заказчик знает «информационную карту», «пакет ввода», «уверенность распознавания» — и не обязан знать InformationMap, InputPacket, RecognitionConfidence.

Правило: идентификаторы классов не выводятся в прозу.

Что Как писать
Термин глоссария GlossaryAnchors.RefTo<Term>("русская словоформа") — ссылка типизированная, на экран идёт заданный текст, склоняемый по месту
Ссылка на артефакт (FR, ADR, эпик, страница) по-русски в тексте; код — в блоке «Связанные артефакты» в конце документа
Технические таблицы (трассировка, структура) коды допустимы: эти таблицы адресованы разработчику

Почему не «просто nameof() везде»: nameof() даёт compile-safe ссылку, но отображается именем класса. Это правильный инструмент для трассируемости и неправильный — для текста, который читает человек со стороны заказчика.

Разделение «этот документ увидит заказчик, а этот нет» на практике не держится: куски цитируют и переносят. Поэтому правило действует на всю спеку, а не на её «внешнюю» часть.

7.1. Блок «Связанные артефакты»

Ссылки, вынесенные из прозы, собираются в конце документа отдельной секцией:

## 8. Связанные артефакты
- {nameof(Ban.Sdaid.Icd.Requirements.FR_003_AttributeExtraction)} — Сегментация и извлечение атрибутов

Полное имя типа в nameof() избавляет от лишних using, а обратные связи генератора строятся по тем же упоминаниям, что и раньше.

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

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

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

Один домен — icd. Вся предметная область — единый домен. Чтобы группировать сущности, домен поделён на доменные модули (50_Domain/<Module>/: Capture, Recognition, Verification, …). Это группировка по смыслу, а не отдельные 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/integration) — в 70_Infrastructure/, тоже по необходимости (сейчас файловое хранилище живёт в 55_Backend/Files).

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

00_Methodology/        как работать со спекой
00_Migration/          инструкция миграции md→SDAID (временная, пока идёт перенос)
10_Vision/             слабоструктурированный вход от заказчика
20_Glossary/           общий проектный словарь
30_Requirements/       FR — материализуются по мере проработки
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/             домен icd, поделён на доменные модули
   <Module>/
      BC_<Module>.cs     overview модуля (IBoundedContextModuleDocument)
      <Entity>.cs        сущности и агрегаты
55_Backend/            поведение бэка
   Handlers/<Area>/      canonical narrative операций (IHandlerDocument)
   Services/<Area>/      доменные / прикладные сервисы
   Import/, Files/       сервисы импорта, файловое хранилище
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-ы
70_Infrastructure/     [при необходимости] инфраструктурные контракты (file/MQ/integration)
80_Subsystems/         [при необходимости] особенности реализации деплой-единиц

9.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-документы (материализуются по мере проработки)
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>/, Import/, Files/ ISdaidDocument / IDomainServiceDocument (+ IFolder<BackendFolder>) Сервисы реализации, импорт, файловое хранилище
60_Epics/<E>/ (readme) IEpicDocument Цель эпика, акторы, scope, открытые вопросы, AC
60_Epics/<E>/UseCases/<UC>/ IUseCaseDocumentPrimaryActor) 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/ ISdaidDocument / IUiSpecDocument (+ IFolder<UiFolder>) Компоненты, layout-ы
70_Infrastructure/<Capability>/ IInfrastructureDocument Контракты на file/MQ/integration (по необходимости)
80_Subsystems/<S>/ IAdrDocument и др. (+ IFolder<SubsystemsFolder>) Особенности реализации подсистемы (по необходимости)

9.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>/ (по необходимости)

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

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

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

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

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

Раскрывается по мере проработки.

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

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

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

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

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

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

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

Артефакт, физически лежащий в <Epic>/-подпапке, может ссылаться на локальные документы эпика (OQ-N в readme.md, R-N в adr.md, AC-* в acceptance.md). Это естественный контекст: пока артефакт привязан к эпику, его комментарии живут в эпик-словаре.

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

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

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

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

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

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

14. Запросы на изменение (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 (статус). Так батчи замечаний не перемешиваются и каждое решение прослеживается до источника.

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

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

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

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

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

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

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

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

Spec-классы:

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

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

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

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

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

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

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

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

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

16.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>/ (или Import/, Files/) Что конкретно делает один шаг — без оркестрационного контекста и без ссылок на конкретного вызывающего (сервис может переиспользоваться). Класс, не интерфейс (см. соответствующую секцию).
State machine (<Aggregate>StateMachine.cs) 60_Epics/<E>/UseCases/<UC>/StateMachines/ Какие статусы и переходы разрешены, при каких условиях. Не содержит «как работает конкретный handler». Описывается через ISdaidDocumentSection с mermaid в Body.
Use case 60_Epics/<E>/UseCases/<UC>/ (сам класс use-case-а как IUseCaseDocument + Given/When/Then секциями) Сквозной сценарий пользователя через несколько endpoint-ов. Если операция ограничена одним endpoint — use-case не нужен.

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

16.3. Длина handler-spec

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

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

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

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

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

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

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

17.2. Доступ

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

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

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

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

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

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

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

19. 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.

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

21. ADR vs adr.md эпика

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

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

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

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

23. Acceptance criteria

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

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

Правила:

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

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

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

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

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

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

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

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

dotnet build sdaid/Ban.Sdaid.Icd/Ban.Sdaid.Icd.csproj

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

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

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

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

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

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

27.1. 2026-08-03