# Portal Full Audit — Findings (2026-05-12 ночь)

> **Связано:** план `docs/superpowers/plans/2026-05-12-portal-full-audit.md`, blocked-вопросы `2026-05-12-portal-full-audit-blocked.md`, summary `2026-05-12-portal-full-audit-report.md`
> **Ветка:** `plan5-frontend-projects` (HEAD `b5849bb`, 79 commits ahead of `main` `615db99`)
> **Режим:** экономия 0%

## Severity легенда

- **P0** — блокирует production / data corruption / security incident → fix немедленно (если не hard-stop)
- **P1** — нарушение функциональности / failing test / type error / a11y violation → fix в Phase 8 первым
- **P2** — warning / style / dead code / stale doc → fix если время есть
- **P3** — cosmetic / nice-to-have → defer если не <5 мин

## Fix-eligibility (Phase 7 классификация)

- `[FIX-NOW]` — попадает под decision-tree «можно делать», фикс ≤30 мин
- `[FIX-DEFER]` — реально починить, но >1 час / большой refactor — в blocked как «следующая сессия»
- `[BLOCKED]` — hard-stop из decision-tree, нужно явное «закрываем» от заказчика → в blocked.md

---

## Phase 0 — Pre-flight

### Findings

- `[Phase 0]` git sync OK: HEAD=`b5849bb` == origin/plan5-frontend-projects, 79 commits ahead of origin/main=`615db99`. **Не находка.**
- `[Phase 0 workspace dirt]` 32 untracked PNG в корне репозитория (от прошлых смок-сессий Quiet Luxury / projects bulk / smoke billing). Согласовано: переместить в `audit-screens/legacy/`. **P3 [FIX-NOW]**.
- `[Phase 0 DemoSeeder]` `app/database/seeders/DemoSeeder.php` untracked, безопасный: idempotent `updateOrCreate` создаёт demo tenant + admin (`admin@demo.local`/`password`) + 3 projects (site/call/sms) + ≤14 demo deals по статусам. Никаких external calls, никаких unsafe ops. Согласовано использовать для UI smoke. **Не находка, но новый artifact**.
- `[Phase 0]` другие untracked в plans/specs: `docs/superpowers/plans/2026-05-10-claude-brain-extraction.md`, `docs/superpowers/specs/2026-05-10-claude-brain-extraction-design.md`, `docs/superpowers/specs/2026-05-12-quiet-luxury-elements-1440-896-design.html` — не трогать (предыдущие сессии). **Не находка.**

---

## Phase 1 — Static analysis

### Subagent A — Backend (Pint + Larastan + composer audit)

**Exit codes:** pint(после форс-фикса)=0, stan=0, audit=0.

**Findings:**

- **P1 [pint] app/database/seeders/DemoSeeder.php** — нарушение `blank_line_before_statement`. **Side-effect:** subagent выполнил `composer pint --test` буквально как в команде; composer absorbed `--test` flag, Pint запустился в WRITE-mode и **автоматически применил fix** (subagent сам это раскрыл). Файл DemoSeeder.php теперь **modified** в working tree. Канонический test-mode: `composer pint:test`. **[FIX-NOW]** — принять fix как валидный (Pint правило корректное, baseline проекта), коммит вместе с DemoSeeder ниже.
- **P0 [composer audit]** 0 advisories, clean. **Не находка.**
- **P1 [larastan]** 0 errors above baseline at level 5, clean. **Не находка.**
- **P2 [tooling-doc]** В `composer.json:62-63` есть `pint` (write) и `pint:test` (test-mode). Каноничная команда test-only — `composer pint:test`. README/CLAUDE.md §4 не подсвечивает разницу, lefthook использует `php vendor/bin/pint {staged_files}` напрямую (write). **[FIX-DEFER]** — обновить CLAUDE.md §4 чтобы избежать повторения.

### Subagent B — Frontend (ESLint + vue-tsc + Prettier + knip)

**Exit codes:** eslint=1, vue-tsc=2, prettier --check=1, knip=0.

**Findings ESLint (17 errors, все в `tests/Frontend/`):**

- **P1 [eslint config-drift] tests/Frontend/NewProjectDialog.spec.ts:62:5, 69:5; tests/Frontend/ProjectsView.spec.ts:64:5** — `vitest/no-disabled-tests` rule definition NOT FOUND. `eslint-plugin-vitest` не зарегистрирован в `eslint.config.js` или плагин отсутствует. **[FIX-NOW]** — либо снять упоминания этого rule из inline-eslint comments в тестах, либо registered plugin (но new package = hard-stop). Простой fix — заменить `// eslint-disable-next-line vitest/no-disabled-tests` на корректный rule или удалить, если тесты больше не disabled.
- **P1 [eslint no-explicit-any × 13]** tests/Frontend/BulkActionsBar.spec.ts:110:31, ProjectsView.spec.ts:32:23/41:23/72:23, projectsStore.spec.ts:16:23/26:23/42:24/43:23/68:24/69:23/93:23/122:23/153:23 — `any` в test mock'ах. Production-код чистый. **[FIX-NOW]** — заменить на `unknown` или `Project | Record<string, unknown>` (memory квирк 53 workaround).
- **P1 [eslint no-unused-vars]** tests/Frontend/ProjectsView.toolbar.spec.ts:1:32 — `beforeEach` imported but never used. **[FIX-NOW]** — удалить из import.

**Findings vue-tsc (9 errors):**

- **P1 [vue-tsc TS2339] resources/js/components/layout/AppSidebar.vue:86:44 + :86:73** — `NavItem.countKey` doesn't exist on type. **Корневая причина (3 гипотезы по systematic-debugging):**
  - H1: countKey добавлен в template без расширения `NavItem` interface (вероятно, последний коммит `f4ec5dc` portal redesign).
  - H2: typo — должно быть другое поле (`badgeKey`/`counterKey`).
  - H3: countKey намеренно optional но не объявлен в interface.
  - Falsify: H2 проверить чтением template; H3 проверить usage. **[FIX-NOW]** — расширить interface или удалить usage.
- **P1 [vue-tsc TS2322] resources/js/views/projects/EditProjectDialog.vue:5:10 + resources/js/views/ProjectsView.vue:71/72/73/74/80 + tests/Frontend/NewProjectDialog.spec.ts:15:9** — **дублирующиеся `Project` interface definitions**. Один имеет `region_mask`/`region_mode`/`delivery_days_mask`, другой нет. Plan 5 frontend Task 11 добавил поля, но не все импорты обновлены. **[FIX-NOW]** — найти оба определения, унифицировать через единый export из `types/project.ts` (или аналог), обновить все импорты.

**Findings Prettier (48 файлов):**

- **P2 [prettier] 48 files mismatch.** 37 production .vue/.ts/.css + 11 test .ts. Auto-fixable через `prettier --write`. **[FIX-NOW]** — batch run после остальных code-fix'ов.

**Findings knip:**

- **0 issues clean** в `--production` mode (3 verification runs). **Не находка.**

### Subagent C — Docs (markdownlint + cspell + lychee)

**Exit codes:** lint:md=1, spell=1, lychee=0.

**Findings markdownlint (165 violations в 3 файлах, все untracked):**

- **P2 [markdownlint] docs/superpowers/plans/2026-05-12-portal-full-audit.md** — **86 violations** (MD031 blanks-around-fences + MD032 blanks-around-lists + MD037 line 399). Это **мой собственный план аудита**, написан без markdown-lint passes. **[FIX-NOW]** — `markdownlint-cli2 --fix` на этом файле.
- **P2 [markdownlint] docs/superpowers/plans/2026-05-10-claude-brain-extraction.md** — 58 violations (MD031/MD032 + 2× MD009 trailing-spaces lines 941, 3311). Файл от прошлой сессии. **[FIX-NOW]** — auto-fix.
- **P2 [markdownlint] docs/superpowers/specs/2026-05-10-claude-brain-extraction-design.md** — 21 violations (MD031/MD032 + 3× MD026 heading-trailing-colon + 1× MD056 table-column-count line 621). **[FIX-NOW]** — auto-fix MD031/MD032, manual MD026/MD056.

**Findings cspell (103 unknown в 12 файлах):**

