23 — CPD & Learning (the CPD LMS)¶
Audience: everyone on the Regnify project — representatives, compliance officers, HR/organisation admins, and the product/engineering team. This is the single reference for how Continuing Professional Development (CPD) works in Regnify, from the MAS rule it satisfies to every screen a user touches.
Regnify includes a native Learning Management System (LMS) — a professional, Pupilfirst-class learning experience (courses → lessons → auto-graded assessments → completion → verifiable certificate) built as an ordinary Regnify module. Completing an accredited course automatically credits the representative's statutory CPD ledger, and that ledger feeds directly into the annual Fit & Proper assessment. There is no second system to reconcile: the LMS, the CPD register, and the Fit & Proper health flag are one dataset.
1. Why CPD exists — the MAS requirement¶
Continuing Professional Development is a mandatory, ongoing obligation for every licensed representative under two MAS instruments (both effective 1 April 2024):
- FAA-N26 §5.4 — for representatives under the Financial Advisers Act (FA reps). Superseded FAA-N13.
- SFA 04-N22 §8.4 — for representatives under the Securities and Futures Act (CMS reps). Superseded SFA 04-N09.
Annual hour requirements, by licence scope¶
The required hours are scope-dependent. Regnify models three scopes:
| Scope | Total CPD / cycle | of which Core (accredited) | Supplementary |
|---|---|---|---|
FA (Full) — FA_FULL |
30 h | 6 h | 24 h |
FA (Limited) — FA_LIMITED |
16 h | 6 h | 10 h |
CMS — CMS |
9 h | 6 h | 3 h |
Core hours are ethics or rules-&-regulations topics, and — critically — they only count toward the Core requirement if the course/activity is accredited by an eligible body:
- FA reps: IBF, SCI, or CPF Board.
- CMS reps: IBF only (the stricter SFA-CPD gate).
An accreditation also has a validity period, and an expired one no longer satisfies the Core minimum. A course records the window its accreditation runs for (both dates optional — a course with no window stated is treated as always valid), and the check is made against the completion date: a representative who finished while the accreditation was live keeps their Core credit afterwards, because those hours were properly earned on the day. Hours completed outside the window are still credited — as Supplementary, not Core — and the resulting record and certificate state that the course was not accredited at that time, while keeping the body and reference so it is clear which accreditation had lapsed.
Unaccredited "core-topic" hours do not satisfy the Core minimum — they spill over and count as Supplementary. This gate is enforced server-side; a representative can never self-certify a manual CPD entry as accredited — a rep-authored record is forced unaccredited whatever it asks for. Marking a record accredited requires a compliance or HR administrator role, exercised after verifying the evidence.
Cycle, exemptions, pro-ration, retention¶
- Cycle — a calendar year (1 Jan – 31 Dec). Hours do not carry forward; the shortfall is measured at cycle close.
- First-year rule — in the calendar year a representative first enters the industry, the requirement depends on the entry date: appointed on or before 1 July → the full requirement; after 1 July and on or before 1 Oct → a pro-rated requirement (same per-day fraction as mid-cycle appointment, anchored to the industry-entry date); after 1 Oct → no requirement for that year (targets are shown as informational).
- Pro-ration — for a partial year, the requirement is pro-rated by days-appointed ÷ 365, Core and Supplementary separately, each rounded up. Earned hours are never pro-rated — only the target is.
- Retention — CPD records are retained for at least 5 years (electronic record-keeping obligation). Regnify persists the ledger and the immutable certificate snapshot.
- Fit & Proper tie — CPD standing is one input to the representative's Fit & Proper health flag, and is snapshotted into the immutable
Form3ACertificationat the moment a declaration is marked Good.
Note on terminology. Earlier builds used an "Ethics / Product / Other" hour split. That model is retired. The correct MAS taxonomy is Core / Supplementary + accreditation, as above. (See §11 for the migration note.)
2. The roles¶
| Role | What they do in CPD |
|---|---|
Representative (FI_USER / REP_USER) |
Browse the catalog, enrol, learn, take assessments, earn certificates, self-log external CPD, track progress. |
Course author (ORG_ADMIN / COMP) |
Author courses, modules, lessons, content blocks and assessments; publish; reorder; archive. |
Compliance officer (COMP / ORG_ADMIN) |
Oversee the rep register; revoke a certificate; grant a CPD waiver; backfill / verify a rep's external CPD record (incl. marking it accredited). |
HR administrator (HR_ADMIN) |
Reads the organisation-scoped Rep Register; backfills / verifies a rep's external CPD record (incl. marking it accredited) and withdraws an enrolment. Granting waivers, voiding/replacing records, revoking certificates and assigning courses are COMP / ORG_ADMIN acts. Not a course author, and not an approval tier in the Form 3A chain. |
System admin (SYS_ADMIN) |
Platform-global course authoring. Deliberately barred from representative PII. |
HR sees the register, not the authoring desk — and not the compliance console either.
HR_ADMINopens the Rep Register and can backfill CPD records and withdraw enrolments, but the waiver / revocation / correction surfaces are gated toCOMP/ORG_ADMIN, and/admin/coursesis not an HR route — the guard redirects HR to the dashboard (course authoring stays withORG_ADMIN/COMP, plusSYS_ADMINfor platform-global courses).
HR's CPD surface: the same organisation-scoped Rep Register as compliance. The Courses route is absent from HR's navigation and redirects when forced:
The guard sends HR straight back to the dashboard — no authoring surface, no error page, no data leak.
All access is organisation-scoped: a compliance officer only ever sees representatives in their own organisation, enforced by a single server-side choke point (AuthzService.assertCanAccessRep). See §8.
3. The representative journey¶
3.1 The CPD dashboard — /rep/cpd¶
The home of a representative's CPD standing. Three meters show accredited Core, Supplementary, and Total hours against the scope-branched target, plus a cycle summary, first-year-exemption note, an accreditation-gap callout, and a "Your courses & certificates" list linking to every credential earned (across all cycles).
A representative mid-cycle: Scope FA (Full), Cycle 2026, 4.8 h to the target by 31 Dec 2026. Core (accredited) 0/6 h with the "6 h to go" callout, Supplementary 25.3/24 h (target met), Total 25.3/30 h. The Total is the server's true earned total — it is not simply Core + Supplementary (that would double-count Core hours that overflowed into the Supplementary pool). The "Your courses & certificates" list links every credential — here the just-completed AML course, Certified, 1.3 CPD h (the dashboard
rounds to one decimal — the course credits 1.25 h, as the catalog and
certificate show).
The same dashboard for a representative whose cycle is satisfied: Core 14/6 h, Supplementary 32/24 h, Total 38/30 h, every meter reading Target met, and the banner flips to "All CPD targets met for this cycle". The credential list shows the accredited Core course behind those hours.
3.2 The learning catalog — /rep/learning¶
Every course available to the representative, with search and filters by scope, category (Core/Supplementary) and accreditation. Each card shows CPD hours, the Core/Supplementary tag, the accreditation badge, and a state-aware call to action (Enrol, Continue, Review course). Draft courses are never shown to learners.
The catalog with scope/category/accreditation filters. Card CTAs are
state-aware — "View & enrol" on a fresh course, "Continue" while in progress,
"Completed" once done. This representative has completed the AML course, so
its card carries the Completed badge beside enrol-state cards (an in-progress
course would show "Continue" instead).
3.3 Enrolment, lessons & the certificate — /rep/learning/:id¶
Enrolling opens the syllabus (modules → lessons), a live progress bar, and — for an active course — Start / Continue and Unenrol controls plus a "Go to assessment" finish step (enabled once every lesson is done).
An enrolled, in-progress AML course: 1 of 40 lessons complete with the progress bar and per-lesson state, Continue on the overview, and the "Finish the course → Go to assessment" step disabled until every lesson is done. Unenrol remains available for elective enrolments until completion.
Once completed, the course page shows the verifiable certificate.
A completed course with its valid certificate open: holder, course, Supplementary / 1.25 CPD hours / Cycle 2026, the certificate serial (REGN-CPD-2026-…), the issue date, and the copy-able public verification link with Download PDF and Open verification. The full syllabus below shows every lesson complete. (A revoked credential instead shows a red "Revoked … no longer valid" banner.)
Inside a lesson, the representative reads the content blocks (markdown, video, embeds, files) and marks the lesson complete.
The lesson player renders the authored content blocks; completing all lessons unlocks the assessment.
3.4 The assessment — /rep/learning/:id/assessment¶
An auto-graded multiple-choice assessment gates completion. The answer key never leaves the server — the learner-facing query strips it, and submissions are graded server-side. The page shows the question progress and the pass mark; if a learner exhausts every attempt without passing, the quiz locks.
Attempts are evidence, and they are retained. Withdrawing and re-enrolling clears lesson progress but never the assessment record — a representative cannot clear a failed attempt about themselves. The only way to reopen a locked assessment is for compliance to grant a further attempt from the course's Assignment & completion panel, which requires a reason and is written to the audit log. Once granted, the learner's remaining-attempt count reflects it immediately.
The live AML assessment: "Question 1 of 15", the 80% pass mark and a per-question progress bar. A disabled control always says why — here "Select an option to continue." under Next; on the last question the footer counts answered questions ("N of 15 answered — every question needs an answer before you can submit.").
Every submission answers back. A passed attempt shows the score card ("Assessment passed — 100%") above the Complete course & claim CPD action that credits the ledger and mints the certificate. A failed attempt shows the same card in red — the score, the pass mark, and how many attempts remain — above a fresh retake, so a learner is never left wondering whether the submit did anything:
The retake screen after a failed attempt: verdict card (40% against the 80% pass mark, "attempts are unlimited on this course") above a clean question 1. This card is the fix for the round-111 defect where a failed submit re-rendered the quiz with no verdict at all.
3.5 The CPD Log (itemised register) — /rep/cpd/log¶
The representative's auditable CPD register: the accreditation-gated YTD totals (matching the dashboard exactly) plus every itemised record — course-earned and manually self-logged. Records sourced from a revoked certificate are excluded here too, so the itemised list can never disagree with the aggregate. Representatives may self-log external CPD, but the system forces such entries unaccredited — only compliance can mark a record accredited.
The itemised CPD register for the same representative: Core 0.0 / Supplementary 25.3 / Total 25.3, agreeing with the dashboard, with the Core-shortfall callout above. Each row shows the course, provider, date, category, accreditation and source (course vs manual) — including the AML course credited on 04 Aug 2026.
3.5b Logging external CPD, with evidence¶
Hours earned outside the platform are logged from the CPD Log. The entry form takes the provider's certificate of attendance or completion (PDF or image, up to 5 MB) alongside the activity details. The file uploads before the record is created, so a logged hour is never momentarily unsubstantiated and a failed upload creates no record at all. Compliance reviews the attachment when verifying the hours — without it, an externally-earned hour is an unverified self-assertion, and it is those hours that make up most of a representative's statutory total.
Evidence is bound to the representative it belongs to: a document uploaded for one rep cannot be attached to another's record.
3.6 The Passport — /rep/passport¶
The representative's shareable Fit & Proper "passport", which includes the CPD register alongside CMFAS module status and exam attempts. It can be exported for MAS or a prospective employer FI, and the itemised CPD section stays consistent with the gated totals (revoked entries excluded).
The passport aggregates CPD and CMFAS standing into one verifiable record; an exam-attempts section joins it whenever the representative has exam attempts on file.
3.7 Reggie — the course- and CPD-aware assistant¶
Reggie, the platform concierge, is mounted on every CPD surface and knows which course or lesson the representative is looking at (the page declares its entity, and Reggie's room carries that context with every message).
- Learners can ask substantive questions about the material they are reading. Reggie answers from the MAS knowledge base — the same 200+ notices and circulars that power the regulatory assistant — with document citations the learner can open, and ends every regulatory answer with the verify-before-acting note:
A learner on the AML course asks how to handle a prospective client evasive
about the origin of funds. The frame captures the close of Reggie's answer —
the decline / exit-the-relationship guidance when CDD cannot be completed,
the verify-before-acting note, and the four citation chips underneath
(FAA-N06 and SFA 04-N02 guidelines, STR reporting to STRO). Grounded,
never invented.
- Compliance officers and authors can ask Reggie to review a course the way a QA reviewer would: content placeholders, pagination, regulatory accuracy against the MAS knowledge base, CPD-accreditation claims, and pedagogy. The review is advisory and not stored — it informs the author; it never edits the course or writes to any register:
From the AML course editor, a compliance officer asks for a QA review and
Reggie returns the completed report — the capture shows its opening:
Overall Status: FAIL — 18 findings (9 high, 6 medium, 3 low) across six
quality checks, headed by the accreditation-claim seed defect. Below the
captured frame the report continues with the [insert hours] placeholder and
the pagination-break findings, and it closes honestly under provider
degradation: claims whose knowledge-base retrieval did not complete in time
are surfaced as "the regulatory review is incomplete — verify the MAS
references manually before release", never folded into a clean pass. Every
answer closes with the verify-against-mas.gov.sg note.
- CPD standing questions ("who in my org is below target?", "what is this rep's shortfall?") route to the same server-computed figures the Rep Register shows — Reggie reports the gated totals, never a re-derived number:
The compliance officer asks who is below the 2026 target; Reggie reads the
rep register (tool trace in frame) and opens with the server-computed summary
— "5 representatives have not met their 2026 CPD target under MAS
Notice FAA-N26" — then names each shortfall with the same Core /
Supplementary / Total figures the Rep Register behind the panel shows. The
per-rep rows continue below the captured frame (Ong Zhi Hao 13.0/24
Supplementary; Demo Rep2 at 25.25/30 with 0.0/6 accredited Core, matching
§3.1; Ahmad bin Ibrahim red on both pools). Reggie never re-derives these
figures.
4. Authoring courses & assessments (author role)¶
Course authors (ORG_ADMIN / COMP for org courses; SYS_ADMIN for
platform-global ones) manage the catalog at /admin/courses.
The authoring catalog as an ORG_ADMIN sees it: every course their organisation can see, with status. Platform-global drafts (e.g. the SYS_ADMIN ESG draft) list only for SYS_ADMIN — see §8.
The course editor authors metadata, the full structure (modules → lessons → content blocks), and the assessment. Modules, lessons, content blocks, and assessment questions/options can all be reordered with move-up/move-down controls; the Core/Supplementary category and the accreditation stamp determine how completions credit CPD. Publishing is gated by validation (every question needs ≥2 options and ≥1 correct answer; single-answer questions exactly one; True/False exactly two; a course needs ≥1 lesson).
Learning materials. A FILE content block takes a real file upload (25 MB) with a display name of your choosing, or a URL if the material is hosted elsewhere. Uploads land in S3 under courses/{courseId}/lessons/{lessonId}/…; only a durable key is stored, and a short-lived signed link is minted per click behind the same read gate as the curriculum — so a link can never expire in place, and can never be handed to someone who may not read the course.
Two authoring options worth knowing. Lessons must be completed in order locks each lesson until its predecessor is done (enforced server-side; the lesson list shows a padlock). Shuffle answer options varies option order per learner — grading compares option IDs, so order can never affect the result.
Re-issuing a course annually. "Duplicate" on the catalog copies structure, assessment and uploaded materials into a new draft ("AML 2026" → "AML 2027"). Each uploaded file is physically copied into the new course's own storage namespace, so the copy is independent: archiving the original never disturbs learners on the duplicate. Enrolments, completions, certificates and earned CPD are never copied — those belong to the representatives who earned them.
The Course details zone: category, hours, framework, scope tags and the
IBF/SCI accreditation stamp. The AML course carries no accreditation body —
unaccredited completions credit as Supplementary only.
The Structure zone: module → lesson → content block, each row with
move-up/down, edit and delete controls, plus add-lesson / add-module forms.
The intro lesson's markdown carries the course's at-a-glance facts (8 modules,
~75 minutes, pass mark 80%).
The Assessment zone: a single-choice question with its options, each
option carrying a Mark correct toggle and reorder controls, plus the
add-option form (the add-question form continues below the frame).
5. Compliance oversight (COMP / ORG_ADMIN)¶
5.0 Assigning a course & tracking completion — /admin/courses/:id¶
Compliance-driven training is course-centric: open a course and the Assignment & completion panel lists every representative in your organisation and where each one stands — Completed, In progress, Overdue, Withdrawn, Not enrolled. The five counts partition your roll exactly, so they always reconcile to the headcount.
The panel on the AML course: the five reconciling counts, the optional deadline with "Select all outstanding", and the per-representative table — status, observed time, attempts (best score), due date, completion date and the certificate serial each completion minted.
Select representatives (or "Select all outstanding"), set an optional deadline, and assign. Assignment is idempotent — re-running over a group is safe, and each representative's result says which path was taken:
| Result | Meaning |
|---|---|
| Assigned | No prior enrolment — a new one was created |
| Reactivated | A withdrawn enrolment was restarted from a clean slate |
| Deadline updated | Already in progress — the mandate and date were stamped, progress kept |
| Already completed | Left untouched; the obligation is already met |
The whole batch is written in one transaction, so a partial failure can never leave a representative mandated with no entry in the audit log.
Every representative taking on the obligation is notified at assignment time, whether or not a deadline was set — an obligation nobody was told about cannot be enforced, and the firm cannot evidence that it communicated it. A representative whose deadline later moves gets a distinct "deadline changed" notice; re-running an unchanged assignment notifies nobody, so a safe re-run stays quiet. Making an elective course mandatory counts as a new obligation and is announced as one.
A deadline also schedules a reminder seven days ahead, delivered through the existing reminder pipeline. Re-assigning replaces the pending reminder rather than stacking a second one, and a representative who has already completed the course is never reminded.
Representatives see the mandate as a "Required — due {date}" badge (turning red and reading "Overdue" once the date passes) on the catalog, the course page, and a Required training card on their CPD dashboard, soonest deadline first.
Lift mandate releases a representative from an obligation assigned in error — the wrong person, a duplicate, a course withdrawn from the programme. The reason is required and recorded. Their lesson progress and assessment history are kept; only the obligation, its deadline and its reminders end. It appears only on a live mandated enrolment: a representative may withdraw from an elective course themselves, and a completed course has nothing left to release. A representative cannot lift a mandate on themselves — the button on their own course page tells them to ask compliance, and this is the control that answers that.
Download CSV exports the table for evidence or offline follow-up.
What can and cannot be edited once representatives are enrolled¶
Course content stays editable at all times — lesson text, material, question wording, pass marks, deadlines. What is frozen once even one representative is enrolled is removing things:
| Action | While anyone is enrolled |
|---|---|
| Edit a lesson, module, question or option | Allowed |
| Delete a lesson or a module | Refused |
| Delete the assessment (if it has questions) | Refused |
| Delete a question-less assessment | Allowed — it gates nothing |
| Delete a question | Refused if it has been answered, or if it is the assessment's last one |
| Delete an option | Refused if the question has been answered, if it would leave fewer than two, or if it would leave no correct answer |
| Add the assessment's first question | Refused while anyone is part-way through — it would impose a test they never agreed to sit |
| Change the course's CPD hours or category | Refused while anyone is part-way through |
| Change the course's accreditation (body, code, validity window, accredited flag) | Allowed — see below |
The reason is arithmetic, not caution. Completion is measured against the course as it stands now: a representative is finished when they have completed every lesson the course currently has, and passed the assessment if it currently has questions. Removing a lesson therefore removes it from the denominator, and every enrolled representative who never opened it is instantly complete — credited the full statutory hours and issued a verifiable certificate for material they never saw. Deleting the assessment does the same to the test the course was accredited on. Their per-lesson progress records are destroyed at the same moment, so nothing in the register shows the shortfall.
Why accreditation is treated differently from hours. The hours and the category are what the platform promises a representative: change them mid-course and someone who enrolled on a 2-hour Supplementary course is credited 8 Core hours, or the reverse — someone who did the work fails their cycle. Accreditation is not a promise, it is a fact about the world: IBF or SCI can withdraw one, or let it lapse, part-way through a cycle. Crediting therefore evaluates accreditation on the day the representative finishes, and the field stays editable so the register can tell the truth. Freezing it would make the platform assert Core hours under an accreditation that no longer exists, which is the worse error.
Each completion also records its own hours and category, so representatives who have already finished are unaffected by any later change.
If you must restructure a live course, clone it (Duplicate, on the course list, copies the structure, assessment and material into an independent DRAFT), edit the clone freely while it is a draft, publish it, and assign that. Representatives mid-way through the original keep their progress and their credit. Archiving the original stops new enrolments without disturbing anyone already on it.
An assessment must have questions¶
A course cannot be published while any of its assessments has no questions. An empty assessment is unanswerable, and every layer beneath had reached its own conclusion about what one meant — completion ignored it, the learner screen called it unfinished, and the grader scored it 0% and recorded a fail against a representative who had nothing to answer. Refusing it at publication is what makes those agree. Add at least one question, or delete the assessment.
Courses published before this rule are still tolerated at completion — an empty assessment there does not block a representative — so no one is stranded by it.
Adding an assessment to a course that is already published creates a question-less one for as long as it takes to write the first question, and there is no way to un-publish a course to do that privately. Delete assessment removes it outright and is the way back out. It is refused once anyone has attempted the assessment — those attempts are evidence an attempt-lockout rests on and a compliance officer reads before granting a further try — and, if the assessment has questions, once anyone is merely enrolled, because removing it would take the test out of the completion gate for all of them. A question-less assessment can always be deleted: it gates nothing, so removing it changes no representative's outcome.
What observed learning time is — and is not¶
While a lesson is open the platform records observed time and reports it on the completion report (per representative, in minutes). It is evidence that a representative actually spent time on the material rather than clicking through.
It is advisory, not a gate. Completion is granted on the documented conditions — every lesson marked complete, and a passing assessment attempt where the course has one — and low observed time does not block crediting. That is a deliberate choice: a minimum-time floor is a rule the firm would have to justify to MAS, and it would penalise a representative whose heartbeat was lost to a closed laptop, a dropped connection, or material read offline. What the platform gives compliance instead is the number, so an implausible one can be questioned. Treat it as a prompt to look, never as a control that has already acted.
Time is only counted while the tab is actually visible, and each tick is capped, so leaving a lesson open overnight does not manufacture hours.
Correcting the record¶
Nothing statutory is ever deleted. Every correction leaves the original in place:
| Act | What happens |
|---|---|
| Void a CPD entry | The entry stops counting; the row is kept, stamped with who voided it and why |
| Correct a CPD entry | Superseded by a replacement; the original is retained and linked to it |
| Revoke a certificate | The certificate stops being valid and its hours leave the ledger. The FIRST revocation is authoritative — re-revoking never rewrites the original timestamp or reason |
| Revoke a waiver | The waiver stops suppressing the shortfall immediately, so the representative's health returns to its true state. The grant and the withdrawal both remain on record |
Each of these requires a reason and is written to the audit log. A regulator asking "when did this stop being valid, and who decided?" can always be answered.
Where these live. All four acts are performed from the rep detail modal
(/admin/rep-register → open a representative). Each ledger row carries a
Correct action offering Void or Replace with corrected hours; each
waiver row carries Revoke. Revoked waivers stay in the list, struck through
and annotated with the reason, rather than disappearing.
Void vs. correct. Void is for hours that should never have been claimed — they simply stop counting. Correct is for hours that were wrong: the entry is superseded by a replacement carrying the right figures, and the two rows are linked, so the corrected entry has provenance back to what it fixed. A replacement's accreditation is re-entered, never inherited: the form asks for it again, defaulting to unaccredited, and requires a reference when it is claimed. Accreditation is a fresh compliance judgement on the corrected hours, not something that rides along from the row being replaced — a mistyped accredited Core hour must not keep its accreditation through a correction nobody re-verified.
Certificate revocation is final. There is no reinstate path. If a certificate was revoked in error, issue a new completion — the revoked one stays on record.
5.1 The Rep Register — /admin/rep-register¶
A Red/Amber/Green Fit & Proper rollup for every representative in the organisation, evaluated server-side (CMFAS + CPD). The CPD column shows earned-vs-target for the cycle.
How the colour is decided:
| Flag | Meaning |
|---|---|
| Green | CMFAS is good and the CPD thresholds for the scope are met — or the representative's first-year position carries no outstanding requirement (appointed after 1 Oct, per §1) |
| Amber | A shortfall exists but the cycle is still open (there is time to remediate), or a waiver applies, or the requirement cannot be established (see below) |
| Red | A required CMFAS module is missing/expired, or the cycle has closed with an unmet Core/total requirement and no active waiver |
Missing appointment date. Without an appointment date the platform cannot tell whether the first-year exemption applies, nor what the requirement should be pro-rated to. Rather than assume a full-year requirement and escalate to Red — inventing a regulatory finding out of a missing field — the shortfall is reported as Amber with the cause named, so the data gets fixed and the true position emerges. Set the representative's appointment date to get an accurate flag.
The register: R/A/G health summary and, per representative, CMFAS status and CPD-vs-target for 2026.
5.2 The rep detail modal + compliance actions¶
Opening a representative shows their CPD meters, CMFAS status, course completions (with a certificate-revoke action), and a Compliance actions panel with two authoring surfaces that close the loop between what the health calculator reads and what compliance can do:
- Grant CPD waiver — record a
carry_over,make_up, orleave_exemptionwaiver (with a mandatory justification and optional expiry) that suppresses a closed-cycle shortfall flag. Every grant is audited (CPD_WAIVER_GRANTED) and stamped with the approving officer. - Add CPD record — backfill or verify an external CPD activity on the representative's behalf, including marking it accredited (something a representative's own self-log cannot do).
- Revoke a waiver — withdraw a waiver granted in error. The suppressed shortfall reappears in the representative's health immediately, and both the grant and the withdrawal remain on record (
CPD_WAIVER_GRANTED/CPD_WAIVER_REVOKED). - Correct the CPD ledger — void or supersede any individual statutory entry, per the table above.
The rep detail modal for a blocked representative: the CPD meters (the
shortfall banner sits just above the frame), the CMFAS table (M6A Failed),
and the Compliance actions section —
per-row Correct on the CPD ledger, and the Grant CPD waiver form open
at its kind choices (carry-over / make-up window / leave exemption) with a
required justification at submit. "Add CPD record" opens the matching form for
backfilling an accredited external record.
5.3 Revoking a certificate¶
Any issued certificate can be revoked by COMP / ORG_ADMIN with a mandatory reason (audited COURSE_CERTIFICATE_REVOKED). Revocation is not cosmetic: it immediately removes the credited hours from every reader — the aggregate totals, the itemised register, the passport — and flips the public verification page to "revoked". The credit vanishes as if it had never been earned.
6. Verifiable certificates & public verification¶
Completing a course mints an immutable, unguessable certificate serial (REGN-CPD-{year}-{96-bit hex}) inside the same database transaction that credits CPD. The certificate can be verified by anyone — MAS, a prospective FI, an auditor — with no Regnify login, at /verify/certificate/{serial}. The page renders outside the authentication gate and reflects the credential's live status (valid / revoked) at the moment of the check.
The public, unauthenticated verification page for a valid certificate — "This is a genuine CPD completion certificate issued by regnIFY", with the holder, course, hours, cycle, accreditation and serial.
The same page for a revoked certificate — a red "Revoked certificate … no longer valid" banner with the revocation date. The status is live: revoking in the compliance console flips this page immediately.
Download PDF. Both the representative's certificate card and the public verification page offer a printable A4 PDF carrying the holder, course, hours, category, accreditation, cycle, serial and a QR code back to the verification page. A revoked certificate still produces a PDF — stamped REVOKED — because refusing to render one would let a holder conceal a revocation simply by not producing the document. The verification page, not the paper, remains the source of truth.
7. How a CPD hour is credited (the model)¶
When a representative completes a course, a single database transaction:
- Verifies the completion gate (all lessons done + a passing assessment when the course has one).
- Writes a
CourseCompletionand mints the certificate serial. - Writes a
CpdRecordin the Core/Supplementary taxonomy, applying the accreditation gate for the representative's scope (CMS → IBF-only; FA → IBF/SCI/CPF Board). - Sets the cycle year to the completion year and the retention date to cycle + 5 years.
The compliance calculator (computeRepHealth) then derives the standing:
earnedCoreAccredited= accredited core-topic hours that count toward the Core minimum.supplementaryPool= raw Supplementary + unaccredited-Core spill + Core overflow (accredited Core beyond the 6 h minimum).cpdHoursYtd= the true earned total (never the sum of the two meters, which would double-count the overflow).
Every rep-facing surface (dashboard, register, passport, CPD log, AI reminder) reads these server-computed numbers — there is no client-side re-derivation of a statutory figure.
8. Security & multi-organisation isolation¶
Regnify is multi-tenant, and CPD data is representative PII. The isolation model:
- One choke point. Every representative-keyed read/write runs through
AuthzService.assertCanAccessRep, which pins the target to the caller's organisation (RepProfile.principalOrganisationId). A compliance officer in org A can never read, credit, waive, or revoke for a representative in org B. The refusal carries the same message whether the representative does not exist or simply belongs to another firm, so the endpoint cannot be used to probe which representative ids are real. - SYS_ADMIN is barred from rep PII by default — platform administration and representative oversight are separated duties.
- The answer key never leaves the server. Learner-facing assessment reads strip
isCorrect; grading is server-side only. - Authoring is org-scoped. An author can only edit their own organisation's courses (global courses are platform-admin only); reorder and enrolment operations validate parent-child ownership.
- Certificate serials are unguessable (96-bit) and holder-shared — the public verify endpoint cannot be used to enumerate representatives.
- Every compliance action is audited — issue, credit, revoke, waiver-grant, record-create — with the acting user, target, and reason.
- Enrolled-representative access survives archival. Archiving withdraws a course from the catalog; it does not discharge an obligation already placed. A representative who completed a course keeps read-only access to it and its material (for their certificate), and one who is still working through it keeps access to the lessons and the assessment until they finish or are withdrawn. Neither can re-enrol.
- Material downloads are ownership-checked. A lesson's file is served only if its stored key belongs to that lesson, so an author cannot point a content block at another organisation's document — on download or when cloning.
9. Roles & permissions at a glance¶
| Action | REP | HR_ADMIN | COMP / ORG_ADMIN | SYS_ADMIN |
|---|---|---|---|---|
| Browse catalog / enrol / learn / assess | ✅ | ✅ | ✅ | — |
| Self-log external CPD (forced unaccredited) | ✅ | ✅ | ✅ | — |
| Earn / view own certificate | ✅ | — | — | — |
| Author org courses & assessments | — | — | ✅ | — |
| Author platform-global courses | — | — | — | ✅ |
| Grant CPD waiver | — | — | ✅ | — |
| Backfill CPD record (incl. accredited) | — | ✅ | ✅ | — |
| Void / replace a CPD record | — | — | ✅ | — |
| Revoke a certificate | — | — | ✅ | — |
| Withdraw an enrolment | — | ✅ | ✅ | — |
| Assign / re-assign a course | — | — | ✅ | — |
| See another representative's data | — | ✅ (own org only) | ✅ (own org only) | 🚫 (barred from rep PII) |
HR_ADMIN's CPD reach is real but narrow — and that is the segregation of duties. HR backfills CPD records at the API level (no HR-facing screen exists for it; the compliance console UI stays COMP / ORG_ADMIN), and marking a record accredited is what turns unverified hours into statutory Core hours feeding the Fit & Proper assessment. HR also withdraws enrolments, alongside the register read. Granting waivers, voiding/replacing records, revoking certificates and mandating courses stay with
COMP/ORG_ADMIN— each resolver's role fence enforces the split, and the UI hides the compliance console from HR entirely. HR_ADMIN is not an approval tier in the Form 3A chain (that is Supervisor → Manager → CEO → Compliance).
10. Glossary¶
- Core CPD — ethics or rules-&-regulations hours; count toward the Core minimum only if accredited by an eligible body for the representative's scope.
- Supplementary CPD — any other relevant CPD; also receives unaccredited-Core spill and Core overflow.
- Accreditation body — IBF, SCI, CPF Board (FA); IBF only (CMS).
- Cycle year — the calendar year a CPD requirement is measured against.
- CPD waiver — a compliance-granted suppression of a closed-cycle shortfall flag (
carry_over/make_up/leave_exemption). - Certificate serial — the unguessable, publicly-verifiable identifier of a completion credential.
cpdHoursYtd— the representative's true earned total for the cycle (the number every "Total" meter shows).
11. For the product & engineering team¶
- Backend (NestJS/Prisma): the CPD-LMS domain modules live under
services/nestjs/src/domain/{course,course-module,lesson,content-block,enrollment,lesson-progress,assessment,course-completion,cpd-record,cpd-waiver}/. The scope-aware accreditation gate andcomputeRepHealthlive incpd-record/andrep-profile/. Certificate minting + revocation are incourse-completion/. - Frontend (React): learner surfaces under
client/src/pages/rep/lms/, authoring underclient/src/pages/admin/lms/+client/src/components/lms/, compliance actions inclient/src/components/RepDetailModal.tsx. - Migration note (Rule 6): the historical "Ethics / Product / Other" CPD split has been fully replaced by Core / Supplementary + accreditation. Any older doc or screenshot referencing an "8 h ethics + 4 h product" rule is stale — the correct model is in §1.
- Completion snapshots: every completion freezes the course revision, its module/lesson titles and a SHA-256 digest of each lesson's content blocks (
CourseCompletion.contentSnapshot/contentHash), so a later edit to published material is detectable and an auditor can reconstruct what was actually completed. The snapshot also freezes the assessment that gated the credit — its questions, options and answer key — plus what the representative actually achieved: their score, the attempt number, and the pass mark they were graded against, which is recorded on the attempt itself. Raising a course's pass mark afterwards therefore cannot restate the standard an existing certificate attests to. - Certificate PDF:
services/fastapi/app/services/certificate_exporter.py, served atGET /api/verify/course-certificate/{serial}/pdf(the router is mounted with the/apiprefix); shares brand tokens and the QR helper withpassport_exporter.py. - Schema note (Rule 6):
prisma/migrations/is stale repo-wide — deploys applyschema.prismaviaprisma db pushinentrypoint.sh. Treat the schema file, not the migration history, as the source of truth. - Design record: see
notes.mdADR-PRP-CPD-LMSand the consolidated design docdocs/ai-integration/cpd-lms-design.md.
Screenshots in this guide were captured against the live application — full-page for page surfaces, panel-open viewport shots for Reggie exchanges.