Статус: план / RFC. Не нормативная спека (
spec/— это), не proposal про ядро (docs/proposals/— это). Это карта работ по аудит-оркестратору: инструменту, который натравливает максимум готовых, реальных анализаторов и профайлеров на наш древний.NET Framework 4.7.2/ WPF / DevExpress 12.2 проект, сводит их находки в один структурированный отчёт и отвечает на вопрос «где болит, насколько и почему» — с доказательствами, а не догадками.
Own.NET Audit — это оркестратор диагностики, а не ещё один анализатор: он запускает чужие зрелые инструменты (Roslyn-аналайзеры, CodeQL, Infer#, own-check, профайлеры), нормализует всё в SARIF, скорит по cross-tool-согласию и выдаёт категоризированный отчёт о здоровье проекта с ранжированием «где хуже всего».
- Не новый чекер. Ни одной собственной эвристики «на регулярках», которая
плодит FP. Если для класса проблем нет надёжного готового инструмента — пишем в
отчёт
NO-TOOL: skippedс причиной, а не имитируем покрытие. Это прямое следствие культуры репозитория (honest skip /OWN050) и явного требования заказчика на минимальный FP. - Не раздувание ядра. Код аудита живёт в изолированном поддереве
audit/и не импортируетownlang/. own-check потребляется как внешний инструмент через его CLI и SARIF — ровно так же, как CodeQL или Infer#. Единственный общий контракт — SARIF. Поддерево можно поднять в отдельный репозиторий в одинgit mv. - Не «AI сам найдёт баги». LLM подключается строго ниже детерминированного
слоя как фальсифицируемый ревьюер/объяснятель/генератор фиксов. Он никогда не
решает «это точно leak» — это решают инструменты и рантайм-доказательства
(позиция из
docs/notes/research-landscape-2026.md, раздел про LLM).
| Принцип | Откуда и почему |
|---|---|
| Оркестратор, не анализатор | Заказчик: «помимо own.net разработок нет, время на свои детекторы тратить не хотим». Берём готовое. |
| SARIF — единственный нормализованный формат | Уже так в репозитории: scripts/oracle_compare.py и mine_report.py читают все инструменты через один parse_sarif. Любой инструмент, не умеющий SARIF, оборачивается тонким адаптером raw → SARIF. |
| Cross-tool agreement = confidence | Прямое обобщение oracle-паттерна (docs/notes/oracle.md): нашли двое+ независимых движков в одном месте — high confidence; один — кандидат/уникальный улов. Это, а не «severity из тулзы», определяет приоритет. |
| Honest skip / минимальный FP | Категория без надёжного инструмента помечается NO-TOOL, а не закрывается regex-скриптом. Каждый прогон несёт coverage-карту (что реально проверено, что пропущено и почему) — обобщение own-check --stats и OWN050. |
| Детектируемость-матрица | Не обещаем рантайм-баг статике. Каждая категория заранее привязана к слою (static / runtime / impossible) по матрице из docs/ROADMAP.md. |
| Decoupling / extractability | Высокая cohesion внутри audit/, нулевой coupling к ядру. Лифтится как самостоятельный продукт. |
| Reproducible & deterministic | Прогон по фиксированному коммиту даёт стабильный, диффабельный артефакт (как mining/oracle сейчас). Версии инструментов пиннятся и печатаются в шапке отчёта. |
| AI downstream, фальсифицируемо | LLM предлагает — детерминированный слой и рантайм проверяют. Никогда не trusted-base. |
Слой 1 — СТАТИКА (делаем первым, это основной deliverable)
target.sln ──► [fleet прогонов, каждый → SARIF] ──► normalize ──► score ──► health report
build-free: own-check, CodeQL (build-mode: none) [устойчивы к несобираемому solution]
build-req: Roslyn analyzer packs, Infer# [нужен успешный MSBuild-билд]
── весь прогон по target'у идёт на ЛОКАЛЬНОЙ Windows-машине (VS Build Tools + DevExpress 12.2);
CI самого Own.NET (Linux) гейтит только Python-selftests агрегации, а не аудит target'а ──
Слой 2 — РАНТАЙМ (после статики)
FlaUI-сценарии (открыть/загрузить/закрыть ×N) + SematixTrace breadcrumbs
──► PerfView/ETW + procdump + ClrMD heap-diff ──► retained/duplicate/storm evidence ──► тот же отчёт
Слой 3 — AI REVIEWER (поверх готовых доказательств)
structured findings + сниппеты ──► кластеризация / объяснение retained paths / кандидаты-фиксы / confidence
Берём матрицу из docs/ROADMAP.md «What static analysis can and cannot catch» и
расширяем её на наши категории. Это не косметика: она запрещает вешать
рантайм-проблему (дубликаты в куче, storm частоты, LOH) на статический инструмент,
тем самым убивая целый класс FP заранее.
Это сердце плана и прямой ответ на «какими тулзами что гоним, и где у нас дырка».
HIGH/MED/LOW — ожидаемая уверенность статического покрытия; → RUNTIME означает,
что статикой честно не ловится и переносится в Слой 2; NO-TOOL — нет надёжного
готового инструмента, помечаем в отчёте, не имитируем.
| # | Категория | Слой | Реальные инструменты | Уверенность | Честная дырка |
|---|---|---|---|---|---|
| 1 | IDisposable leak (local/field, dispose-on-throw) |
static | CA2000/CA2213, IDisposableAnalyzers (IDISP0xx), CodeQL cs/local-not-disposed&friends, Infer# *RESOURCE_LEAK, own-check OWN001 |
HIGH (4–5 движков → agreement) | interproc-передача владения даёт расхождения — это и есть сигнал, не шум |
| 2 | Event/subscription leak (+= без -=, static event, EventAggregator/Messenger) |
static + runtime-confirm | own-check OWN001/OWN014 (наш дифференциатор), WpfAnalyzers — частично |
MED static (уникален own-check, warn-tier), HIGH если подтверждён рантаймом | CodeQL/Infer# этот класс не выражают вообще (oracle.md); подтверждаем leak-harness'ом |
| 3 | Timer leak (DispatcherTimer/Timer без stop/detach) |
static + runtime-confirm | own-check WPF002, WpfAnalyzers — частично |
MED | — |
| 4 | DependencyPropertyDescriptor.AddValueChanged leak |
runtime | — (нет надёжного выделенного аналайзера) | NO-TOOL static | переносим в leak-harness (retained после закрытия окна) |
| 5 | INPC-корректность (нет equality-check, кривой/пустой nameof, неверный arg) |
static | PropertyChangedAnalyzers (INPC0xx) |
HIGH | измеряет корректность, не частоту |
| 6 | PropertyChanged-штормы/каскады, дорогие getter'ы под binding | runtime | SematixTrace counters + INPC-инструментация; PerfView CPU/alloc | NO-TOOL static → RUNTIME | частота — это рантайм, статикой не мерится |
| 7 | WPF binding errors (битые байндинги) | runtime | PresentationTraceSources/WPF-trace во время FlaUI-сценариев |
NO-TOOL static → RUNTIME | — |
| 8 | Сломанная/выключенная virtualization (ScrollViewer вокруг virtualized, StackPanel вместо VirtualizingStackPanel) |
runtime | visual-tree element counts + render-профиль (PerfView WPF ETW, Perforator) | NO-TOOL static → RUNTIME | регексом по XAML — это FP-генератор, сознательно отказываемся |
| 9 | Freezable не заморожен; brush/geometry per-instance | static (частично) + runtime | WpfAnalyzers (часть правил); иначе alloc-профиль | LOW–MED | остаток → runtime alloc stacks |
| 10 | Аллокации в конвертерах/getter'ах, layout-invalidation storms | runtime | PerfView allocation stacks + WPF render ETW | NO-TOOL static → RUNTIME | — |
| 11 | Размножение immutable/should-be-static данных (units/countries/currencies, повторяющиеся строки, копии справочников по строкам) | runtime | ClrMD/PerfView heap grouping (duplicate detector) — free-стек | NO-TOOL static → RUNTIME | зависит от данных рантайма, не от формы кода; статикой = FP. Это «золото» проекта — приоритет в Слое 2. dotMemory не используем (нет лицензии) → retention paths считаем сами по дампу ClrMD. |
| 12 | Тяжёлые справочники / LOH-фрагментация / Gen2-bloat / eager-load | runtime | PerfView (GC stats, LOH, Gen2), счётчики | impossible static → RUNTIME | — |
| 13 | Cross-thread ObjectDisposedException, PropertyChanged из бэкграунд-потока |
runtime | WPF dispatcher-checks + ETW во время сценариев | impossible/weak static → RUNTIME | happens-before, не структура |
| 14 | Общие баги / perf / best-practice / async (топливо для «где хуже») | static | .NET SDK NetAnalyzers, Meziantou.Analyzer, Roslynator, AsyncFixer, SonarAnalyzer.CSharp, CodeQL security-and-quality |
HIGH breadth | — |
| 15 | Архитектурные метрики / циклы / hotspots / coupling-cohesion (ранжирование «где хуже») | static | Roslynator/SonarAnalyzer метрики (free); CodeQL cs/ quality-запросы |
LOW–MED | NDepend не используем (нет лицензии) → ранжирование архитектуры деградирует до метрик из Sonar/Roslynator + CodeQL; помечаем это в coverage |
Ключевой разворот для own-check. В категориях 2–4 (subscription/timer/region leaks) own-check — единственный, кто их вообще выражает; CodeQL и Infer# тут слепы (доказано на ScreenToGif, см.
docs/notes/real-world-mining.md). А в категории 1 own-check пересекается с ними → их совпадение даёт нам high-confidence, а расхождения — материал для корпуса. То есть аудит попутно прогоняет наш oracle-харнес наоборот и кормит обратную связь в OwnLang (см. §6).
Требование: понаставить максимум Roslyn-аналайзеров, но чтобы при обычной разработке их не было и они не светили 900 warnings в IDE.
Решение (zero-touch для dev-копий): прогон в одноразовом git worktree с
инъекцией Directory.Build.props/.targets, гейтнутых флагом OwnAudit=true.
git worktree add ../target-audit <commit> # рабочие копии разработчиков не трогаем
trap 'git worktree remove --force ../target-audit' EXIT # tree сносится даже если билд упал
# MSBuild авто-импортит ТОЛЬКО файлы с именами Directory.Build.props/.targets, поэтому
# кладём инъекцию ПОД ЭТИМ именем (не OwnAudit.Directory.Build.*, иначе импорт молча не сработает).
# Если в target'е уже есть свой Directory.Build.props — сохраняем его как *.orig, а наш
# импортит его первой строкой (Condition="Exists(...)"), чтобы не затереть настройки билда.
cp audit/static/inject/OwnAudit.Directory.Build.props ../target-audit/Directory.Build.props
cp audit/static/inject/OwnAudit.Directory.Build.targets ../target-audit/Directory.Build.targets
msbuild Target.sln /p:OwnAudit=true /p:Configuration=Release \
/bl:artifacts/build.binlog
# worktree снимается trap'ом на EXIT — временные деревья не накапливаются при падении билдаOwnAudit.Directory.Build.props (по сути — <Analyzer Include=…> из
восстановленного audit-кэша + общий ErrorLog/ReportAnalyzer):
<Project>
<PropertyGroup Condition="'$(OwnAudit)' == 'true'">
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisMode>All</AnalysisMode>
<ReportAnalyzer>true</ReportAnalyzer>
<!-- SARIF на проект; aggregate/ их потом сольёт -->
<ErrorLog>$(MSBuildProjectDirectory)\..\artifacts\own-audit\$(MSBuildProjectName).sarif,version=2.1</ErrorLog>
</PropertyGroup>
<ItemGroup Condition="'$(OwnAudit)' == 'true'">
<!-- DLL из заранее restore'нутого audit-кэша, НЕ PackageReference в дереве проекта -->
<Analyzer Include="$(OwnAuditAnalyzers)\**\*.dll" />
</ItemGroup>
</Project>Почему так, а не <PackageReference … PrivateAssets="all">:
- легаси-проект почти наверняка non-SDK csproj +
packages.config(ему 12 лет), где условныйPackageReference— боль;<Analyzer Include>из вынесенного кэша работает и там, и там; Directory.Build.propsMSBuild подхватывает и для легаси-csproj (VS2017+), а worktree гарантирует, что в IDE разработчика этого файла нет в принципе.
Конфиг-профили (что включать) держим отдельно — audit/config/profiles/:
desktop-wpf, paranoid, backend-di. Включается через .editorconfig/rulesets,
сгенерированные из профиля; severity-разрешение — там же.
Это критично и должно быть в отчёте честно (как асимметрия в oracle.md):
| Tier | Инструменты | Нужен ли успешный билд target'а |
|---|---|---|
| Build-free (бегут всегда, даже на сломанном solution) | own-check (error-tolerant SemanticModel), CodeQL (build-mode: none, из исходников) |
нет |
| Build-required (нужен успешный MSBuild-билд) | NetAnalyzers, Meziantou, Roslynator, AsyncFixer, SonarAnalyzer, IDisposableAnalyzers, WpfAnalyzers, PropertyChangedAnalyzers; Infer# (нужны .dll+.pdb) |
да |
Решение по окружению (подтверждено): весь аудит target'а гоняется на локальной
Windows-машине (VS Build Tools + DevExpress 12.2) — и build-free, и build-required, и
рантайм. Никакого авто-прогона target'а в CI нет. CI самого Own.NET (Linux) гейтит
только Python-selftests слоя агрегации (как oracle_compare --selftest сейчас), а не
аудит легаси. Tiers здесь — это про устойчивость: build-free даёт каркас отчёта даже
когда solution не собирается; build-required добавляет глубину, когда билд прошёл.
Артефакты/отчёты коммитятся в репозиторий. NDepend выпадает (нет лицензии) —
архитектурное ранжирование идёт на free-метриках (Roslynator/Sonar/CodeQL), помечается
в coverage.
Каждый build-required прогон — continue-on-error: упавший билд даёт частичный
отчёт, а не пустой, и явную пометку «tier недоступен: билд не собрался».
audit/static/run_static.py оркеструет; по одному тонкому runner'у на инструмент в
audit/static/tools/. Каждый кладёт SARIF в artifacts/own-audit/<tool>.sarif.
- Roslyn-пак (один билд, много аналайзеров) → per-project SARIF через
ErrorLog. Пиннить версии паков к тем, чей Roslyn-рантайм совместим с MSBuild build-окружения: свежайшие паки иногда требуют новее SDK, чем легаси-билд. Несовместимый пак — помечаемNO-TOOL: incompatible toolchain, не тянем силой. - CodeQL —
init build-mode: none+analyzeсsecurity-and-quality(dispose/leak — это quality-запросы, в default security-сьюте их нет; иначе CodeQL молча даст ноль — грабли уже задокументированы вoracle.yml). - Infer# —
dotnet build -o _bin→microsoft/infersharpactionпо бинарям (нужен pdb). Для net472 на CI это самый хрупкий шаг — отсюдаcontinue-on-error. - own-check —
scripts/own-check.sh --format sarif --severity warning(переиспользуем как есть;OWN_EXTRA_REF_DIRSдля WPF/WinForms ref-паков, как вoracle.yml). NDepend— не используем (нет лицензии); архитектурное ранжирование (§2 кат. 15) идёт на free-метриках Roslynator/Sonar/CodeQL с пометкой о деградации.
DevExpress-шум (подтверждено): baseline-suppress целиком. Находки из
DevExpress-namespace (DevExpress.*) глушатся на этапе нормализации (§3.4) — первый
отчёт чище и сфокусирован на нашем коде. Сам факт глушения и счётчик подавленных
находок печатаются в coverage-секции (ничего не прячем молча).
audit/aggregate/normalize.py — читает все SARIF через тот же parse_sarif, что и
oracle_compare.py (переиспользуем, не дублируем парсер). Затем мэппит (tool, ruleId)
→ нашу категорию из §2 по таблице знаний audit/static/taxonomy/categories.yml:
# rule-id (или префикс) -> категория §2
"CA2000": {category: 1, name: "idisposable-leak"}
"IDISP0*": {category: 1, name: "idisposable-leak"}
"cs/local-not-disposed": {category: 1}
"*RESOURCE_LEAK": {category: 1}
# OWN001 — ЗОНТИЧНЫЙ leak-код: один ruleId на IDisposable-поля/локалы, subscription и timer.
# Различает их тег [resource: …] в тексте диагностики. Плоский маппинг OWN001->кат.1
# схлопнул бы subscription/timer-утечки (кат. 2/3) в IDisposable и недосчитал бы их —
# поэтому normalize.py парсит тег и разводит по подтипу:
"OWN001":
by_resource: # ключ = [resource: …]
"subscription token": {category: 2, name: "subscription-leak"}
"timer": {category: 3, name: "timer-leak"}
"*": {category: 1, name: "idisposable-leak"} # disposable field/local
"OWN014": {category: 2, name: "region-escape"} # escape в более долгоживущий
# регион (vm->App, SystemEvents);
# НЕ generic subscription-leak
"INPC0*": {category: 5, name: "inpc-correctness"}
# ... правила без маппинга падают в "uncategorized" и видны отдельно (не теряются)Непромэпленные правила не выкидываются, а собираются в uncategorized — чтобы
таксономия росла осознанно, а не глотала находки молча (та же дисциплина, что
«unparsed-line bucket» в oracle_compare).
DevExpress baseline-suppress — отдельный фильтр нормализации: находки в путях/типах
DevExpress.* (сторонний код) отбрасываются из основного отчёта, но считаются и
попадают в coverage отдельной строкой («suppressed: DevExpress — N findings»). Это не
regex-эвристика по нашему коду (тех заказчик запретил), а honest-фильтр стороннего
namespace — снижает шум, ничего не пряча.
audit/aggregate/score.py — обобщение oracle_compare.compare():
- Cross-tool agreement на (basename + line-window), как в
oracle_compare(устойчиво к разным префиксам путей). Финдинг, подтверждённый ≥2 независимыми инструментами →confidence: high; одиночный →confidence: candidate(+ помечен, уникальный это улов own-check или кандидат-FP). - Severity-нормализация между инструментами (у всех своя шкала) в единую
P0..P3по таблице из ROADMAP («как ранжировать по impact»). - Roll-up по локации → heatmap: per-file → per-namespace/module → per-assembly, взвешенный по (категория × severity × agreement × плотность). Это и есть прямой ответ «где хуже всего / где почти норм»: отсортированный список модулей с индексом боли, а не свалка из 3000 «possible issue».
- Coverage / honesty секция: какие tiers отработали, какие категории
NO-TOOL, что пропущено и почему (категории 6–13 со статус-пометкой «deferred to runtime»).
audit/aggregate/report.py — рендереры:
- Markdown (для людей / GitHub run summary — как сейчас в oracle/mine),
- HTML (heatmap, сортировки),
- JSON (машинный, для AI-слоя и регрессий),
- merged SARIF (один лог → GitHub code scanning, переиспользуя exporter из
docs/notes/sarif-export.md).
Формат одной находки — как уже принято в проекте (стабильно, годно для AI и diff):
{
"id": "SUBSCRIPTION-LEAK-0001", "category": 2, "severity": "P1",
"confidence": "high", "tools": ["own-check", "<runtime:leak-harness>"],
"file": "VideoSource.xaml.cs", "line": 123,
"evidence": ["own-check OWN014: subscribes to AppEventBus.Changed, no -=",
"leak-harness: +10 VideoSource retained after close ×10"],
"suggested_fix": "Unsubscribe on Unloaded/Dispose or WeakEventManager"
}UI «надо проверять на вшивость», и руками кликать мы не хотим — значит автоматизация
сценариев. Стек под net472/WPF/DevExpress (точность важнее моды — dotnet-gcdump/
dotnet-counters заточены под CoreCLR, для Framework честнее ETW + дамп):
| Роль | Инструмент | Заметка по net472 |
|---|---|---|
| Драйвер UI (детерминированные сценарии вместо кликов) | FlaUI (UIA3) | .NET-библиотека поверх MS UI Automation; гоняет Win32/WinForms/WPF |
| Семантические breadcrumbs (связать сценарий ↔ снимок) | SematixTrace | Step/Entity/Counter/Mark/RequestGc; только в diagnostic-билде |
| GC/alloc/CPU/WPF-render телеметрия | PerfView (ETW) | отлично работает с Framework: GC stats, alloc stacks, LOH, Microsoft-Windows-WPF провайдер |
| Снимок кучи / дамп | procdump + (heap snapshot PerfView) | dotnet-dump/gcdump к Framework-процессу не цепляются; берём full dump |
| Разбор кучи (retained, duplicates, retention paths) | ClrMD (Microsoft.Diagnostics.Runtime) |
по дампу: типы, counts, кто держит; движок duplicate-детектора и retention-paths (считаем сами) |
| Битые байндинги | PresentationTraceSources capture |
включаем WPF binding trace на время сценария |
Стек подтверждён как полностью бесплатный (нет NDepend/dotMemory): retention paths и duplicate-grouping считаем сами поверх ClrMD по full-dump'у (procdump) — чуть больше своего кода в
audit/runtime/, нулевая лицензионная зависимость.
Детерминированный раннер (C#, audit/runtime/): сценарий из YAML, который ИИ
набрасывает, а ассерты — детерминированные:
scenario: open-close-declaration
repeat: 10
steps:
- launch: LegacyApp.exe
- click: { automationId: OpenDeclarationButton }
- wait: { automationId: DeclarationGrid }
- close: { automationId: DeclarationWindow }
- force_gc: true
assert:
retained: { DeclarationWindow: 0, DeclarationViewModel: 0 }
max_growth: { DeclarationRowVm: 100 } # baseline + допускПетля: open → load → close → RequestGc → snapshot → повторить N. Diff baseline vs
after по counts типов (Window/ViewModel/domain/BindingExpression/DevExpress
grid/editor). Признак leak — unbounded growth при повторении, не разовый рост
(методика из PerfView heap-leak tutorial). Ассерт детерминированный — ИИ не оракул.
ClrMD по full-dump'у (procdump): группировка объектов по type + value-signature, поиск
N× одинаковых мелких объектов / строк / справочных DTO, размноженных по строкам
грида/отчёта; для топ-кандидатов считаем retention path до GC-root тем же ClrMD (без
dotMemory). Выхлоп — кандидаты на static readonly/flyweight/intern/reference-id.
Это «архитектурный ксерокс», который маскируется под утечку, и которого статикой не
видно — поэтому он живёт здесь, а не в Слое 1.
SematixTrace-counter + лёгкая INPC-инструментация (только diagnostic-билд): за одну пользовательскую операцию считаем, какие свойства как часто стреляют, какие — без изменения значения, где getter под байндингом аллоцирует/делает тяжёлый расчёт. Выхлоп — топ-«крикунов» с числами, скоррелированный с PerfView CPU/alloc.
Связываем SematixTrace-события (WindowClosed/ForcedGC/Mark) ↔ ETW ↔ heap-diff
↔ статические находки по тому же файлу/типу. Результат — не «возможно leak», а
судебная экспертиза: сценарий → событие → снимок → удерживается → плюс статическая
улика «подписался, не отписался». Эти runtime-находки текут в тот же отчёт §3.5.
Поверх готовых структурированных находок + сниппетов кода. LLM полезен и безопасен по построению только в фальсифицируемых ролях:
- кластеризация и дедуп находок между инструментами;
- объяснение retained paths человеческим языком;
- кандидаты-фиксы (
-=,using,static readonly,WeakEventManager) — каждый как гипотеза, которую пере-проверяет чекер + компиляция + тесты; - ранжирование «top risks / likely root cause / next diagnostic step»;
- черновики FlaUI-сценариев (но ассерт всегда детерминированный).
LLM никогда не выносит вердикт «это leak» и не является источником спеки/правды
(аргументация trusted-base vs falsifiable — research-landscape-2026.md). Если когда-то
появится fix-loop — он gated детерминированным чекером + метаморфным харнесом, а не
доверием к модели.
Scope-пометка (подтверждено): готовый результат первого шага — это только health-отчёт по легаси (§9, Фаза 1). Описанное ниже — поздний отдельный трек (Фаза 4), а не часть первой поставки. Записано, чтобы возможность не испарилась.
«Натаскать наш анализатор» получается потом почти бесплатно как побочка:
- agree (own-check ∩ зрелые тулзы) → подтверждённые кейсы для регресс-корпуса;
- own-only → кандидат-FP (харденить) или уникальный улов (фича) →
corpus/real-world/минимальным reduced-кейсом (before.cs/after.cs/expected-diagnostics.txt), по той же дисциплине, что уже есть; - oracle-only → recall-gap own-check → задача на ядро;
- рантайм-подтверждённые leak'и → лейблы (bug/no-bug) для benchmark-корпуса (P-012),
которого, по
research-landscape-2026.md, проекту как раз и не хватает.
То есть аудит — это масштабный mining-прогон по нашему проекту, чьи находки становятся labeled-корпусом. Связь односторонняя по коду (audit → corpus артефактами), без code-coupling.
audit/ # cohesive; ничего не импортирует из ownlang/
README.md
static/
run_static.py # оркестратор статики
tools/ # по одному тонкому runner'у; каждый → SARIF
roslyn_pack.ps1 codeql.sh infersharp.sh owncheck.py
# (без ndepend.ps1 — NDepend исключён принятым решением «только free-стек», §8/§11)
inject/
OwnAudit.Directory.Build.props / .targets
taxonomy/
categories.yml # (tool, ruleId) -> категория §2 (база знаний)
aggregate/
normalize.py # SARIF -> findings (переиспользует parse_sarif)
score.py # agreement / severity / heatmap (обобщает oracle_compare)
report.py # md / html / json / merged-sarif рендереры
runtime/
OwnAudit.Runtime.sln # C#: FlaUI driver + ClrMD heap tool + SematixTrace hooks
scenarios/*.yml
config/profiles/ # desktop-wpf, paranoid, backend-di
artifacts/ # gitignored выхлоп (как corpus/mined/)
Правило decoupling (тест в CI): grep по audit/ не должен содержать import ownlang
или ссылок в ownlang/. own-check вызывается только через scripts/own-check.sh.
Язык оркестрации — Python (подтверждено; переиспользуем parse_sarif/oracle-логику,
OS-agnostic, нулевой coupling); C# — только там, где вынужденно (Infer#-билд, FlaUI, ClrMD).
| Риск / предпосылка | Влияние | Что делаем |
|---|---|---|
| Весь аудит target'а — на локальной Windows-машине (нет авто-прогона в CI) | результат зависит от того, что машину держат в форме (DevExpress, SDK) | принято осознанно: запуск локальный и воспроизводимый по коммиту; CI Own.NET гейтит только Python-selftests агрегации; build-free tier даёт каркас даже на несобираемом solution |
Легаси packages.config/non-SDK csproj |
условный PackageReference ненадёжен |
механизм §3.1 (<Analyzer Include> + worktree) специально под это |
| Свежие версии аналайзеров требуют новее Roslyn/SDK, чем легаси-билд | пак не грузится | пиннинг версий под toolchain; несовместимый → NO-TOOL, не силой |
dotnet-gcdump/dotnet-counters не цепляются к net472 |
ложная надежда на CoreCLR-тулинг | рантайм-стек на ETW(PerfView) + procdump + ClrMD, не на dotnet-* |
| Infer# на net472-бинарях на Linux-CI хрупок | частые пропуски | continue-on-error, частичный отчёт честно помечается |
| DevExpress генерит шум в аналайзерах | FP-вал | принято: baseline-suppress DevExpress.* целиком на нормализации, со счётчиком подавленного в coverage |
| Коммерции нет (NDepend/dotMemory) | слабее ранжирование архитектуры (кат. 15) и retention paths (кат. 11) | принято: только free-стек; архитектуру ранжируем метриками Sonar/Roslynator/CodeQL, retention paths считаем сами по ClrMD — помечаем деградацию, не блокер |
Делаем в рамках Own.NET, но в изолированном
audit/, готовом к выносу.
Фаза 0 — Каркас и решения (S)
audit/скелет, decoupling-CI-тест,categories.ymlv0, профильdesktop-wpf.- Deliverable: пустой, но связный конвейер «прогон → SARIF → normalize → отчёт» на одном инструменте (own-check) end-to-end.
Фаза 1 — Статический fleet (основной этап, M–L)
- 1a: механизм audit-only анализаторов (worktree + props +
OwnAudit),desktop-wpfпрофиль. - 1b: runner'ы build-free (own-check, CodeQL) → SARIF (каркас отчёта даже на несобираемом solution).
- 1c: runner'ы build-required (Roslyn-пак, Infer#) на собравшемся билде;
continue-on-error; SARIF кладётся вartifacts/own-audit/. Всё локально на Windows-машине. - 1d:
normalize+ таксономия + DevExpress baseline-suppress;score(agreement/severity/heatmap); рендереры. (NDepend нет — кат. 15 на free-метриках.) - Deliverable (= done-критерий шага 1): health-отчёт по легаси — категоризированный, с ранжированием «где хуже / где почти норм» + честной coverage-картой. Только это. Фид находок обратно в OwnLang (§6) в первый deliverable НЕ входит — отдельный поздний трек.
Фаза 2 — Рантайм (M–L)
- 2a: FlaUI-харнес + YAML-формат сценариев (ИИ-черновики, детерминированные ассерты).
- 2b: SematixTrace-интеграция (diagnostic-билд).
- 2c: capture-пайплайн PerfView/procdump/ClrMD; leak-harness; duplicate-detector; binding-error collector; PropertyChanged-storm profiler.
- 2d: корреляция → рантайм-находки в общий отчёт.
- Deliverable: для топ-N экранов (тяжёлый справочник, декларация/графа 47, отчёт, DevExpress-грид, часто открываемое окно) — open/close-leak отчёт, дубликаты в куче, storm-профиль.
Фаза 3 — AI reviewer (S–M)
- кластеризация/объяснение/кандидаты-фиксы/ранжирование поверх JSON-находок.
- Deliverable: «старший ревьюер с доказательствами», не шаман.
Фаза 4 — Вынос и обратная связь (S, поздний трек, ВНЕ первого deliverable)
git mv audit/→ отдельный репозиторий (coupling уже нулевой).- промоушн подтверждённых находок в
corpus/+ benchmark-лейблы (§6).
Первый практический прогон (минимальный путь к ценности, не дожидаясь всего): build-free tier (own-check + CodeQL) по target'у → normalize → черновой heatmap-отчёт. Это даёт «карту минного поля» за день-два, дальше наслаиваем build-required и рантайм.
- Не пишем собственные детекторы/регексы для закрытия дырок —
NO-TOOLчестнее. - Не тащим
roslyn-tools/NDepend как зависимость ядра (см.roslyn-tools-and-cli.md). - Не делаем autonomous-агента с shell-доступом по 600k строк; делаем детерминированный runner + AI-ревьюер сверху.
- Не чиним всё подряд: фиксим топ-5 подтверждённых после первого отчёта, остальное — в backlog по индексу боли.
- AI не становится оракулом и источником спеки.
- Окружение — только локальная Windows-машина. Весь аудит target'а (build-free, build-required, рантайм) гоняется локально на Windows (VS Build Tools + DevExpress 12.2). Авто-прогона target'а в CI нет; CI Own.NET (Linux) гейтит только Python-selftests агрегации. Tiers — про устойчивость к несобираемому solution, не про машину. (§1, §3.2, §8, §9)
- Готовый результат первого шага — только health-отчёт по легаси. Категоризированный, с ранжированием «где хуже/почти норм» и честной coverage-картой. Фид находок обратно в OwnLang (§6) — поздний отдельный трек (Фаза 4), ВНЕ первой поставки. (§6, §9)
- Язык оркестрации статики/агрегации — Python. Переиспользуем
parse_sarifи oracle-логику, нулевой coupling к ядру, OS-agnostic. C# — только в рантайме (FlaUI, ClrMD). (§7) - Только free-стек, без коммерции. Нет NDepend → архитектурное ранжирование (кат. 15) на метриках Roslynator/Sonar/CodeQL с пометкой о деградации. Нет dotMemory → duplicate-detector и retention paths (кат. 11) считаем сами поверх ClrMD по full-dump'у. Помечаем деградацию, не блокер. (§2, §4, §8)
- DevExpress — baseline-suppress целиком. Находки в
DevExpress.*глушатся на нормализации, со счётчиком подавленного в coverage-секции (honest-фильтр стороннего namespace, не regex по нашему коду). (§3.3, §3.4)
- Точный список топ-N экранов для рантайм-сценариев Фазы 2 (тяжёлый справочник, декларация/графа 47, отчёт, DevExpress-грид, часто открываемое окно) — уточним перед Фазой 2 (рантайм — это уже не первый deliverable).
- Пиннинг версий Roslyn-паков под toolchain легаси-билда (свежие могут требовать новее
MSBuild) — выяснится на 1c, несовместимые помечаем
NO-TOOL.