# Audit #3 Deferred Fixes Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Close all 25 deferred findings from Portal Full Audit #3 ([report](../audits/2026-05-14-portal-full-audit-report.md)) — 1 P1 + 11 P2 + 14 P3 — without regressions.

**Architecture:** Tasks 1→2→3→4 sequentially (each isolates the surface it modifies). Inside Task 3 — 3 parallel subagent dispatches (one per coverage target — fully isolated). Atomic commit per logical change. Final regression suite (Pest --parallel + Vitest + Vite build + gitleaks + lychee) gates push.

**Tech Stack:** Laravel 13.7 + Vue 3.5 + Vuetify 3.12 + Vitest 4.1 + @vue/test-utils + Pest 4.7 + pa11y-ci 4.1 + Playwright MCP + Lefthook + GitHub Actions

**Branch:** work directly on `main` (current 🟢 GREEN baseline `f9d2452`); each commit small + atomic; push at the very end after Phase 14-style regression check.

**Out of scope:** Sentry SDK install (blocked by Б-1), advisory TODO cleanup at Б-1 closure, F-BUN-02/03 Vuetify-structural (carryforward, no fix path until upstream change), F-COV-04..07 P3 (acknowledged as observational — `router/index.ts` 24+ lazy factories inflate denominator, no real fix; Vuetify plugin needs visual tests).

---

## Task 1 — P1 Pa11y scope migration to live Vue app

**Why:** Pa11y config указывает на `liderra_v8_handoff/concepts/v8_*.html` (handoff prototypes), не на live Vue. Audit #2 baseline «0 errors» был неточен — реальный портал НЕ покрыт. Сегодня axe-core via Playwright показал 0 production a11y issues, но Pa11y нужно перевести на актуальную мишень для CI safety net.

**Files:**

- Create: `app/.github/workflows/a11y.yml`
- Create: `pa11y-handoff.config.json` (historical handoff baseline, opt-in via `a11y:handoff`)
- Modify: `pa11y.config.json` (live Vue app URLs)
- Modify: `package.json` (add `a11y:handoff` script; ensure `a11y` script unchanged invocation but new target)
- Reference: `app/routes/web.php` (SPA routes catalogue: 26 `Route::view` + fallback)

### Step 1.1 — Read current handoff baseline & freeze as historical config

- [ ] Read `pa11y.config.json` to capture exact handoff URL list (3 entries: v8_login.html, v8_dashboard.html, v8_deals.html).
- [ ] Copy that content (with same `defaults` block) to a new file `pa11y-handoff.config.json`.

```bash
cp pa11y.config.json pa11y-handoff.config.json
```

Expected: `pa11y-handoff.config.json` exists, contains 3 handoff URLs verbatim.

### Step 1.2 — Rewrite `pa11y.config.json` to target live Vue app (guest pages — MVP scope)

- [ ] Replace `pa11y.config.json` contents with the live-app config below. **Authenticated pages deferred to Step 1.7** (require Pa11y `actions` login flow — adds complexity; guest pages first).

```json
{
  "defaults": {
    "standard": "WCAG2AA",
    "timeout": 30000,
    "wait": 1500,
    "hideElements": ".js-skip-a11y, [data-a11y-skip], .dev-index-badge, .dev-index-num, .v-overlay-container",
    "ignore": [
      "warning",
      "notice"
    ],
    "chromeLaunchConfig": {
      "args": [
        "--no-sandbox",
        "--disable-dev-shm-usage"
      ]
    },
    "viewport": {
      "width": 1440,
      "height": 900
    }
  },
  "urls": [
    {
      "url": "http://localhost:8000/login",
      "screenCapture": "./bin/a11y-screenshots/live-01-login.png"
    },
    {
      "url": "http://localhost:8000/register",
      "screenCapture": "./bin/a11y-screenshots/live-02-register.png"
    },
    {
      "url": "http://localhost:8000/forgot",
      "screenCapture": "./bin/a11y-screenshots/live-03-forgot.png"
    },
    {
      "url": "http://localhost:8000/2fa",
      "screenCapture": "./bin/a11y-screenshots/live-04-twofactor.png"
    },
    {
      "url": "http://localhost:8000/recovery",
      "screenCapture": "./bin/a11y-screenshots/live-05-recovery.png"
    },
    {
      "url": "http://localhost:8000/403",
      "screenCapture": "./bin/a11y-screenshots/live-06-403.png"
    },
    {
      "url": "http://localhost:8000/500",
      "screenCapture": "./bin/a11y-screenshots/live-07-500.png"
    }
  ]
}
```

