Статус: зафиксировано 2026-09-11 по итогам обсуждения с владельцем; проверено продюсером (см. «Поправки продюсера» ниже). Кода нет.
FDML — не один pipeline, а два конвейера, сходящиеся в один граф:
USER intent / value
│ forward: LLM-черновик, человек объявляет
▼
FORWARD ───────────► FEATURE ◄──────────── REVERSE
│ ▲ candidate, inferred
├── SOLUTION │
├── ENGINEERING PRODUCT DOMAIN (интерпретация)
├── CODE ▲
└── EVIDENCE ENGINEERING (observed)
▲
SCAN ◄── SOURCE CODE
- Forward flow. USER INTENT → LLM → VALUE + CONSUMER + PRODUCT DOMAIN → FEATURE → SOLUTION → ENGINEERING → CODE → EVIDENCE. Доказано на 3–5 реальных фичах, что domain и consumer реально помогают сформировать Feature.
- Reverse flow. SOURCE CODE → SCAN → OBSERVED ENGINEERING STRUCTURE → PRODUCT DOMAIN → LLM → CANDIDATE FEATURE. Доказано на существующем проекте (Doom), что из кода получаются осмысленные candidate features.
- Граница reverse. Reverse восстанавливает что система делает (observed /
inferred), но не имеет права утверждать зачем, для кого, какую ценность. Нет в
источниках —
unknown. - Provenance на каждом шаге:
declared | observed | inferred | verified | unknown; определяется источником знания, не доменом. - Domain — не отдельная модель проекта и не второй глоссарий: внешний термин →
существующая сущность проекта →
owns/invariants. Импорт только по требованию. - Язык. После эксперимента фиксируются только необходимые:
consumers,domains,value.for,feature.domain,consumer.kind, правила lint. - Доказательство архитектуры. Оба потока сходятся в один граф, не в две модели.
Один и тот же Feature эволюционирует по эпистемическому статусу: observed structure → inferred candidate → declared feature → verified implementation.
- Критерий «пользователь не пишет Feature руками» противоречит п. 4.
declaredозначает «человек написал или принял». Точная формулировка: LLM пишет черновик (inferred), человек объявляет (declared). Автор фичи — человек, автор текста — кто угодно. Иначе forward-flow производит фичи со статусом inferred, которые никто не объявил, и вся цепочка доказывает inferred-намерение. - Forward-flow с LLM — не измерен.
enrich --tier llmсуществует в ядре (локальный Ollama, схемно-ограниченная проза), но записанный опыт с локальными моделями отрицательный: сборка платформы падала на малых моделях,search --llmдал +4 п.п. за 8,5 с на запрос. DoD п.1 опирается на неизмеренный компонент; измерять его надо на reverse (п. 2), где ошибка модели дешевле — она предлагает, а не объявляет. - Порядок. Reverse-эксперимент на Doom идёт раньше consumers/domains: он покажет, какие именно domain-связи нужны, а не какие красиво выглядят. Скан Doom уже есть (граф вызовов сверен с clang), цена эксперимента — один прогон enrich.
- Число для п. 2 надо назначить до прогона, иначе «осмысленные» решит вкус:
из N candidate features человек принимает k как declared без переписывания; порог
и N записываются до запуска, результат — в
research/.
Старый подход — дамп наблюдаемой структуры (18,8k символов) в модель — даёт кандидатов уровня подсистем и не рентабелен: малая локальная модель 70 с и 4/10 продуктовых формулировок, средняя не ответила за 40 минут.
Новый порядок SOURCE → static analysis → index → behavioral units → compact index → LLM работает: юниты извлекаются детерминированно из сверенного с clang графа
вызовов (270 точек входа → 98 юнитов, два прогона — один дайджест), top-30 занимает
3,5k токенов, и Haiku по нему даёт 10/10 кандидатов с нулевой утечкой намерения,
полной привязкой к индексу и продуктовыми формулировками 9/10; с внешним словарём
жанра 10/10 и все названы терминами домена. Восемь из десяти кандидатов в двух
независимых прогонах указывают на одни и те же юниты: выбор делает индекс, модель
делает только переход observed → product. Подробности и таблица —
research/reverse-flow/PROTOCOL.md. Принятие владельцем (k ≥ 5/10) ещё не снято.
Владелец принял эксперимент. Формулировка результата:
Behavioral index — достаточное компактное представление наблюдаемой системы для reverse candidate extraction. Детерминированный индекс выбирает поведенческий субстрат; LLM выполняет только перевод observed → product-domain candidate. Словарь домена улучшает терминологию, не меняя выбранное поведение.
Доказано на одном проекте (C, выраженные точки входа, хорошие имена). Не доказано: другой тип приложения, другой язык, плохие имена, граф без точек входа, огромный граф, способность, размазанная по слабосвязанным участкам. Не чинить заранее.
Граница ответственности:
SOURCE → STATIC ANALYSIS (entities, functions, calls, types, flows)
→ BEHAVIORAL UNITS (entry points, reachability, clustering, deterministic ranking)
→ BEHAVIORAL INDEX (observed facts + product-domain vocabulary)
→ LLM → CANDIDATE FEATURE (inferred)
LLM не имеет права утверждать: why, value, consumer, original intent.
Старый путь (дамп → LLM) — экспериментальная база, не production path. Порог принятия не повышать: гипотеза подтверждена, эксперимент закончен.
Долг, зафиксированный как потребность, не как фикс: юниту нужен types_used —
факт статического анализа «функция использует тип», а не совпадение слова в имени.
Из двух дизайн-документов 1992 и 1994 годов и заметки владельца собран эталонный
vision.fdml для внешнего C-проекта: 7 корней с value из метрик самой игры, 41 фича
со статусом против кода (26 implemented, 6 changed, 7 dropped, 2 design), реализующие
функции проверены по индексу, трассируемость от сканированных фич к эталону
сгенерирована детерминированно. fdml validate принимает его как граф ценности.
Что эталон дал сразу:
- три оси gap числом: 95 из 263 сканированных фич реализуют замысел, 168 — структура без замысла; 7 замыслов без кода (dropped); 2 замысла, которые не код;
- reverse-кандидаты измеримы: 10 кандидатов достигают 21 из 32 подкреплённых кодом фич эталона; список недостигнутых — это «что искать в коде дальше», список dropped — «чего нет и не будет»;
- баг ядра: файл с суффиксом
_specсчитался тестом, 18 функций выпадали из графа; починено.
Эталон живёт в research/reverse-flow/external-c-etalon.fdml и генерируется etalon.py.
Эталон 41 → подкреплены кодом 32. Десять кандидатов достигают 21 из 32 (unit-level), двадцать — 28 из 32, при нулевой утечке намерения и полной привязке. Таксономия одиннадцати промахов первого прогона, решённая по данным: ни одна фича не отсутствовала в детерминированной абстракции; восемь были показаны и не выбраны (лимит на число кандидатов, снят вторым прогоном); две опосредованы хабами (сквозные поведения вроде урона не живут ни в одном юните); одна ушла ниже top-30.
Строгий recall, когда считаются только функции, названные кандидатом, ниже: 14 и 17 из 32. Разрыв — «названо слишком общо»: кандидат указывает на нужный юнит и называет два-три его члена. Для связи кандидат → код хватает первого, для формулировки фичи честнее второе.
Три остаточных ограничения v1 записаны, не чинятся: хабы, хвост ранжирования,
целое против частей. Карты домена построены только из того, что появилось в
источниках, эталоне и восстановленном; solution-домен — из механизмов FAQ и 168
сканированных фич без замысла по семействам модулей. Всё в research/reverse-flow/.
Observed abstraction COMPLETE vs etalon (A1 = 0: все 32 кодовые фичи представлены)
│
Candidate retrieval WORKING (10 → 21/32, 20 → 28/32 unit-level; strict 14 → 17)
├── B ranking / budget — снято бюджетом кандидатов
├── A2 long tail — ниже top-30, чистый retrieval
├── C coarse naming — 28 против 17: указывает на юнит, называет 2–3 члена
└── D cross-cutting hubs — сквозные поведения через хабы не представлены v1
Никакая улика сейчас не оправдывает изменение сканера, абстракции, построения
юнитов, новой онтологии, RAG или разрезания хабов. Следующий объект — не
extraction, а стыковка forward и reverse: как восстановленный product domain и
эталонный vision.fdml ложатся на то, что FDML уже умеет для forward-flow, и нужны
ли consumers/domains из планируемого 1.4.3 по факту, а не по теории.
Forward-инструменты принимают эталонный vision.fdml как есть: strict-валидация
чистая, 142 связи realizes разрешаются, evidence пуст, ни один из 41 сценариев ещё
не верифицирован тестом. Граф сходится без новых конструкций; не хватает тестов и
доказательств, не языка.
Проверка полей 1.4.3 по факту, на всех 41 фичах эталона: потребителей в источниках
два, игрок и автор уровней; второй владеет пятью фичами и его появление создало
восьмой корень и перенесло три фичи. beneficiary из 1.4.1 этого выразить не может:
оба user. value.for оправдан. Область домена не изменила ни одной формулировки и
ни одного корня; единственную переклассификацию сделал существующий layer: system.
domains откладывается. Оба поля сегодня молчат: парсер игнорирует неизвестные ключи,
поэтому for без lint ничего не значит.
Уточнение к предыдущему разделу: domains как формальная конструкция языка
откладывается; доменное знание (термины, механики, правила, инварианты, связи с
фичей, решением и кодом, источник) обогащается уже сейчас, и цель этого слоя —
поиск. Проверено ленивой версией на эталоне: четыре доменных вопроса к индексу
внешнего проекта промахивались 4 из 4; пять механизмов из дизайн-FAQ, записанные
штатными нотами навигатора с якорем на функцию и алиасами, дали 5 из 5 первым
результатом: продуктовый смысл, механика, код и источник в одной строке. Новых
конструкций ноль. Порядок: consumers + for + lint → tests + evidence → обогащённый
домен на эталоне → поиск по нему → vision FDML → gap → decisions.
- Feature — база: единственный объект, через который сходятся документы и код.
- Notes — knowledge layer: термины, механики, правила, инварианты, решения, с якорем на код и provenance в теле.
- Evidence — доказательство: тест → verifies → сценарий → фича.
- Consumers /
value.for— кому принадлежит ценность; доказано вторым потребителем. - Layer — граница product / system; пока хватает.
- Domain — обогащается через ноты, в язык не входит до доказанной необходимости.
- Decision — не вводится. Вычеркнут из очереди по данным: Feature нужен
постоянно, Decision ни разу не заставил себя ввести. Решение «X вместо Y» — нота
kind rejected/method/invariant с provenance. Возврат только при постоянной
потребности машинно связывать решения с фичами, искать их и проверять supersedes.
Provenance
statusиз 1.4.2 §4 остаётся отдельным вопросом на момент, когда reverse-кандидат пойдёт в FDML-файл как inferred.
Порядок до среза v1: consumers + for + lint → 41 сценарий → tests → evidence → домен в ноты → бенчмарк поиска по нотам → vision.fdml самого FDML (3–5 фич с for) → gap → посмотреть, что осталось. Срез v1: DOCUMENT INTENT → FEATURE ↔ KNOWLEDGE ↔ OBSERVED CODE → EVIDENCE.
Почти всё — в существующие элементы. Правила и инварианты домена — это constraints
из 1.3 с ссылкой actions на сканированные действия: шесть правил дизайн-FAQ
добавлены в эталон, strict-валидация чистая. Механики и solution-цепочки — ноты
навигатора с якорем на функцию, они уже отвечают на доменные вопросы 5/5. Термины —
описания сущностей скана и алиасы нот. Способности — фичи эталона. Потребители —
единственная новая конструкция, 1.4.3. Без дома остаётся только область как
группировка, и она пока ничего не меняет, поэтому и отложена. Настоящая дыра —
переносимость нот: они живут в индексе, не в файле; это задача инструмента, не языка.
В одном исходнике три объекта рассмотрения, и переходы между ними не автоматические:
- Stack — техническая среда: язык, компилятор, движок, инструменты. Контекст, в котором продукт существует; сам по себе не набор пользовательских фич.
- Product — то, у чего есть потребитель и ценность (игрок, автор уровней).
- Engine Product — самостоятельный продукт со своими capabilities (рендер, звук, коллизии, секторы, триггеры, загрузка данных), который реализует фичи продукта. Одна продуктовая фича реализуется десятками engine-capabilities.
- Domain knowledge описывает мир. Оно объясняет, ограничивает и обогащает фичу, но само по себе не создаёт ни фичу, ни требование к движку или стеку.
- Требование к Engine или Stack возникает только из объявленной Product Feature через явную реализационную связь.
DOMAIN ── enriches ──► PRODUCT ── requires realization ──► SOLUTION ──► ENGINE PRODUCT ──► STACK
DOMAIN ──────X───────────────────────────────────────────► ENGINE (никогда напрямую)
Reverse идёт иначе и тоже без автоматики: CODE → observed engine capability → candidate feature → возможно связано с продуктом и доменом.
Следствие для gap: gap считается относительно объявленного Product Intent, а не относительно всего, что есть в domain knowledge. «В домене есть возможность X, в коде её нет» — не gap. Gap: «продукт объявил X, код не реализует», «код реализует, замысел не объявлен», «потребитель объявлен, фичи для него нет».
Следствие для эталона: он смешивает два продукта. Фича «рендер» и 168 сканированных фич без замысла — это capabilities engine-продукта, а не игры; у engine-продукта может быть свой vision со своим потребителем (разработчик игры на этом движке). Не переписывать сейчас; учесть при gap и при vision самого FDML, где такой же вопрос: FDML-язык и FDML-инструмент — два продукта с разными потребителями.
Потребитель не обязан быть в сканируемом репозитории: мы сканируем движок, а
потребители и ценность принадлежат игре и живут во внешнем контексте. Четыре режима
на одном движке и одном эталоне: без контекста модель достигает 27 из 32 фич
эталона движковыми словами; с идентичностью продукта то же покрытие, но появляется
атрибуция потребителя и второй потребитель не восстанавливается; с тем же кодом под
другим продуктом, SDK, 17 из 20 юнитов те же, 2 из 20 названий те же, потребители
меняются полностью и рендер становится фичей. Выбор делает индекс, интерпретацию —
контекст продукта: фичу продукта нельзя восстановить из кода движка без контекста.
Негативный контроль на внутренних юнитах: выдуманных фич ноль, ошибка только в
гранулярности. Описательный слой поверх юнитов проверен и отклонён: recall, названия
и выбор не изменились. Артефакты 04 и 05 в research/reverse-flow/.
Проверено на shipped-данных игры: детерминированный инспектор превращает карту в описание в словаре самой игры (старты, монстры, оружие, ключи, двери, триггеры, выходы, секреты, опасные полы). Слепой модели эти данные вредят: кандидаты превращаются в инвентарь уровня, покрытие эталона падает с 27 до 18 из 32. С контекстом продукта те же данные становятся уликами: покрытие 28 из 32, продуктовых формулировок 17 из 20, 12 кандидатов цитируют факт из уровня, выдуманных ноль. Описание данных не мост от структуры к фиче, а evidence для фичи, которую контекст продукта уже сделал именуемой. Знание обогащает, не создаёт.
Verdict: aligns для инварианта; conflicts для любой новой конструкции под него.
Роли — это разные источники, а не разные слои документа:
| роль | что это | где живёт уже сегодня |
|---|---|---|
| Domain | знание о мире; объясняет, не порождает | ноты, constraints 1.3 |
| Product | что обещает продукт: consumers, value, intent | vision.fdml: его system + consumers (1.4.3) + фичи с value |
| Engine | что предоставляет реализация: observed capabilities | сгенерированный спек: system со system_type/technology из discovery, 263 фичи |
| Data | evidence о реальном продукте | tests + evidence файлы; WAD-описание — ещё один раннер |
Разводить — не полем и не layer. layer отвечает «на каком уровне», а не «чей
это продукт»; и layer: system уже занят рендером. Разводит пара документов:
vision.fdml = продукт, .fdml/spec/* = наблюдаемый проект, realizes = мост.
Второй продукт на том же движке (SDK) — это второй vision-файл с другими
consumers, не второй скан.
Product Context declaration уже существует: vision.fdml есть декларация
продукта (system.id + consumers), а сгенерированный спек есть декларация
наблюдаемого проекта (system.id + system_type). Не хватает одного слова — роли
наблюдаемого проекта относительно продукта (engine | game | tool | library). Ленивая
версия: не поле языка, а description у system в vision («realized by the engine
repository X»), пока роль не понадобится машине.
Reverse-run контракт — тег прогона, не конструкция: каждый candidates.json
несёт {analyzed: <spec system.id>, product: <vision system.id | none>, consumers: [...]}; без vision — product: none, и кандидаты остаются engine capabilities.
Это исследовательское правило в research/reverse-flow/PROTOCOL.md; в язык — когда
кандидат впервые пойдёт в .fdml со статусом inferred.
Cost of yes: ноль новых понятий; одно правило протокола; один абзац в 1.4.1 §3,
называющий два потока по имени. Не делать: scope, role у Feature, новый
документ product context.
Из одного сканированного спека сгенерированы две папки. engine/vision.fdml — продукт
движка: потребители разработчик игры и автор уровней, 30 фич по одной на behavioral
unit со статусом inferred в описании, 447 связей realizes от сканированных фич, 25
флоу из скана. product/vision.fdml — игра: потребители игрок и автор уровней, 41
объявленная фича и 8 корней с value, 142 связи от скана, 84 связи realized_by к
фичам движка, 6 констрейнтов из дизайн-FAQ. Обе папки проходят strict-валидацию, все
связи разрешаются. Ни один сценарий пока не верифицирован тестом. Обнаруженная дыра
инструмента: набор документов собирается из одной папки, два продукта на одном спеке
подцеплены симлинками; нужен набор проекта с несколькими vision-файлами.
fdml index . && fdml all . структура и сканированный спек
fdml propose --product product/vision.fdml --consumer player --consumer level_author
.fdml/spec/candidates.fdml: 30 фич status: inferred,
тег контекста, 505 realizes от сканированных действий
fdml note --import .fdml/notes.json домен из внешних источников (round-trip проверен)
fdml validate product/vision.fdml набор проекта: несколько vision на один спек
fdml gap product/vision.fdml 71/80 покрыто, 9 roadmap (= 7 dropped + 2 design
эталона), 103/293 orphan — capabilities движка
Кандидаты из команды воспроизводят исследование дословно: 98 юнитов, top-30
достигают 28 из 32 кодовых фич эталона, те же четыре мимо. Провenance status в
парсере; realized_by понимается как обратное realizes. Ноты имеют файловую
форму, но .fdml/ игнорируется git — экспортированные ноты в репозиторий не
попадают без решения владельца и аудита содержимого.
Не сделано из очереди: consumers + value.for с lint (1.4.3), fdml accept, тесты
и evidence на сценарии, карточка во viewer. Гипотезы, проверенные и снятые по дороге:
описательный слой, данные продукта как слой, домен как детектор.