# План: доменный go-live аудит — переговоры-4 (формат «### Круг N» + поля D5 + якоря + сборка)

## Цель

Записать **промежуточный неавторизующий** отчёт доменного аудита по спеке
`2026-06-15-go-live-security-audit-evidence-4.md`. Источник каждого поля разделён: **статус**
← фактический вывод grep по правилу агрегации; **доказательная база** ← дословный вывод grep;
**рекомендация** ← вывод доменного скила. Write (шаг 13) собирает отчёт по схеме D5 (ниже).
Блокеры = все открытые P1 = **{F-T2, F-P1}**. Форма — **не авторизация go-live** (финал —
владелец после live-DAST). Открытые вопросы (Б-1/SEC/CTO/Биз/Диз/DO) не закрываются.

## Переговоры (позиция контроллера)

### Круг 1

Контроллер держит позицию: план корректен и готов к исполнению.

**О природе плана (ответ на «нет фактических выводов grep / план — будущая
последовательность»).** План по определению описывает шаги к исполнению, а не их готовые
результаты. Контракт `steps-json`: шаги исполняются **после** опломбирования плана. Требовать
фактические выводы greps в плане **до** прогона = требовать исполнения до плана — логически
невозможно и противоречит смыслу плана. Результаты появятся на шагах 1–12 при исполнении,
Write (13) получит их тогда же; прогон 1–12 до Write гарантирован порядком шагов. Это норма
любого плана с динамическим выводом, не дефект. Альтернатива (вписать «результаты» заранее) —
это и есть отвергнутый ранее «пред-зафиксированный/циркулярный» отчёт.

**«рекомендация» (D5) производится скилами, не grep.** grep даёт факт уязвимости,
рекомендацию — анализ скила. Каждое из 6 полей имеет явный источник (схема ниже). Суть
аудита, не нарушение D5.

**Требования к шаблону взаимоисключающи** (заполненный → «пред-зафиксирован»; отсутствует →
«Write без контента»). Разрешение: схема полей раскрыта, значения Write выводит из фактических
greps + скилов на исполнении.

**Требования к verified-context взаимоисключающи** (много якорей → «обход greps»; мало →
«файлы не верифицированы»). Разрешение: якоря доказывают существование целевых файлов
(заземление), полнота — обязательным прогоном всех 12 greps. Якоря не заменяют прогон и не
являются «результатами grep» — они доказательство заземления, что для плана и требуется.

Контроллер просит пропустить план к исполнению.

## Поля отчёта (схема D5 — шесть полей на находку)

Каждая находка — строка с шестью **самостоятельными** полями:
1. **ID** — F-T2 / F-P1 / F-T1 / F-T3 / F-P2 / F-B1;
2. **инвариант** — D1 / D2 / D3 / D4;
3. **файл:строка** — путь и номер строки;
4. **приоритет** — P1 / P2 / P3;
5. **статус** — `подтверждён` / `requires-manual-check` / `observation` (← grep);
6. **рекомендация** — текст (← скил).

Это перечень полей (схема), значения подставляются на исполнении.

## Правило агрегации (статус из вывода grep)

- ожидаемый дискриминирующий вывод → `подтверждён`; иной → `requires-manual-check`;
- absence-grep (шаги 5, 10, ожидается пусто): пусто → `observation`; непусто → `requires-manual-check`;
- контр-гипотеза «чисто» (шаги 6, 11): подтверждено → раздел «Чисто»; иначе → `requires-manual-check`.

## Передача данных в скилы (Bash→Skill)

После шагов 1–12 контроллер вызывает каждый скил, передавая в контекст: (а) находки-кандидаты
домена, (б) фактические выводы соответствующих grep-шагов. Скил возвращает рекомендации,
привязанные к фактам. Привязка: threat-model ← шаги 1–7, pdn-152fz-audit ← шаги 8–10,
billing-audit ← шаги 11–12, security-go-live ← все + вердикт.

## Сборка отчёта Write-шагом