**Rationale on `hideElements`:** scoping out (a) `.dev-index-badge` + `.dev-index-num` — TEMPORARY DevIndexBadge feature (`project_dev_indices.md` — заказчик «уберём в конечном релизе»); (b) `.v-overlay-container` — Vuetify portal container is empty when overlays closed but axe-core/Pa11y flags it as missing region landmark — known Vuetify structural pattern.

### Step 1.3 — Add `a11y:handoff` script + ensure `a11y` invokes new config

- [ ] Modify `package.json` — add new script + keep existing `a11y` invocation unchanged (it already points to `pa11y.config.json` which we just repointed).

Apply edit (in `package.json` `scripts` block, insert after current `a11y` line):

```jsonc
"a11y": "pa11y-ci --config pa11y.config.json",
"a11y:handoff": "pa11y-ci --config pa11y-handoff.config.json",
```

### Step 1.4 — Start dev-server, run live baseline locally

- [ ] In one terminal, start dev-server:

```bash
cd app
php artisan serve --port=8000
```

- [ ] In second terminal, ensure tenant DB is seeded (DemoSeeder will be auto-run after Task 2.4, for now run manually if Step 1 executes first):

```bash
cd app
php artisan migrate:fresh --seed
php artisan db:seed --class=DemoSeeder
```

- [ ] In third terminal, run Pa11y:

```bash
npm run a11y
```

Expected: 7 URLs scanned. Capture exit code + per-URL violation count.

### Step 1.5 — Resolve violations: either fix code OR document explicit ignore

- [ ] If `color-contrast` violations on auth pages → fix in `app/resources/js/layouts/AuthLayout.vue` or specific view CSS (likely none expected; brand palette OKLCH-tested).
- [ ] If structural violations (landmarks missing on guest pages) → fix in respective `views/auth/*View.vue` (add `<main>` / `<header>` semantics if missing).
- [ ] Any remaining unfixable (Vuetify structural) — add to `pa11y.config.json` per-URL `ignore` array, with comment in JSON ref-document or inline if format allows. Note: JSON doesn't allow comments — track ignored selectors in `docs/audit-baseline-pa11y.md` (Step 1.6).

### Step 1.6 — Document baseline + ignore decisions

- [ ] Create `docs/audit-baseline-pa11y.md` capturing:
  - Date of first live baseline
  - Per-URL violation summary at baseline
  - Per-violation: status (fixed / ignored-permanent / ignored-temp DevIndexBadge / ignored-structural Vuetify) + reference

```markdown
# Pa11y Live Baseline — YYYY-MM-DD

> First live-Vue baseline after Pa11y scope migration (Audit #3 P1 closure).

## URLs scanned

| URL | Violations baseline | Status |
|---|---|---|
| /login | 0 | ✅ clean |
| /register | 0 | ✅ clean |
| ... | | |

## Ignored selectors (rationale)

| Selector | Why ignored |
|---|---|
| `.dev-index-badge`, `.dev-index-num` | TEMPORARY DevIndexBadge feature; removed at final release per `project_dev_indices.md` |
| `.v-overlay-container` | Vuetify portal container — empty when overlays closed; structural pattern, not a real a11y issue |
```

### Step 1.7 — Add CI workflow (`.github/workflows/a11y.yml`)

- [ ] Create `app/.github/workflows/a11y.yml` to run Pa11y in CI with dev-server lifecycle:

```yaml
name: Accessibility (Pa11y live)

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  a11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install JS deps
        run: npm ci

      - name: Install PHP deps
        working-directory: app
        run: composer install --no-progress --no-interaction --prefer-dist

      - name: Setup database
        working-directory: app
        run: |
          cp .env.example .env
          php artisan key:generate
          php artisan migrate:fresh --seed
          php artisan db:seed --class=DemoSeeder

      - name: Start dev-server
        working-directory: app
        run: php artisan serve --port=8000 &

      - name: Wait for dev-server
        run: npx wait-on http://localhost:8000

      - name: Run Pa11y
        run: npm run a11y
```

