fix(audit-wave-9): PDF correctness + brand asset hardening (pdf-auditor)
Address the pdf-auditor findings that survived the 2026-05-12 PDF stack overhaul (pdfme → react-pdf). Items C-2/C-3 (tiptap-to-pdfme bugs) were resolved when that 571-LOC bridge was deleted; remaining items: - **M-7 wrong-port brand fallback** — replace `'Port Nimara'` defaults in PDF-rendering services. `reports.service` and `expense-export` throw when the port row is missing (the job is FK-keyed on a real port, so absence = broken state, must not stamp a competitor brand). `record-export` uses `'(port)'` as the visible placeholder. - **M-2 silent field drift in fill-eoi-form** — promote the always-silent catch in `setText` / `setCheckbox` to log a structured warning per missing field (mirroring the existing `setBerthRange` pattern). A re-cut template with drifted AcroForm field names now surfaces in ops logs instead of shipping with empty values. - **M-3 form not flattened** — `fillEoiFormFields` now flattens the AcroForm before save. Documenso pathway flattens server-side; this brings the in-app pathway to parity, so the signer can't edit pre-filled yacht dimensions / address / berth number after the fact. - **M-1 PDF metadata** — set Title / Author / Subject / Lang / Producer / Creator on the generated EOI PDF for downstream readers and a11y tooling. - **M-4 noisy berth-range warnings** — downgrade per-mooring warn to debug; emit a single summary warn per call when any passthrough occurred. Multi-berth EOIs with archived/legacy moorings no longer spam the log on every render. - **M-6 source PDF sha pinning** — pin `assets/eoi-template.pdf` sha256 via `EXPECTED_EOI_SHA256` (exported for tests); `loadEoiTemplatePdf` warns once per process when the bytes drift without an explicit hash bump. Documented the intentional-update workflow in `assets/README.md`. Tests updated in `tests/unit/pdf/fill-eoi-form.test.ts` to reflect flatten + metadata (form fields are gone after flatten; pdf-lib has no getLanguage so we assert the other setters round-trip). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -134,10 +134,13 @@ export async function exportParentCompany(
|
||||
db.query.ports.findFirst({ where: eq(ports.id, portId) }),
|
||||
resolvePortLogo(portId),
|
||||
]);
|
||||
if (!port) {
|
||||
throw new Error(`Cannot render expense export: port ${portId} not found.`);
|
||||
}
|
||||
|
||||
return renderPdf(
|
||||
<ParentCompanyExpensePdf
|
||||
portName={port?.name ?? 'Port Nimara'}
|
||||
portName={port.name}
|
||||
logoBuffer={logo.buffer}
|
||||
rows={convertedRows}
|
||||
subtotal={subtotal}
|
||||
|
||||
@@ -107,7 +107,7 @@ export async function exportClientPdf(clientId: string, portId: string): Promise
|
||||
|
||||
return renderPdf(
|
||||
<ClientSummaryPdf
|
||||
portName={port?.name ?? 'Port Nimara'}
|
||||
portName={port?.name ?? '(port)'}
|
||||
logoBuffer={logo.buffer}
|
||||
client={{
|
||||
fullName: client.fullName,
|
||||
@@ -218,7 +218,7 @@ export async function exportBerthPdf(berthId: string, portId: string): Promise<U
|
||||
|
||||
return renderPdf(
|
||||
<BerthSpecPdf
|
||||
portName={port?.name ?? 'Port Nimara'}
|
||||
portName={port?.name ?? '(port)'}
|
||||
logoBuffer={logo.buffer}
|
||||
berth={{
|
||||
mooringNumber: berth.mooringNumber,
|
||||
@@ -315,7 +315,7 @@ export async function exportInterestPdf(interestId: string, portId: string): Pro
|
||||
|
||||
return renderPdf(
|
||||
<InterestSummaryPdf
|
||||
portName={port?.name ?? 'Port Nimara'}
|
||||
portName={port?.name ?? '(port)'}
|
||||
logoBuffer={logo.buffer}
|
||||
interest={{
|
||||
id: interest.id,
|
||||
|
||||
@@ -230,8 +230,16 @@ export async function generateReport(reportJobId: string): Promise<void> {
|
||||
const port = await db.query.ports.findFirst({
|
||||
where: eq(ports.id, portId),
|
||||
});
|
||||
const portName = port?.name ?? 'Port Nimara';
|
||||
const portSlug = port?.slug ?? 'port';
|
||||
// The job is keyed on a real port id - port should always resolve here.
|
||||
// If the row is missing, the deployment is in a broken state and we
|
||||
// must not stamp a competitor port's brand on the artifact. Fail loudly.
|
||||
if (!port) {
|
||||
throw new Error(
|
||||
`Cannot render report PDF: port ${portId} not found. Check report job FK integrity.`,
|
||||
);
|
||||
}
|
||||
const portName = port.name;
|
||||
const portSlug = port.slug;
|
||||
const logo = await resolvePortLogo(portId);
|
||||
|
||||
// 6. Render PDF via react-pdf brand kit
|
||||
|
||||
Reference in New Issue
Block a user