Application Logic
:::info Source
Sourced from services/certification-service/APPLICATION_LOGIC.md in the documentation repo.
:::
1. Application Services
CertificateIssuanceService— derives + issues from completion.CertificateRenderService— PDF/PNG/OpenBadges artifact generation.VerificationService— public verify endpoint.RevocationService— revoke with reason + reindex.OfflineClaimService— verify local signatures and issue.TemplateService— template CRUD + preview.
2. Commands
| Command | Trigger |
|---|---|
IssueCertificate(enrollmentId, attemptId) | progress.completion.recorded.v1 |
IssueCertificateFromClaim(claimId) | certification.offline_claim.submitted.v1 |
RevokeCertificate(id, reason) | Admin action or compliance event |
VerifyCertificate(token) | Public GET /verify/{token} |
CreateTemplate, UpdateTemplate, ArchiveTemplate | Admin |
3. Queries
getCertificate(id)/getByToken(token)(public).listUserCertificates(userId).listTemplates(tenantId).getIssuanceAudit(certificateId).
4. Sagas
- Participates in GDPR Erasure Saga: decisions on certification under legal hold (retain for verifiability) with anonymization of user PII (keep name-at-issuance if public verifier relies on it? per tenant policy).
- Participates in Course Revocation flow: if
catalog.course_version.withdrawn.v1and serious defect, admin may bulk-revoke certificates.
5. Policies
- Idempotency: one certificate per (enrollment, courseVersion). Duplicate completion events no-op.
- Template selection: from course setting → tenant default → platform default.
- Revocation visibility: public
verifyreturnsrevoked+publicReasonif set.
6. Use Case Flows
6.1 Online Issuance
Consume progress.completion.recorded.v1
│
├─ Check existing certificate (idempotent)
├─ Resolve template (course.certificateTemplateId || tenant.default)
├─ Gather artifact inputs (user display name, course title, issued date, signatory)
├─ Render PDF/PNG via headless Chrome
├─ Generate OpenBadges 3.0 VC (JSON-LD with proof)
├─ Sign JWS proof (tenant key)
├─ Persist Certificate
├─ Upload artifacts to S3 (signed URL prefix)
├─ Emit certification.certificate.issued.v1
6.2 Offline Issuance Claim
Learner completes offline → PlayPackage emits local signature.
On sync, client pushes OfflineIssuanceClaim.
│
├─ Verify localSignature against bundle key (content-service verifies)
├─ Verify attempt + completion in progress-service (replay completion event)
├─ If valid: issue certificate with state='issued'; evidence includes local signature
├─ If invalid: reject; emit rejection event; learner notified with reason
6.3 Verification (Public)
GET /api/v1/certificates/verify/{token}
│
├─ Lookup Certificate by token (HMAC verify)
├─ Check revocation
├─ Verify JWS proof
├─ Return minimal public info: userDisplayName, courseTitle, issuedAt, state, tenantPublicName
├─ Rate limit per IP
├─ Log verification event (analytics)
6.4 Revocation
Admin: POST /certificates/{id}/revoke with reason.
│
├─ Update Certificate.state = 'revoked'
├─ Create RevocationRecord
├─ Emit certification.certificate.revoked.v1
├─ Search reindex (status update)
├─ Notify certificate holder (optional)