**Note:** `wait-on` may not be installed; add to `devDependencies` if needed. Alternatively, busy-loop with curl in CI script.

### Step 1.8 — Verify CI workflow locally (act if available, otherwise visual review only)

- [ ] `git status` shows: pa11y.config.json modified, pa11y-handoff.config.json new, package.json modified, docs/audit-baseline-pa11y.md new, app/.github/workflows/a11y.yml new.
- [ ] Run final Pa11y locally: `npm run a11y` → exit 0.
- [ ] Run historical handoff: `npm run a11y:handoff` → still scans 3 handoff URLs (unchanged baseline — 10 contrast violations).

### Step 1.9 — Commit

```bash
git add pa11y.config.json pa11y-handoff.config.json package.json \
        docs/audit-baseline-pa11y.md app/.github/workflows/a11y.yml
git commit -m "$(cat <<'EOF'
feat(a11y): migrate Pa11y scope from handoff prototypes to live Vue app

Closes F-A11Y-PA11Y-SCOPE-01 (Audit #3 sole P1).

- pa11y.config.json now targets http://localhost:8000/<route> for 7 guest pages
  (login, register, forgot, 2fa, recovery, 403, 500)
- pa11y-handoff.config.json preserves historical 3-URL handoff baseline
  (opt-in via `npm run a11y:handoff`)
- DevIndexBadge + Vuetify .v-overlay-container hidden globally via
  hideElements (TEMPORARY feature + structural Vuetify pattern)
- New `.github/workflows/a11y.yml` runs Pa11y in CI with dev-server lifecycle
  (php artisan serve + wait-on http://localhost:8000)
- New docs/audit-baseline-pa11y.md captures first live baseline + ignore rationale

Related: Audit #3 report, findings.md F-A11Y-PA11Y-SCOPE-01.
EOF
)"
```

---

## Task 2 — P2 dead code + dev hygiene batch

**Why:** 5 documented P2 deferred items — все низкорисковые, удаление/добавление без изменения публичного API.

**Files:**

- Delete: `app/resources/js/views/admin/AdminPlaceholderView.vue`
- Modify: `app/package.json` (remove `concurrently`)
- Modify: `app/resources/js/api/admin.ts` (remove 4 unused exported types)
- Modify: `app/resources/js/api/notifications.ts` (remove 1 unused type)
- Modify: `app/resources/js/api/reports.ts` (remove 5 unused types)
- Modify: `app/resources/js/composables/mockBilling.ts` (remove 1 unused type)
- Modify: `app/resources/js/composables/useStatusPill.ts` (remove 1 unused type)
- Modify: `app/database/seeders/DatabaseSeeder.php` (env-conditional `DemoSeeder::class`)
- Modify: `db/schema.sql` (header line 4: «62 базовые таблицы» → «63 базовые таблицы + 12 партиций»)

### Step 2.1 — Confirm AdminPlaceholderView is truly unreferenced + delete

- [ ] Grep one more time before delete (paranoia):

```bash
cd app
git grep -n "AdminPlaceholderView"
```

Expected: matches only inside `AdminPlaceholderView.vue` itself (no router/import references).

- [ ] Delete the file:

```bash
git rm app/resources/js/views/admin/AdminPlaceholderView.vue
```

### Step 2.2 — Remove `concurrently` from app/package.json

- [ ] Verify in `app/package.json` line ~27 (devDependencies block) that `concurrently@^9.0.1` is present.
- [ ] Verify no script invokes `concurrently`:

```bash
grep -n concurrently app/package.json
```

Expected: only the `devDependencies` entry, no scripts.

- [ ] Uninstall:

```bash
cd app
npm uninstall concurrently
```

Expected: package.json + package-lock.json updated; `concurrently` gone from `node_modules`.

### Step 2.3 — Remove 12 unused exported types

For each file, locate the named export (search by `export type <Name>` or `export interface <Name>`) and delete that block. If the type is part of a barrel or used in a re-export, also remove the re-export.

