Перейти к основному содержимому

ADR 0068 — Диаграмма C4 Arquiteto

  • Статус: принят
  • Дата: 2026-08-12
  • Контекст: запрос пользователя — Arquiteto получает новый результат, отрисовываемый в Visão Geral проекта
  • Расширяет: тот же шаблон артефакта-без-таблицы, что ADR 0065 (artifact.project_image)

Контекст​

Результаты Arquiteto сегодня — это текст и структурированные данные: module_map (сетка карточек), ADR (список со ссылкой на PR), insights. Ни один не является изображением архитектуры. Запрос — диаграмма C4 (модель Саймона Брауна — как минимум Context и Container), действительно отрисованная на экране, а не ссылка наружу.

Два архитектурных вопроса до написания кода:

Кто пишет уровень Container — модель или репозиторий? module_map уже существует, проверенный на отсутствие циклов (create_module_map). Позволить модели ПЕРЕПЕЧАТАТЬ модули и зависимости в tool call диаграммы — риск тихого расхождения: диаграмма говорила бы одно, действующий module_map говорил бы другое, и не было бы способа узнать, кто солгал. Альтернатива — выводить уровень Container из действующего module_map в use case и никогда из того, что модель пишет заново, — закрывает это расхождение по построению. У уровня Context такого источника нет: кто внешние акторы (пользователь, git- provider) — это суждение Arquiteto, без module_map акторов, из которого можно вывести. Поэтому только Context приходит из tool call.

Чем отрисовывать? В apps/web не было ни одной библиотеки диаграмм (ноль вхождений "mermaid" в package.json до этого изменения); сайт документации использует Mermaid, но во время СБОРКИ (Docusaurus). Mermaid — де-факто стандартный движок для диаграмм-как-текста, включая нативную поддержку C4Context/C4Container, — мы не пишем движок раскладки. Решение пользователя, подтверждённое до этого изменения: mermaid входит как новая RUNTIME-зависимость React-приложения, первая такого рода.

Решение​

Диаграмма C4 — версионируемый артефакт в event log, без таблицы, — та же конструкция, что artifact.project_image (ADR 0065), — с уровнем Container, ВЫВЕДЕННЫМ из действующего module_map, никогда не перепечатываемым моделью.

  • artifact.c4_diagram: CreateC4DiagramUseCase получает действующий module_map (ModuleMapRepository.findCurrent); без него отказывает с 400 — нет уровня Container без модулей. Генерирует оба синтаксиса Mermaid (gerarDiagramaContexto/gerarDiagramaContainer, чистые, в domain/architecture/c4-diagram.ts) и записывает событие с version = GetC4DiagramUseCase.execute(projectId).version + 1 — как чтение "действующий — с наибольшим version" в ObterContainerDoProjetoUseCase. Повторная выдача — это генерация заново; история не переписывается.
  • Без regex при генерации синтаксиса: имя модуля/актора приходит от модели (или из того, что модель уже написала в create_module_map), и js/polynomial-redos здесь был бы той же HIGH, которой project-container.ts уже избегает. Экранирование метки и генерация id Mermaid посимвольные, в том же стиле, что referenciaDeImagemValida.
  • Инструмент create_c4_diagram у Arquiteto: system_name (обязательно), system_description и actors (имя + person/ external_system + описание). Тонкий — лишь нормализует и передаёт дальше; валидирует и выводит api, тот же шаблон, что create_module_map/ choose_project_image.
  • mermaid изолирован за apps/web/src/lib/mermaid-render.ts (renderMermaid(id, sintaxe)), с ДИНАМИЧЕСКИМ import() — тот, кто никогда не открывает Visão Geral со сгенерированной диаграммой, не платит за bundle. vite build подтверждает: eager загружается только index-*.js (entrypoint); чанки Mermaid (mermaid.core, c4Diagram-* и его тяжёлые зависимости — cytoscape.esm 435 KB, katex 258 KB — используемые только ДРУГИМИ типами диаграмм, которые этот продукт не генерирует) остаются по требованию.
  • C4DiagramView.tsx (apps/web) — три состояния на диаграмму (RN-088): rendering (Skeleton), erro (Alert с сообщением Mermaid + сырой синтаксис, свёрнутый в <details>, НИКОГДА не ломающийся экран) и pronto (SVG через dangerouslySetInnerHTML — содержимое, которое МЫ генерируем через mermaid.render, а не переданный HTML третьей стороны, с securityLevel: 'strict' в Mermaid). Четвёртое состояние, "диаграммы нет вовсе", принадлежит разделу, вызывающему компонент (ArchitectureSection), как уже существует для moduleMap/ADR.
  • Тема Mermaid читается из токенов дизайн-системы в runtime (getComputedStyle по --surface-*/--text-*/--border*), никогда фиксированным цветом — не потому, что у приложения сегодня есть переключатель темы (его нет: dark основная), а чтобы не хардкодить палитру, которая уже есть в tokens.css.

CSP — подтверждено, без изменений​

У nginx образа web закрытый CSP со времён ADR 0058 (script-src 'self', без unsafe-eval/unsafe-inline; style-src 'self' 'unsafe-inline'). Проверено на продакшен-ассетах (grep -rl "new Function(\|eval(" dist/assets/*.js): НИ ОДИН чанк Mermaid не использует eval/new Function. style-src уже имел unsafe-inline до этого изменения (по другой причине — inline-CSS самого приложения), и именно он покрывает <style>, который SVG Mermaid может встраивать. Ни одна строка nginx.conf не изменилась.

Последствия​

  • Bundle больше, но по требованию. pnpm build подтвердил корректное code-splitting: entrypoint (index-*.js) не вырос на вес всего Mermoid — чанки скачивает только тот, кто действительно отрисовывает диаграмму. Реальная стоимость (не измерена в байтах сети, лишь подтверждена как lazy) приходится на первый раз, когда Visão Geral показывает сгенерированную диаграмму.
  • Только Context и Container. Component и Code (уровни 3 и 4 C4) остаются вне — у module_map нет гранулярности компонентов/кода, а выдумывать эту гранулярность не просили.
  • Плотная диаграмма не получает особой обработки. module_map с десятками модулей даёт большую диаграмму Container; нет виртуализации и сворачивания — тот же срез, что FASE 26 уже сделала для вкладки Code ("a mais custosa do programa"), пока принят.
  • Расхождение между диаграммой и module_map только временное, а не в данных. Поскольку Container ВЫВОДИТСЯ в момент генерации, старая диаграмма может устареть, если module_map изменится позже, — но она никогда не будет ЛГАТЬ о том, что существовало в момент генерации, потому что модули описывала не модель.