Files
pn-new-crm/docs/AUDIT-CATALOG.md
Matt 3b3ac287e0
All checks were successful
Build & Push Docker Images / lint (push) Successful in 2m6s
Build & Push Docker Images / build-and-push (push) Successful in 22s
docs(audit): comprehensive 320+ check catalog organized by area
Companion to the 2026-05-15 sweep findings. Catalogues every audit-worthy
surface across 19 areas:

  0. Already-known issues (A1-A20 cross-reference)
  1. Legacy stage enum bleed (the deposit_10pct class) — 20 checks
  2. Routes / page reachability — 30 checks
  3. UX consistency (forms, lists, tables, badges, modals, mobile) — 100 checks
  4. Sales workflows happy + edge cases — 52 checks
  5. Admin workflows — 60 checks
  6. Multi-tenancy port isolation — 11 checks
  7. Security — 30 checks
  8. Realtime / sockets — 9 checks
  9. Performance — 14 checks
  10. Documents / files — 22 checks
  11. Audit log surface — 14 checks
  12. Email / SMTP / IMAP — 19 checks
  13. Integrations (Documenso, NocoDB, S3, AI, BullMQ) — 29 checks
  14. Schema / migration — 15 checks
  15. i18n / l10n — 8 checks
  16. Browser / device — 7 checks
  17. Specific behavioral correctness (legacy stage drift, A1 hard-delete fallout, etc) — 22 checks
  18. Data clean-up jobs — 5 checks
  19. CI / dev experience — 13 checks

Each check tagged with effort (XS/S/M/L), severity (🔴/🟠/🟡/🟢), and
current coverage (/⚠️//). Recommended priority tiering at the bottom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 00:54:08 +02:00

76 KiB
Raw Blame History

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/ with X-Port-Id=this-port → 404 XS 🟠 (R2-N)
MT-02 PATCH /api/v1/interests/ → 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.