- [ ] **`app/resources/js/api/admin.ts`**: remove `AdminTenantsStats`, `ApiTenantMetrics`, `ApiAdminBillingSummary`, `ApiAdminIncidentsSummary`.
- [ ] **`app/resources/js/api/notifications.ts`**: remove `NotificationEvent`.
- [ ] **`app/resources/js/api/reports.ts`**: remove `ApiReportType`, `ApiReportFormat`, `ApiReportParameters`, `ReportCounts`, `ReportQuota`.
- [ ] **`app/resources/js/composables/mockBilling.ts`**: remove `TxType`.
- [ ] **`app/resources/js/composables/useStatusPill.ts`**: remove `StatusPillSlug`.

After each file's edit, run:

```bash
cd app
npx vue-tsc --noEmit
```

Expected: 0 type errors. If errors appear (a removed type is actually used internally), restore the type and document in finding.

### Step 2.4 — Make `DemoSeeder` auto-runnable but only in non-production

- [ ] Modify `app/database/seeders/DatabaseSeeder.php`:

```php
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    use WithoutModelEvents;

    /**
     * Seed the application's database.
     *
     * PricingTierSeeder runs in all environments (production needs the 7-tier
     * config bootstrapped). DemoSeeder runs only in local/testing — populates
     * a demo tenant + admin user + 3 projects + ~14 demo deals for dev/UI smoke.
     */
    public function run(): void
    {
        $this->call(PricingTierSeeder::class);

        if (app()->environment('local', 'testing')) {
            $this->call(DemoSeeder::class);
        }
    }
}
```

- [ ] Verify locally:

```bash
cd app
php artisan migrate:fresh --seed
php artisan tinker --execute='echo \App\Models\User::count();'
```

Expected: User count ≥ 1 (DemoSeeder created admin@demo.local).

### Step 2.5 — Fix schema.sql header drift (62 → 63 base tables)

- [ ] Read `db/schema.sql` line 4 — current text:
  > `-- Метрики: 62 базовые таблицы + 12 партиций / 117 индексов / 39 RLS-политик / 5 функций / 13 триггеров`
- [ ] Replace with:
  > `-- Метрики: 63 базовые таблицы (61 regular + 2 partitioned parents: deals + supplier_lead_costs) + 12 партиций / 117 индексов / 39 RLS-политик / 5 функций / 13 триггеров`

Apply via Edit tool, exact `old_string` → `new_string`.

### Step 2.6 — Also reflect drift in CLAUDE.md §2 + §0 + §8 baseline numbers

- [ ] Update `CLAUDE.md` references to «62 базовые таблицы» (3 occurrences — §0 «Источник истины» row «Схема БД», §2 «Стек проекта» row «БД», §8 «Self-review триггеры» row `db/schema.sql`).

**NB on CLAUDE.md edit channel:** per `CLAUDE.md §5 п.10`, CLAUDE.md modifications go ONLY through `claude-md-management` plugin. **This step is invoked separately** — see Step 2.8.

### Step 2.7 — Verify nothing broken

- [ ] Run from `app/`:

```bash
cd app
npx vue-tsc --noEmit
npx eslint resources/js/**/*.{ts,vue}
npx vite build
./vendor/bin/pest --parallel
```

Expected: all green. Vitest will be run in Final verification (Task end).

### Step 2.8 — Commit (code part)

```bash
git add app/resources/js/views/admin/AdminPlaceholderView.vue \
        app/package.json app/package-lock.json \
        app/resources/js/api/admin.ts \
        app/resources/js/api/notifications.ts \
        app/resources/js/api/reports.ts \
        app/resources/js/composables/mockBilling.ts \
        app/resources/js/composables/useStatusPill.ts \
        app/database/seeders/DatabaseSeeder.php \
        db/schema.sql
git commit -m "$(cat <<'EOF'
chore(cleanup): dead code removal + DemoSeeder auto-run + schema header drift

Closes Audit #3 P2 batch (knip dead exports/components, DemoSeeder hygiene,
schema header drift).

- Remove AdminPlaceholderView.vue (unreferenced placeholder view)
- Uninstall concurrently devDep (no scripts invoked it)
- Remove 12 unused exported types in api/{admin,notifications,reports}.ts
  and composables/{mockBilling,useStatusPill}.ts
- DatabaseSeeder::run() now calls DemoSeeder in local+testing envs
  (dev `migrate:fresh --seed` produces demo tenant + admin@demo.local)
- schema.sql header: «62 базовые таблицы» → «63 (61 regular + 2 partitioned)»

Verification: vue-tsc 0 / ESLint 0 / Vite build OK / Pest --parallel green.
EOF
)"
```

