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

> **Связано:** план `docs/superpowers/plans/2026-05-12-portal-full-audit.md`, findings `2026-05-12-portal-full-audit-findings.md`
> **Когда читать:** утром, перед началом следующей сессии Claude

## Категории

- **Q.HARD** — hard-stop из decision-tree (schema, открытые вопросы реестра, ТЗ-правки, новые packages, force-push) — нужно явное «закрываем X» / «делай Y» от заказчика
- **Q.PRODUCT** — продуктовое/дизайнерское решение
- **Q.DEFER** — реально починить, но >1 час / большой refactor / захватывает несколько модулей — предлагаемая задача следующей сессии
- **Q.INFO** — информационный пункт (например, известный flaky, обнаружено новое quirk)

## Открытые вопросы

### Q.HARD.001 — Admin role-guard frontend + backend `/api/admin/*` auth middleware

**✅ CLOSED 12.05.2026 ночь (post-audit continuation):** Выбран вариант **(A) Через Б-1**. Заказчик подтвердил, что закрытие admin role-guard — через регистрацию ООО → saas-admin SSO Yandex 360 → production middleware. До Б-1 это **documented MVP defer** — `router/index.ts:128` TODO + comments в `routes/web.php:75-87,90,93,97,105,115` остаются как есть. Не затрагивает текущий код.

**Из:** Phase 5 (UI smoke admin views ×8) + Phase 6 (cross-check router + routes).
**Severity при отказе:** P1 на production (на dev — by design).
**Контекст:**

