# Documents/Files Audit (D-01-22) — agent #5

**Headline:** Structurally solid across all 22 checks. One medium real-time event mismatch + 2 low documentation divergences.

**Counts:** 0 critical · 0 high · 1 medium · 2 low · 19 passing

---

## 🟡 MEDIUM D-01/02/03: Real-time invalidation event name mismatch after upload

- **File:** `src/components/documents/documents-hub.tsx:141`
- **What:** Hub subscribes to `'file:created': [['files']]`, but emitter (`files.ts:128`) and socket-events type def (`events.ts:264`) use `'file:uploaded'`.
- **Why it matters:** After remote upload (other session, webhook auto-deposit), hub Files sections don't auto-refresh. Local `FolderDropZone` upload bypasses this via direct `queryClient.invalidateQueries`, but remote uploads invisible until reload.
- **Suggested fix:** Change line 141 to `'file:uploaded': [['files']]` to match `client-files-tab.tsx:32`, `company-files-tab.tsx:32`, `interest-documents-tab.tsx:62`.

## 🟢 LOW D-13: HubRootView has 2 sections, not 3

- **File:** `src/components/documents/hub-root-view.tsx:50-100`
- **What:** Spec says 3 cards; component renders 2 ("Signing in progress" + "Recent files"). Doc-only.
- **Suggested fix:** Update CLAUDE.md to "2 sections."

## 🟢 LOW D-16: `interest.yachtId` branch in chain doc spec doesn't exist in code

- **File:** `src/lib/services/documents.service.ts:1225-1251`
- **What:** Spec is `doc.clientId ?? .companyId ?? .yachtId ?? interest.clientId ?? interest.yachtId`. Code stops at `interest.clientId` because `interests.clientId` is NOT NULL — so the yachtId fallback is unreachable. Comment line 1239 explains.
- **Suggested fix:** Update CLAUDE.md to drop the unreachable trailing branch, or annotate with `// unreachable: interests.clientId is NOT NULL`.

---

## ✅ Passing checks

- D-01 A16 fix verified — `formStr()` returns `undefined` (not `null`) for absent FormData fields; root upload omits `folderId` correctly
- D-02 entity-folder drag-drop carries `folderId`+`entityType`+`entityId`+typed FK
- D-03 file picker dialog passes `folderId` (null for root) correctly
- D-04 PDF inline preview via `PdfViewer` lazy-loaded
- D-05 image inline preview + lightbox via `<img>` for jpeg/png/gif/webp
- D-06 Word/Excel: `FileGrid` gates "Preview" with `PREVIEWABLE_MIMES.has(...)` so only "Download" shows; `FilePreviewDialog` never opened
- D-07 download endpoint wraps with `withPermission('files', 'view', ...)`; `getFileById` enforces port via `file.portId !== portId`
- D-08 `deleteFolderSoftRescue` (`src/lib/services/document-folders.service.ts:294-337`) wrapped in `db.transaction()`, re-parents folders + documents + files explicitly (no CASCADE)
- D-09 `syncEntityFolderName` called in updateClient (clients.service.ts:554), updateCompany (companies.service.ts:187), updateYacht (yachts.service.ts:167)
- D-10 `moveFolder` cycle prevention: rejects self at line 213, `pg_advisory_xact_lock` per port (line 233), walks ancestor chain with `seen` set, checks `cursor === folderId` at each step
- D-11 `assertNotSystemManaged` called in renameFolder (line 172), moveFolder (line 217), deleteFolderSoftRescue (line 299)
- D-12 `listFilesAggregatedByEntity` walks Client↔Companies (via companyMemberships INNER JOIN companies on portId)↔Yachts; cap 20 + total
- D-14 EntityFolderView uses `useAggregatedWorkflows` (filters to INFLIGHT_STATUSES `['draft','sent','partially_signed']`); files with `signedFromDocumentId` show "View signing details"
- D-15 `GET /api/v1/documents/[id]/signing-details` returns `{ data: { workflow, signers, events } }`; `getDocumentById` enforces portId
- D-16 idempotency: outer gate `doc.status === 'completed' && doc.signedFileId` returns; inner `SELECT ... FOR UPDATE` re-check inside transaction
- D-17 Defense-in-depth port at every join: `companies` INNER JOIN with `portId` (line 451), `clients` INNER JOIN with `portId` (line 497), `yachts/files` WHERE portId everywhere, LEFT JOIN `documents` with `or(eq(documents.portId, portId), isNull(documents.id))` (line 588-590). companyMemberships has no portId column but is port-scoped via INNER JOIN to companies/clients
- D-18 `?folder=<uuid>` URL state — three-state (absent → undefined hub root, `=root` → null, `=<uuid>` → uuid); `decodeFolderParam`/`encodeFolderParam` symmetric; deep folder works
- D-19 `ensureEntityFolder` race-safety: fast-path re-SELECT before insert; two distinct catch branches for `uniq_document_folders_entity` (re-SELECT winner) and `uniq_document_folders_sibling_name` (increment suffix)
- D-20 magic-byte: `bufferMatchesMime` in files.ts:58 covers 8 MIME types in-server; presign-PUT only used by berth-pdf/brochure (both stream first 5 bytes + `isPdfMagic()`)
- D-21 filename HTML-escape (`document-sends.service.ts:415-422`)
- D-22 `streamAttachmentOrLink` size-threshold + 24h presigned URL fallback; `fallbackToLinkReason: 'size_above_threshold'` audited