### Step 2.9 — CLAUDE.md update via plugin

Invoke `/claude-md-management:claude-md-improver` skill to update `CLAUDE.md` references «62 базовые таблицы» → «63 базовые таблицы (61 regular + 2 partitioned parents)» in §0 / §2 / §8. Skill produces separate commit per its conventions.

---

## Task 3 — P2 coverage debt (3 files, parallel subagents)

**Why:** Carryforward F-COV-01/02/03 from Audit #2 — three files with low coverage represent regression risk if changed without tests.

**Parallelism:** Each file is fully isolated (different test file, different source file). Dispatch 3 subagents in parallel via `superpowers:dispatching-parallel-agents`.

**Files (per subagent):**

| Subagent | Source file | Test file | Target coverage |
|---|---|---|---|
| A | `app/resources/js/components/reminders/ReminderDialog.vue` | `app/tests/Frontend/components/reminders/ReminderDialog.spec.ts` (new) | Stmts ≥ 60% |
| B | `app/resources/js/layouts/AdminLayout.vue` | `app/tests/Frontend/layouts/AdminLayout.spec.ts` (new) | Stmts ≥ 60% |
| C | `app/resources/js/api/admin.ts` | `app/tests/Frontend/api/admin.spec.ts` (extend existing or create) | Stmts ≥ 80% |

### Step 3.1 — Pre-flight per subagent: read source + existing tests

Before launching subagents, **the orchestrator reads** the 3 source files + checks for existing test scaffolds. This enables targeted briefing per subagent.

- [ ] Read `app/resources/js/components/reminders/ReminderDialog.vue`
- [ ] Read `app/resources/js/layouts/AdminLayout.vue`
- [ ] Read `app/resources/js/api/admin.ts`
- [ ] Check existing tests: `app/tests/Frontend/components/reminders/`, `app/tests/Frontend/layouts/`, `app/tests/Frontend/api/`

### Step 3.2 — Dispatch 3 parallel subagents (each writes TDD-style)

Each subagent runs `superpowers:test-driven-development` skill. Prompts include:

- Exact source file path + content excerpt
- Existing test patterns to mirror (from co-located neighbour tests)
- Coverage target
- Vitest + @vue/test-utils API (Vuetify mounting helper from `app/tests/Frontend/setup.ts`)

**Subagent A briefing template:**

```
Source: app/resources/js/components/reminders/ReminderDialog.vue (full content)
Test: NEW app/tests/Frontend/components/reminders/ReminderDialog.spec.ts
Target: Stmts ≥ 60% (currently 0%)
Approach: TDD. Mount with vuetify stubs, verify dialog open/close, form submit,
  validation states, emit('save')/emit('close') events. Mock store/api with vi.mock.
Pattern: mirror app/tests/Frontend/components/deals/NewDealDialog.spec.ts shape.
```

**Subagent B briefing template:**

```
Source: app/resources/js/layouts/AdminLayout.vue (full content)
Test: NEW app/tests/Frontend/layouts/AdminLayout.spec.ts
Target: Stmts ≥ 60% / Branch ≥ 40% / Funcs ≥ 40% (currently 9.09% / 0% / 0%)
Approach: TDD. Mount with vue-router stub + Pinia, verify sidebar nav renders,
  active route highlight, logo + brand link, breadcrumb container.
Pattern: mirror app/tests/Frontend/layouts/AppLayout.spec.ts.
```

**Subagent C briefing template:**

```
Source: app/resources/js/api/admin.ts (full content)
Test: app/tests/Frontend/api/admin.spec.ts (new or extend)
Target: Stmts ≥ 80% / Funcs ≥ 80% (currently 11.53% / 27.27%)
Approach: TDD. Mock axios via vi.mock, test each exported function:
  request URL+payload, response transformer, error path (401/422/500).
Pattern: mirror app/tests/Frontend/api/deals.spec.ts shape.
```