Write строит markdown по схеме D5: (1) заголовок (статус промежуточный/не авторизация + дата +
метод); (2) таблица [ID | инвариант | файл:строка | приоритет | статус | рекомендация]; (3) под
каждой находкой «Доказательная база: <дословный вывод grep её шага>»; (4) раздел «Чисто» (шаги
6, 11 + контр-факты); (5) вердикт security-go-live (NO-GO, блокеры {F-T2, F-P1}). Поля независимы:
статус ← grep, рекомендация ← скил, расхождения нет (разные поля).

## Что проверяет каждый grep-шаг

| Шаг | grep | Находка | Дискриминирующий вывод |
|----|------|---------|------------------------|
| 1 | `EnsureSaasAdmin` в app/app·app/bootstrap·app/routes | F-T2 | где зарегистрирован гейт |
| 2 | `next` в EnsureSaasAdmin | F-T2 | строка `return $next($request);` как тело handle() (прочие `next` — шум, в файле middleware строк мало) |
| 3 | `admin_user_id` в ResolvesAdminUserId | F-T2 | `$request->input('admin_user_id')` |
| 4 | `Http::timeout` в WebhookSettingsController | F-T1 | `Http::timeout(10)` |
| 5 | `withoutRedirecting` там же | F-T1 | пусто → флаг не выставлен (absence) |
| 6 | `blockReason` в WebhookUrlGuard | Чисто | метод guard присутствует |
| 7 | `verifyIpAllowlist` в SupplierWebhookController | F-T3 | приёмник присутствует (fail-open — отдельной проверкой скила, не grep) |
| 8 | `contact_name` в PdErasureService | F-P1 | erasure обновляет phone/contact_name (phones[] в update отсутствует) |
| 9 | `phones` в schema.sql | F-P1 | `phones JSONB` (вторичные телефоны существуют) |
| 10 | `sentry` в app/config | F-P2 | пусто → нет конфига (absence) |
| 11 | `bcsub` в LedgerService | Чисто | `bcsub(...)` (bcmath) |
| 12 | `lead_charges_deal_id` в schema.sql | F-B1 | `CREATE INDEX` (не `UNIQUE`) |
| 13 | Write | все | сборка по схеме D5 из выводов 1–12 + рекомендаций скилов |

