amruthpillai/reactive-resume · error · ORPCError

BAD_REQUEST

BAD_REQUEST

Error message

Invalid public PDF fallback metadata.

What it means

createPublicResumePdf() validates the semantic-CSS fallback metadata before doing any work: mismatchReason must be one of the eight PUBLIC_RESUME_PDF_MISMATCH_REASONS, and any provided clientRegistryFingerprint/clientAdapterFingerprint must match /^[a-f0-9]{64}$/ (lowercase SHA-256 hex). This is the trust boundary for the public fallback render path.

Source

Thrown at packages/api/src/features/resume/public-pdf.ts:71

	rateLimiter: publicRenderRateLimiter,
	renderPdf: async (input) => (await import("@reactive-resume/pdf/server")).createResumePdfFile(input),
	getFingerprints: async () =>
		(await import("@reactive-resume/pdf/public-projection")).getPublicStyleProjectionFingerprints(),
	now: Date.now,
	observe: console.info,
};

export async function createPublicResumePdf(
	input: CreatePublicResumePdfInput,
	dependencies: PublicResumePdfDependencies = defaultDependencies,
): Promise<{ body: File; filename: string }> {
	const fingerprintPattern = /^[a-f0-9]{64}$/;
	if (
		!PUBLIC_RESUME_PDF_MISMATCH_REASONS.includes(input.mismatchReason) ||
		(input.clientRegistryFingerprint !== undefined && !fingerprintPattern.test(input.clientRegistryFingerprint)) ||
		(input.clientAdapterFingerprint !== undefined && !fingerprintPattern.test(input.clientAdapterFingerprint))
	) {
		throw new ORPCError("BAD_REQUEST", { status: 400, message: "Invalid public PDF fallback metadata." });
	}
	const currentUserId = await dependencies.resolveCurrentUserId(input.requestHeaders);
	const resume = await loadAuthorizedPublicRenderResume(
		{
			username: input.username,
			slug: input.slug,
			requestHeaders: input.requestHeaders,
			trustedClient: input.trustedClient,
			...(currentUserId ? { currentUserId } : {}),
		},
		dependencies,
	);
	dependencies.rateLimiter.consume({ trustedClient: input.trustedClient, resumeId: resume.id });
	const startedAt = dependencies.now();
	const fingerprints = await dependencies.getFingerprints();
	const event = (success: boolean) => {
		dependencies.observe({
			name: "semantic_css.render_fallback",

View on GitHub (pinned to 3a5b12e2a4)

Solutions

  1. Send only mismatchReason values from PUBLIC_RESUME_PDF_MISMATCH_REASONS.
  2. Compute fingerprints as lowercase hex SHA-256 (exactly 64 chars).
  3. Omit fingerprints when unknown rather than sending malformed values.

Example fix

// before
createPublicResumePdf({ ..., mismatchReason: 'unknown', clientRegistryFingerprint: 'ABC123' });
// after
const REASONS = ['missing-projection','format-version','language-version','semantic-tree-version','registry-fingerprint','adapter-fingerprint','render-data-hash','invalid-projection'] as const;
const fp = crypto.createHash('sha256').update(payload).digest('hex'); // 64 lowercase hex
createPublicResumePdf({ ..., mismatchReason: REASONS[i], clientRegistryFingerprint: fp });
Defensive patterns

Strategy: validation

Validate before calling

const REASONS = ['missing-projection','format-version','language-version','semantic-tree-version','registry-fingerprint','adapter-fingerprint','render-data-hash','invalid-projection'] as const;
const FP = /^[a-f0-9]{64}$/;
function validateFallbackMeta(reason, registryFp, adapterFp) {
  if (!REASONS.includes(reason)) throw new Error(`Bad mismatchReason: ${reason}`);
  if (registryFp !== undefined && !FP.test(registryFp)) throw new Error('Bad registry fingerprint');
  if (adapterFp !== undefined && !FP.test(adapterFp)) throw new Error('Bad adapter fingerprint');
}

Type guard

function isMismatchReason(value: unknown): value is PublicResumePdfMismatchReason {
  return typeof value === 'string' && PUBLIC_RESUME_PDF_MISMATCH_REASONS.includes(value as any);
}

Prevention

When it happens

Trigger: Calling the public resume PDF fallback with an unknown mismatchReason, a fingerprint of the wrong length, or non-hex characters (uppercase, a 'sha256:' prefix, base64, etc.).

Common situations: A client using an outdated reason vocabulary after a schema bump, a fingerprint computed with a different hash algorithm or encoding, or a malformed probe request.

Related errors


AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12). Data as JSON: /api/errors/3c07acd980d3ad89. Report an issue: GitHub.