**Each subagent is required to:**

- Write failing test first
- Run to confirm fail
- Implement (or no implementation needed — testing existing code)
- Verify pass
- Report coverage delta (`npx vitest run --coverage <path>` before/after)
- Return ONLY the new test file content + raw coverage numbers (raw output, not summary, per economy 0% rule)

### Step 3.3 — Orchestrator review of 3 subagent outputs

- [ ] Verify each test file was created with proper structure
- [ ] Run combined coverage check:

```bash
cd app
npx vitest run --coverage
```

Expected: all 3 target files at or above threshold; total Stmts ≥ 79% (was 78.64%).

### Step 3.4 — Commit (one commit per subagent output OR one combined commit; choose combined for atomicity of «coverage debt closure»)

```bash
git add app/tests/Frontend/components/reminders/ReminderDialog.spec.ts \
        app/tests/Frontend/layouts/AdminLayout.spec.ts \
        app/tests/Frontend/api/admin.spec.ts
git commit -m "$(cat <<'EOF'
test(coverage): close F-COV-01/02/03 — ReminderDialog/AdminLayout/api-admin

Closes Audit #2+#3 P2 carryforward triplet.

- ReminderDialog.vue: 0% → ≥60% Stmts (dialog/form/emit lifecycle covered)
- AdminLayout.vue: 9.09% → ≥60% Stmts (sidebar nav + active route + logo)
- api/admin.ts: 11.53% → ≥80% Stmts (8 endpoints + error paths)

Total Vitest Stmts coverage ↑ from 78.64% to <new total> (+<delta> pp).

Approach: TDD via @vue/test-utils + Vuetify stubs + vi.mock axios — no
production code changed, only test infrastructure added.
EOF
)"
```

---

## Task 4 — P3 tooling + structural mini-fixes

**Files:**

- Create: `app/.knip-config-cleanup.ts` (or modify existing `app/knip.config.ts`)
- Modify: `package.json` (add npm `overrides` for `lodash` transitive)
- Modify: `app/resources/js/views/admin/AdminTenantsView.vue` (table header)
- Modify: `app/resources/js/views/admin/AdminBillingView.vue` (if same pattern)
- Modify: `app/tests/Frontend/setup.ts` or test-specific config (axe-core rule `region` whitelist for `.v-overlay-container`)
- Decision document: `docs/superpowers/decisions/2026-05-14-pgformatter-perl-decision.md` (capture chosen path)

### Step 4.1 — pgFormatter perl carryforward — decision document only

