Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing-
progress redesign + env-to-admin migration + dev-mode banner) with the
2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW).
CRITICAL (3):
- C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths
no longer silently drop interest links
- C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed
- C-03 generic PATCH /interests/[id] no longer accepts pipelineStage —
callers must go through /stage with the override-guard chain
HIGH (14/15):
- H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across
interests/documents/reservations/reminders/invoices (migration 0070)
- H-02 login page reads ?redirect= param with same-origin guard
- H-03 CRM invite token moves to URL fragment so it never lands in
nginx access logs / Referer headers
- H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4)
- H-05 toggleAccount writes an audit row
- H-06 upsertSetting masks any value whose key ends with _encrypted
- H-07 archiveClient cascade fires per-interest audit rows
- H-08 createSalesTransporter applies SMTP_TIMEOUTS
- H-09 AppShell stable children — viewport flip across breakpoint no
longer destroys in-progress form drafts
- H-10 portal documents page swaps Unicode glyph status icons for
Lucide CheckCircle2/XCircle/Circle + aria-labels
- H-12 list components swap alert(...) for toast.warning(...)
- H-13 5 icon-only buttons gain aria-label
- H-14 parseBody treats empty bodies as {}
- H-15 admin layout renders a 403 panel instead of silent bounce
- H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet
MEDIUM (28+):
- M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE
WHEREs across custom-fields, notes (all 6 entity types x update +
delete), client-contacts, yacht ownerClient lookup, webhook reads
- M-D01 documents-hub realtime event-name typo (file:created -> uploaded)
- M-EM01 portal-auth emails thread through portId
- M-EM02 sendEmail accepts cc/bcc params
- M-EM04 notification_digest catalog key
- M-IN01 portal presigned download URLs use 4h TTL
- M-IN02 OpenAI client lazy-instantiated
- M-IN04 stale pdfme refs updated to pdf-lib AcroForm
- M-IN05 umami.testConnection returns tagged union
- M-L01 reservations tenure_type unified with berths
- M-L02 report-generators canonicalize stage values
- M-AU01 audit log placeholder copy fixed
- M-AU04 outcome_set / outcome_cleared distinct audit verbs
- M-NEW-2 activity feed entity name+type separator
- M-R01 portal allowlist narrowed + portal_session backstop in proxy
- M-SC02 companies archived partial index
- M-SC04 audit_logs.searchText documented as DB-managed
- M-S01 storage_s3_access_key_encrypted admin field
- M-U01 audit log empty state uses <EmptyState>
- M-U09 invoice delete dialog -> <AlertDialog>
- M-U10 toast.success on ClientForm + InterestForm create/edit
- M-U11 settings-form-card logo preview alt text
- M-U14 mobile topbar title on clients/yachts/interests/berths
- M-U15 Invoices in mobile More-sheet
LOW (6/8):
- L-AU01 severity defaults for security-relevant verbs
- L-AU02 +13 missing actions in admin audit filter
- L-AU03 +7 missing entity types in admin audit filter
- L-AU04 dead listAuditLogs stubbed
- L-D02 CLAUDE.md Owner-wins chain tightened
Bonus — Document detail polish (#67 partial, 3/6 deliverables):
- state-aware action button per signer
- watcher Add UI with display-name resolution
- cleanSignerName cleanup
Prior session work bundled in:
- Documenso v2 webhook + envelope-ID normalization + sequential signing
- SigningProgress UI redesign (avatars, per-signer state, timestamps)
- env->admin settings registry + RegistryDrivenForm + encrypted creds
- Embedded-signing card + Test connection + setup help
- Dev-mode EMAIL_REDIRECT_TO banner
- Pipeline rules admin page
- Sales email config card
- Audit log details Sheet
- EOI tab: Finalising badge, absolute timestamps, sequential indicator
- Notes pipeline_stage_at_creation (migration 0069)
- Documenso numeric ID dual-key webhook (migration 0068)
- Dimensions criterion copy (migration 0067)
Tests: 1374/1374 vitest pass. tsc clean. lint clean.
See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and
the user-input items still pending.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.8 KiB
assets/
Server-side runtime assets bundled by Next.js (via outputFileTracingIncludes
in next.config.ts). These files are read with fs.readFile from
process.cwd() at runtime, so they are NOT served as public URLs — use
public/ for that.
eoi-template.pdf
The source PDF used by the in-app EOI generation pathway
(src/lib/pdf/fill-eoi-form.ts). It must be the same PDF that the
Documenso EOI template uploads, so both pathways produce equivalent
documents.
The PDF must contain AcroForm fields with these exact names (mirroring the
Documenso template's formValues keys — see
docs/eoi-documenso-field-mapping.md):
| Field name | Type | Filled with |
|---|---|---|
Name |
Text | EoiContext.client.fullName |
Email |
Text | EoiContext.client.primaryEmail |
Address |
Text | street, city, country |
Yacht Name |
Text | EoiContext.yacht.name |
Length |
Text | EoiContext.yacht.lengthFt |
Width |
Text | EoiContext.yacht.widthFt |
Draft |
Text | EoiContext.yacht.draftFt |
Berth Number |
Text | EoiContext.berth.mooringNumber |
Lease_10 |
Checkbox | always false (legacy default — Purchase, not Lease) |
Purchase |
Checkbox | always true |
The fill path flattens the AcroForm after writing values, so the recipient can't edit pre-filled values (yacht dimensions, address, berth number) after the fact. Documenso pathway flattens server-side; the in-app pathway brings the artifact to parity.
Expected sha256
The source PDF's sha256 is pinned to guard against silent template swaps (an unreviewed asset swap would change legal output without a code diff):
ba495fd88d99ebe4b7f61acbe397fb2f1cd116e1e1f1b217de93106915c7c44b
scripts/check-eoi-template-sha.ts verifies this at boot of the in-app
pathway; the function exposes the expected hash via EXPECTED_EOI_SHA256
so tests can re-check after a deliberate template revision.
To intentionally update the template:
- Drop the new PDF as
eoi-template.pdf. - Run
shasum -a 256 assets/eoi-template.pdf. - Update the hash in this README and in
src/lib/pdf/fill-eoi-form.ts(search forEXPECTED_EOI_SHA256).
Override path
In dev/test, set EOI_TEMPLATE_PDF_PATH=/abs/path/to/your/template.pdf to
point at a different file (e.g. a fixture).
How to extract this PDF
The legacy flow uploads this PDF to Documenso template ID 8. To get the exact bytes:
- In Documenso, open the EOI template.
- Download the source PDF.
- Drop it here as
eoi-template.pdf.
Known asset issue: Email field clipped at top
The current eoi-template.pdf has the Email AcroForm field box positioned
slightly too low — long email addresses render with the top pixel row
clipped. Fix is asset-side, not code-side: pdf-lib only fills field
boxes, it can't move them. To resolve:
- Open
eoi-template.pdfin any PDF form editor (Acrobat, PDFescape, PDF Studio, or Documenso's own template editor). - Select the
Emailfield box; nudge itsyorigin down by ~3 pt (or increase its height by ~3 pt) so the rendered text has visual margin from the top edge. - Save → re-upload to Documenso (so both pathways stay in sync) →
bump the sha256 in this README +
EXPECTED_EOI_SHA256per the steps above.
Affects both the in-app pathway (renders via pdf-lib AcroForm fill) and the Documenso pathway (Documenso's own renderer respects the same field geometry).