# Comprehensive Audit Catalog — 2026-05-15

Every audit-worthy surface in Port Nimara CRM, organized by area. Each entry is a discrete check we _could_ run. Pick the subset you want to actually execute.

**Legend:**

- **Effort:** XS (~minutes) · S (~30 min) · M (~half day) · L (~1+ day)
- **Severity if broken:** 🔴 critical · 🟠 high · 🟡 medium · 🟢 cosmetic
- **Coverage today:** ✅ confirmed working · ⚠️ partially checked · ❓ unchecked · ❌ known broken (see prior audits)

---

## 0. Already-known issues (cross-reference)

These were caught in the 2026-05-15 sweep (`docs/audit-2026-05-15.md`) but listed here so we don't re-discover them:

| ID    | Issue                                                                      | Status                |
| ----- | -------------------------------------------------------------------------- | --------------------- |
| A1    | Dashboard activity feed surfaces raw `permission_denied` rows, no label    | ❌ unfixed            |
| A2    | Activity feed renders legacy 9-stage enum values (`deposit_10pct` etc.)    | ❌ unfixed            |
| A3    | react-grab CSP error spam in dev                                           | ❌ unfixed (dev only) |
| A4    | New Client form silently rejects when contact row has empty value          | ❌ unfixed            |
| A5    | Socket.IO WebSocket never connects in `pnpm dev`                           | ❌ unfixed            |
| A6    | Some DialogContent missing `aria-describedby`                              | ❌ unfixed            |
| A8    | Legacy `statusOverrideMode = "auto"` values still in DB                    | ❌ unfixed            |
| A9    | Catch-up wizard defaults to "New Enquiry" instead of "EOI" for under_offer | ❌ unfixed            |
| A16   | File upload at documents-hub root fails with null vs string validator      | ❌ unfixed            |
| A17   | `/api/v1/admin/ports` is super-admin-only but used as bootstrap resolver   | ❌ unfixed            |
| A18   | 404 vs 403 inconsistency on permission denials                             | ❌ unfixed            |
| A19   | F27 same-stage PATCH returns 200 + body instead of 204                     | ❌ unfixed            |
| A20   | OwnerPicker Client/Company toggle hidden until popover opens               | ❌ unfixed            |
| A19_b | Portal `/portal/login` shows "unavailable" — scope undefined               | ❌ unfixed            |

---

## 1. Legacy stage enum bleed (the `deposit_10pct` class of bug)

**Why this matters:** the pipeline was refactored 9 stages → 7 stages but historical data still carries the old enum values in audit logs, soft-deleted rows, and possibly some hard-coded UI lookups. Every place that renders a stage value should map legacy → modern.

| ID    | Check                                                                                                                                                                                                         | Effort | Severity | Coverage |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -------- | -------- |
| L-001 | Grep entire `src/` for hard-coded references to legacy stage names: `details_sent`, `in_communication`, `eoi_sent`, `eoi_signed`, `deposit_10pct`, `contract_sent`, `contract_signed`, `completed` (as stage) | S      | 🟠       | ❓       |
| L-002 | Audit log diff display: does old `pipelineStage` value get human-friendly mapping?                                                                                                                            | S      | 🟡       | ❌ (A2)  |
| L-003 | Activity feed labels: same mapping needed                                                                                                                                                                     | S      | 🟡       | ❌ (A2)  |
| L-004 | Email templates: any merge token surfacing raw stage values?                                                                                                                                                  | XS     | 🟡       | ❓       |
| L-005 | Documenso payload (`buildDocumensoPayload`): any stage references?                                                                                                                                            | XS     | 🟠       | ❓       |
| L-006 | Public berths API: is `status` filter accepting any legacy values?                                                                                                                                            | XS     | 🟡       | ❓       |
| L-007 | Webhook payloads: do outbound `interest.updated` events use 7-stage or legacy?                                                                                                                                | S      | 🟠       | ❓       |
| L-008 | Reports / analytics SQL: are funnel rollups using 7-stage enum exclusively?                                                                                                                                   | M      | 🟠       | ❓       |
| L-009 | Search FTS indexes: do they include the mapped human stage or the raw enum?                                                                                                                                   | S      | 🟡       | ❓       |
| L-010 | Notification copy: does "Stage moved to X" use the mapped label?                                                                                                                                              | XS     | 🟢       | ❓       |
| L-011 | CSV import templates / column mappers: does anyone still accept legacy stage names?                                                                                                                           | XS     | 🟢       | ❓       |
| L-012 | Seed data: confirm no legacy stages in current seed (was migrated in `seed-synthetic-data.ts`)                                                                                                                | XS     | 🟢       | ✅       |
| L-013 | Migration safety: would a re-import via NocoDB re-introduce legacy values?                                                                                                                                    | S      | 🟠       | ❓       |
| L-014 | Status override mode: legacy `"auto"` value (see A8) — same class of bug                                                                                                                                      | XS     | 🟢       | ❌ (A8)  |
| L-015 | Outcome enum: confirm `won` / `lost_*` are the only modern values; no legacy `completed` outcome anywhere                                                                                                     | S      | 🟡       | ❓       |
| L-016 | Lead category enum: any legacy values?                                                                                                                                                                        | XS     | 🟢       | ❓       |
| L-017 | Lead source enum: ditto                                                                                                                                                                                       | XS     | 🟢       | ❓       |
| L-018 | Tenure type enum: ditto                                                                                                                                                                                       | XS     | 🟢       | ❓       |
| L-019 | Document doc-status sub-states: `sent`, `signed`, `completed`, `expired`, `rejected` — are they consistently applied?                                                                                         | S      | 🟡       | ❓       |
| L-020 | Reservation/contract status enum: any legacy / deprecated values lingering?                                                                                                                                   | S      | 🟡       | ❓       |

---

## 2. Routes — every page reachable and correct

