fix(audit): documenso — M2 (reservation EOI-milestone pollution), L11 (v2 numericId GET fallback), L12 (API URL normalize/validate), L13 (event dedup)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -459,6 +459,31 @@ export async function generateDocumentFromTemplate(
|
||||
portId,
|
||||
).then(normalizeDocument);
|
||||
|
||||
// Resolve an authoritative numeric pk. `/template/use` returns the
|
||||
// `envelope_xxx` string under `id`/`envelopeId` but does NOT reliably
|
||||
// surface the internal numeric pk, so `created.numericId` is frequently
|
||||
// null. DOCUMENT_COMPLETED (and other v2 webhooks) carry ONLY that
|
||||
// numeric pk as `payload.id`, and `resolveWebhookDocument` matches it
|
||||
// against `documents.documenso_numeric_id`. Persisting null there means
|
||||
// the webhook resolves against neither column and the completion is
|
||||
// dropped (signed PDF never downloads, stage never advances, no
|
||||
// completion email/tenancy) until the poll worker reconciles by
|
||||
// envelope id. Re-fetch the full envelope (GET /api/v2/envelope/{id})
|
||||
// when the numeric pk is missing so we persist a non-null value.
|
||||
let numericId = created.numericId;
|
||||
if (!numericId) {
|
||||
try {
|
||||
const fetched = await getDocument(created.id, portId);
|
||||
if (fetched.numericId) numericId = fetched.numericId;
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
{ docId: created.id, err: err instanceof Error ? err.message : err },
|
||||
'Documenso envelope re-fetch for numericId failed - documenso_numeric_id will be null; ' +
|
||||
'completion webhooks rely on the poll-worker reconciliation until then.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const desiredTitle =
|
||||
typeof v2Payload.title === 'string' && v2Payload.title.length > 0 ? v2Payload.title : null;
|
||||
// `/template/use` silently drops the `meta` field on the request body -
|
||||
@@ -571,11 +596,12 @@ export async function generateDocumentFromTemplate(
|
||||
const normalized = normalizeDocument({
|
||||
envelopeId: distributed.id ?? created.id,
|
||||
// Distribute doesn't return the numeric id, so we synthesize it
|
||||
// from the original /template/use response by passing the numeric
|
||||
// id as Documenso's `id` field - normalizeDocument picks it up
|
||||
// as numericId. Without this, the row would lose its numeric id
|
||||
// on distribute and webhooks couldn't resolve back to it.
|
||||
id: created.numericId,
|
||||
// from the authoritative numericId resolved above (created response
|
||||
// or envelope re-fetch) by passing it as Documenso's `id` field -
|
||||
// normalizeDocument picks it up as numericId. Without this, the row
|
||||
// would lose its numeric id on distribute and webhooks couldn't
|
||||
// resolve back to it.
|
||||
id: numericId,
|
||||
status: 'PENDING',
|
||||
recipients: distributed.recipients,
|
||||
});
|
||||
@@ -585,7 +611,9 @@ export async function generateDocumentFromTemplate(
|
||||
{ docId: created.id, err: err instanceof Error ? err.message : err },
|
||||
'Documenso envelope distribute failed - signingUrl will be null. Send-invitation will fail until the envelope is distributed.',
|
||||
);
|
||||
return created;
|
||||
// Preserve the authoritative numericId resolved above so the persisted
|
||||
// documenso_numeric_id is non-null even when distribute fails.
|
||||
return { ...created, numericId };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user