- **P2 [cspell] web/v8/v8_landing.html** — 30 unknown words. **78% это lorem ipsum** (amet/consectetur/adipiscing/elit/eiusmod/tempor/incididunt/enim/veniam/quis/nostrud/ullamco/laboris/consequat) — placeholder-текст handoff Платона. **[FIX-NOW]** — добавить lorem ipsum words в `cspell-words.txt` (или dedicated `cspell-lorem.txt` include).
- **P2 [cspell] web/v8/v8_brand.html** — 20 unknown (бренд-нейминг: Volna, Vento, Potok, Fraunces (font)). **[FIX-NOW]** — добавить в cspell-words.txt.
- **P2 [cspell] web/v8/v8_settings.html** — 10 unknown (ПАО, Authy, test fixtures). **[FIX-NOW]** — добавить значимые в словарь.
- **P2 [cspell] docs/superpowers/specs/2026-05-10-claude-brain-extraction-design.md** — 9 unknown. **[FIX-NOW]** — добавить «нормативка», «релизный», «консьюмер», «консьюмерах»; «pply/erge/uit» возможно artifact обрезанного слова — manual review.
- **P2 [cspell] web/v8/v8_admin.html** — 8 unknown (логируются ×3, ПАО, LTV, SMSC, **импersonator (mixed-script)**, фичефлаги). **[FIX-NOW]** — добавить в словарь, исправить mixed-script на «импесонатор» или «impersonator».
- **P2 [cspell] web/v8/v8_palette_options.html** — 5 unknown (proverено mixed-script, тиловый, слейт, вайб, бо́льшей с combining acute). **[FIX-NOW]** — fix mixed-script, добавить остальное.
- **P2 [cspell] web/v8/v8_login.html** — 4 unknown (MRT/VLW/YHC/GVB — test fixtures). **[FIX-NOW]** — словарь или ignore.
- **P2 [cspell] web/v8/v8_deal_card.html** — 4 unknown (potolki/msk translit). **[FIX-NOW]** — добавить.
- **P2 [cspell] docs/superpowers/plans/2026-05-12-portal-full-audit.md** — 3 unknown (капчёй, фиксим, гипотезного). **[FIX-NOW]** — добавить в словарь.
- **P2 [cspell] web/v8/v8_kanban.html** — 2 unknown (Бузо́ва, Габбасов — фамилии лидов). **[FIX-NOW]** — добавить.
- **P2 [cspell] web/v8/v8_dashboard.html** — 2 unknown (синхр, SMSC). **[FIX-NOW]** — добавить.
- **P2 [cspell] docs/superpowers/plans/2026-05-10-claude-brain-extraction.md** — 1 unknown (jqlang — GitHub org name). **[FIX-NOW]** — добавить.

**Findings lychee:** 0 broken (339 total / 141 unique URLs, 3 redirects informational). **Не находка.**

### Subagent D — SQL (squawk + pgFormatter)

**Exit codes:** squawk=0, diff(pgFormatter)=1.

**Findings:**

- **P3 [pgFormatter] db/schema.sql** — 6284-line formatting diff. Первый hunk `1,725c1,706` — reformat comment block. Style-only, no semantic SQL changes. **[FIX-DEFER]** — package.json convention «review diff before replacing source», большой ручной review. Не fix автоматически.
- **[squawk] 0 issues**, clean. **Не находка.**

### Phase 1 итог

**Severity rollup:**

- P0: 0
- P1: **44** (1 Pint, 17 ESLint, 9 vue-tsc, 17 untracked items)
- P2: **316** (48 Prettier + 165 markdownlint + 103 cspell)
- P3: 1 (pgFormatter)

**FIX-NOW count:** 44 P1 + 316 P2 = 360 еталонных правок (с учётом что cspell — массовое добавление в словарь и markdownlint — auto-fix).

**FIX-DEFER count:** 2 (composer pint:test doc-update, pgFormatter manual review).

**BLOCKED count:** 0.

---

## Phase 2 — Test suites

### Pest без --parallel

**Exit code 1.** **742 tests → 736 passed, 3 failed, 3 skipped, 2239 assertions, 128.806s.**

**Failed (все 3 — manifestation квирка 62 «cumulative state в liderra_testing без --parallel»):**

- **P1 [pest] tests/Feature/LookupsTest.php:31** — `GET /api/managers возвращает active users тенанта` — expected size 2, got 827. Накопление users в `liderra_testing` между sequential runs. **[FIX-DEFER]** — root cause не в коде тестов, а в test-DB hygiene (квирк 62: cleanup через DROP+CREATE с ICU + `migrate --env=testing`). Не блокер production.
- **P1 [pest] tests/Feature/LookupsTest.php:48** — `initials с fallback на email` — expected `admin@example.ru`, got `Абрам К.`. То же — accumulated user сортируется раньше свежего fixture. **[FIX-DEFER]**.
- **P1 [pest] tests/Feature/Models/ProjectExtensionsTest.php:89** — `Project scopeActiveOnDay returns only projects with today bit set` — expected count=1, got 4922. Тот же класс ошибки на projects. **[FIX-DEFER]**.

**Skipped (3, все ожидаемые):**

- **P3 [pest] tests/Browser/SmokeTest.php:41-58** — Browser test pending Linux CI (квирк 42).
- **P3 [pest] tests/Browser/SmokeTest.php:60-78** — то же.
- **P3 [pest] tests/Browser/SupplierIntegrationE2ETest.php:77** — Plan 3 Task 9 Linux CI (требует react/http + ext-sockets, квирк 42).

### Pest с --parallel

**Exit code 0.** **742 tests → 739 passed, 0 failed, 3 skipped, 2243 assertions, 33.105s.**

