Date: 2026-04-26
Test count: 1017 / 1017 passing (29 test files)
Phase 44 tests: 46 new tests in server/phase44.test.ts
Phase 44 delivers five production-ready features across the ROPA, Automated Decisions, Privacy Notices, Accreditation, and Public Registry modules, together with a new ropaPdf.ts module and a live stats panel on the Home page.
| File | Description |
|---|---|
server/ropaPdf.ts |
New module exporting generateRopaPdf(records, orgName) using PDFDocument from pdfkit. Generates a formatted PDF with a cover page, summary table, and per-record detail sections. |
server/phase44.test.ts |
46 vitest tests covering all Phase 44 features. |
CHANGELOG_PHASE44.md |
This file. |
A new export procedure was added to the ropaRouter using the PBAC exportProcedure middleware. It accepts { format: "json" | "csv" | "pdf" } and rejects unauthenticated requests with UNAUTHORIZED. For PDF format it delegates to generateRopaPdf from ropaPdf.ts; for JSON/CSV it returns the raw records.
A new update procedure was added using the PBAC updateProcedure middleware to allow editing existing ROPA records.
Two new protected procedures were added:
requestReview— accepts{ id: z.number().positive() }, setsreview_requested = trueandhuman_review_requested_at = NOW()on the matchingautomated_decision_recordsrow. Rejects unauthenticated requests withUNAUTHORIZEDand missing/negative IDs withBAD_REQUEST.completeReview— accepts{ id: z.number().positive(), outcome: z.string().min(1) }, records the review outcome andreview_completed_attimestamp. Rejects unauthenticated requests withUNAUTHORIZED, missing outcome withBAD_REQUEST, and empty outcome strings withBAD_REQUEST.
updateprocedure added using the PBACupdateProceduremiddleware. Accepts{ id, title?, content?, status? }wherestatusisz.enum(["draft", "published", "archived"]). Rejects invalid status values withBAD_REQUESTand unauthenticated requests withUNAUTHORIZED.deleteprocedure confirmed to usedeleteProcedurePBAC middleware.
A new submitRenewal protected procedure was added. It creates a renewal application record in the accreditation_applications table with application_type = 'renewal' and status = 'pending'. Rejects unauthenticated requests with UNAUTHORIZED.
The sectorStats query was fixed to use getSharedPool with a raw pg.Pool.query() call instead of the drizzle sql.raw() template. This resolves a runtime error caused by a reference to ndpc_registration_status, a column that does not exist in the organizations table. The procedure remains a publicProcedure and returns an array of { sector, count } objects.
Column name fixes applied:
purpose_of_processing→purposelawful_basis→ropa_lawful_basiscross_border_transfer_countries→cross_border_countriesdata_subject_categories→data_subjects
These align the Python worker with the actual ropa_records table schema.
An export mutation was wired to the ropa.export procedure. A dropdown allows the user to select JSON, CSV, or PDF format. On success the response is downloaded as a file. On error a toast is shown.
Two mutations were wired:
requestReview— triggered by a "Request Human Review" button on each record row. Shows a success toast on completion.completeReview— triggered by a "Complete Review" button with an outcome input field. Shows a success toast on completion.
An update mutation was wired for the publish workflow. A "Publish" button on each draft notice row calls privacyNotices.update with { id, status: "published" }. Shows a success toast on completion.
A submitRenewal mutation was wired with a confirmation dialog. The user is prompted to confirm before the renewal application is submitted. Shows a success toast on completion.
A requestReview mutation was wired inline on the Automated Decisions panel. The DPO can trigger a human review request directly from the workbench without navigating to the full Automated Decisions page.
A publicRegistry.sectorStats live query was wired to the hero stats panel. The panel now shows real-time sector breakdown data (sector name and count) fetched from the database without requiring authentication.
The 46 tests in server/phase44.test.ts cover:
| Test Group | Tests |
|---|---|
| Server Health | 1 |
| ropa.export PBAC | 3 |
| automatedDecisions.requestReview | 3 |
| automatedDecisions.completeReview | 3 |
| privacyNotices.update PBAC | 3 |
| accreditation.submitRenewal PBAC | 2 |
| publicRegistry.sectorStats | 2 |
| ropaPdf.ts Module | 3 |
| ropa_generator.py column names | 4 |
| RopaRecords.tsx export mutation | 2 |
| AutomatedDecisions.tsx mutations | 3 |
| PrivacyNotices.tsx update mutation | 2 |
| AccreditationStatus.tsx submitRenewal | 2 |
| DpoDashboard.tsx requestReview | 2 |
| Home.tsx sectorStats query | 2 |
| ropa router procedures | 3 |
| privacyNotices router procedures | 2 |
| ropa.list Public Access | 2 |
| automatedDecisions.list Public Access | 1 |
| privacyNotices.list Public Access | 1 |
Note on superjson: The two array-return tests (sectorStats returns an array, ropa.list returns an array) unwrap the superjson envelope {json: [...]} before asserting Array.isArray(), since the tRPC server uses superjson as its transformer.
| Phase | Tests Added | Total |
|---|---|---|
| Phase 1–41 | 895 | 895 |
| Phase 42 | 39 | 934 |
| Phase 43 | 37 | 971 |
| Phase 44 | 46 | 1017 |