M5-10 PDF Verification Report Generation¶
Milestone: M5 — Disclosure and Verification SOW Reference: FR3, INT7 Requirement Clarity: ✅ Clear — concrete rules added 2026-08-26 Dev Status: 🟡 Planned, not built — two-phase plan agreed 2026-09-15 (stream-only first, S3 storage second); see update below before starting Phase 1
Overview¶
Let a Staffing Company download and save a PDF report of a verification outcome, for compliance/audit purposes — covering credentials within the whole Velocity ecosystem, not just those issued/verified via this platform.
2026-08-26, from Velocity's CV Capture spec — concrete rules, not previously specified anywhere:
- One credential and its verification checks per page — not one combined table for all disclosed credentials.
- Printing must be disabled on the rendered/viewed report.
- If generation fails, show a clear, visible error. Never render a download link/button with an empty href — a dead-looking-live control is explicitly called out as a failure state to avoid.
- The PDF's content must match the on-screen verification results exactly (same checks, same pass/fail state) — this is a testable acceptance criterion, not just a suggestion (see M5-13).
Backend Tasks¶
- Add a PDF generation library (
pdfkit,puppeteer, or similar) — none currently present - Build a PDF report template: one credential + its 6 checks (see M5-09) + timestamp + issuer/credential metadata per page
- Wire the existing
VerificationReport.pdfS3Keycolumn up to actual storage (S3) and generation - Per the SOW, note VerifyMyCreds already contains this capability — confirm whether that code can be reused rather than built fresh
Frontend Tasks¶
- Add a "Download PDF" action on the verification result view
- Handle the generation-failure case explicitly — visible error state, disabled/absent button rather than a broken link
Dependencies¶
- M5-09 display verification checks — the PDF mirrors this content.
Acceptance Criteria¶
- PDF report generated
- PDF is downloadable
- PDF includes audit evidence (per SOW Appendix 3, FR3 acceptance criteria)
Existing Reference Material¶
Blockers & Risks¶
- Risk: confirm the VerifyMyCreds PDF-generation code-reuse question here too — same underlying uncertainty as M5-08.
Update 2026-09-15 — VerifyMyCreds code reuse resolved: not possible¶
Same finding as M5-00's DEP3 update. VerifyMyCreds' own public Terms of Service grant only "a limited, non-exclusive, non-transferable, non-sublicensable license to access and use the Service", and explicitly list "attempting to derive the source code for the Site and Service" as prohibited. It's a closed hosted SaaS product — there is no PDF-generation code to reuse, regardless of what the SOW assumed. This needs to be built from scratch, same as the rest of M5's verification UI (which already was, once the same dependency turned out not to hold).
Still not started — no PDF library present, pdfS3Key still unused. The disclosure detail page already has a disabled placeholder button (disclosure-requests/[id].tsx, tooltip: "PDF export is not yet available — CSV covers this for now") — so the UI hook point exists, generation itself doesn't.
Update 2026-09-15 — confirmed no Velocity PDF endpoint exists; two-phase build plan agreed¶
Checked both live Velocity API specs (CIH and Registrar — the full OpenAPI JSON pulled for other investigations this milestone) for anything PDF/report-related: nothing exists. This isn't a Velocity integration at all — combined with the DEP3 finding above, it's 100% an in-house rendering task. This also resolves disclosure.controller.ts:200-213's own open question ("PDF ownership... is an open decision") — confirmed in-house, no external service to call.
Also found while planning storage: the M5-00 guide's earlier assumption that the Attachment entity is "reusable as-is for storing verification PDFs" doesn't hold up — checked, and there is no S3 upload code anywhere in this backend at all, despite AWS_S3_BUCKET/credentials already being configured in env. Attachment.s3Key is unused dead schema exactly like VerificationReport.pdfS3Key — nothing has ever actually uploaded a file to S3 in this codebase. Storage isn't "wire up the existing pattern," it's a small service built from scratch.
Agreed plan — split into two phases, so the content/layout gets validated before the storage complexity is added:
Phase 1 — stream-only PDF (build first)¶
- Add
pdfkittoapp/backend/package.json(pure Node, no headless-browser dependency — better fit for this simple templated layout thanpuppeteer) - Replace the
NotImplementedExceptionstub inreportPdf()(disclosure.controller.ts:200-213) with real generation - Build directly from the same data
getDetail()already returns — one page per disclosed credential: candidate name, credential type, disclosed fields (employer/role/dates/location, from thesubjectdata captured 2026-09-15), all 6 checks with pass/fail, verification timestamp. Same source as the on-screen UI, so the PDF can't drift from what's displayed (the acceptance criterion above). - Render in-memory, stream the bytes straight back as the HTTP response (
Content-Type: application/pdf,Content-Disposition: attachment) — no S3, noAttachmentrows, nothing persisted - Frontend: replace the disabled placeholder "Download PDF" button with a real action
- Failure case handled explicitly per the spec's own rule — clear visible error, never a dead-looking link
Phase 2 — secure S3 storage (after Phase 1 sign-off, not started until the rendered output is confirmed correct)¶
- Build a small
StorageService(@aws-sdk/client-s3) — upload + presigned, time-limited download URLs, not public bucket access, for genuinely secure retrieval - Wire
VerificationReport.pdfS3Keyto the real upload - Decide then (easier with real output in hand): re-render fresh every download vs. serve the stored copy vs. re-render-and-replace on demand
Not started yet — this is the plan, execution begins on Phase 1 next.