Все 3 sequential-failures прошли. Параллельных воркеров: 8 (`_test_1..8`). Δ assertions +4 (3 failed теста в sequential не дошли до своих остаточных expect'ов).

### Diff между Pest runs

- **Регрессия sequential → parallel:** 0 (все sequential-fails — про cumulative state).
- **Регрессия parallel → sequential:** 3 (LookupsTest×2 + ProjectExtensionsTest×1).
- **Stable failures (упали в обоих):** 0.
- **Memory project_state.md `742/738/1 flaky/3 skipped` устарел:** фактически parallel `739/0/3 skipped`, sequential `736/3/3`. Memory нуждается в update в Phase 6.

### Vitest

**Exit code 0.** **79 test files / 79 passed; 614 tests / 614 passed + 3 skipped; 56.09s.**

0 failed. Регрессий нет. Skipped 3 не enumerated reporter'ом, но известно `motion-css`/`typography-css`/etc на reduced-motion.

**Findings:**

- **P2 [vitest stderr] tests/Frontend/TwoFactorView.spec.ts** × 3 — Vue Router warn `No match found for location with path "/recovery-use"`. Не fail, но указывает на mismatch между Auth state stub в test-router и реальным маршрутом. **[FIX-NOW]** — добавить `/recovery-use` (или корректный `/use-recovery`) в test-router setup.
- **P2 [vitest] tests/Frontend/dealsApiMapper.spec.ts** — дубликат теста «name берёт contact_name; fallback на phone когда contact_name = null» (двойной ✓ в reporter). **[FIX-NOW]** — удалить дубликат.
- **P2 [vitest] tests/Frontend/vuetifyTheme.spec.ts** — дубликат теста «VTextField defaults to outlined + density comfortable». **[FIX-NOW]** — удалить дубликат.

### Histoire build

**Exit code 1.** **BUILD ABORTED** — `getActivePinia() was called but there was no active Pinia` в `resources/js/components/projects/BulkActionsBar.story.vue` (setup line 16: `useProjectsStore()` без `app.use(pinia)` в histoire-pipeline). Uncaught exception валит весь build.

- **P0 [histoire] resources/js/components/projects/BulkActionsBar.story.vue:16** — **REGRESSION** от baseline (24 stories / 31 variants на v1.87). Storybook artifact не публикуется. **3 гипотезы (systematic-debugging):**
  - H1: BulkActionsBar.story.vue добавлен в коммите `8f40ea4 feat(projects-bulk): Histoire stories for 3 bulk dialogs` без проверки histoire build pass.
  - H2: histoire.config.ts → setupFile не регистрирует Pinia plugin для всех stories.
  - H3: useProjectsStore() должен вызываться в onMounted, а не в setup top-level (но это валидный pattern для других stories — значит H2 более вероятна).
  - **Falsify H1/H2/H3:** прочитать BulkActionsBar.story.vue + histoire.config.ts + сравнить с рабочими story-файлами (KanbanCard.story passed в этом же run).
  - **[FIX-NOW]** — добавить Pinia setup в `histoire.config.ts` или wrap useProjectsStore() в `() => { ... }` factory.

### Vite production build

**Exit code 0.** **688 modules transformed, 1.80s, без warnings.**

Bundle сводка (gzip): CSS ~74 kB + JS ~321 kB = **~395 kB gzip total**.

**Top-10 chunks (largest, для bundle inventory):**

1. app-Ck7MVZNV.css — 297.87 kB (gzip 37.63 kB) — Vuetify CSS bundle
2. VBtn-BjfIn8tM.js — 183.87 kB (gzip 69.00 kB) — VBtn Vuetify async
3. KanbanView-CAT3r96d.js — 182.38 kB (gzip 63.37 kB) — vue-draggable + статусы
4. app-DDPPvwJX.js — 130.96 kB (gzip 43.85 kB) — main entry
5. ProjectsView-Bp96yMNM.js — 47.09 kB (gzip 13.54 kB)
6. axios-CXa6lsi0.js — 40.75 kB (gzip 15.85 kB)
7. VDataTable-BQN09VrW.js — 37.66 kB (gzip 11.99 kB)
8. DealsView-CEdEQdpK.js — 24.69 kB (gzip 8.50 kB)
9. VOverlay-OkoxMB75.js — 23.70 kB (gzip 8.29 kB)
10. autofocus-j0TpvUy_.css — 23.48 kB

**Findings:**

- **P2 [vite build] VBtn-BjfIn8tM.js 184 kB / KanbanView-CAT3r96d.js 182 kB** — под threshold 500 kB Vite, не блокер. **[FIX-DEFER]** — кандидаты на code-split в будущей итерации (вынести vuedraggable из KanbanView, разрезать VBtn).
- **P3 [vite build] app-Ck7MVZNV.css 297.87 kB raw / 37.63 kB gzip** — единый Vuetify CSS bundle, gzip приемлем для FCP. **Не находка.**

### Phase 2 итог

**Severity rollup:**

- P0: **1** (Histoire build broken)
- P1: 3 (Pest sequential failures — все FIX-DEFER из-за квирка 62)
- P2: 4 (vitest router warns, 2 vitest dup tests, vite chunk-size кандидаты)
- P3: 4 (Pest skipped × 3, vite app.css)

---

## Phase 3 — Schema integrity

**Подключение через Laravel Boost MCP к dev DB `liderra` (postgres superuser).**

| Метрика | Заявлено CLAUDE.md (v8.19) | Фактически (dev `liderra`) | Diff | Severity |
|---|---|---|---|---|
| Base tables (root, без partition children) | 62 | **75** | +13 | P2 stale doc |
| Partitions (children) | 12 | **102** | +90 | P2 stale doc |
| Partition parents | (не задек.) | 2 | n/a | informational |
| Indexes | 117 | **289** | +172 | P2 stale doc |
| RLS policies | 39 | **39** | 0 | ✅ |
| Functions (total in public) | 5 | 159 | +154 (extensions) | informational |
| Functions (user-defined, не из extensions) | 5 | **5** (audit_block_mutation, audit_chain_hash, calc_lead_score, report_jobs_log_export, set_pd_subject_request_deadline) | 0 | ✅ |
| Triggers | 13 | **19** | +6 | P2 stale doc |
| DB roles `crm_*` | 5 | **0** | by-design | ✅ documented |
| Orphan FK | 0 | **0** | 0 | ✅ |

**Extensions активны:** btree_gin 1.3, pg_trgm 1.6, pgcrypto 1.3, plpgsql 1.0 (4 шт.).

**75 base tables (actual list):**
activity_log, api_keys, auth_log, balance_transactions, comment_templates, deal_tag_pivot, deal_tags, document_sequences, email_verifications, failed_jobs, failed_webhook_jobs, impersonation_tokens, import_log, in_app_notifications, incidents_log, lead_charges, lead_statuses, legal_entities, migrations, outbound_webhook_deliveries, outbound_webhook_subscriptions, password_resets, payment_gateways, pd_processing_log, pd_subject_requests, pricing_tiers, project_limit_adjustments, project_suppliers, project_user_assignments, projects, push_subscriptions, refund_requests, rejected_deals_log, reminders, report_jobs, saas_admin_audit_log, saas_admin_recovery_codes, saas_admin_sessions, saas_admin_users, saas_invoice_items, saas_invoices, saas_transactions, saas_upd_documents, supplier_csv_reconcile_log, supplier_invoices, supplier_leads, supplier_projects, supplier_sync_log, suppliers, system_settings, tariff_plans, tariff_subscriptions, tenant_consents, tenant_custom_domains, tenant_status_overrides, tenants, user_recovery_codes, user_sessions, users, webhook_dedup_keys, webhook_log.

Плюс `migrations` (Laravel) — не должна считаться в product-schema. **Фактически 60 product-tables** (75 − migrations − 14 SaaS-admin = ~60), но и это нужно проверить по schema.sql отдельно.

### Findings (Phase 3)

- **P2 [docs-drift] CLAUDE.md §0/§2/§6/§8 — schema метрики застарели.** Все 4 числа застряли на v8.19 (Plan 4 closure), но dev `liderra` после `migrate:fresh` имеет 75 root tables / 102 partition children / 289 indexes / 19 triggers — заметно больше. **3 гипотезы (systematic-debugging):**
  - **H1**: dev DB фактически расширена за пределы schema.sql через accidental `tinker` / manual SQL — недоказуема без диффа schema.sql vs pg_dump. **Маловероятно**, миграция одна (`load_initial_schema.php` raw `DB::unprepared(file_get_contents(...))`).
  - **H2**: метрики CLAUDE.md использовали другой query-фильтр (например, `LIKE 'liderra_%'` или whitelist). **Маловероятно**, обычно используется `count(*) WHERE schemaname='public'`.
  - **H3**: drift накопился за Plan 1+2+2.5+3+4+5 без re-counter'инга в CLAUDE.md шапке. `partitions:create-months` Artisan-команда регулярно нарезает новые partition periods → 102 children, не 12. **Наиболее вероятно** + согласуется с факт-листом таблиц (включает Plan 3 `supplier_*`, Plan 4 `supplier_csv_reconcile_log`, Plan 5 фронтовые расширения — нет, фронтовые не добавляют tables).
  - **Falsify:** считать diff schema.sql `CREATE TABLE` vs actual list → подтвердит H3.
  - **[FIX-NOW]** через `claude-md-management:claude-md-improver`: targeted update §0/§2/§6/§8 на 75/102/289/19/39/3-5/5.
- **P2 [docs-drift] CLAUDE.md §6 «5 functions» — non-actionable** без явного списка user-defined functions. **[FIX-DEFER]**: добавить в §6 список 3-5 user-функций (audit_block_mutation, audit_chain_hash, calc_lead_score) явно вместо абстрактного числа.
- **✅ [Phase 3 — clean]** RLS policies = 39 (точно), orphan FK = 0, DB roles by design (на dev — postgres superuser, на prod `db/00_create_roles.sql` развёртывается отдельно).

---

## Phase 4 — Security

### gitleaks полная история

**Exit code 0. 0 leaks. 333 commits scanned, 11.14 MB, 1.96s.**

**Не находка.** Repository clean по gitleaks rules.

### Semgrep SAST

**Tool unavailable** (Semgrep — Python пакет, не npm; на этой машине не установлен; установка blocked Pravila §13.10).

- **P2 [security-tooling] Semgrep не запущен** — нужен `pip install semgrep` (запрещено session-mode) или вынести в CI (GitHub Actions Ubuntu runner). **[BLOCKED]** — вопрос заказчику в `blocked.md`.

**Audit methodology gap (зафиксирован 13.05.2026 day +1):** Phase 4 SAST coverage check must begin with `ls .github/workflows/` to detect existing CI infrastructure BEFORE concluding «SAST not configured». Audit 12.05.2026 missed Semgrep workflow в `.github/workflows/sast.yml` (commit `53fb1ec` from PR #25, 10.05.2026, за 2 дня до audit) — concluded «SAST tool unavailable» based only на local `npx semgrep` check + `pip install` blocked. Post-audit re-check (Q.INFO.001 closure 12.05.2026 ночь) обнаружил existing CI workflow: `semgrep/semgrep-action@v1` с конфигами p/php + p/javascript + p/typescript + p/secrets, push/PR triggers на main с path-filters, SARIF upload в GitHub Security tab. **Lesson:** future Phase 4 SAST/security tool checks must enumerate `.github/workflows/` / CI configs FIRST, до conclusions про tool availability. Generalize: any «X not configured» finding должен включать explicit check репо-уровневых configurations (`.github/`, `.gitlab-ci.yml`, `lefthook.yml`, etc.) перед verdict.

### composer audit (cross-link Phase 1.A)

**0 advisories, clean.**

### Phase 4 итог

- P0: 0
- P1: 0
- P2: 1 (Semgrep blocked)
- P3: 0

---

## Phase 5 — UI smoke (Playwright)

**Servers:** Laravel `http://127.0.0.1:8000` + Vite `http://localhost:5175` (5173/5174 заняты).
**Seed:** `php artisan db:seed --class=DemoSeeder` — tenant id=1 «Demo Tenant», admin@demo.local / password, 3 projects (site/call/sms), 14 demo deals (по статусам).
**Screenshots:** `audit-screens/views/01-login.png` … `24-error-404.png`.

### Per-view results (24 views)

| # | View | URL | Status | Console | Net API | Notes |
|---|---|---|---|---|---|---|
| 01 | LoginView | /login | ✅ OK | 401 /api/auth/me (guest expected) | — | Forest brand, email+password+Yandex SSO. DevIndex #1 |
| 02 | RegisterView | /register | ✅ OK | 401 expected | — | |
| 03 | ForgotPasswordView | /forgot | ✅ OK | 401 expected | — | |
| 04 | TwoFactorView | /2fa → /login | ✅ guard redirect | — | — | Router-guard: partial-auth state required, guest redirected |
| 05 | RecoveryCodesView | /recovery | ✅ OK | 401 expected | — | |
| 06 | UseRecoveryCodeView | /use-recovery | ✅ OK | 401 + 1 warn (DevIndexOverlay fragment) | — | |
| 07 | ResetPasswordView | /reset?token=...&email=... | ❌ ErrorView 404 | 401 + DevIndexOverlay warn | — | **P1** — `/reset` маршрут отсутствует в Vue Router |
| 08 | DashboardView | /dashboard | ✅ OK после login | DevIndexOverlay warn только | login 200, notifications 200, reminders 200 | KPI-row + balance + nav-tree рендерятся |
| 09 | DealsView | /deals | ✅ OK | 0 err | auth/me 200, lead-statuses 200, deals 200 | 14 demo deals в списке |
| 10 | KanbanView | /kanban | ✅ OK | 0 err | (likely deals refresh) | Колонки по статусам |
| 11 | ProjectsView | /projects | ✅ OK | 1 warn (DevIndex) | auth/me 200, projects 200 | 3 demo projects |
| 12 | ReportsView | /reports | ✅ OK | 0 err | reports/jobs 200 | |
| 13 | BillingView | /billing | ✅ OK | 0 err | auth/me 200 (нет билл-API call — lazy?) | **P2** — нет API call billing/* при mount, возможно tabs-driven loading |
| 14 | SettingsView | /settings | ✅ OK | 0 err | auth/me 200 (тоже lazy) | |
| 15 | RemindersView | /reminders | ✅ OK | 0 err | reminders 200 | |
| 16 | AdminTenantsView | /admin/tenants | ⚠️ доступен tenant-admin | 0 err | (530+ console entries — много network) | **P1** — нет router-guard на saas_admin role; demo user (tenant admin) получает доступ |
| 17 | AdminTenantDetailView | /admin/tenants/1 | ⚠️ доступен | 1 err | — | **P1** same. Уточнить console error в Phase 6 |
| 18 | AdminBillingView | /admin/billing | ⚠️ доступен | 0 err | — | **P1** same |
| 19 | AdminIncidentsView | /admin/incidents | ⚠️ доступен | — | — | **P1** same |
| 20 | AdminSystemView | /admin/system | ⚠️ доступен | — | — | **P1** same |
| 21 | AdminImpersonationView | /admin/impersonation | ⚠️ доступен | — | — | **P1** same |
| 22 | AdminPricingTiersView | /admin/pricing-tiers | ⚠️ доступен | — | — | **P1** same |
| 23 | AdminSupplierPricesView | /admin/supplier-prices | ⚠️ доступен | — | — | **P1** same |
| 24 | ErrorView 404 | /no-such-path-anywhere | ✅ catch-all OK | 1 warn | — | h1=«404», h2=«Страница не найдена» |

### Interactive flows

- **Login** (admin@demo.local / password) — ✅ работает: form submit → POST /api/auth/login 200 → redirect /dashboard. notifications + reminders fetched on mount.
- **Logout** — ✅ работает: avatar dropdown «Выйти» → POST /api/auth/logout (assumed 200) → redirect /login.
- **Profile dropdown discovery** — avatar `.v-avatar.bg-primary` (initials «DA») в topbar → click открывает menu с «Выйти». **P2 [discoverability]** — нет direct visible Logout button; UI hides behind 1 click.

### Phase 5 findings (sumary)

- **P1 [routing] resources/js/router/index.ts — отсутствует маршрут `/reset`** (или путь иначе называется). ResetPasswordView существует в `resources/js/views/auth/ResetPasswordView.vue`, но не подключен к Vue Router. **[FIX-NOW]** — добавить `{ path: '/reset', component: ResetPasswordView, meta: { layout: 'auth' } }`.
- **P1 [security] resources/js/router/index.ts — admin/* маршруты без role-guard.** Все 8 admin views доступны обычному tenant admin. Нужен router-guard `requiresAdmin` или `meta.role: 'saas_admin'` + check в `auth-store`. **3 гипотезы (systematic-debugging):**
  - H1: Frontend router-guard для admin отсутствует, backend API enforce'ит permission и admin pages показывают пустые data (graceful degradation). **Проверить:** открыть AdminTenantsView → если data есть (другие tenant'ы) — реальная уязвимость.
  - H2: Demo seed сделал admin@demo.local saas_admin (но в seeder этого нет — read confirmed). **Маловероятно.**
  - H3: Router имеет guard, но он проверяет только `isAuthenticated`, не role. **Наиболее вероятно** + согласуется с memory pattern `Plan 5 router refactor` (`requiresAuth=true` для admin без `requiresAdmin`).
  - **Falsify:** прочитать `resources/js/router/index.ts` + grep `meta.requiresAdmin|saas_admin|isAdmin` → подтвердит H3.
  - **[FIX-NOW]** — расширить router-guard для admin/* с проверкой `saas_admin` role.
- **P2 [dev-overlay] DevIndexOverlay fragment-root inheritance** — Vue warn «Extraneous non-props attributes (data-dx) were passed to component but could not be automatically inherited because component renders fragment or text or teleport root nodes». Повторяется на 4+ views (use-recovery, reset, dashboard, projects). Affected: ErrorMeta, DashboardKpiRow. **[FIX-NOW]** — wrap в single root element ИЛИ `inheritAttrs: false` в фрагмент-компонентах ИЛИ удалить data-dx прокидку для fragment-root компонентов в dev-indices plugin.
- **P2 [discoverability]** Logout скрыт за avatar dropdown — на mobile/narrow viewport может быть не очевидно. **[FIX-DEFER]** — UX-улучшение.
- **P2 [admin-tenants console]** ≥530 console entries на /admin/tenants — повышенный noise (probably DevIndexOverlay walking all data-dx elements). Нужен `npm run build` production mode для подтверждения, что в prod это исчезнет.

### Phase 5 итог

- P0: 0
- P1: **2** (missing /reset route, admin/* no role-guard)
- P2: 4 (dev-overlay fragment warn ×4 views, logout discoverability, admin-tenants console noise, billing/settings lazy mount API)
- P3: 0

---

## Phase 6 — Cross-doc integrity

### Версии связанных документов (factual vs memory claims)

| Документ | Factual (read) | Memory description claim | Drift? |
|---|---|---|---|
| `CLAUDE.md` (root) | v1.87 (claudeMd context, 11.05.2026 Plan 4 sync) | «v1.88» (12.05.2026 Quiet Luxury §5 п.12) | Memory forward-stale by 1 |
| `docs/Pravila_raboty_Claude_v1_1.md` | v1.10 (line 3) | «v1.11» (12.05.2026 §11.5/§13.2 16→15) | Memory forward-stale |
| `docs/Plugin_stack_rules_v1.md` | v1.7 (line 1) | «v2.0» (12.05.2026 major bump, R15 removed) | Memory forward-stale by major |
| `docs/Tooling_v8_3.md` | v1.15 (line 3) | «v1.16» (12.05.2026 §9.2 reformulated) | Memory forward-stale |
| `docs/Открытые_вопросы_v8_3.md` | v1.73 (memory project_state line 31) | «v1.78» (memory description, Plan 4 closure) | Memory forward-stale |
| `db/schema.sql` | v8.19 (CLAUDE.md §0) | v8.18 (memory `project_state.md`) | Memory backward-stale |

### Phase 6 findings

- **P2 [memory-drift] `memory/reference_archive.md` description (line 3) предсказывает версии файлов, которые ещё не существуют.** Memory заявляет CLAUDE.md v1.88, Pravila v1.11, PSR_v1 v2.0, Tooling v1.16, реестр v1.78 — фактически файлы на v1.87, v1.10, v1.7, v1.15, v1.73. **3 гипотезы (systematic-debugging):**
  - **H1**: Memory обновлено в прошлой сессии «авансом» (после планирования bump'ов) без выполнения самого bump'а в файлах. **Маловероятно** — Memory структурно опережает на 1 patch + 1 major у PSR — это не «план», а зафиксированные deltas.
  - **H2**: Memory отражает изменения, которые **были** сделаны в Plan 4/Quiet Luxury сессии 12.05.2026, но изменения **не закоммичены** в файлы из-за rollback / aborted commit. **Подтверждение:** git log показывает commit `b5849bb fix(projects): cyrillic ILIKE via PG ICU + clearable workaround` — это последний commit, без bump'ов normative docs.
  - **H3**: Memory был обновлён по error — copy-paste из черновика, который никогда не применялся. **Маловероятно.**
  - **Falsify через git log:** `git log --all --oneline | grep -E "Pravila v1.11|PSR.*v2.0|Tooling v1.16|CLAUDE.md v1.88"` → если 0 results, memory stale forward.
  - **[FIX-NOW]** для memory: понизить version claims в `reference_archive.md` description до фактических (v1.87/v1.10/v1.7/v1.15/v1.73). Memory должно отражать **фактическое** состояние файлов, а не plan.
- **P2 [docs-drift] CLAUDE.md §0 «Открытые_вопросы v1.78»** vs факт v1.73. **[FIX-NOW]** через `claude-md-management:claude-md-improver` — таргет §0 строка «Открытые вопросы» → v1.73 ИЛИ обновить файл `Открытые_вопросы_v8_3.md` сначала до v1.78 если все Plan 4 решения внесены.
- **P2 [docs-drift] CLAUDE.md §6 «Plan 4 ready for FF-merge»** — фактически Plan 4 merged в main (origin/main `615db99` — это R15 PSR removal post-merge). **3 гипотезы:**
  - **H1**: Plan 4 действительно merged, но §6 не обновлён. **Подтверждение:** memory `project_supplier_integration.md` говорит «Plans 1+2+2.5+2.6+3+4 merged на origin/main». git log origin/main содержит Plan 4 commits.
  - **H2**: Plan 4 commits НЕ merged, тогда `b5849bb` не должна быть на 79 ahead of main. **Опровергнуто** — 79 commits ahead — это Plan 5 + Quiet Luxury + dev-indices, не Plan 4 (Plan 4 уже в main).
  - **H3**: §6 описывает Plan 4 как «pre-merge state» намеренно (history capture). **Маловероятно** — §6 «Текущая фаза» это live state.
  - **[FIX-NOW]** через `claude-md-improver` — обновить §6 «Post-MVP (10.05.2026)» → «Post-MVP + Plan 5 frontend (12.05.2026)».
- **P2 [docs-drift] CLAUDE.md §0/§2/§6/§8 schema метрики «62/12/117/39/5/13/5»** vs факт «75/102/289/39/5/19/0 (dev)». Подтверждено в Phase 3. **[FIX-NOW]** через `claude-md-improver`.

### Router/Routes integrity (Phase 5 P1 reduction)

- **P1 → P2/false-positive: `/reset` маршрут** — Vue Router имеет `/reset/:token` (line 74), мой audit-тест `/reset?token=...&email=...` использовал query-style вместо path-param. Это **корректное** поведение: email reset link имеет формат `/reset/{token}?email=...`. Catch-all `/:pathMatch(.*)*` (line 195) ловит `/reset` без token и рендерит ErrorView 404 — это **expected** UX (показывает 404 если токен потерян). **[FIX-DEFER]** — добавить explicit `/reset` route с UI «токен недействителен» в Vue Router (вместо 404) — UX-улучшение.
- **P1 → confirmed: admin/* нет role-guard.** `router/index.ts:128` комментарий: `// TODO: дополнительный role-guard на super_admin.`. router.beforeEach (line 209-228) проверяет только `requiresAuth + isAuthenticated`. Backend `routes/web.php:75-87,90,93,97,105,115` — `/api/admin/*` БЕЗ middleware. Комментарии: «На MVP без middleware (saas-admin auth не реализован)», «Б-1 ⏸». **[BLOCKED] → Q.HARD.001** — требует saas-admin SSO (Б-1 разблокировка).
- **P2 [routes/web.php] inconsistent explicit `Route::view` list (line 191-206):** комментарий явно говорит «Регистрируем явно, а не catch-all», но в list есть `/, /login, /register, /forgot, /reset, /2fa, /recovery, /recovery-use, /dashboard, /deals, /kanban, /billing, /settings, /reports, /403, /500` — **ОТСУТСТВУЮТ `/reminders`, `/projects`, `/admin/*`** (8 admin paths). Работает через `Route::fallback` (line 211), но нарушает test-isolation rationale (line 188-190): runtime-routes тестов могут перехвачиваться fallback'ом перед catch-all. **[FIX-NOW]** — добавить `/reminders`, `/projects` и `/admin/tenants`, `/admin/tenants/{id}`, `/admin/billing`, `/admin/incidents`, `/admin/system`, `/admin/pricing-tiers`, `/admin/supplier-prices`, `/admin/impersonation` в explicit list.

### Phase 6 итог

- P0: 0
- P1: 0 (admin role-guard переведён в BLOCKED Б-1)
- P2: **5** (memory-drift forward, CLAUDE.md §0 реестр, §6 фаза, §0/§2/§6/§8 schema, routes/web.php inconsistent)
- P3: 0

---

## Phase 7 — Fix Queue (categorized)

### Severity rollup всей сессии (Phases 0-6)

| Phase | P0 | P1 | P2 | P3 |
|---|---|---|---|---|
| Phase 0 | 0 | 0 | 0 | 1 (ghost PNG move) |
| Phase 1 | 0 | **44** (1 pint + 17 eslint + 9 vue-tsc + 17 untracked items) | **316** (48 prettier + 165 markdownlint + 103 cspell) | 1 (pgFormatter) |
| Phase 2 | **1** (Histoire broken) | 3 (Pest sequential — FIX-DEFER) | 4 (vitest dup ×2, router warns, vite chunk-size) | 4 (Pest skipped ×3, vite app.css) |
| Phase 3 | 0 | 0 | 4 (schema docs-drift ×4 метрик) | 0 |
| Phase 4 | 0 | 0 | 1 (Semgrep unavailable) | 0 |
| Phase 5 | 0 | 0 (downgraded /reset + admin role-guard) | 4 (dev-overlay fragment, logout discoverability, admin console noise, billing lazy) | 0 |
| Phase 6 | 0 | 0 | 5 (memory drift, §0/§2/§6/§8 + reestr + phase + routes inconsistent) | 0 |
| **TOTAL** | **1** | **47** | **339** | **6** |

### P0 (fix немедленно)

1. **[Phase 2] Histoire build broken** — `resources/js/components/projects/BulkActionsBar.story.vue:16` `useProjectsStore()` без `app.use(pinia)` валит весь histoire build. **[FIX-NOW]** — wrap setup в onMounted ИЛИ обновить `histoire.config.ts` setupFile с Pinia plugin.

### P1 (fix в первой очереди Phase 8)

1. **[Phase 1A] DemoSeeder Pint fix** — уже применён subagent'ом, нужен только git add + commit (как часть DemoSeeder addition). **[FIX-NOW]**
2. **[Phase 1B] AppSidebar NavItem.countKey** missing in interface (2 vue-tsc errors). **[FIX-NOW]** — расширить interface ИЛИ удалить usage из template.
3. **[Phase 1B] Project type duplicate** — `EditProjectDialog.vue:5`, `ProjectsView.vue:71-80`, `NewProjectDialog.spec.ts:15` — 7 vue-tsc errors. **[FIX-NOW]** — unify через single export.
4. **[Phase 1B] ESLint 17 errors** в tests/Frontend/*: 13 `no-explicit-any` + 3 `vitest/no-disabled-tests` rule-not-found + 1 unused-import. **[FIX-NOW]** — `any → unknown / Project | Record<string, unknown>`, удалить vitest/no-disabled-tests refs или убрать комментарии, удалить unused `beforeEach`.

### P2 (fix если время есть)

1. **[Phase 0] Ghost PNG already moved** to `audit-screens/legacy/`. **[FIX-NOW]** — commit как часть audit-finalize.
2. **[Phase 1B] Prettier --write 48 files**. **[FIX-NOW]** — `npx prettier --write`.
3. **[Phase 1C] markdownlint --fix 165** в 3 untracked plans/specs. **[FIX-NOW]** — `npx markdownlint-cli2 --fix`.
4. **[Phase 1C] cspell 103 unknown в 12 files**. **[FIX-NOW]** — batch add в `cspell-words.txt` (~80 валидных слов + lorem ipsum в separate include).
5. **[Phase 2] vitest dealsApiMapper.spec.ts dup test** — `name берёт contact_name; fallback на phone когда contact_name = null` дубликат. **[FIX-NOW]** — удалить.
6. **[Phase 2] vitest vuetifyTheme.spec.ts dup test** — `VTextField defaults to outlined + density comfortable` дубликат. **[FIX-NOW]** — удалить.
7. **[Phase 2] TwoFactorView.spec.ts Vue Router warn** — `/recovery-use` not found в test-router. **[FIX-NOW]** — добавить route в test-setup.
8. **[Phase 3] CLAUDE.md schema metric drift** §0/§2/§6/§8 (62→75 / 12→102 / 117→289 / 13→19). **[FIX-NOW]** через `claude-md-management:claude-md-improver` (но т.к. CLAUDE.md §6 говорит «на dev — postgres superuser; deploy script отдельно», некоторые метрики на dev != prod — нужно явно отметить «(dev текущий)» vs «(prod target)»).
9. **[Phase 4] Semgrep unavailable** — добавить в blocked.md.
10. **[Phase 5] DevIndexOverlay fragment-root inheritance** Vue warns на 4+ views. **[FIX-DEFER]** — wrap fragment-root компоненты ИЛИ skip data-dx прокидку в plugin.
11. **[Phase 6] memory `reference_archive.md` forward-stale description**. **[FIX-NOW]** — обновить description.
12. **[Phase 6] CLAUDE.md §0 реестр v1.78 vs факт v1.73**. **[FIX-NOW]** через `claude-md-improver`.
13. **[Phase 6] CLAUDE.md §6 «Plan 4 ready for FF-merge»** — Plan 4 merged. **[FIX-NOW]** через `claude-md-improver`.
14. **[Phase 6] routes/web.php inconsistent explicit Route::view** — добавить `/reminders, /projects, /admin/*`. **[FIX-NOW]**

### P3 (defer/cosmetic)

1. **[Phase 1D] pgFormatter 6284 line diff** — style-only, manual review. **[FIX-DEFER]**.
2. **[Phase 2] vite chunks VBtn 184 kB + KanbanView 182 kB** — code-split кандидаты. **[FIX-DEFER]**.
3. **[Phase 2] Pest sequential 3 fails** — квирк 62 test-DB cumulative. **[FIX-DEFER]** — DROP+CREATE с ICU + migrate.

### BLOCKED (нужно от заказчика)

- **Q.HARD.001 [Phase 5 + Phase 6]** Admin role-guard frontend + backend `/api/admin/*` auth middleware. Documented в коде как ⏸ Б-1 (saas-admin SSO). Закрывает реальный security gap — не аудитное произвольное замечание, а уже tracked open issue.
- **Q.INFO.001 [Phase 4]** Semgrep SAST не запущен (tool unavailable, npm wrapper отсутствует). Установка blocked Pravila §13.10. Решение: либо `pip install semgrep` локально (требует session-mode permission), либо CI job ubuntu-latest.

### Fix order (Phase 8 plan)

1. **Fix #1 (P0):** Histoire build — BulkActionsBar.story.vue Pinia setup.
2. **Fix #2 (P1):** AppSidebar NavItem.countKey.
3. **Fix #3 (P1):** Project type unification.
4. **Commit gate + self-review §8 #1** (3 fix-commits).
5. **Fix #4 (P1):** ESLint 17 errors в tests/Frontend/*.
6. **Fix #5 (P2):** Vitest 2 dup tests + Vue Router warn.
7. **Fix #6 (P2):** Prettier --write 48 files (одним commit'ом).
8. **Commit gate + self-review §8 #2** (3 fix-commits).
9. **Fix #7 (P2):** markdownlint --fix 165.
10. **Fix #8 (P2):** cspell 103 в словарь.
11. **Fix #9 (P2):** routes/web.php добавить missing Route::view.
12. **Commit gate + self-review §8 #3** (3 fix-commits).
13. **Fix #10 (P2):** CLAUDE.md §0/§2/§6/§8 через claude-md-improver (включает реестр, фазу, schema метрики).
14. **Fix #11 (P2):** memory `reference_archive.md` description sync.
15. **Fix #12 (P0/P3):** ghost PNG commit + DemoSeeder commit (untracked items).
16. **Commit gate + self-review §8 #4** (3 fix-commits).
17. **Final** Phase 9 verification + summary report + push.

---

## Phase 10 — Live-URL Pa11y (extra, post-finalize)

**Trigger:** заказчик «продолжай еще тут». Phase 5 audit запускал Pa11y только на `liderra_v8_handoff/concepts/v8_*.html` static HTML (config `pa11y.config.json` lines 22-35). Live портал не покрывался. Phase 10 closes this gap.

**Method:** `npx pa11y --standard WCAG2AA --timeout 30000 --wait 1500 <URL>` на 4 guest URLs (auth-required требуют session cookie — skipped в Phase 10, см. Q.DEFER.002 для следующей сессии).

### Per-URL results

| URL | Status | Errors |
|---|---|---|
| `/login` | ✅ clean | 0 |
| `/register` | ✅ clean | 0 |
| `/forgot` | ⚠️ 2 errors | 2× contrast `4.18:1 < 4.5:1` |
| `/no-such-path-404` (ErrorView) | ⚠️ 1 error | 1× contrast `2.77:1 < 4.5:1` |

### Findings

- **P1 [a11y WCAG2AA G18] `app/resources/js/views/auth/ForgotPasswordView.vue` rate-limit alert** — `<div class="v-alert__content">` + nested `<strong>` имеют contrast `4.18:1 < 4.5:1` для текста «Лимит — 5 попыток в 15 минут». **Pa11y recommends:** background `#fffcf5`. Корень — Vuetify `v-alert` info-variant tint. **[FIX-DEFER → blocked Q.DEFER.002]** — fix потребует либо theme-tweak в `resources/js/plugins/vuetify.ts` (default info-variant bg light shift), либо override class на specific alert, либо переоценки contrast pair (foreground text `#0F6E56` teal на info bg). Дизайнерское решение, не code-only.
- **P1 [a11y WCAG2AA G18] `app/resources/js/views/errors/ErrorView.vue` support link** — `<a class="text-primary" href="mailto:support@liderra.app">` contrast `2.77:1 < 4.5:1`. **Pa11y recommends:** text color `#fcfffe` (но это white-on-white false suggest — Pa11y компьютит против wrong bg). Корень — Vuetify `text-primary` utility (= teal `#0F6E56`) на ErrorView body background. **3 гипотезы (systematic-debugging):**
  - **H1**: ErrorView имеет gradient/illustration background где link рендерится поверх non-ivory color — contrast вычисляется правильно, но фактический pair неожиданный.
  - **H2**: Pa11y видит `transparent` bg и computes против `body` или parent — false positive из-за CSS cascade.
  - **H3**: `text-primary` фактически рендерит другой цвет в ErrorView context (CSS-var override?).
  - **Falsify:** прочитать `ErrorView.vue` + computed-style на `support@liderra.app` link через Playwright `browser_evaluate`. Подтвердит H1 vs H2 vs H3.
  - **[FIX-DEFER → blocked Q.DEFER.002]** — diagnose first, then fix.
- **P3 [audit-tooling] `pa11y.config.json` targets handoff concepts, не live** — `urls` (lines 22-35) указывают на `./liderra_v8_handoff/concepts/v8_*.html`. **[FIX-DEFER]** — расширить config двумя списками («handoff concepts» + «live SPA») с CI gate; вне scope этой сессии.

### Phase 10 итог

- P0: 0
- P1: **2** (a11y violations на live + ErrorView; оба design-related → blocked.md Q.DEFER.002)
- P2: 0
- P3: 1 (pa11y.config doesn't cover live)

**Auth-required views (/dashboard, /projects, /deals, /kanban, /billing, /settings, /reminders, /admin/*) — NOT verified в Phase 10** (Pa11y standalone требует session cookie injection; defer to Q.DEFER.002 next session).

---

## Phase 11 — TODO/FIXME sweep (extra, informational)

**Method:** `Grep -E '\b(TODO|FIXME|XXX|HACK)\b'` over `app/**/*.{php,vue,ts}` (production + tests).
**Total:** 19 matches in 15 files.

### Категоризация

**MVP-defer ⏸ Б-1 (6, все cross-link Q.HARD.001):**

- `app/resources/js/router/index.ts:184` — `// TODO: дополнительный role-guard на super_admin.`
- `app/app/Http/Controllers/Api/AdminSystemSettingsController.php:47` — `// TODO: $request->user()->id` (когда saas-admin auth готов)
- `app/app/Http/Controllers/Api/ImpersonationController.php:98` — `// TODO: $request->user()->id когда saas-admin auth готов`
- `app/app/Http/Controllers/Api/ImpersonationController.php:125` — `// TODO: отправить email на $tenant->contact_email с $plainCode.`
- `app/app/Http/Controllers/Api/ImpersonationController.php:212` — `// TODO: уведомление клиенту по email о завершении.`
- `app/resources/js/components/admin/ImpersonationDialog.vue:15` — `* chargeback_unrecovered_rub > 0 — TODO после saas-admin auth.`

**Feature-defer (планы 5-7, не блокер):**

- `app/app/Services/SupplierResolver.php:22` — fallback на `system_settings.default_supplier_id`
- `app/resources/js/views/settings/ProfileTab.vue:6` — `MVP: form-fields без save (TODO: PATCH /api/me)`
- `app/resources/js/views/auth/RecoveryCodesView.vue:14` — `TODO(phase2): получать из API после step1 настройки 2FA`
- `app/resources/js/views/auth/RecoveryCodesView.vue:47` — `TODO(phase2): редирект на /dashboard`
- `app/resources/js/views/projects/NewProjectDialog.vue:171` — `TODO: разобрать region_mask обратно в codes (Plan 6)`
- `app/resources/js/views/DealsView.vue:8` — `Не входит в этот коммит (отдельные TODO)`
- `app/resources/js/views/DashboardView.vue:9` — `Все числа сейчас mock'и — TODO: GET /api/dashboard/summary`
- `app/resources/js/components/kanban/KanbanCard.vue:8` — `Click → emit('open') — TODO: правая панель DealDetailDrawer`

**Production-readiness (1):**

- `app/app/Jobs/ProcessWebhookJob.php:375` — `// TODO(production): Sentry::captureException($e);`

**Test infra (3, known квирки):**

- `app/tests/Frontend/ProjectsView.spec.ts:68` — `TODO: VSelect dropdown в jsdom не открывает items-list через teleport` (квирк 54)
- `app/tests/Frontend/NewProjectDialog.spec.ts:69` — Vuetify-форма rendering в JSDOM (skip rationale)
- `app/tests/Frontend/NewProjectDialog.spec.ts:75` — same as 69

**False-positive (1):**

- `app/tests/Feature/Auth/TwoFactorSetupTest.php:107` — `'totp_secret' => 'XXX'` (это **string literal**, не FIXME). Pattern match артефакт.

### Findings

- **P2 [tracking-hygiene] 6 TODO упирающихся в Б-1** — все ОК (documented MVP defer; cross-link Q.HARD.001).
- **P3 [tracking-hygiene] 8 feature-defer TODO** — все запланированы на Plan 6+ ИЛИ post-MVP. Не блокеры. **[FIX-DEFER]**.
- **P2 [production-readiness] `ProcessWebhookJob.php:375` Sentry::captureException** — должен быть включён до production launch. **[FIX-DEFER → blocked Q.PRODUCT.001 для prod-readiness checklist]**.
- **P3 [test-infra] 3 TODO про VSelect/Vuetify teleport в jsdom** — known квирк 54, documented. **[FIX-DEFER]**.
- **P3 [false-positive] XXX в TwoFactorSetupTest** — string literal, не FIXME. **Не находка.**

### Phase 11 итог

- P0: 0
- P1: 0
- P2: 2 (admin TODO group cross-link Б-1 + Sentry prod-readiness)
- P3: 12 (feature-defer + test-infra documented work)

---

## Phase 12 — Bundle analyzer (extra, informational)

**Method:** `BUILD_ANALYZE=1 npm run build:analyze` → rollup-plugin-visualizer HTML отчёт в `app/storage/bundle-analyze.html` (547 KB, gzipSize+brotliSize). Subagent fresh run 2026-05-12 ночь.

### Top-15 chunks (raw + gzip)

| # | Chunk | Raw | Gzip | Notes |
|---|---|---:|---:|---|
| 1 | `VBtn-BjfIn8tM.js` | 183.87 kB | 69.00 kB | **Mislabeled** — Rollup auto-named "VBtn" но фактически Vue 3 runtime/reactivity + Vuetify base composables. **Не runtime concern** — Vue+Vuetify core unavoidable. |
| 2 | `KanbanView-Xhli1bV_.js` | 182.38 kB | 63.37 kB | Sortable (vuedraggable underlying, ~80 kB raw) **+ eager DealDetailDrawer + NewDealDialog** (статический import despite DealsView lazy-loading same). |
| 3 | `app-PPqOqZSR.js` | 130.96 kB | 43.84 kB | Entry: AppShell + 3 layouts + AppSidebar + AppTopbar + stores + Pinia/router glue. |
| 4 | `ProjectsView-V8gny-Pj.js` | 47.09 kB | 13.54 kB | view + NewProjectDialog + EditProjectDialog + ProjectCard + BulkActionsBar. Clean. |
| 5 | `axios-CXa6lsi0.js` | 40.75 kB | 15.85 kB | Уже clean vendor chunk. |
| 6 | `VDataTable-BStlQrAR.js` | 37.66 kB | 11.99 kB | Lazy-loaded only by DealsView. |
| 7 | `DealsView-T-PnYbrC.js` | 24.69 kB | 8.49 kB | |
| 8 | `VOverlay-OkoxMB75.js` | 23.70 kB | 8.29 kB | Vuetify overlay primitive. |
| 9 | `SettingsView-DwLkVsfl.js` | 23.33 kB | 7.35 kB | |
| 10 | `VSelect-D4i0vELF.js` | 19.15 kB | 7.47 kB | |
| 11-15 | BillingView 16.41 / ReportsView 14.83 / autofocus 14.06 / AdminTenantDetailView 14.06 / AdminTenantsView 12.49 | | | |

**Critical-path payload (FCP eager):** VBtn (mislabeled vendor) + app entry + Vuetify CSS = **~150 kB gzip cold-load total**.

### Findings

- **P2 [bundle code-split] R1: `KanbanView.vue` eager `vuedraggable`/Sortable.js (~80 kB raw / ~30 kB gzip win)** — Lift `draggable` в `KanbanColumn.vue:14` в `defineAsyncComponent(() => import('vuedraggable'))`, ИЛИ pre-split в `vite.config.js` `manualChunks: { vuedraggable: ['vuedraggable'] }`. **[FIX-DEFER]** — non-critical, оставить для post-MVP perf-tuning.
- **P2 [bundle code-split] R2: KanbanView eager NewDealDialog + DealDetailDrawer** (lines 28-29) — `defineAsyncComponent` для обоих. Inline comment lines 26-27 цитирует Vitest EnvironmentTeardownError (квирк 57) — workaround через `vi.mock` в tests + lazy в production. **Win:** ~17 kB raw / ~7 kB gzip. **[FIX-DEFER]**.
- **P3 [bundle dev-mocks-in-prod] R3: `composables/mock{Deals,Admin}.ts` ship в production bundle** — 3-3.5 kB gzip каждый. Если intentional (empty-state placeholders) — оставить; если dev-fixtures — gate за `import.meta.env.DEV` (mirroring `DevIndexOverlay` pattern в AppShell.vue:22-24). **[BLOCKED → Q.PRODUCT.002]** — нужен confirm от заказчика что mock-data dev-only vs prod-fallback.
- **P3 [bundle naming] R4-R5: vendor chunks misnamed** — VBtn 184 kB на самом деле Vue+Vuetify core. Add explicit `manualChunks` в `vite.config.js`. **[FIX-DEFER]**.

  **Experiment tried + reverted (12.05.2026 ночь, не в git):** Попытался добавить `build.rollupOptions.output.manualChunks: (id) => { ... }` (Vite 8 / Rolldown требует function form — object form ломает Rolldown с `manualChunks is not a function`). После build:
  - Pinia chunk вырос с **implicit-bundled ~5kB до 127 kB raw / 50 kB gzip** — Rolldown засосал все consumers stores в pinia chunk через transitively-import резолюцию.
  - Total critical-path payload (app+vue-core+VBtn+pinia+vue-router+axios) **+50 kB gzip vs baseline** — net negative.
  - **3 гипотезы:** H1 stores transitively-pulled через pinia API import; H2 cycles vue-core↔pinia в Rolldown greedy chunking; H3 `return null` в function manualChunks ломает auto-split fallback.
  - **Reverted** — Vite auto-split работает better для этого app shape. "VBtn 184 kB" — naming artefact (Rollup auto-named первый Vuetify consumer'ом), не actual perf-issue. R4-R5 **closed без fix** (informational only).

### Phase 12 итог

- P0/P1: 0
- P2: 2 (KanbanView code-split candidates)
- P3: 2 (mock-data в prod, vendor naming)
- BLOCKED Q.PRODUCT.002: 1

---

## Phase 13 — Vitest coverage (extra, informational)

**Method:** `npx vitest run --coverage --coverage.reporter=text-summary --coverage.reporter=text` через `@vitest/coverage-v8`. Subagent fresh run 2026-05-12 ночь.

### Coverage totals

```
Stmts 75.27%  Branch 75.25%  Funcs 67.48%  Lines 77.12%
(3105/4125 stmts, 2013/2675 branches, 1015/1504 funcs, 2822/3659 lines)
```

**Functions vs Lines gap (10pp):** много exported utility-функций в `api/*` и `router/index.ts` не вызываются ни одним тестом.

### Top files с lowest coverage

| # | File | Stmts | Branch | Funcs | Lines |
|---|---|---:|---:|---:|---:|
| 1 | `resources/js/api/auth.ts` | **0** | 100 | 0 | 0 |
| 2 | `resources/js/api/deals.ts` | **0** | 0 | 0 | 0 |
| 3 | `resources/js/api/notifications.ts` | **0** | 0 | 0 | 0 |
| 4 | `resources/js/api/reminders.ts` | 7.69 | 100 | 20 | 7.69 |
| 5 | `resources/js/layouts/AdminLayout.vue` | 9.09 | 0 | 0 | 10.52 |
| 6 | `resources/js/api/admin.ts` | 11.53 | 100 | 27.27 | 11.53 |
| 7 | `resources/js/components/settings/security/RecoveryCodesCard.vue` | 28.12 | 6.25 | 5.55 | 27.58 |
| 8 | `resources/js/components/settings/security/TwoFactorCard.vue` | 28.57 | 21.42 | 12.12 | 29.48 |
| 9 | `resources/js/api/client.ts` | 29.41 | 20.68 | 50 | 39.13 |
| 10 | `resources/js/components/reminders/ReminderDialog.vue` | 0 | 0 | 0 | 0 |
| (доп) | `resources/js/router/index.ts` | 33.33 | 90 | 7.4 | 33.33 |
| (доп) | `resources/js/views/RemindersView.vue` | 39.13 | 51.61 | 17.39 | 41.46 |

### Untested production files (zero coverage в отчёте)

- `resources/js/api/reports.ts` — Reports HTTP-обёртки
- `resources/js/components/charts/FunnelChart.vue` — UI component
- `resources/js/components/dashboard/DashboardBalance.vue` — UI component
- `resources/js/components/billing/BalanceCard.vue` — UI component
- `resources/js/views/admin/AdminPlaceholderView.vue` — view-stub

(Доп: 7 composables `mock*.ts` — фейк data, **intentionally untested**, не дефицит.)

### Findings

- **P1 [coverage-debt] api/* layer ~0% (5 files)** — `auth.ts, deals.ts, notifications.ts, reminders.ts, reports.ts`. Самая дешёвая ROI: unit-тесты HTTP-обёрток с моком axios (метод+URL+body+headers + error mapping). ~30 тестов поднимут functions coverage с 67% до ~80%. **[FIX-DEFER → blocked Q.DEFER.003]** — отдельная test-suite задача.
- **P1 [coverage-debt security] RecoveryCodesCard (28%) + TwoFactorCard (28%) + ChangePasswordCard (0%)** — security-чувствительные пути; spec'ов нет, SettingsView stub'ит карты. **[FIX-DEFER → blocked Q.DEFER.003]**.
- **P2 [coverage-debt auth-views] TwoFactorView/Register/RecoveryCodes/Login** — 40-62% coverage. Существующие spec'ы покрывают только happy-path render. Расширить на error/validation branches. **[FIX-DEFER]**.
- **P2 [coverage-debt router]** `router/index.ts` 33% — guards и lazy-imports без integration tests. **[FIX-DEFER]**.
- **P3 [intentional] mock-composables** — без coverage by design (dev fixtures).

### Phase 13 итог

- P0: 0
- P1: 2 (api/* layer + security cards — оба defer Q.DEFER.003)
- P2: 2 (auth-views shallow tests + router)
- P3: 1 (intentional mock-untested)

---

## Phase 8 — Fix log

Будет заполнен по ходу выполнения.

| # | Finding ID | Commit SHA | Status | Notes |
|---|---|---|---|---|

---

## Phase 9 — Final verification

Будет заполнено перед финальным commit'ом.

### Метрики до/после

| Метрика | Phase 1/2 baseline | Phase 9 final | Delta |
|---|---|---|---|
| Pest passed | … | … | |
| Vitest passed | … | … | |
| Larastan errors above baseline | … | … | |
| ESLint errors | … | … | |
| vue-tsc errors | … | … | |
| markdownlint issues | … | … | |
| lychee broken links | … | … | |
| gitleaks findings | … | … | |