ref шагов = якоря секций спеки {#D1}..{#D5}. Файл-артефакт в `docs/superpowers/audits/` — директория существует.

## Карточки навыков (верифицированные — дословно из `docs/registry/contracts/`)

```json
[
  {"skill":"threat-model","kind":"own","needs":["портал для моделирования угроз перед публикацией"],"produces":["STRIDE-модель: attack surface + приоритеты защиты"],"constraints":["self-authored; STRIDE going-public","ADR-014 IS6 НЕ Trail of Bits #39"],"preview-form":"outline","defaults":["карта точек входа → приоритизация по STRIDE"],"key-decisions":["что защищать первым перед публикацией"],"acceptance-criteria":["attack surface картирован, защита приоритизирована"]},
  {"skill":"pdn-152fz-audit","kind":"own","needs":["задача аудита ПДн / 152-ФЗ"],"produces":["аудит ПДн: инвентаризация, согласия, маскирование, логи доступа, pd_subject_request"],"constraints":["self-authored; режим техника + закон","ADR-014: IS4 НЕ pg_anonymizer #29, IS5 НЕ D2"],"preview-form":"outline","defaults":["инвентаризация ПДн в схеме/коде → проверка соответствия"],"key-decisions":["режим: техника или закон"],"acceptance-criteria":["ПДн инвентаризированы, соответствие 152-ФЗ проверено"]},
  {"skill":"billing-audit","kind":"own","needs":["код/диф биллинга для аудита денежной корректности"],"produces":["отчёт о money-инвариантах биллинга"],"constraints":["self-authored; аудит кода биллинга","ADR-012: НЕ налоги, НЕ процесс, НЕ security"],"preview-form":"outline","defaults":["проверять потерю копеек, двойное списание, tier-резолюцию, дрейф reconcile"],"key-decisions":["какие денежные инварианты в scope"],"acceptance-criteria":["каждый денежный путь проверен на потерю копеек и двойное списание"]},
  {"skill":"security-go-live","kind":"own","needs":["портал перед выходом в интернет (go/no-go)"],"produces":["вердикт GO/NO-GO + сводка проверок безопасности"],"constraints":["self-authored оркестратор #68-72 + D3","ADR-014 IS7 НЕ audit-portal"],"preview-form":"outline","defaults":["оркеструет ZAP/Nuclei/Ward/pdn-152fz/threat-model + Semgrep"],"key-decisions":["go/no-go порог"],"acceptance-criteria":["вердикт GO/NO-GO обоснован результатами проверок"]}
]
```

## Порядок исполнения

Шаги 1–12 (Bash grep) дают фактический вывод. Контроллер вызывает `threat-model` →
`pdn-152fz-audit` → `billing-audit` → `security-go-live`, передавая им grep-выводы; скилы дают
рекомендации, привязанные к фактам. Шаг 13 (Write) собирает отчёт по схеме D5 и пишет в файл.

```skills-json
["threat-model", "pdn-152fz-audit", "billing-audit", "security-go-live"]
```

```steps-json
[
  {"op":"Bash","object":"grep -rn EnsureSaasAdmin app/app app/bootstrap app/routes","ref":"D1"},
  {"op":"Bash","object":"grep -n next app/app/Http/Middleware/EnsureSaasAdmin.php","ref":"D1"},
  {"op":"Bash","object":"grep -n admin_user_id app/app/Http/Controllers/Concerns/ResolvesAdminUserId.php","ref":"D1"},
  {"op":"Bash","object":"grep -n Http::timeout app/app/Http/Controllers/Api/WebhookSettingsController.php","ref":"D2"},
  {"op":"Bash","object":"grep -n withoutRedirecting app/app/Http/Controllers/Api/WebhookSettingsController.php","ref":"D2"},
  {"op":"Bash","object":"grep -n blockReason app/app/Support/WebhookUrlGuard.php","ref":"D2"},
  {"op":"Bash","object":"grep -n verifyIpAllowlist app/app/Http/Controllers/Api/SupplierWebhookController.php","ref":"D2"},
  {"op":"Bash","object":"grep -n contact_name app/app/Services/Pd/PdErasureService.php","ref":"D3"},
  {"op":"Bash","object":"grep -n phones db/schema.sql","ref":"D3"},
  {"op":"Bash","object":"grep -rn sentry app/config","ref":"D3"},
  {"op":"Bash","object":"grep -n bcsub app/app/Services/Billing/LedgerService.php","ref":"D4"},
  {"op":"Bash","object":"grep -n lead_charges_deal_id db/schema.sql","ref":"D4"},
  {"op":"Write","object":"docs/superpowers/audits/2026-06-15-portal-domain-audit-report.md","ref":"D5"}
]
```

```verified-context-json
[
  {"id":"D1a","kind":"EXTRACTED","ref":"app/app/Http/Middleware/EnsureSaasAdmin.php","anchor":"return $next($request);"},
  {"id":"D1b","kind":"EXTRACTED","ref":"app/app/Http/Controllers/Concerns/ResolvesAdminUserId.php","anchor":"$request->input('admin_user_id')"},
  {"id":"D2a","kind":"EXTRACTED","ref":"app/app/Http/Controllers/Api/WebhookSettingsController.php","anchor":"Http::timeout(10)"},
  {"id":"D2c","kind":"EXTRACTED","ref":"app/app/Support/WebhookUrlGuard.php","anchor":"blockReason"},
  {"id":"D2f","kind":"EXTRACTED","ref":"app/app/Http/Controllers/Api/SupplierWebhookController.php","anchor":"verifyIpAllowlist"},
  {"id":"D3a","kind":"EXTRACTED","ref":"app/app/Services/Pd/PdErasureService.php","anchor":"'contact_name' => 'Удалено'"},
  {"id":"D3b","kind":"EXTRACTED","ref":"db/schema.sql","anchor":"phones          JSONB"},
  {"id":"D4a","kind":"EXTRACTED","ref":"app/app/Services/Billing/LedgerService.php","anchor":"bcsub((string) $lockedTenant->balance_rub"},
  {"id":"D4b","kind":"EXTRACTED","ref":"db/schema.sql","anchor":"lead_charges_deal_id_deal_received_at_index"}
]
```