| ID    | Check                                                                                                       | Effort | Severity | Coverage            |
| ----- | ----------------------------------------------------------------------------------------------------------- | ------ | -------- | ------------------- |
| R-001 | All `/[portSlug]/*` routes return 200 for super-admin (sweep)                                               | S      | 🟠       | ⚠️ admin only       |
| R-002 | All `/[portSlug]/*` routes return 200 or proper 403/redirect for sales-agent                                | S      | 🟠       | ⚠️ partial          |
| R-003 | All `/[portSlug]/*` routes for viewer                                                                       | S      | 🟡       | ❓                  |
| R-004 | Cross-port URL access: paste `/port-amador/clients/<port-nimara-uuid>` → expects 404, not silent            | XS     | 🟠       | ✅ (F17)            |
| R-005 | Archived entity detail page: 404 with "Restored?" affordance                                                | XS     | 🟡       | ❓                  |
| R-006 | Soft-deleted folder URL: expects 404 / fallback to parent                                                   | XS     | 🟡       | ❓                  |
| R-007 | Hard-deleted berth UUID URL (e.g. A1 in port-amador): expects 404                                           | XS     | 🟡       | ❓                  |
| R-008 | URL-encoded mooring number (`A1` vs `A%201` vs `a1`): canonicalization                                      | XS     | 🟡       | ❓                  |
| R-009 | Trailing slash redirects                                                                                    | XS     | 🟢       | ❓                  |
| R-010 | Query-string preservation across nav (filters, sort, page)                                                  | S      | 🟡       | ❓                  |
| R-011 | Browser back/forward state on detail pages (does Tab selection persist?)                                    | S      | 🟡       | ❓                  |
| R-012 | Deep-link with `?folder=<id>` on documents (F25 verified for root, what about deep folder?)                 | XS     | 🟢       | ⚠️                  |
| R-013 | Deep-link to specific interest tab (`?tab=documents`)                                                       | XS     | 🟢       | ❓                  |
| R-014 | Deep-link with filter pre-applied (`/interests?stage=eoi`)                                                  | XS     | 🟡       | ❓                  |
| R-015 | typedRoutes enforcement: any string-as-route escapes via `as never` casts that point to non-existent paths? | M      | 🟡       | ❓                  |
| R-016 | Middleware / proxy.ts: public-path allow-list correctness (regex anchors, prefix matches)                   | S      | 🟠       | ❓                  |
| R-017 | Auth redirect: visiting `/dashboard` while logged-out → `/login?next=...`                                   | XS     | 🟠       | ❓                  |
| R-018 | Post-login redirect honours `next` param                                                                    | XS     | 🟠       | ❓                  |
| R-019 | Portal routes when `client_portal_enabled=false`: gate page (verified A19_b)                                | XS     | 🟢       | ✅                  |
| R-020 | Portal routes when `client_portal_enabled=true`: dashboard, docs, activate flows                            | S      | 🟠       | ❓                  |
| R-021 | `/setup` bootstrap flow on fresh DB (no super admin yet)                                                    | M      | 🔴       | ❓ (F1 fixed proxy) |
| R-022 | Reset-password token validity + expiry                                                                      | S      | 🟠       | ❓                  |
| R-023 | Set-password (first-time after invite) flow                                                                 | S      | 🟠       | ❓                  |
| R-024 | Portal activate via `#token` fragment                                                                       | S      | 🟠       | ❓                  |
| R-025 | API routes that should be HEAD-cacheable (public/berths) return correct cache headers                       | S      | 🟢       | ❓                  |
| R-026 | Public health: anonymous mode minimal payload                                                               | XS     | 🟡       | ❓                  |
| R-027 | Public health: secret mode full payload                                                                     | XS     | 🟡       | ❓                  |
| R-028 | OPTIONS preflight on API routes (CORS)                                                                      | XS     | 🟡       | ❓                  |
| R-029 | API rate-limit headers on auth endpoints                                                                    | XS     | 🟡       | ❓                  |
| R-030 | `/api/v1/me` returns expected user shape                                                                    | XS     | 🟢       | ✅                  |

---

## 3. UX consistency — every list, detail, form

### 3a. Empty / loading / error states

| ID    | Surface                                                                                                                   | Effort | Severity | Coverage |
| ----- | ------------------------------------------------------------------------------------------------------------------------- | ------ | -------- | -------- |
| U-001 | Clients list: empty state copy + CTA                                                                                      | XS     | 🟢       | ❓       |
| U-002 | Yachts list: empty state                                                                                                  | XS     | 🟢       | ❓       |
| U-003 | Companies list: empty state                                                                                               | XS     | 🟢       | ❓       |
| U-004 | Interests list: empty state                                                                                               | XS     | 🟢       | ❓       |
| U-005 | Berths list: empty state                                                                                                  | XS     | 🟢       | ❓       |
| U-006 | Reservations list: empty state                                                                                            | XS     | 🟢       | ❓       |
| U-007 | Invoices list: empty state                                                                                                | XS     | 🟢       | ❓       |
| U-008 | Inbox: empty state                                                                                                        | XS     | 🟢       | ❓       |
| U-009 | Documents hub root: empty state                                                                                           | XS     | 🟢       | ❓       |
| U-010 | Documents hub folder: empty state (verified earlier)                                                                      | XS     | 🟢       | ✅       |
| U-011 | Audit log: empty state (filter to nothing)                                                                                | XS     | 🟢       | ❓       |
| U-012 | Reconcile berths: empty state (verified)                                                                                  | XS     | 🟢       | ✅       |
| U-013 | Recommender: empty result copy (verified F28)                                                                             | XS     | 🟢       | ✅       |
| U-014 | All list pages: loading skeleton vs spinner — is the pattern consistent?                                                  | S      | 🟢       | ❓       |
| U-015 | All detail pages: 404 fallback (DetailNotFound) — confirmed for 5 entities, check residential/reservation/invoice/expense | S      | 🟡       | ⚠️       |
| U-016 | All forms: server-error toast surfaces requestId                                                                          | S      | 🟡       | ❓       |
| U-017 | All forms: validation summary at top vs inline messages                                                                   | S      | 🟡       | ❓       |
| U-018 | All forms: submit-while-pending state (button disabled + spinner)                                                         | S      | 🟢       | ❓       |
| U-019 | Drag-drop file zone: hover state visible                                                                                  | XS     | 🟢       | ❓       |
| U-020 | Drag-drop file zone: drop-target overlay on entity folder                                                                 | XS     | 🟢       | ❓       |

### 3b. Form design

| ID    | Check                                                                 | Effort | Severity | Coverage |
| ----- | --------------------------------------------------------------------- | ------ | -------- | -------- |
| U-021 | Required-field markers consistent ("\*" vs label suffix vs help text) | S      | 🟢       | ❓       |
| U-022 | Field-help-text discoverability (tooltip vs always-visible)           | S      | 🟢       | ❓       |
| U-023 | Field-level errors: every field has visible error after blur+submit   | M      | 🟡       | ❓       |
| U-024 | Cancel behaviour: discards or saves draft?                            | S      | 🟡       | ❓       |
| U-025 | Unsaved changes warning on dialog dismiss                             | S      | 🟡       | ❓       |
| U-026 | Multi-step wizards: persist state across step nav                     | M      | 🟡       | ❓       |
| U-027 | Phone E.164 conversion preview                                        | S      | 🟢       | ❓       |
| U-028 | Currency input: locale-aware separators                               | S      | 🟡       | ❓       |
| U-029 | Date picker: keyboard input + calendar both work                      | S      | 🟢       | ❓       |
| U-030 | Date range constraint enforcement (start ≤ end)                       | XS     | 🟡       | ❓       |
| U-031 | File-type accept attribute matches server magic-byte check            | XS     | 🟡       | ❓       |
| U-032 | File-size limit copy matches server limit                             | XS     | 🟢       | ❓       |
| U-033 | Combobox keyboard nav (↑↓, Enter, Esc, type-ahead)                    | S      | 🟢       | ❓       |
| U-034 | Multi-select chip removal (X button + backspace)                      | S      | 🟢       | ❓       |
| U-035 | Tag colour-picker: contrast check                                     | XS     | 🟢       | ❓       |
| U-036 | "Save changes" copy consistency (vs "Update" vs "Save")               | S      | 🟢       | ❓       |
| U-037 | Inline-edit save trigger (blur vs Enter vs explicit save)             | S      | 🟢       | ❓       |
| U-038 | Inline-edit cancel (Esc reverts)                                      | XS     | 🟢       | ❓       |
| U-039 | Inline-tag-editor: tab order across the chip strip                    | XS     | 🟢       | ❓       |

### 3c. Tables / lists / filters

| ID    | Check                                                   | Effort | Severity | Coverage |
| ----- | ------------------------------------------------------- | ------ | -------- | -------- |
| U-040 | Sort direction indicator on column header               | XS     | 🟢       | ❓       |
| U-041 | Multi-column sort (shift-click)                         | S      | 🟢       | ❓       |
| U-042 | Filter chips dismissable via X                          | XS     | 🟢       | ❓       |
| U-043 | "Clear all filters" button presence                     | XS     | 🟢       | ❓       |
| U-044 | Pagination: page size selector                          | XS     | 🟢       | ❓       |
| U-045 | Pagination: jump-to-page                                | XS     | 🟢       | ❓       |
| U-046 | Pagination: total count accuracy with filters           | XS     | 🟡       | ❓       |
| U-047 | Row selection: select-all-page vs select-all-filtered   | S      | 🟡       | ❓       |
| U-048 | Bulk action toolbar appearance + dismiss                | S      | 🟢       | ❓       |
| U-049 | Sticky header on scroll                                 | XS     | 🟢       | ❓       |
| U-050 | Column resize / reorder / show-hide persistence         | S      | 🟢       | ❓       |
| U-051 | Virtual list performance with 1000+ rows                | M      | 🟡       | ❓       |
| U-052 | CSV export of current view (respects filters + columns) | S      | 🟡       | ❓       |
| U-053 | Sorted-by-relevance vs sorted-by-date default           | XS     | 🟢       | ❓       |