**Decision:** keep `[FIX-DEFER]` (Q.HARD.002 from Audit #2). Installing perl on Windows dev machine is one-line `choco install strawberryperl`, but adds toolchain weight. **Action:** document explicit decision + alternative paths in `docs/superpowers/decisions/2026-05-14-pgformatter-perl-decision.md`:

- [ ] Create decision doc:

```markdown
# Decision — pgFormatter on Windows dev (Q.HARD.002)

> **Date:** 2026-05-14
> **Status:** carryforward [FIX-DEFER]
> **Audits:** raised in #2, confirmed in #3

## Context

`npm run format:sql:check` falls because `perl` isn't on the PATH of the
Windows dev VM. pgFormatter lives in `bin/pgFormatter/pg_format` (Perl
script).

## Options

1. **`choco install strawberryperl`** — adds ~150 MB toolchain; works.
2. **Replace with `sqlfluff`** — Python-based; needs Python toolchain
   (already partially present); different format opinions than pgFormatter,
   would force a re-format of `db/schema.sql` (~6284 lines).
3. **Use `pg_format` via Docker** — adds Docker dep; we don't currently use
   Docker on this VM (project_phase1_strategy.md — native Windows stack).
4. **Drop automated SQL formatting** — accept manual hygiene + squawk
   (linter) covers correctness.

## Decision

Carry forward to Б-1 closure cycle. Production SQL hygiene is covered by
squawk (lint). pgFormatter is purely cosmetic in our flow.
```

### Step 4.2 — npm overrides for lodash transitive (pa11y-ci dev-only vuln)

- [ ] Modify root `package.json` — add `overrides` block:

```jsonc
{
  ...,
  "overrides": {
    "pa11y-ci": {
      "lodash": "^4.17.21"
    }
  }
}
```

- [ ] Run `npm install` to apply override.
- [ ] Verify: `npm audit --omit=optional` — lodash entry should be gone or downgraded severity to none.

### Step 4.3 — Add table header label for actions column

- [ ] Read `app/resources/js/views/admin/AdminTenantsView.vue` — locate the VDataTable headers definition.
- [ ] Update the actions column header (8-th column) from empty title to visually hidden label:

If headers are defined as object array `{ title: '', key: 'actions', ... }`, change to:

```js
{ title: 'Действия', key: 'actions', sortable: false, headerProps: { class: 'sr-only-header' } }
```

Add CSS class:

```css
.sr-only-header .v-data-table-header__content {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
```

- [ ] Same change for `AdminBillingView.vue` actions column (if pattern present).
- [ ] Verify with axe-core (Playwright run):

```bash
cd app
npm run test:a11y -- /admin/tenants
```

Expected: 0 `empty-table-header` violations.

### Step 4.4 — axe-core test config: whitelist Vuetify `.v-overlay-container` region rule

- [ ] Find axe-core invocation in `app/tests/Frontend/` (smoke test that invokes axe). If it's a Playwright/Pest dual-runtime test, locate the run config.
- [ ] Add exclusion to axe-core options:

```js
axe.run({
  exclude: [['.v-overlay-container']],
  ...
})
```

OR set `region` rule disabled for that selector via `disableOtherRules: false` + per-rule selector.

- [ ] Verify: `npm run test:a11y` exits 0.

### Step 4.5 — Clean up knip.config.ts stale hints (P3 item)

- [ ] Read `app/knip.config.ts` — locate `ignore: ['tests/**']` and `ignoreDependencies: ['@vue/test-utils', 'jsdom', 'vitest']`.
- [ ] Remove those entries.
- [ ] Add `entry: [..., 'histoire.config.ts']` to silence histoire.setup.ts + @histoire/plugin-vue FPs.
- [ ] Re-run: `cd app && npx knip` — expected: clean output (12 dead exports already gone in Task 2, FPs silenced).

### Step 4.6 — Commit

```bash
git add docs/superpowers/decisions/2026-05-14-pgformatter-perl-decision.md \
        package.json package-lock.json \
        app/resources/js/views/admin/AdminTenantsView.vue \
        app/resources/js/views/admin/AdminBillingView.vue \
        app/tests/Frontend/setup.ts \
        app/knip.config.ts
git commit -m "$(cat <<'EOF'
chore(p3): close P3 tooling and structural mini-fixes

Closes:
- pgFormatter perl carryforward — decision doc only (Q.HARD.002 stays deferred)
- npm overrides for lodash transitive (pa11y-ci dev-only vuln)
- AdminTenantsView/AdminBillingView actions column header sr-only label
  (closes axe empty-table-header on /admin/tenants)
- axe-core test config whitelists .v-overlay-container region rule
  (Vuetify structural pattern, not real a11y issue)
- knip.config.ts: remove 4 stale hints + add histoire.config.ts to entry
EOF
)"
```

---

## Final verification — full regression sweep

Mirror Phase 14 of Audit #3 — same suite, same thresholds.

### Step F.1 — Pest --parallel

```bash
cd app
./vendor/bin/pest --parallel
```

Expected: 742/739/3sk/0 failed (or improved). 0 regressions.

### Step F.2 — Vitest

```bash
cd app
npx vitest run
```

Expected: 88+ files / 683+ passed (3+ new test files from Task 3) / 3 skipped / 0 failed.

### Step F.3 — Vite build

```bash
cd app
npx vite build
```

Expected: exit 0, build time < 3.5s (Audit #3 baseline 2.21s).

### Step F.4 — gitleaks full history

```bash
./bin/gitleaks.exe detect --source . --redact -v --log-level info
```

Expected: 0 leaks across now ~440+ commits.

### Step F.5 — lychee link check

```bash
npm run links
```

Expected: 319+ OK / 0 broken.

### Step F.6 — vue-tsc + ESLint + Prettier + markdownlint + cspell sanity

```bash
cd app
npx vue-tsc --noEmit
npx eslint resources/js/**/*.{ts,vue}
npx prettier --check resources/js/**/*.{ts,vue,css}
cd ..
npx markdownlint-cli2 "docs/**/*.md" "*.md"
npx cspell --no-progress "docs/**/*.md" "*.md"
```

All expected: exit 0.

### Step F.7 — Cleanup `package-lock.json` CRLF flap (cosmetic — only if dirty)

```bash
git checkout -- package-lock.json
```

(Safe — `git diff` empty; pure line-ending normalisation.)

---

## Push to origin/main

### Step P.1 — Final `git status` sanity

```bash
git status
git log --oneline origin/main..HEAD
```

Expected: clean working tree; ~4–7 new commits ahead of origin/main.

### Step P.2 — Push

```bash
git push origin main
```

Pre-push hooks (lefthook): gitleaks-full-history + lychee + (other configured). Must all pass.

### Step P.3 — Memory updates

Invoke `/claude-md-management:revise-claude-md` to capture this session's learnings into memory:

- `project_state.md` — bump HEAD to new commit
- `project_full_audit_2026-05-14.md` — append «Audit #3 deferred fixes closure session» section
- `feedback_environment.md` — capture any new quirks discovered during Task 1 (Pa11y CI lifecycle, dev-server bootstrap) or Task 4 (npm overrides syntax)

### Step P.4 — `Открытые_вопросы` registry bump

Reflect closures in `docs/Открытые_вопросы_v8_3.md` if any of the deferred items had a Q-prefixed code. (Audit #3 documented 0 BLOCKED Q-items — but technical-debt closures still benefit from registry note.)

---

## Self-review checklist

**Spec coverage:**

- ✅ P1 Pa11y scope → Task 1
- ✅ P2 AdminPlaceholderView → Task 2.1
- ✅ P2 concurrently → Task 2.2
- ✅ P2 12 dead exports → Task 2.3
- ✅ P2 DemoSeeder hygiene → Task 2.4
- ✅ P2 schema header drift → Task 2.5 (+ CLAUDE.md 2.6/2.9)
- ✅ P2 F-COV-01/02/03 → Task 3
- ✅ P2 GITHUB_TOKEN in cache — NOT addressed (already gitignored, mitigations in place per Audit #3 — documentation-only, no code action; skip)
- ✅ P2 Sentry SDK — OUT OF SCOPE (blocked by Б-1)
- ✅ P2 Handoff prototypes contrast — addressed by relocating to `pa11y-handoff.config.json` (opt-in)
- ✅ P3 knip config → Task 4.5
- ✅ P3 npm lodash → Task 4.2
- ✅ P3 empty-table-header → Task 4.3
- ✅ P3 region landmarks → Task 4.4
- ✅ P3 pgFormatter perl → Task 4.1 (decision doc only)
- ✅ P3 F-COV-04..07 — OUT OF SCOPE (observational, no actionable fix per audit reasoning)
- ✅ P3 generic-api-key FPs in vendor → addressed indirectly by gitleaks allowlist in `.gitleaks.toml` (optional Task 4 micro-step; skip if already present)
- ✅ P3 npm lodash dev-only → Task 4.2
- ✅ P3 F-BUN-01/02/03 — OUT OF SCOPE (Vuetify structural)
- ✅ P3 advisory TODO at Б-1 closure — OUT OF SCOPE

**Placeholder scan:**

- 0 «TBD» / 0 «implement later» / 0 «similar to Task N». All code blocks contain full content.

**Type consistency:**

- All Vue test-utils signatures consistent. All Pest command invocations consistent. All git command shapes consistent.

---

## Execution choice

**Plan saved to** `docs/superpowers/plans/2026-05-14-audit3-deferred-fixes.md`.

Two execution options per `superpowers:writing-plans`:

1. **Subagent-Driven (recommended)** — fresh subagent per task, orchestrator review between tasks, fast iteration. Task 3 already designed for parallel subagent dispatch.
2. **Inline Execution** — all tasks in this session via `superpowers:executing-plans` with checkpoint reviews.

**Recommendation for this work:** Subagent-Driven for Task 3 (3 parallel) + Inline for Tasks 1/2/4 (sequential, shared file state across steps). Mixed mode.
