Skip to content

feat(certification): apply wedge spec via manifest + signing guard (closes #24) - #34

Merged
rubenvdlinde merged 1 commit into
developmentfrom
feature/wedge/certification
May 11, 2026
Merged

feat(certification): apply wedge spec via manifest + signing guard (closes #24)#34
rubenvdlinde merged 1 commit into
developmentfrom
feature/wedge/certification

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Summary

  • Schema added: Credential in lib/Settings/scholiq_register.jsonappendOnly: true, lifecycle issued → revoked | expired with a declarative issue transition guard (CredentialSigningService).
  • Lifecycle states: issued (initial), revoked, expired. The expiring threshold is modelled via calculations (isExpiringIn90Days, isExpiringIn30Days) that trigger idempotency-keyed notifications; the hard expired state fires via alsoDispatchLifecycle: expire on the expiredAlert notification — no TimedJob, no ExpiryDetectionService.
  • Calculated-change wiring for expiring: daysUntilExpiry (OR dateDiff expression) feeds isExpiringIn30Days and isExpiringIn90Days. The expiredAlert notification triggers when isExpired = true and carries alsoDispatchLifecycle: expire.
  • Calculations (6): daysUntilExpiry, expiryStatus (string enum), isOpenBadgesV3Signed (bool), isExpiringIn90Days, isExpiringIn30Days, isExpired — all materialise: true.
  • Notifications (5): issuedToLearner (lifecycleEnter:issued), expiringSoonAlert T30 (idempotency-keyed), expiryT90 (idempotency-keyed), expiredAlert (dispatches expire transition), revoked (lifecycleEnter:revoked).
  • PHP files (5, all ADR-031 legitimate):
    • lib/Service/CredentialSigningService.php — OB3 JSON-LD assembly + RS256 signing; acts as OR lifecycle guard on issue transition via requires[] in schema.
    • lib/Service/KeyManagementService.php — RSA-2048 keypair generation; encrypts private key via ICrypto.
    • lib/Listener/CredentialIssuanceHandler.phpObjectTransitionedEvent listener; bridges Enrolment.completed → Credential save via OR.
    • lib/Controller/CredentialVerifyController.php@PublicPage @NoCSRFRequired verify endpoint, no personal data.
    • lib/Controller/KeyAdminController.php — admin-only generate-key + key-status endpoints.
  • Manifest entries: CredentialDetail (type:detail) and CredentialVerify (type:custom, public:true). src/views/CredentialVerify.vue added.
  • No CredentialController, CredentialService, CredentialExpiryJob, AuditEventTypes::KNOWN, or app-local stores.

Test plan

  • JSON validates: python3 -c "import json; json.load(open('lib/Settings/scholiq_register.json'))" passes.
  • PHP lint: composer lint — all files pass, no syntax errors.
  • Seed a Credential with expiresAt = today + 30, trigger OR calculation refresh → isExpiringIn30Days = true, expiringSoonAlert dispatched once.
  • Seed a Credential with expiresAt = today - 1, trigger OR calculation refresh → isExpired = true, expiredAlert fires, expire transition executes (lifecycle becomes expired).
  • Navigate to /credentials/:id/verify without auth → {valid: true} + QR code renders.
  • Admin POST /api/credentials/admin/generate-key → keypair stored, key-status returns configured: true.
  • Enrolment transitions to completed on Course with certificateTemplate → Credential auto-issued within 30s, issuedToLearner notification dispatched.

🤖 Generated with Claude Code

…loses #24)

Adds Credential schema (appendOnly, lifecycle issued→revoked|expired) with
declarative calculations (daysUntilExpiry, expiryStatus, isOpenBadgesV3Signed,
isExpiringIn90Days/30Days, isExpired), notifications (issuedToLearner,
expiringSoonAlert, expiryT90, expiredAlert with alsoDispatchLifecycle:expire,
revoked), and relations to LearnerProfile + Course.

Legitimate PHP seams (ADR-031): CredentialSigningService (OB3 RS256 signing,
lifecycle guard on issue transition), KeyManagementService (RSA-2048 keypair
gen), CredentialIssuanceHandler (ObjectTransitionedEvent bridge), and
CredentialVerifyController (public @publicpage verify endpoint).

Manifest extended with CredentialDetail (type:detail) and CredentialVerify
(type:custom, public:true) pages. CredentialVerify.vue renders verification
card with valid/invalid badge, metadata, and QR code.
@rubenvdlinde
rubenvdlinde force-pushed the feature/wedge/certification branch from 22c04b0 to 9451604 Compare May 11, 2026 21:58
@rubenvdlinde
rubenvdlinde merged commit ab18e0e into development May 11, 2026
19 of 28 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/scholiq @ c992893

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 100/100
npm
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-11 22:01 UTC

Download the full PDF report from the workflow artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant