fix(audit-tier-2): error-surface hygiene — toastError + CodedError sweep

Two mechanical sweeps closing the audit's HIGH §16 + MED §11 findings:

* 38 client components / 56 toast.error sites converted to
  toastError(err) so the new admin error inspector becomes usable from
  user-reported issues — every failed inline-edit, save, send, archive,
  upload, etc. now carries the request-id + error-code (Copy ID action).
* 26 service files / 62 bare-Error throws converted to CodedError or
  the existing AppError subclasses.  Adds new error codes:
  DOCUMENSO_UPSTREAM_ERROR (502), DOCUMENSO_AUTH_FAILURE (502),
  DOCUMENSO_TIMEOUT (504), OCR_UPSTREAM_ERROR (502),
  IMAP_UPSTREAM_ERROR (502), UMAMI_UPSTREAM_ERROR (502),
  UMAMI_NOT_CONFIGURED (409), and INSERT_RETURNING_EMPTY (500) for
  post-insert returning-empty guards.
* Five vitest assertions updated to match the new user-facing wording
  (client-merge "already been merged", expense/interest "couldn't find
  that …", documenso "signing service didn't respond").

Test status: 1168/1168 vitest, tsc clean.

Refs: docs/audit-comprehensive-2026-05-05.md HIGH §16 (auditor-H Issue 1)
+ MED §11 (auditor-G Issue 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-05 20:18:05 +02:00
parent 6a609ecf94
commit fc7595faf8
69 changed files with 426 additions and 166 deletions

View File

@@ -1,7 +1,8 @@
import { env } from '@/lib/env';
import { CodedError } from '@/lib/errors';
import { logger } from '@/lib/logger';
import { getPortDocumensoConfig, type DocumensoApiVersion } from '@/lib/services/port-config';
import { fetchWithTimeout } from '@/lib/fetch-with-timeout';
import { fetchWithTimeout, FetchTimeoutError } from '@/lib/fetch-with-timeout';
interface DocumensoCreds {
baseUrl: string;
@@ -27,19 +28,36 @@ async function documensoFetch(
portId?: string,
): Promise<unknown> {
const { baseUrl, apiKey } = await resolveCreds(portId);
const res = await fetchWithTimeout(`${baseUrl}${path}`, {
...options,
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
...options?.headers,
},
});
let res: Response;
try {
res = await fetchWithTimeout(`${baseUrl}${path}`, {
...options,
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
...options?.headers,
},
});
} catch (err) {
if (err instanceof FetchTimeoutError) {
throw new CodedError('DOCUMENSO_TIMEOUT', {
internalMessage: `${path} timed out after ${err.timeoutMs}ms`,
});
}
throw err;
}
if (!res.ok) {
const err = await res.text();
logger.error({ path, status: res.status, err, portId }, 'Documenso API error');
throw new Error(`Documenso API error: ${res.status}`);
if (res.status === 401 || res.status === 403) {
throw new CodedError('DOCUMENSO_AUTH_FAILURE', {
internalMessage: `${path}${res.status}`,
});
}
throw new CodedError('DOCUMENSO_UPSTREAM_ERROR', {
internalMessage: `${path}${res.status}: ${err}`,
});
}
return res.json();
@@ -242,14 +260,32 @@ export async function sendReminder(
export async function downloadSignedPdf(docId: string, portId?: string): Promise<Buffer> {
const { baseUrl, apiKey } = await resolveCreds(portId);
const res = await fetchWithTimeout(`${baseUrl}/api/v1/documents/${docId}/download`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const path = `/api/v1/documents/${docId}/download`;
let res: Response;
try {
res = await fetchWithTimeout(`${baseUrl}${path}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
} catch (err) {
if (err instanceof FetchTimeoutError) {
throw new CodedError('DOCUMENSO_TIMEOUT', {
internalMessage: `${path} timed out after ${err.timeoutMs}ms`,
});
}
throw err;
}
if (!res.ok) {
const err = await res.text();
logger.error({ docId, status: res.status, err, portId }, 'Documenso download error');
throw new Error(`Documenso download error: ${res.status}`);
if (res.status === 401 || res.status === 403) {
throw new CodedError('DOCUMENSO_AUTH_FAILURE', {
internalMessage: `${path}${res.status}`,
});
}
throw new CodedError('DOCUMENSO_UPSTREAM_ERROR', {
internalMessage: `${path}${res.status}: ${err}`,
});
}
const arrayBuffer = await res.arrayBuffer();
@@ -367,7 +403,14 @@ export async function placeFields(
if (!res.ok) {
const err = await res.text();
logger.error({ docId, status: res.status, err, portId }, 'Documenso v2 placeFields error');
throw new Error(`Documenso v2 placeFields error: ${res.status}`);
if (res.status === 401 || res.status === 403) {
throw new CodedError('DOCUMENSO_AUTH_FAILURE', {
internalMessage: `v2 placeFields ${docId}${res.status}`,
});
}
throw new CodedError('DOCUMENSO_UPSTREAM_ERROR', {
internalMessage: `v2 placeFields ${docId}${res.status}: ${err}`,
});
}
return;
}
@@ -414,7 +457,14 @@ export async function placeFields(
{ docId, status: lastError.status, err: lastError.body, portId },
'Documenso v1 placeField error',
);
throw new Error(`Documenso v1 placeField error: ${lastError.status}`);
if (lastError.status === 401 || lastError.status === 403) {
throw new CodedError('DOCUMENSO_AUTH_FAILURE', {
internalMessage: `v1 placeField ${docId}${lastError.status}`,
});
}
throw new CodedError('DOCUMENSO_UPSTREAM_ERROR', {
internalMessage: `v1 placeField ${docId}${lastError.status}: ${lastError.body}`,
});
}
}
}
@@ -482,6 +532,13 @@ export async function voidDocument(docId: string, portId?: string): Promise<void
if (!res.ok) {
const err = await res.text();
logger.error({ docId, status: res.status, err, portId }, 'Documenso voidDocument error');
throw new Error(`Documenso voidDocument error: ${res.status}`);
if (res.status === 401 || res.status === 403) {
throw new CodedError('DOCUMENSO_AUTH_FAILURE', {
internalMessage: `voidDocument ${docId}${res.status}`,
});
}
throw new CodedError('DOCUMENSO_UPSTREAM_ERROR', {
internalMessage: `voidDocument ${docId}${res.status}: ${err}`,
});
}
}