- `app/resources/js/router/index.ts:128` явно содержит `// TODO: дополнительный role-guard на super_admin.`
- `router.beforeEach` (lines 209-228) проверяет только `meta.requiresAuth + isAuthenticated`, **нет** role-check.
- Все 8 admin views (`/admin/tenants`, `/admin/tenants/:code`, `/admin/billing`, `/admin/incidents`, `/admin/system`, `/admin/pricing-tiers`, `/admin/supplier-prices`, `/admin/impersonation`) → доступны обычному tenant-admin (verified в Phase 5 через demo seed).
- `app/routes/web.php:75-87,90,93,97,105,115` — backend endpoints `/api/admin/*` **БЕЗ middleware** (no `auth:sanctum`, no role). Комментарии: «На MVP без middleware (saas-admin auth не реализован)», «Без auth (saas-admin SSO ⏸ Б-1)», «admin_user_id параметром; production: middleware('auth:saas-admin')».
**Что нужно от заказчика:**
- (A) Подтвердить, что закрытие — через Б-1 (регистрация ООО → saas-admin SSO Yandex 360 → production middleware).
- (B) Если Б-1 длинный — добавить **временный** frontend role-guard на admin/* (`meta.requiresAdmin + auth.user.is_saas_admin`) с фейковым флагом в demo, чтобы хотя бы UI hide'ил admin views (UX-улучшение, не security — backend всё равно без auth).
- (C) Никаких изменений, оставить TODO до Б-1.
**Если ответа нет → план Б:** не трогать (это **documented MVP defer**, не surprise regression).

### Q.INFO.001 — Semgrep SAST не запущен

**✅ CLOSED 12.05.2026 ночь (bonus finding — audit miss):** Выбран вариант **(B) Semgrep в CI** — но уже реализовано! `.github/workflows/sast.yml` существует с PR #25 commit `53fb1ec` (10.05.2026, за 2 дня до audit). Конфигурация уже лучше минимального setup: `semgrep/semgrep-action@v1` с конфигами **p/php + p/javascript + p/typescript + p/secrets**, триггеры push/PR на main с path-filters (`app/app/**` / `app/resources/js/**` / `app/database/migrations/**`), SARIF upload в GitHub Security tab (`github/codeql-action/upload-sarif@v3`). Audit Phase 4 Subagent G пропустил — искал только локальный `npx semgrep` без проверки existing CI infra. Действия не требуются. **Audit gap note:** в `findings.md` отдельно зафиксировать «Phase 4 SAST coverage check should include `ls .github/workflows/` first».

**Из:** Phase 4 Subagent G.
**Severity при отказе:** P2 (security tooling coverage gap, не блокер).
**Контекст:** Semgrep — Python пакет, не npm; `npx semgrep --config=...` падает с «could not determine executable». Локальная установка через `pip install semgrep` blocked Pravila §13.10 (запрет на новые packages в session). gitleaks полная история — clean (333 commits scanned).
**Что нужно от заказчика:**

- (A) `pip install semgrep` локально на dev-машине (один раз; не affects production deps).
- (B) Добавить Semgrep в CI (GitHub Actions ubuntu-latest job `pip install semgrep && semgrep --config=p/php --config=p/javascript ...`).
- (C) Оставить без Semgrep (rely на manual review + Roave/SecurityAdvisories + gitleaks).
**Если ответа нет → план Б:** B (CI job) — наиболее устойчивое решение, рекомендую.

### Q.INFO.002 — schema metric drift в CLAUDE.md (dev vs prod)

**✅ CLOSED 12.05.2026 ночь (post-audit continuation):** Выбран вариант **(B) Оставить двойную разбивку** (различать prod-target vs dev-actual). Это уже реализовано в CLAUDE.md v1.88 §0/§2/§6/§8: «commit baseline v8.19» (62/12/117/39/5/13/5) + «dev-actual factual» (75/102/289/39/5/19/0). Confirmed формат — никаких правок не требуется.

**Из:** Phase 3 + Phase 6.
**Severity при отказе:** P2 (doc accuracy).
**Контекст:** CLAUDE.md §0/§2/§6/§8 заявляют v8.19 = 62 tables / 12 partitions / 117 indexes / 39 RLS / 5 funcs / 13 triggers / 5 roles. Фактически на dev `liderra` через `migrate:fresh` + `partitions:create-months`:

- 75 root tables (+13)
- 102 partition children (+90 — accumulate by cron)
- 289 indexes (+172)
- 39 RLS ✅
- 5 user functions ✅
- 19 triggers (+6)
- 0 DB roles (by design на dev)
**Что нужно от заказчика:**
- (A) Обновить CLAUDE.md метрики на фактические dev цифры (`/claude-md-management:claude-md-improver`).
- (B) Различать prod-target vs dev-actual: схема **на prod** будет иметь меньше partition children + меньше indexes (свежий deploy), но больше DB roles (5). Указать оба числа в §0 / §2 / §6 / §8.
- (C) Не трогать (метрика-snapshot из v8.19 commit точна для статичного схема-моменти).
**Если ответа нет → план Б:** A (auto-update в Phase 8) с пометкой «(dev текущий)».

### Q.HARD.002 — schema.sql 6284-line pgFormatter diff

**✅ CLOSED 12.05.2026 ночь (post-audit continuation):** Выбран **План Б — Не трогать**. Стиль schema.sql ручной, разработчик-friendly; 6284-line cosmetic diff в next PR review создал бы noise без semantic SQL changes. Defer на будущий refactor sprint. Никаких правок не требуется.

**Из:** Phase 1D Subagent D.
**Severity при отказе:** P3 (cosmetic).
**Контекст:** `pg_format db/schema.sql` производит 6284-line diff vs текущий ручной style. Первый hunk `1,725c1,706` reformat комментариев. Style-only, нет semantic SQL changes. Package.json convention «review diff before replacing source». NB: метрика-snapshot из v8.19 commit точна для статичного схема-моменты.
**Что нужно от заказчика:** разрешить ли `pg_format -o db/schema.sql.formatted` + manual swap? Это вызовет **большой** noise diff в next PR review.
**Если ответа нет → план Б:** **НЕ трогать** (стиль schema.sql ручной, defer на будущий refactor).

### Q.DEFER.002 — ✅ CLOSED 12.05.2026 ночь (a11y contrast violations) + ✅ CLOSED 12.05.2026 ночь sub-B (16 auth-views manual Pa11y)

**12.05.2026 ночь sub-B closure (post-audit continuation):** User chose (B) Ручной audit-pass. Playwright MCP login → axe-core CDN inject (https://cdn.jsdelivr.net/npm/axe-core@4.10.0/axe.min.js) → 16 auth-required views scanned. Found ≈13 unique violation patterns. **12 patterns FIXED** в коммите:

1. AdminLayout sidebar palette (8 admin views): `<v-navigation-drawer color="secondary" theme="dark">` resolved Teal-mid `#54b6b2` (Vuetify default dark theme's secondary) вместо liderraForest `#012019` теало-нуар. Fix: `color="#012019"` direct hex (теало-нуар preserved). `.brand-sub` red `#b94837` → `#e06155` (3.41 → 5.08). `.nav-count` gray `#7a8c87` → `#8a9c95` (4.26 → 5.34). Plus `role="navigation"` + `aria-label="Админ навигация"` on `<v-list nav>` fixes aria-required-children critical violation (`<v-list>` had children `[role=link]` — недопустимы для role="list").
2. `.runway-bar` (DashboardBalance.vue): добавлен `role="img"` для aria-prohibited-attr fix.
3. `.delta-up` (DashboardKpiRow.vue): `#2e8b57` → `#1b6e3b` (4.27 → 6.25).
4. `.tx-amount-up` (TransactionsTable.vue): `#2e8b57` → `#1b6e3b`.
5. `.empty-hint` (RemindersList.vue): `#9a9690` → `#6b6356` (2.98 → 5.74).
6. `.kanban-board` (KanbanView.vue): добавлены `tabindex="0" role="region" aria-label="..."` для scrollable-region-focusable fix.
7. ProjectCard.vue: `.v-progress-linear` + `:aria-label="Прогресс дневной нормы: ${progressPercent}%"` (aria-progressbar-name fix). Icon menu button: `:aria-label="Меню действий проекта «${project.name}»"` (button-name fix). Bulk-select `.card-check input`: `:aria-label="Выбрать проект «${project.name}»"` (label fix).
8. `useStatusPill.ts`: `in_progress` color `#3F7C95` → `#2A5A6E` (4.07 → 6.11). Sync test: `useStatusPill.spec.ts` line 29.
9. ProjectsView.vue toolbar select-all checkbox: добавлен `aria-label="Выбрать все проекты по текущим фильтрам"`.
10. AdminTenants impersonate button: `:aria-label="Войти как клиент (impersonation) для ${item.name}"`.
11. Global app.css: `.v-messages, .v-field-label { --v-medium-emphasis-opacity: 0.7; }` (rendered ≈#595959 → 7.9:1+ vs Vuetify default ≈0.52 → #7a7a7a fail 4.29).

**Vitest verified post-fix: 79 files / 614 passed / 3 skipped / 0 failed (baseline preserved). Re-axe verified clean** на /dashboard, /kanban, /reminders, /billing, /settings, /admin/billing, /admin/tenants, /admin/supplier-prices, /projects, /admin/billing — all clean except DEV-only `.dev-index-num` chip (tree-shaked в prod, не real violation).

**3 patterns DEFERRED как новый sub-item (Q.DEFER.004 ниже):**

A. DealsTable.vue VDataTable `show-select` auto-rendered bulk-select checkboxes (6 nodes): Vuetify-internal, нет slot для row checkbox aria-label.
B. AdminSupplierPrices form inputs (9 nodes): требуется v-text-field label или aria-label на каждом из 9 полей формы.
C. Vuetify v-tooltip eager-mount artifact (aria-tooltip-name): role="tooltip" overlay создаётся pre-emptively без content. Button уже has aria-label (effective a11y OK), axe complains library-level — не реальный UX issue.

---

**Изначально:** 3 a11y violations на guest URLs + 16 auth views неверифицированы.

**12.05.2026 ночь (follow-up аудит):**

- ✅ **ErrorView** support link contrast 2.77:1 → ~12:1 (commit `fff2dff` — diagnose через Playwright + targeted CSS override `err-help__link` color `#d3dad8` на noir bg).
- ✅ **ForgotPasswordView** rate-limit alert contrast 4.18:1 → ~7.5:1 (commit `5cebe24` — diagnose computed-style + scoped CSS override `:deep(.v-alert__content)` color `#2a5a6e` через class `a11y-info-darker`).
- ✅ Pa11y re-verify final: `/login`, `/register`, `/forgot`, `/no-such-path-404` — **all 4 URLs No issues found**.

**Остаётся открытым (defer):**

- 16 auth-required views (/dashboard, /deals, /kanban, /projects, /reports, /billing, /settings, /reminders + 8 admin) **не verified** — Pa11y standalone не auth'ится без cookie injection. Q.DEFER.002 закрыт по диагностированным findings, но auth-coverage gap остаётся.
- **Что нужно от заказчика:** (A) расширить `pa11y.config.json` или добавить CI job с auth-flow / (B) ручной audit-pass следующей сессии.

### Q.DEFER.003 — ✅ FULLY CLOSED 2026-05-13 — sub-A (api/* 43 tests, 12.05.2026 night) + sub-B (security cards 18 specs) + sub-C (router 5 integration tests) all done

**Closure summary (13.05.2026 day, commits `4c6d593..f2627e4` + Task 6 docs):** 5 atomic commits + ~23 new tests across security cards (Change/Recovery/TwoFactor) and router integration scenarios. Vitest baseline 660 → 683 (+23). vue-tsc 0 errors.

**Real coverage (post-Task-5 Vitest --coverage verification, transparent reporting):**

- `ChangePasswordCard.vue`: 0% → ~100% (17-line static placeholder, single button + heading)
- `RecoveryCodesCard.vue`: Stmts 28% → **59.37%**, Branches → 28.12%, Funcs → 27.77%. Uncovered: lines 55-73/75-85 (dialog template render-functions — VDialog stubbed in unit-tests so template branches don't execute).
- `TwoFactorCard.vue`: Stmts 28% → **71.42%**, Branches → 37.14%, Funcs → 30.3%. Uncovered: lines 149-199 (setup wizard dialog templates).
- `router/index.ts`: Stmts 33% → **46.15%**, Branches → **100%** (all guard branches), Funcs → 22.22%. Funcs denominator inflated by 24+ lazy-import factories `() => import('...')` not executed in jsdom (tests stop at guard layer без mount view-components).

**Honest assessment:** Commit messages of Tasks 2/3/4 claimed «70%+ / 80%+ / 85% Funcs» — overstated. Real headline должно было быть «Stmts +31/+43/+13 pp; Branches reached 100% (router); guard logic exercised end-to-end across 4 new paths». Coverage gain meaningful but smaller than projected. Sub-A (api/* 43 tests via vi.mock client → Task 2 prior session) — covered separately в commit `95f5f94` 12.05.2026 night.

**Total Vitest baseline post-closure:** 88 files / 683 passed / 3 skipped / 0 failed. Coverage all files: Stmts 78.67% / Branches 76.21% / Funcs 70.56% / Lines 80.89%.

**Future polishing (not in scope here):**

- Factory extraction `tests/Frontend/helpers/auth-factory.ts` to dedupe AuthUser literal across 3 specs (Tasks 2-3 + router test).
- Test order pin (`describe.sequential`) or refactor to be order-independent for router tests (current code relies on `authInitialized` flag persistence + `beforeEach createPinia` reset coordinated).
- Dialog content integration tests via `stubs: { VDialog: false }` + Vuetify portal teleport handling → would push RecoveryCodes/TwoFactor coverage closer to 90%.

---

**✅ CLOSED 12.05.2026 ночь (post-audit continuation) — sub-A api/* tests:**User chose (A) api/* unit tests first (highest ROI). Created 5 new spec files (43 new tests):

- `tests/Frontend/auth-api.spec.ts` — 13 tests (login/register/me/logout/verify2FA/recoveryCode/twoFactorInit/Confirm/Disable/RegenerateRecoveryCodes/forgotPassword/resetPassword/updateNotificationPreferences)
- `tests/Frontend/deals-api.spec.ts` — 12 tests (createDeal/bulkDelete/bulkRestore/update/transition/exportCSV/exportXLSX/getDeal/listDeals×2/listManagers/listProjects)
- `tests/Frontend/notifications-api.spec.ts` — 6 tests (listNotifications×3 для unreadOnly variants/markRead/markAllRead/delete)
- `tests/Frontend/reminders-api.spec.ts` — 6 tests (listReminders×2/create/update/complete/delete)
- `tests/Frontend/reports-api.spec.ts` — 6 tests (listReportJobs×2/create/retry/cancel/delete)

Подход: `vi.mock('../../resources/js/api/client')` → mock `apiClient.{get,post,patch,delete}` + `ensureCsrfCookie`. Каждый тест проверяет: (1) correct HTTP method, (2) correct URL, (3) correct params/body (camelCase→snake_case mapping), (4) data unwrap, (5) `ensureCsrfCookie` called for mutating endpoints.

**Vitest delta: 614 → 657 passed (+43); 79 → 84 files (+5). 0 failed.** Coverage api/* layer bumped from ≈0% Stmts на функциях (auth/deals/notifications/reminders/reports) к высокому покрытию (каждая exported функция exercised).

**Sub-B (security cards 28%) + sub-C (router guards 33%) remain DEFERRED:** не критичны для Q.DEFER.003 first-phase closure (sub-A api/* — highest ROI per blocked.md рекомендации). Если потом сессия требует — отдельные Q-items в новой сессии.

**Из:** Phase 13 Vitest coverage report (subagent fresh run 2026-05-12 ночь).
**Severity при отказе:** P1 long-term maintainability + security regression risk.
**Контекст:**

- **`resources/js/api/*` layer ≈0% Stmts:** `auth.ts` 0% / `deals.ts` 0% / `notifications.ts` 0% / `reminders.ts` 7.69% / `reports.ts` (zero в отчёте) / `client.ts` 29% / `admin.ts` 11.53%. Causes: views мокают axios напрямую через `vi.mock('axios')` вместо использования API-обёрток в test-paths.
- **Security UI cards 28%:** `RecoveryCodesCard.vue` 28.12% / `TwoFactorCard.vue` 28.57% / `ChangePasswordCard.vue` (zero в отчёте). SettingsView spec stub'ит карты — own-spec нет.
- **Router 33%:** `resources/js/router/index.ts` Stmts 33% / **Funcs 7%**. router.spec.ts покрывает только setup, без integration tests на guards/redirects.
- **Total:** Stmts 75% / Branch 75% / Funcs 67% / Lines 77%.

**Что нужно от заказчика:**

- (A) Запланировать test-add sprint (приоритет: api/* unit-тесты с мок axios → +13pp Funcs cheapest).
- (B) Точечный addition: own-spec для каждой security карточки (RecoveryCodesCard + TwoFactorCard + ChangePasswordCard) — ~30 тестов.
- (C) Add integration tests для router guards (login → /dashboard redirect, /admin role-guard когда Б-1 закрыт).
- (D) Defer до post-MVP / pre-prod stabilization sprint.

**Если ответа нет → план Б:** (A) первый — api/* unit-тесты самая high-ROI. Расчёт ~30 тестов × ~5 мин/тест = 2.5 часа frontend-only work.

### Q.PRODUCT.002 — Mock-data в production bundle (Phase 12 extra)

**✅ CLOSED 12.05.2026 ночь (post-audit continuation):** Выбран вариант **(B) Прод-fallback**. Заказчик подтвердил: mock-data в `mockDeals.ts` (178 lines) + `mockAdmin.ts` (216 lines) — это **prod-fallback**, показывается реальным пользователям при first-load до подключения реальных API. Оставить как есть (~7kB gzip overhead приемлем). Когда реальные API подключатся (Plan 6+), mock-data можно будет gate за `import.meta.env.DEV` отдельным решением.

**Из:** Phase 12 bundle analyzer R3 finding.
**Severity при отказе:** P3 bundle-size + дезинформация в prod.
**Контекст:** `resources/js/composables/mockDeals.ts` (178 lines, `MOCK_DEALS` array) и `mockAdmin.ts` (216 lines, `MOCK_*`) **ship в production bundle** (3.17 kB + 3.27 kB gzip каждый). Используются статически KanbanView, DealsView, NewDealDialog, admin views.

**Вопрос:** Mock-data — это (X) **dev-fixtures** (intentional empty-state placeholder при отсутствии API-данных) или (Y) **prod-fallback** (показывается реальным пользователям при first-load перед fetch)? Если (X) — gate за `import.meta.env.DEV` (mirroring `DevIndexOverlay` pattern в AppShell.vue:22-24); если (Y) — оставить.

**Что нужно от заказчика:**

- (A) Confirm mock-data = dev-only → migrate за `import.meta.env.DEV` gate.
- (B) Mock-data = prod-fallback → оставить (запланировано до подключения реальных API).
- (C) Defer review post-MVP.

**Если ответа нет → план Б:** (C) defer — не критично, ~7 kB gzip.

### Q.DEFER.001 — Memory `reference_archive.md` forward-stale description

**✅ CLOSED 12.05.2026 ночь (post-audit continuation):** Выбран вариант **(A) Понизить memory до факта**. Применено 5 description-edits в memory/: MEMORY.md (5 lines: feedback_plugin_paired_stack, project_state, reference_archive, reference_github, project_full_audit) + 4 file frontmatter `description` fields. Содержание: описывают актуальное состояние **`plan5-frontend-projects` branch** (workspace) с явным notice про divergence с `origin/main` `615db99` R15 motion-runtime removal merge (PSR_v1 v1.7→v2.0, Pravila v1.10→v1.11, Tooling v1.15→v1.16). **Bonus finding:** CLAUDE.md §6 содержит factual error — «Plan 4 MERGED `615db99` post-merge after f4ec5dc PSR_v1 R15 removal» неверно: `615db99` это R15 removal commit (chore(rules): remove R15 motion-runtime restrictions), `f4ec5dc` это sidebar position:fixed hotfix; Plan 4 commits были раньше. Fix через `/claude-md-management:claude-md-improver` — out of Q.DEFER.001 scope, flagged separately.

**Из:** Phase 6.
**Severity при отказе:** P3 (memory accuracy).
**Контекст:** `memory/reference_archive.md` description (line 3) предсказывает версии файлов, которые ещё не существуют (CLAUDE.md v1.88 vs факт v1.87, Pravila v1.11 vs v1.10, PSR_v1 v2.0 vs v1.7, Tooling v1.16 vs v1.15, реестр v1.78 vs v1.73). Memory был обновлён авансом без bumping файлов.
**Что нужно от заказчика:** разрешить ли (A) понизить memory description до фактических версий ИЛИ (B) повысить файлы до versions из memory description (bump CLAUDE.md v1.87→v1.88, Pravila v1.10→v1.11, etc) — но это требует обоснованных правок, не пустой bump.
**Если ответа нет → план Б:** **A** (понизить memory) — безопаснее. Если потом окажется что в Quiet Luxury сессии 12.05.2026 были реальные изменения которые надо вынести в bump — заказчик скажет.

### Q.DEFER.004 — ✅ CLOSED 2026-05-13 — A+B fixed via slots/aria-label, C documented as Vuetify-internal known limitation

**Closure summary (13.05.2026 day session):** sub-A + sub-B fixed (commits `d9fc3d9` + `c8005e0`). sub-C — Vuetify v-tooltip eager-mount artifact задокументирован как **known library-level limitation**, не application defect (button уже имеет explicit `aria-label`, effective a11y покрытие OK; axe-core complains только о Vuetify-internal `<div role="tooltip">` pre-emptively созданном без content).

**Sub-A details:** DealsTable.vue — Vuetify 3.12 typed slots `header.data-table-select` + `item.data-table-select` override; 6 bulk-checkboxes теперь имеют aria-label «Выбрать все сделки» + «Выбрать сделку «`<name>`»». Test coverage: tests/Frontend/DealsTable.spec.ts (2 specs).

**Sub-B details:** AdminSupplierPricesView.vue — `:aria-label` binding на 3× v-text-field (cost_rub) + 3× v-text-field (quality_score) + 3× v-switch (is_active) = 9 elements. Pattern: «`<Field>` для `<supplier.name>`» (e.g. «Cost (₽) для B1 — Сайты и Звонки»). Test coverage: AdminSupplierPricesView.spec.ts 4-й spec.

**Sub-C status:** Vuetify v-tooltip создаёт `<div role="tooltip" id="v-tooltip-v-N">` pre-emptively (до показа), без content. axe-core reports aria-tooltip-name serious violation. **Application-level mitigation уже на месте:** activator button (TenantsTable.vue:89) имеет `:aria-label="Войти как клиент (impersonation) для ${item.name}"`. Workaround «убрать v-tooltip и использовать HTML title» — теряем Vuetify styling + теряем tooltip activator behavior; не оправдано ради cosmetic violation. **Upstream fix tracking:** оставить как known issue до Vuetify 3.13+ (если содержит fix), либо до polish-sprint post-MVP.

**Verification 2026-05-13 (Playwright + axe-core 4.10 CDN inject, dev mode, hard-reloaded):**

- **`/deals` (sub-A post-fix):** ✅ **0 `label` violations.** 6 bulk-checkbox elements (`#input-v-41..48`) корректно получают aria-label через `<v-checkbox-btn :aria-label="...">` — Vuetify VSelectionControl.js:163 forward'ит `aria-label` от $attrs (через `inputAttrs` после `filterInputAttrs`) на native input. Verified: header «Выбрать все сделки», row «Выбрать сделку «Иван Петров»» / «Мария Кузнецова» / «Дмитрий Соколов» / «Елена Морозова» (×2). Only residual violation: 1 `region` moderate (не related to Q.DEFER.004).
- **`/admin/supplier-prices` (sub-B post-fix):** ✅ **0 `label` violations.** 9 form-control elements (6× v-text-field `#input-v-23/26/33/36/43/46` + 3× v-switch `#switch-v-28/38/48`) имеют correct aria-label: «Cost (₽) / Quality / Active для B1 — Сайты и Звонки», «B2 — СМС с ключевым словом», «B3 — СМС по наименованию». Все 9 значений присутствуют на native input. Residual violations not Q.DEFER.004 scope: 1 `color-contrast` (separate finding), 1 `empty-table-header` minor, 1 `region` moderate.
- **`/admin/tenants` (sub-C):** ✅ Подтверждено как documented: 1 `aria-tooltip-name` violation serious на Vuetify-internal `<div role="tooltip" id="v-tooltip-v-59">` (pre-emptively mounted overlay) — button activator имеет own `aria-label` (application-level a11y OK). 0 `label` violations.

**Initial Task 4 false-alarm note (audit transparency):** первичная Task 4 verification (subagent 13.05.2026, commit `e79fe95`) reported критические failures с пометкой «Vuetify silently drops aria-label». Re-verification после hard-reload + 500ms render-wait показала **противоположное** — фиксы работают как задумано. Причина первичной ошибки — наиболее вероятно: (1) HMR partial update без full template reload, (2) axe-core scan race-condition с Vue reactive render (не было `setTimeout` delay перед `axe.run()`), (3) кешированные Vite chunks без свежих aria-label binding'ов. Quirk worth memory entry — в Vue+Vuetify+HMR-based dev mode required `await new Promise((r) => setTimeout(r, 500))` перед axe scan, иначе scan может видеть stale render.

**Verdict:** Q.DEFER.004 sub-A + sub-B **закрыты честно**. sub-C — Vuetify-internal library limitation, остаётся documented (не application defect, button activator имеет explicit aria-label).

---

(оригинальное содержание Q.DEFER.004 ниже — оставить как historical context)

**Из:** Q.DEFER.002 sub-B audit-pass 12.05.2026 ночь.
**Severity при отказе:** P1 (a11y coverage gap; критичные `label` violations требуют explicit aria-label на каждом input).
**Контекст:** 3 паттерна остались после batch-фикса 12 patterns. Каждый требует или Vuetify-deep workaround или массовых правок:

A. **DealsTable.vue VDataTable `show-select` bulk-checkboxes (6 nodes, critical label):** Vuetify авто-генерирует checkbox per row + select-all header без labels. Нет prop для aria-label на этих checkbox'ах в Vuetify 3.12 API. Workaround: использовать `item.data-table-select` slot с custom render + aria-label per row, или `headers.data-table-select` slot для select-all. ~30 мин рефактор. Аналогично может быть applicable на /admin/tenants details + другие VDataTable usages с show-select.

B. **AdminSupplierPrices form inputs (9 nodes, critical label):** `#input-v-23, #input-v-26, #switch-v-28, #input-v-33, #input-v-36` etc. — Vuetify form fields без explicit label prop или aria-label. Investigate file: `app/resources/js/views/admin/AdminSupplierPricesView.vue` + связанные components. ~30 мин (add label prop к каждому v-text-field/v-switch).

C. **Vuetify v-tooltip eager-mount artifact (1 node, serious aria-tooltip-name):** `<div role="tooltip" id="v-tooltip-v-N">` создаётся pre-emptively Vuetify до показа. axe expects accessible name на role="tooltip" но content lazy-rendered. Mitigation: button уже has `aria-label` → effective a11y покрытие OK. Workaround: либо remove v-tooltip и use HTML `title` attribute (loses Vuetify styling), либо upstream Vuetify fix. Library-level issue, не application defect.

**Что нужно от заказчика:**

- (A) Plan для следующей сессии: implement A + B (~1h work, deals + supplier-prices labels via Vuetify slots/props).
- (B) Defer всё до post-MVP a11y polish sprint.
- (C) Только A (DealsTable — high-visibility) + skip B (admin-only) + skip C (cosmetic Vuetify-level).

**Если ответа нет → план Б:** (A) — но не блокирующий. Total residual: 16 nodes (6 deals checkbox + 9 supplier-prices + 1 tooltip) из ≈90 nodes pre-fix.

---

## Шаблон вопроса

```
### Q.<CAT>.NNN — короткий заголовок

**Из:** Phase X / file:line / finding-id
**Severity при отказе:** P0/P1/P2/P3
**Контекст:** что обнаружено, что сделано, почему остановился
**Что нужно от заказчика:** конкретный выбор (A/B/C или «делай Y»)
**Если ответа нет → план Б:** ...
```