### 3d. Badges, icons, colours

| ID    | Check                                                                         | Effort | Severity | Coverage |
| ----- | ----------------------------------------------------------------------------- | ------ | -------- | -------- |
| U-054 | Stage badge palette: 7 stages each have a distinct, consistent colour         | XS     | 🟢       | ❓       |
| U-055 | Outcome badge: won = green, lost\_\* = red shades, distinct enough            | XS     | 🟢       | ❓       |
| U-056 | Berth status pill: available/under_offer/sold colour consistency              | XS     | 🟢       | ✅       |
| U-057 | Document status pill: draft/sent/partial/completed/expired/cancelled/rejected | XS     | 🟢       | ❓       |
| U-058 | "Manual" chip on berth list (F67 phase 2)                                     | XS     | 🟢       | ✅       |
| U-059 | Icon usage: Lucide-only — no decorative unicode glyphs (memory: avoid emoji)  | S      | 🟡       | ⚠️       |
| U-060 | Button hierarchy: primary/secondary/ghost/destructive used consistently       | S      | 🟢       | ❓       |
| U-061 | Destructive actions colour-coded red                                          | XS     | 🟡       | ❓       |
| U-062 | Loading spinner sizing consistent (size-3.5 vs size-4 vs animate-spin)        | S      | 🟢       | ❓       |
| U-063 | Tooltip delay + position consistency                                          | S      | 🟢       | ❓       |
| U-064 | Status pill withDot vs no dot: is the rule consistent?                        | XS     | 🟢       | ❓       |

### 3e. Modal / sheet / drawer doctrine

| ID    | Check                                                                          | Effort | Severity | Coverage |
| ----- | ------------------------------------------------------------------------------ | ------ | -------- | -------- |
| U-065 | Sheet used for forms + previews on desktop AND mobile (per CLAUDE.md doctrine) | S      | 🟡       | ❓       |
| U-066 | Vaul Drawer only used for mobile-bottom-sheet (only `MoreSheet` qualifies)     | XS     | 🟢       | ❓       |
| U-067 | AlertDialog used for destructive confirmations                                 | XS     | 🟢       | ❓       |
| U-068 | Dialog used for short interactive forms (new yacht, catch-up, won-dialog)      | XS     | 🟢       | ❓       |
| U-069 | Esc closes all overlays consistently                                           | XS     | 🟢       | ❓       |
| U-070 | Click-outside closes / doesn't close: rule consistent                          | S      | 🟡       | ❓       |
| U-071 | Focus trap inside overlays                                                     | S      | 🟠       | ❓       |
| U-072 | Focus restoration to trigger element on close                                  | S      | 🟡       | ❓       |

### 3f. Toasts / feedback

| ID    | Check                                                                      | Effort | Severity | Coverage |
| ----- | -------------------------------------------------------------------------- | ------ | -------- | -------- |
| U-073 | Toast position consistent (top-right, sonner config)                       | XS     | 🟢       | ✅       |
| U-074 | Success toast on every mutation (create, update, archive, delete, restore) | M      | 🟡       | ⚠️       |
| U-075 | Error toast includes copyable requestId                                    | S      | 🟡       | ⚠️       |
| U-076 | Toast timing (auto-dismiss vs persistent for errors)                       | XS     | 🟢       | ❓       |
| U-077 | Multiple toasts stack vs replace                                           | XS     | 🟢       | ❓       |

### 3g. Accessibility / keyboard

| ID    | Check                                                      | Effort | Severity | Coverage |
| ----- | ---------------------------------------------------------- | ------ | -------- | -------- |
| U-078 | Tab order natural on each form                             | M      | 🟡       | ❓       |
| U-079 | All icons inside buttons have `aria-label` or sibling text | S      | 🟡       | ❓       |
| U-080 | All `<img>` have alt                                       | XS     | 🟡       | ❓       |
| U-081 | Heading hierarchy (h1 → h2 → h3, no skips)                 | S      | 🟢       | ❓       |
| U-082 | Color contrast WCAG AA (4.5:1 body, 3:1 large)             | M      | 🟡       | ❓       |
| U-083 | Focus rings visible on all interactive elements            | S      | 🟡       | ❓       |
| U-084 | Skip-to-content link                                       | XS     | 🟢       | ❓       |
| U-085 | Reduced-motion media query honoured                        | S      | 🟢       | ❓       |
| U-086 | `aria-describedby` set on DialogContent (A6)               | S      | 🟡       | ❌       |
| U-087 | Live regions for async updates (toast, notification count) | S      | 🟢       | ❓       |
| U-088 | Form errors announced to screen readers                    | S      | 🟡       | ❓       |
| U-089 | Touch target min 44×44px on mobile                         | S      | 🟡       | ❓       |

### 3h. Mobile-specific UX

| ID    | Check                                                             | Effort | Severity | Coverage |
| ----- | ----------------------------------------------------------------- | ------ | -------- | -------- |
| U-090 | Bottom-tab nav reachable on every page                            | XS     | 🟢       | ✅       |
| U-091 | Mobile topbar shows correct title via `useMobileChrome`           | S      | 🟢       | ⚠️       |
| U-092 | More sheet contains every nav item not on bottom bar              | XS     | 🟡       | ❓       |
| U-093 | Search overlay covers viewport on tap                             | XS     | 🟢       | ❓       |
| U-094 | iOS safe-area-inset-top / bottom respected                        | S      | 🟡       | ❓       |
| U-095 | Pull-to-refresh: present or absent? (consistency)                 | XS     | 🟢       | ❓       |
| U-096 | Camera capture on file upload (image\* mime type triggers camera) | S      | 🟢       | ❓       |
| U-097 | Soft keyboard occlusion on form input (visualViewport handling)   | S      | 🟡       | ❓       |
| U-098 | Long-press menu absence (not native iOS overrides)                | XS     | 🟢       | ❓       |
| U-099 | Sheet side="right" responsiveness                                 | XS     | 🟢       | ❓       |
| U-100 | Mobile bottom tab active-state highlight                          | XS     | 🟢       | ❓       |

---

## 4. Sales workflows — every end-to-end path

### 4a. Happy paths

| ID    | Flow                                                                                                                                                                                                                       | Effort | Severity | Coverage |
| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -------- | -------- |
| W-001 | Create client → create interest → link yacht → advance to EOI → send EOI → receive webhook → auto-advance to Reservation → record deposit → auto-advance to Deposit Paid → send contract → mark contract signed → mark won | L      | 🔴       | ⚠️       |
| W-002 | Multi-berth interest: link 3 berths, mark one primary, send EOI bundle with range formatter                                                                                                                                | M      | 🟠       | ❓       |
| W-003 | Company-owned yacht: company → membership → yacht owned by company → interest                                                                                                                                              | M      | 🟠       | ❓       |
| W-004 | Residential client + residential interest end-to-end                                                                                                                                                                       | M      | 🟡       | ❓       |
| W-005 | Public berth inquiry → admin/inquiries triage → create client via prefill                                                                                                                                                  | M      | 🟠       | ❓       |
| W-006 | Catch-up wizard from berth list row-menu                                                                                                                                                                                   | S      | 🟠       | ⚠️       |
| W-007 | Catch-up wizard from reconcile queue (verified)                                                                                                                                                                            | S      | 🟢       | ✅       |
| W-008 | Mark won → reopen → outcome cleared toast (F26)                                                                                                                                                                            | XS     | 🟢       | ⚠️       |
| W-009 | Mark lost (each lost reason)                                                                                                                                                                                               | S      | 🟢       | ❓       |
| W-010 | Mark externally signed                                                                                                                                                                                                     | S      | 🟡       | ❓       |

### 4b. Edge cases

| ID    | Flow                                                                   | Effort | Severity | Coverage  |
| ----- | ---------------------------------------------------------------------- | ------ | -------- | --------- |
| W-011 | Try to leave Enquiry without yacht → F23 inline prereq picker fires    | XS     | 🟢       | ✅        |
| W-012 | Try forbidden transition (e.g. Reservation → Enquiry) without override | XS     | 🟡       | ❓        |
| W-013 | Override transition: requires reason ≥ 5 chars                         | XS     | 🟡       | ❓        |
| W-014 | Override transition: insufficient permission → blocked tooltip         | XS     | 🟡       | ❓        |
| W-015 | Rewind to enquiry with linked berths → unlink-or-keep prompt           | S      | 🟡       | ❓        |
| W-016 | Same-stage write (F27): expects 204                                    | XS     | 🟢       | ❌ (A19)  |
| W-017 | Concurrent stage edits (two browser tabs)                              | M      | 🟡       | ❓        |
| W-018 | Stage transition emits audit log + realtime event                      | S      | 🟡       | ❓        |
| W-019 | Auto-advance via berth-rule on `deposit_received`                      | S      | 🟠       | ❓        |
| W-020 | Auto-advance via Documenso webhook (`DOCUMENT_SIGNED`)                 | S      | 🟠       | ❓        |
| W-021 | Webhook arrives twice (idempotency)                                    | S      | 🟠       | ✅ (R2-G) |
| W-022 | Webhook with v2 envelope shape                                         | S      | 🟠       | ❓        |
| W-023 | Webhook lowercase-dotted event name → forward-compat                   | XS     | 🟢       | ❓        |
| W-024 | Webhook with wrong secret → 401 + rate limit                           | S      | 🟠       | ❓        |
| W-025 | Berth unlink mid-EOI → rule fires?                                     | S      | 🟡       | ❓        |
| W-026 | Yacht reassignment mid-deal                                            | S      | 🟡       | ❓        |
| W-027 | Client merge (duplicate dedup) — interest carry-over                   | M      | 🟠       | ❓        |
| W-028 | Recommender on 0ft yacht (empty dims)                                  | XS     | 🟢       | ❓        |
| W-029 | Recommender on 300ft yacht (no matching berth)                         | XS     | 🟢       | ✅ (F28)  |
| W-030 | Recommender weight tuning re-ranks                                     | S      | 🟡       | ❓        |
| W-031 | Recommender fallthrough policy (cooldown after lost)                   | M      | 🟡       | ❓        |
| W-032 | Recommender tier ladder A/B/C/D classification                         | M      | 🟠       | ❓        |
| W-033 | Heat scoring weights (recency, furthest stage, count, EOI count)       | M      | 🟡       | ❓        |
| W-034 | Reservation cancel mid-flow                                            | S      | 🟡       | ❓        |
| W-035 | EOI document expiry                                                    | S      | 🟡       | ❓        |
| W-036 | Contract sent + bounced email                                          | S      | 🟡       | ❓        |
| W-037 | Reminder snooze / dismiss                                              | S      | 🟢       | ❓        |
| W-038 | Reminder digest delivery                                               | M      | 🟢       | ❓        |
| W-039 | Default-owner auto-assign on new interest                              | XS     | 🟢       | ❓        |
| W-040 | Reassignment notification email                                        | S      | 🟢       | ❓        |
| W-041 | Cascading invites (secondary signers)                                  | M      | 🟠       | ❓        |
| W-042 | Field-level signing verification                                       | M      | 🟡       | ❓        |
| W-043 | Voice-note attach on activity                                          | S      | 🟢       | ❓        |
| W-044 | Quick-template log entry                                               | S      | 🟢       | ❓        |
| W-045 | Note add / edit / delete (polymorphic across entities)                 | S      | 🟢       | ❓        |
| W-046 | Tag add via inline-tag-editor (verified F16 inline create flow)        | XS     | 🟢       | ⚠️        |
| W-047 | Tag delete cascade (remove tag from all entities)                      | S      | 🟡       | ❓        |
| W-048 | Bulk archive (clients)                                                 | S      | 🟡       | ❓        |
| W-049 | Bulk archive (interests)                                               | S      | 🟡       | ❓        |
| W-050 | Restore archived (any entity)                                          | S      | 🟡       | ❓        |
| W-051 | Hard-delete request (GDPR Article 17)                                  | M      | 🟠       | ❓        |
| W-052 | GDPR export download                                                   | M      | 🟠       | ✅ (R2-O) |

---

## 5. Admin workflows

| ID     | Flow                                                                         | Effort | Severity | Coverage        |
| ------ | ---------------------------------------------------------------------------- | ------ | -------- | --------------- |
| AD-001 | Role create + permission edit                                                | S      | 🟠       | ❓              |
| AD-002 | Per-port role override                                                       | S      | 🟠       | ❓              |
| AD-003 | User invite send + email delivered                                           | M      | 🟠       | ❓              |
| AD-004 | Invite accept + activate (token in #fragment)                                | S      | 🟠       | ❓              |
| AD-005 | Invitation revoke / resend                                                   | XS     | 🟡       | ❓              |
| AD-006 | User edit (display name, residential access toggle)                          | XS     | 🟢       | ❓              |
| AD-007 | User deactivate                                                              | S      | 🟠       | ❓              |
| AD-008 | System settings key update                                                   | XS     | 🟡       | ❓              |
| AD-009 | Branding logo upload + render in email templates                             | S      | 🟢       | ❓              |
| AD-010 | Branding primary colour propagation                                          | S      | 🟢       | ❓              |
| AD-011 | Document template create with merge tokens                                   | S      | 🟠       | ❓              |
| AD-012 | Template merge field validation (unknown token rejected)                     | XS     | 🟢       | ❓              |
| AD-013 | Email template subject preview / override                                    | S      | 🟢       | ❓              |
| AD-014 | Tag create + colour pick + delete                                            | XS     | 🟢       | ✅              |
| AD-015 | Vocabulary list edit (interest temperatures, etc)                            | S      | 🟢       | ❓              |
| AD-016 | Custom field add (text, number, select, date)                                | S      | 🟡       | ❓              |
| AD-017 | Custom field retrofit on existing rows                                       | S      | 🟡       | ❓              |
| AD-018 | Webhook create + secret rotate                                               | S      | 🟠       | ❓              |
| AD-019 | Webhook delivery log + retry                                                 | S      | 🟡       | ❓              |
| AD-020 | Brochure upload + magic-byte check                                           | S      | 🟡       | ❓              |
| AD-021 | Brochure default toggle (partial unique index)                               | S      | 🟡       | ❓              |
| AD-022 | Brochure archive                                                             | XS     | 🟢       | ❓              |
| AD-023 | Per-berth PDF upload + parse                                                 | M      | 🟠       | ❓              |
| AD-024 | Per-berth PDF version rollback                                               | S      | 🟡       | ❓              |
| AD-025 | OCR parse confidence threshold + AI parse fallback                           | M      | 🟡       | ❓              |
| AD-026 | NocoDB import: --apply, --force, --update-snapshot                           | M      | 🟠       | ❓              |
| AD-027 | NocoDB import idempotency (re-run after no changes)                          | S      | 🟡       | ❓              |
| AD-028 | NocoDB import vs human-edited row skip (updated_at > last_imported_at)       | S      | 🟡       | ❓              |
| AD-029 | Bulk berth add wizard end-to-end                                             | S      | 🟠       | ⚠️ (loads only) |
| AD-030 | CSV import (clients) — column mapper                                         | M      | 🟠       | ❓              |
| AD-031 | CSV import (yachts)                                                          | M      | 🟡       | ❓              |
| AD-032 | CSV import error report (rejected rows)                                      | S      | 🟡       | ❓              |
| AD-033 | Duplicates queue review + merge                                              | M      | 🟠       | ❓              |
| AD-034 | Duplicates queue: false-positive dismiss                                     | XS     | 🟢       | ❓              |
| AD-035 | Audit log search/FTS — text query                                            | S      | 🟡       | ❓              |
| AD-036 | Audit log filter by action / entity / user / date range                      | S      | 🟡       | ❓              |
| AD-037 | Audit log diff display (old vs new)                                          | S      | 🟡       | ❓              |
| AD-038 | Audit log mask of sensitive fields (passwords, tokens)                       | S      | 🟠       | ❓              |
| AD-039 | Backup status read                                                           | XS     | 🟢       | ❓              |
| AD-040 | Storage backend swap dry-run (filesystem ↔ s3)                               | M      | 🟠       | ❓              |
| AD-041 | Multi-node deployment refuses filesystem backend                             | XS     | 🟠       | ❓              |
| AD-042 | Documenso health check Test button (v1 + v2)                                 | S      | 🟠       | ❓              |
| AD-043 | Documenso API version toggle per-port                                        | S      | 🟠       | ❓              |
| AD-044 | Documenso signing-order setting (parallel/sequential)                        | S      | 🟡       | ❓              |
| AD-045 | Documenso redirect URL setting                                               | XS     | 🟢       | ❓              |
| AD-046 | AI provider credentials test                                                 | S      | 🟡       | ❓              |
| AD-047 | Receipt OCR config + retry on bad parse                                      | M      | 🟡       | ❓              |
| AD-048 | Send-from account config + encrypted secret roundtrip                        | M      | 🟠       | ❓              |
| AD-049 | Bounce monitoring (IMAP probe + dev-imap-probe script)                       | M      | 🟡       | ❓              |
| AD-050 | Reminders default behaviour + digest window edit                             | S      | 🟢       | ❓              |
| AD-051 | Residential pipeline stages edit + reassignment on stage removal             | M      | 🟡       | ❓              |
| AD-052 | Qualification criteria reorder (DnD)                                         | S      | 🟢       | ❓              |
| AD-053 | Berth rules engine config (7 triggers, 3 modes)                              | M      | 🟠       | ❓              |
| AD-054 | Recommender weights tune                                                     | S      | 🟡       | ❓              |
| AD-055 | Onboarding checklist progression                                             | S      | 🟢       | ❓              |
| AD-056 | Reports: pipeline funnel, occupancy timeline, revenue breakdown, lead source | S      | 🟡       | ❓              |
| AD-057 | Forms: form template create + public submission roundtrip                    | M      | 🟠       | ❓              |
| AD-058 | Inquiry inbox triage → convert to client                                     | M      | 🟠       | ❓              |
| AD-059 | Website analytics (Umami) config                                             | S      | 🟢       | ❓              |
| AD-060 | Queue monitoring dashboard (BullMQ stats)                                    | XS     | 🟢       | ❓              |

---

## 6. Multi-tenancy (port isolation)

| ID    | Check                                                                | Effort | Severity | Coverage  |
| ----- | -------------------------------------------------------------------- | ------ | -------- | --------- |
| MT-01 | GET /api/v1/clients/<other-port-uuid> with X-Port-Id=this-port → 404 | XS     | 🟠       | ✅ (R2-N) |
| MT-02 | PATCH /api/v1/interests/<other-port-uuid> → 404                      | XS     | 🟠       | ❓        |
| MT-03 | Berth recommender cross-port leak guard (entry + SQL CTE)            | S      | 🔴       | ✅        |
| MT-04 | Document folder defense-in-depth port_id filter on every join        | S      | 🟠       | ❓        |
| MT-05 | Audit log scope per port                                             | XS     | 🟠       | ❓        |
| MT-06 | Webhook subscriptions scoped to port                                 | XS     | 🟠       | ❓        |
| MT-07 | System settings per-port                                             | XS     | 🟡       | ❓        |
| MT-08 | Tags scoped to port                                                  | XS     | 🟡       | ❓        |
| MT-09 | Custom fields scoped to port                                         | XS     | 🟡       | ❓        |
| MT-10 | Vocabularies scoped to port                                          | XS     | 🟡       | ❓        |
| MT-11 | Seed runs idempotent across ports                                    | S      | 🟡       | ❓        |

---

## 7. Security

| ID   | Check                                                      | Effort | Severity | Coverage  |
| ---- | ---------------------------------------------------------- | ------ | -------- | --------- |
| S-01 | XSS via client.fullName render (verified ✓)                | XS     | 🟠       | ✅        |
| S-02 | XSS via tag.name                                           | XS     | 🟠       | ❓        |
| S-03 | XSS via note.content (markdown)                            | S      | 🟠       | ❓        |
| S-04 | XSS via email body markdown (verified)                     | S      | 🟠       | ✅ (R2-I) |
| S-05 | SQL injection via search query                             | S      | 🔴       | ❓        |
| S-06 | Path traversal in folder name                              | S      | 🟠       | ❓        |
| S-07 | Path traversal in file name                                | XS     | 🟠       | ❓        |
| S-08 | SSRF via attachment URL or webhook target                  | S      | 🟠       | ❓        |
| S-09 | Open redirect on `next` param                              | XS     | 🟠       | ❓        |
| S-10 | CSRF on state-changing requests (proxy.ts checks)          | S      | 🟠       | ❓        |
| S-11 | Cookie flags: HttpOnly, Secure, SameSite                   | XS     | 🟠       | ❓        |
| S-12 | CSP headers (production)                                   | S      | 🟡       | ❓        |
| S-13 | CORS allow-list narrow                                     | XS     | 🟡       | ❓        |
| S-14 | Rate limit on login (verified F7)                          | XS     | 🟠       | ✅        |
| S-15 | Rate limit on forget-password                              | XS     | 🟠       | ✅        |
| S-16 | Rate limit on file upload                                  | S      | 🟡       | ❓        |
| S-17 | Session fixation (regen sid on login)                      | S      | 🟠       | ❓        |
| S-18 | Token expiry / refresh (better-auth)                       | S      | 🟠       | ❓        |
| S-19 | Audit log tamper-resistance (append-only)                  | S      | 🟡       | ❓        |
| S-20 | Documenso webhook secret rotation (verified)               | S      | 🟠       | ✅        |
| S-21 | SMTP credential at-rest encryption (AES-256-GCM)           | S      | 🟠       | ❓        |
| S-22 | IMAP credential at-rest encryption                         | S      | 🟠       | ❓        |
| S-23 | Storage credential at-rest encryption                      | S      | 🟠       | ❓        |
| S-24 | Privilege escalation: viewer → agent → admin paths         | M      | 🔴       | ❓        |
| S-25 | Direct ID enumeration (UUID guess immune)                  | XS     | 🟢       | ✅ (R2)   |
| S-26 | Audit log read-back of own permission denials              | S      | 🟢       | ❓        |
| S-27 | Magic-byte verification on every uploaded file (verified)  | S      | 🟠       | ✅        |
| S-28 | Filename HTML-escape in download links                     | XS     | 🟡       | ❓        |
| S-29 | Bounce-monitor email subject parsing (injection)           | S      | 🟡       | ❓        |
| S-30 | Email body redirect mode never escapes in prod (env guard) | XS     | 🟠       | ❓        |

---

## 8. Realtime / sockets

| ID    | Check                                                          | Effort | Severity | Coverage |
| ----- | -------------------------------------------------------------- | ------ | -------- | -------- |
| RT-01 | Socket.IO server actually running in dev (A5)                  | S      | 🟡       | ❌       |
| RT-02 | Realtime invalidation: interest:updated fires from another tab | S      | 🟡       | ❓       |
| RT-03 | document:completed event invalidates files                     | S      | 🟡       | ❓       |
| RT-04 | folder:created event invalidates document-folders              | S      | 🟡       | ❓       |
| RT-05 | berth:statusChanged event invalidates berths                   | S      | 🟡       | ❓       |
| RT-06 | Subscription teardown on unmount (no leaks)                    | S      | 🟡       | ❓       |
| RT-07 | Cross-tab broadcast (BroadcastChannel?)                        | M      | 🟢       | ❓       |
| RT-08 | Reconnect after server restart                                 | S      | 🟡       | ❓       |
| RT-09 | Room-level scoping (port:X room)                               | XS     | 🟠       | ❓       |

---

## 9. Performance

| ID   | Check                                                                    | Effort | Severity | Coverage                    |
| ---- | ------------------------------------------------------------------------ | ------ | -------- | --------------------------- |
| P-01 | Web vitals report endpoint accepts beacons (verified — A2 is dev cancel) | XS     | 🟢       | ✅                          |
| P-02 | LCP under 2.5s on dashboard                                              | S      | 🟡       | ❓                          |
| P-03 | CLS under 0.1                                                            | S      | 🟢       | ❓                          |
| P-04 | TTI under 3s                                                             | S      | 🟡       | ❓                          |
| P-05 | N+1 detection on interests list (tags / berths / yacht joins)            | M      | 🟡       | ❓                          |
| P-06 | DataTable virtual rendering for 1000+ rows                               | M      | 🟡       | ⚠️ (audit-log uses virtual) |
| P-07 | Image lazy-load on documents list                                        | XS     | 🟢       | ❓                          |
| P-08 | Bundle size growth budget                                                | S      | 🟢       | ❓                          |
| P-09 | Slow-query log review                                                    | M      | 🟡       | ❓                          |
| P-10 | DB connection pool exhaustion behaviour (verified F8 fix landed)         | S      | 🟠       | ✅                          |
| P-11 | Memory leak after long session (open same form 50 times)                 | M      | 🟡       | ❓                          |
| P-12 | Worker queue throughput under load                                       | M      | 🟡       | ❓                          |
| P-13 | Search FTS query plan (uses GIN index?)                                  | S      | 🟡       | ❓                          |
| P-14 | API response size budget (paginated list ≤ 256 KB)                       | XS     | 🟢       | ❓                          |

---

## 10. Documents / files

| ID   | Check                                                                         | Effort | Severity | Coverage |
| ---- | ----------------------------------------------------------------------------- | ------ | -------- | -------- |
| D-01 | Upload via drag-drop on hub root (A16 — broken)                               | XS     | 🟠       | ❌       |
| D-02 | Upload via drag-drop on entity folder                                         | S      | 🟠       | ❓       |
| D-03 | Upload via file picker on dialog                                              | XS     | 🟠       | ❌ (A16) |
| D-04 | PDF preview inline                                                            | S      | 🟢       | ❓       |
| D-05 | Image preview inline (jpg, png, webp, gif)                                    | S      | 🟢       | ❓       |
| D-06 | Word / Excel: download fallback                                               | XS     | 🟢       | ❓       |
| D-07 | Signed PDF download from completed workflow                                   | S      | 🟠       | ❓       |
| D-08 | Folder soft-rescue on delete (children re-parent)                             | S      | 🟠       | ❓       |
| D-09 | Folder rename → entity name sync                                              | S      | 🟡       | ❓       |
| D-10 | Folder move cycle prevention                                                  | S      | 🟡       | ❓       |
| D-11 | Folder permission: system folders immutable through API                       | S      | 🟠       | ❓       |
| D-12 | Aggregated entity view (Clients/Companies/Yachts subfolders)                  | S      | 🟡       | ❓       |
| D-13 | Hub root view: 3 cards (in-progress, files, completed)                        | S      | 🟢       | ❓       |
| D-14 | EntityFolderView: signing-in-progress + files                                 | S      | 🟢       | ❓       |
| D-15 | "View signing details" link on signed file row                                | XS     | 🟢       | ❓       |
| D-16 | Auto-deposit on signing completion (resolves owner via Owner-wins chain)      | M      | 🟠       | ❓       |
| D-17 | listFilesAggregatedByEntity walks Client↔Company↔Yacht reach symmetrically    | M      | 🟠       | ❓       |
| D-18 | Folder URL state with `?folder=<uuid>` (F25 deep folder)                      | XS     | 🟢       | ⚠️       |
| D-19 | Concurrent ensureEntityFolder race-safety (partial unique index)              | M      | 🟡       | ❓       |
| D-20 | Magic-byte verification on presign + post-upload paths                        | S      | 🟠       | ✅       |
| D-21 | Filename HTML-escape in fallback download link                                | XS     | 🟡       | ❓       |
| D-22 | File size > email_attach_threshold_mb → signed-URL link instead of attachment | M      | 🟡       | ❓       |

---

## 11. Audit log

| ID    | Check                                                                             | Effort | Severity | Coverage |
| ----- | --------------------------------------------------------------------------------- | ------ | -------- | -------- |
| AU-01 | Every mutation creates an audit row (sample 10 endpoints)                         | M      | 🟠       | ⚠️       |
| AU-02 | Sensitive-field mask works (test: password rotation row)                          | S      | 🟠       | ❓       |
| AU-03 | FTS query returns expected results                                                | S      | 🟡       | ❓       |
| AU-04 | Filter by action: only stage_change shows                                         | XS     | 🟢       | ❓       |
| AU-05 | Filter by entity type: only berth/interest/etc shows                              | XS     | 🟢       | ❓       |
| AU-06 | Filter by user                                                                    | XS     | 🟢       | ❓       |
| AU-07 | Filter by date range                                                              | XS     | 🟢       | ❓       |
| AU-08 | Diff display correctly highlights old vs new                                      | S      | 🟡       | ❓       |
| AU-09 | "Reconcile" event tag visible in metadata                                         | XS     | 🟢       | ✅       |
| AU-10 | Cascade events grouped or distinct? (e.g. archive client + auto-archive interest) | S      | 🟡       | ❓       |
| AU-11 | Permission-denied entries render readable (A1)                                    | XS     | 🟡       | ❌       |
| AU-12 | Audit log export to CSV                                                           | S      | 🟢       | ❓       |
| AU-13 | Outcome-change action tag distinct from generic 'update' (R2-B finding)           | S      | 🟡       | ❓       |
| AU-14 | Tier-mapping (audit_logs.audit_tier_map) — high-tier vs noise tier                | S      | 🟡       | ❓       |

---

## 12. Email / SMTP / IMAP

| ID    | Check                                                            | Effort | Severity | Coverage |
| ----- | ---------------------------------------------------------------- | ------ | -------- | -------- |
| EM-01 | Per-port SMTP override picks up                                  | S      | 🟠       | ❓       |
| EM-02 | Default sales send-from (`sales@portnimara.com`)                 | XS     | 🟢       | ❓       |
| EM-03 | Default noreply send-from (`noreply@portnimara.com`)             | XS     | 🟢       | ❓       |
| EM-04 | EMAIL_REDIRECT_TO in dev: subject prefix `[redirected from ...]` | XS     | 🟡       | ❓       |
| EM-05 | Branded template render (logo, blurred bg, max-w-600)            | S      | 🟢       | ❓       |
| EM-06 | Reply-to override                                                | XS     | 🟡       | ❓       |
| EM-07 | CC/BCC handling                                                  | S      | 🟡       | ❓       |
| EM-08 | Send rate limit 50/user/hour                                     | XS     | 🟡       | ❓       |
| EM-09 | Send size > threshold falls back to signed link                  | M      | 🟡       | ❓       |
| EM-10 | IMAP bounce probe (`dev-imap-probe.ts`)                          | M      | 🟢       | ❓       |
| EM-11 | Bounce subject parse + interest linking                          | M      | 🟡       | ❓       |
| EM-12 | Document_sends audit row per send                                | S      | 🟡       | ❓       |
| EM-13 | Portal activation email arrives & token works                    | M      | 🟠       | ❓       |
| EM-14 | Reset-password email                                             | S      | 🟠       | ❓       |
| EM-15 | Invite email                                                     | M      | 🟠       | ❓       |
| EM-16 | Reminder digest email                                            | M      | 🟢       | ❓       |
| EM-17 | EOI generated PDF attached or inline?                            | S      | 🟡       | ❓       |
| EM-18 | Outbound email markdown body XSS (verified)                      | S      | 🟠       | ✅       |
| EM-19 | Subject override CSP/XSS                                         | S      | 🟠       | ✅       |

---

## 13. Integrations

| ID    | Check                                                                 | Effort | Severity | Coverage |
| ----- | --------------------------------------------------------------------- | ------ | -------- | -------- |
| IN-01 | Documenso send EOI via v1 template-generate                           | M      | 🟠       | ❓       |
| IN-02 | Documenso v2 envelope/create multipart                                | M      | 🟠       | ❓       |
| IN-03 | Documenso distribute (v2)                                             | S      | 🟠       | ❓       |
| IN-04 | Documenso redistribute / send reminder                                | S      | 🟡       | ❓       |
| IN-05 | Documenso downloadSignedPdf                                           | S      | 🟠       | ❓       |
| IN-06 | Documenso voidDocument                                                | S      | 🟡       | ❓       |
| IN-07 | Documenso placeFields (v2 field/create-many)                          | M      | 🟡       | ❓       |
| IN-08 | Documenso normalizeDocument id ↔ documentId                           | XS     | 🟡       | ❓       |
| IN-09 | NocoDB import idempotency                                             | S      | 🟡       | ❓       |
| IN-10 | S3 / MinIO upload + download                                          | S      | 🟠       | ❓       |
| IN-11 | S3 presigned URL expiry                                               | XS     | 🟡       | ❓       |
| IN-12 | Filesystem backend: MULTI_NODE_DEPLOYMENT guard                       | XS     | 🟠       | ❓       |
| IN-13 | BullMQ job retry on failure                                           | S      | 🟡       | ❓       |
| IN-14 | BullMQ Redis `noeviction` policy (verified)                           | XS     | 🟠       | ✅       |
| IN-15 | Worker process boot + queue subscribe                                 | S      | 🟠       | ❓       |
| IN-16 | Public berths API: anon cache headers                                 | XS     | 🟢       | ❓       |
| IN-17 | Public berths API: status filter (`Under Offer`, `Sold`, `Available`) | S      | 🟡       | ❓       |
| IN-18 | Public berths single endpoint via mooringNumber (canonical format)    | S      | 🟡       | ❓       |
| IN-19 | Public health anonymous mode (verified A26)                           | XS     | 🟡       | ✅       |
| IN-20 | Public health secret mode (verified A26)                              | XS     | 🟡       | ✅       |
| IN-21 | OpenAI / AI parser credentials test                                   | S      | 🟡       | ❓       |
| IN-22 | Tesseract OCR positional heuristics on per-berth PDF                  | M      | 🟡       | ❓       |
| IN-23 | Receipt OCR: full receipt parse end-to-end                            | M      | 🟡       | ❓       |
| IN-24 | Pdfme PDF generation (any per-port template)                          | M      | 🟡       | ❓       |
| IN-25 | PDF-lib AcroForm fill (in-app EOI pathway)                            | M      | 🟠       | ❓       |
| IN-26 | EOI merge token expansion (`{{eoi.berthRange}}` etc)                  | S      | 🟠       | ❓       |
| IN-27 | Berth-range formatter (single + multi-berth)                          | S      | 🟡       | ❓       |
| IN-28 | Portal magic-link consume                                             | S      | 🟠       | ❓       |
| IN-29 | Umami analytics widget render                                         | XS     | 🟢       | ❓       |

---

## 14. Schema / migration

| ID    | Check                                                                           | Effort | Severity | Coverage |
| ----- | ------------------------------------------------------------------------------- | ------ | -------- | -------- |
| SC-01 | All migrations idempotent (re-run safe)                                         | M      | 🟠       | ❓       |
| SC-02 | All FKs have ON DELETE behaviour spec'd (CASCADE, SET NULL, RESTRICT)           | S      | 🟠       | ❓       |
| SC-03 | All soft-delete columns indexed (`archivedAt IS NULL`)                          | S      | 🟡       | ❓       |
| SC-04 | All search columns have GIN/FTS indexes                                         | S      | 🟡       | ❓       |
| SC-05 | Composite unique constraints (sibling folder name, default brochure)            | S      | 🟡       | ❓       |
| SC-06 | Partial unique constraints (entity-folder, isPrimary)                           | S      | 🟡       | ❓       |
| SC-07 | CHECK constraints (chk_system_folder_shape)                                     | XS     | 🟢       | ❓       |
| SC-08 | Generated column accuracy (FTS search_text)                                     | S      | 🟡       | ❓       |
| SC-09 | Column nullability matches Drizzle schema                                       | M      | 🟡       | ❓       |
| SC-10 | Schema migration restart-after-push (CLAUDE.md gotcha)                          | XS     | 🟠       | ❓       |
| SC-11 | Backfill scripts idempotent (`backfill-document-folders.ts`)                    | S      | 🟡       | ❓       |
| SC-12 | Legacy enum migration drift (every place that compared against an old value)    | M      | 🟠       | ❓       |
| SC-13 | Currency code enum                                                              | XS     | 🟡       | ❓       |
| SC-14 | Address-component enum                                                          | XS     | 🟢       | ❓       |
| SC-15 | Polymorphic owner: every read-site uses the service helper, not raw column read | M      | 🟠       | ❓       |

---

## 15. i18n / l10n

| ID   | Check                                          | Effort | Severity | Coverage |
| ---- | ---------------------------------------------- | ------ | -------- | -------- |
| L-01 | Currency formatting per locale                 | S      | 🟢       | ❓       |
| L-02 | Date formatting per timezone                   | S      | 🟢       | ❓       |
| L-03 | Number formatting (1,000.5 vs 1.000,5)         | S      | 🟢       | ❓       |
| L-04 | Plural forms                                   | S      | 🟢       | ❓       |
| L-05 | RTL support (test with Arabic UA)              | S      | 🟢       | ❓       |
| L-06 | Translation completeness (Phase C status)      | M      | 🟢       | ❓       |
| L-07 | next-intl messages.json coverage               | S      | 🟢       | ❓       |
| L-08 | Server-rendered locale match (Accept-Language) | S      | 🟢       | ❓       |

---

## 16. Browser / device

| ID    | Check                                               | Effort | Severity | Coverage |
| ----- | --------------------------------------------------- | ------ | -------- | -------- |
| BR-01 | Safari (macOS) primary flows                        | M      | 🟡       | ❓       |
| BR-02 | Safari (iOS) primary flows                          | M      | 🟡       | ❓       |
| BR-03 | Firefox (latest)                                    | M      | 🟢       | ❓       |
| BR-04 | Edge (latest)                                       | M      | 🟢       | ❓       |
| BR-05 | Chrome (latest) — primary                           | S      | 🟢       | ✅       |
| BR-06 | iPad (Safari) — tier "click" via computer-use rules | M      | 🟢       | ❓       |
| BR-07 | Print stylesheet (interest detail, invoice)         | S      | 🟢       | ❓       |

---

## 17. Specific behavioral correctness checks

| ID   | Check                                                                                                                         | Effort | Severity | Coverage    |
| ---- | ----------------------------------------------------------------------------------------------------------------------------- | ------ | -------- | ----------- |
| B-01 | Berth A1 hard-deleted earlier; confirm no 404 anywhere (interests' linked-berth, public feed, recommender)                    | M      | 🟠       | ❓          |
| B-02 | Sara Laurent interest in stage=contract WITHOUT yachtId → render correctness                                                  | XS     | 🟡       | ❓          |
| B-03 | Outcome-set interests filtered from active queries via `activeInterestsWhere`                                                 | S      | 🟠       | ❓          |
| B-04 | EOI bundle range formatter: `A1-A3, B5` for non-contiguous berths                                                             | S      | 🟡       | ❓          |
| B-05 | EOI single-berth case formats to just mooring (`A1`)                                                                          | XS     | 🟢       | ❓          |
| B-06 | Activity timeline 7-day window inclusive of today                                                                             | XS     | 🟢       | ✅ (F2 fix) |
| B-07 | Heat-scoring tier B only fires for lost/cancelled-only history                                                                | M      | 🟡       | ❓          |
| B-08 | Permission-denied audit row sequencing (does denied API call still log?)                                                      | S      | 🟡       | ❓          |
| B-09 | Same-stage no-op DOES NOT emit audit/socket event (F27)                                                                       | S      | 🟢       | ⚠️          |
| B-10 | Documenso webhook with empty body / malformed payload                                                                         | S      | 🟠       | ❓          |
| B-11 | Berth status_override_mode transitions through automated → manual → null                                                      | M      | 🟡       | ❓          |
| B-12 | Reconcile clear stamps reason correctly with interest id (verified)                                                           | XS     | 🟢       | ✅          |
| B-13 | Catch-up wizard "contract" stage auto-sets `outcome=won`                                                                      | S      | 🟡       | ❓          |
| B-14 | Catch-up wizard surfaces in API audit log as `reconcile_manual` type                                                          | XS     | 🟢       | ✅          |
| B-15 | Mobile shell when initialFormFactor is wrong (Playwright UA = desktop, viewport = mobile) — shell ends up correct after mount | XS     | 🟢       | ✅          |
| B-16 | Resizing across breakpoint mid-form-edit: state preservation?                                                                 | S      | 🟡       | ❓          |
| B-17 | Berths bulk-add wizard: step transitions persist input                                                                        | M      | 🟡       | ❓          |
| B-18 | NotesList polymorphic across all 4 entity types (clients, interests, yachts, companies)                                       | S      | 🟡       | ❓          |
| B-19 | InlineEditableField on every detail page works                                                                                | M      | 🟡       | ❓          |
| B-20 | InlineTagEditor: focus management (F45 verified)                                                                              | S      | 🟢       | ⚠️          |
| B-21 | OwnerPicker: client+company tabs render correctly (F44 verified)                                                              | XS     | 🟢       | ✅          |
| B-22 | Mark externally signed sets `documentId=null`, `signedAt=now`                                                                 | S      | 🟡       | ❓          |

---

## 18. Data-clean-up jobs

| ID    | Check                                                                                                                                                                                                      | Effort | Severity | Coverage |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -------- | -------- |
| DC-01 | Orphan-blob cleanup on document delete                                                                                                                                                                     | S      | 🟠       | ❓       |
| DC-02 | Soft-deleted entities older than X days hard-purged                                                                                                                                                        | M      | 🟡       | ❓       |
| DC-03 | Test entities in DB (per prior audit notes): `Smoke Test Client (renamed)`, `Aurora Marine Holdings Ltd`, `Bad Email Test`, `Phone Test`, `François 🏄 المعتمد`, `CSRF Test`, etc — `db:reseed:synthetic`? | S      | 🟢       | ❓       |
| DC-04 | Berth A1 hard-deletion in port-amador: was that recovered?                                                                                                                                                 | S      | 🟡       | ❓       |
| DC-05 | Legacy `statusOverrideMode = "auto"` normalize migration                                                                                                                                                   | XS     | 🟢       | ❌ (A8)  |

---

## 19. CI / dev experience

| ID    | Check                                                            | Effort | Severity | Coverage |
| ----- | ---------------------------------------------------------------- | ------ | -------- | -------- |
| CI-01 | Husky lint-staged blocks bad commits                             | XS     | 🟢       | ✅       |
| CI-02 | `pnpm exec tsc --noEmit` clean                                   | XS     | 🟢       | ✅       |
| CI-03 | `pnpm lint` zero errors                                          | XS     | 🟢       | ✅       |
| CI-04 | `pnpm exec vitest run` 1373/1373 pass                            | S      | 🟢       | ✅       |
| CI-05 | `pnpm exec playwright test --project=smoke` ~10min               | M      | 🟢       | ❓       |
| CI-06 | `pnpm exec playwright test --project=destructive`                | M      | 🟢       | ❓       |
| CI-07 | `pnpm exec playwright test --project=realapi` (Documenso + IMAP) | M      | 🟢       | ❓       |
| CI-08 | `pnpm exec playwright test --project=visual` baselines current   | S      | 🟢       | ❓       |
| CI-09 | Gitea CI lint + build-and-push workflows                         | S      | 🟢       | ❓       |
| CI-10 | Docker prod build succeeds                                       | M      | 🟠       | ❓       |
| CI-11 | docker-compose dev startup with all services                     | S      | 🟢       | ❓       |
| CI-12 | Pre-commit hook also blocks `.env*` files                        | XS     | 🟢       | ❓       |
| CI-13 | `SKIP_ENV_VALIDATION=1` actually bypasses in Docker build        | XS     | 🟢       | ❓       |

---

## Recommendation: priority short-list

If we want maximum coverage with limited time, I'd pick:

### Tier 0 — fix what's already known (from A1-A20)

- A4 (client form silent-fail)
- A16 (file upload null vs string)
- A17 (/admin/ports bootstrap)
- A19 (F27 204 implementation)
- A9 (catch-up wizard stage default)
- A1/A2 (activity feed labels)

### Tier 1 — discover new

- **L-001** through **L-020** — legacy stage enum hunt (the user's specific concern)
- **W-001** — full end-to-end happy-path workflow (one full deal)
- **U-001** through **U-013** — every empty state surface
- **MT-01-11** — multi-tenancy cross-port checks (full sweep)
- **AU-01-14** — audit log surface (search, filters, mask, FTS)
- **U-021-039** — form design sweep across major forms

### Tier 2 — fill in coverage

- **R-001-030** — route correctness
- **AD-\* (admin pages)** — at least one mutation per admin section to confirm wiring
- **D-01-22** — documents/files end-to-end

### Tier 3 — depth checks

- **S-\* (security)** — penetration sweep
- **P-\* (performance)** — load + LCP + N+1
- **W-011-052** — every edge-case workflow

---

**Total surfaces catalogued:** 320+ discrete checks across 19 areas.

Pick what you want and I'll run it.
