feat(compliance-audit): apply wedge core spec via declarative schemas (closes #25) - #33
Merged
Merged
Conversation
…closes #25) - Add Regulation schema (9 fields, lifecycle draft→published→archived, 4 aggregations, 2 calculations coveragePercent+ragStatus, officerAlert+onPublished notifications, coverageGrid+boardProof widgets) to scholiq_register.json - Add Attestation schema (12 fields, appendOnly:true, lifecycle drafted→signed→revoked with AttestationSigningGuard on sign transition, 3 relations) to scholiq_register.json - Create lib/Lifecycle/AttestationSigningGuard.php — single check() method: verifies cmi5.completed xAPI statement for (learnerId,lessonId) then computes HMAC-SHA256 using OR tenant-key API; injects signature+signingKeyId into transition payload - Create lib/Controller/AuditPackExportController.php — single export() method; queries OR audit-trail, builds 4-file ZIP (ndjson+csv+manifest+verification.txt), streams as Content-Disposition attachment; ADR-031 document-generation exception - Extend src/manifest.json: Regulations index, RegulationDetail (with auditTrail tab), Attestations index (readOnly), AttestationDetail (readOnly), AuditPackExport custom page, Compliance dashboard with 2 widget-ref entries pointing at Regulation widgets - Add src/views/AuditPackExportModal.vue: regulation dropdown + date pickers + POST to /api/compliance/audit/export → Blob download; registered via manifest customComponents - No ComplianceCampaign schema (design §1.3: campaigns are Enrolment sets, not a schema) - No CoverageComputationService, no AuditPackExportService, no RegulationController, no ComplianceCampaignService (all replaced by declarative schema extensions)
rubenvdlinde
force-pushed
the
feature/wedge/compliance-audit
branch
from
May 11, 2026 21:55
4c47682 to
1063f3e
Compare
Contributor
Quality Report — ConductionNL/scholiq @
|
| 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 21:57 UTC
Download the full PDF report from the workflow artifacts.
rubenvdlinde
added a commit
that referenced
this pull request
May 12, 2026
…m manifest to nc-vue v1.4.0; drop dead scaffold files (#40) ## Root cause All 5 Wave-2 worktrees were branched off the same pre-Wave-2 base, so the sub-agents worked blind to each other. The dashboard agent wrote intentional STUB Enrolment + Regulation schemas ("declare a stub if it doesn't exist yet"). When the dashboard PR was cherry-picked LAST onto a development that already had #31's full Enrolment and #33's full Regulation, git's line-based JSON merge kept the stub (the schemas were declared at different file positions, so no textual conflict — git appended both and `json.load` resolved the duplicate key to the stub). The merge-resolver script was only re-run on manifest.json, not scholiq_register.json. ## Fixes - **Restore full Enrolment schema** (10 props + x-openregister + lifecycle pending→active→completed|withdrawn|failed + relations learner/course + calculations isOverdue/daysRemaining/ragStatus + notifications welcomeOnActivate/completionOnComplete/reminderT30/T7/T1/managerAlertOnOverdue) and merge in the dashboard's myMandatoryTraining widget. - **Restore full Regulation schema** (12 props + x-openregister + lifecycle draft→published→archived + aggregations mandatoryEnrolledCount/mandatoryCompletedCount/attestationCount/validCredentialCount + calculations coveragePercent/ragStatus + notifications officerAlertOnCoverageDrop/onPublished) and merge widgets coverageGrid+boardProof+attestationCount. - **Move `appendOnly: true` to top level** on Credential, XapiStatement, Attestation — OR's Schema::hydrate() only reads top-level `appendOnly` (it calls setAppendOnly()); when nested inside `x-openregister` the flag was silently dropped, so these schemas were NOT actually append-only. - **Conform src/manifest.json to nc-vue manifest schema v1.4.0**: removed `visibleIf` from pages, `public` from CredentialVerify, fixed dashboard widgets to {id,title,type:'custom'}, stripped non-schema keys (href, visibleIf) from the viewInMydash action. `tests/validate-manifest.js` now PASSes (was FAIL with 14 errors). - **Delete dead scaffold files**: src/router/index.js, src/views/Dashboard.vue, src/navigation/MainMenu.vue (none imported anywhere), lib/Controller/DashboardController.php (no route). Follow-ups: nc-vue needs `visibleIf` page predicates, `public` pages, and `widget-ref` page-content (umbrella #200) before the dashboard can be fully declarative; until then the dashboard pages are conformant-but-minimal.
This was referenced May 12, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
draft → published → archived; 4 cross-schema aggregations (mandatoryEnrolledCount,mandatoryCompletedCount,attestationCount,validCredentialCount); 2 materialised calculations (coveragePercent,ragStatuswith per-regulation RAG thresholds); 2 notifications (officerAlertOnCoverageDroponcalculatedChange: ragStatus → red,onPublished); 2 declarative widgets (coverageGrid,boardProof) consumed by the Compliance dashboard.appendOnly: true): lifecycledrafted → signed → revoked;signtransition declaresrequires: OCA\Scholiq\Lifecycle\AttestationSigningGuard; 3 relations (learner/course/lesson). Immutability enforced by OR'sappendOnlyabstraction per ADR-022.regulationSlug + bulkJobId; no separate schema needed at this stage.PHP (ADR-031 legitimate exceptions only)
lib/Lifecycle/AttestationSigningGuard.php— singlecheck()method: (a) queries OR forXapiStatementwith verb.id in {completed, passed} for(learnerId, lessonId); if absent returns false (OR returns 422); (b) callsAuditTrailService::getCurrentTenantKey($tenantId)for the HMAC key; if unavailable fails hard per spec; (c) builds canonical JSON payload (sorted keys,signature/signingKeyId/lifecycleexcluded), computeshash_hmac('sha256', ...), injectssignature+signingKeyIdinto$transitionContext['payload'].lib/Controller/AuditPackExportController.php— singleexport()method: receives{regulationSlug, dateFrom, dateTo}, callsAuditTrailService::query()(OR auto-emitscompliance.audit_pack.exported), callsAuditTrailService::verifyChain(), builds 4-file ZIP (audit-trail.ndjson,audit-trail.csv,manifest.jsonper ADR-008 §6,signature-verification.txt), streams asapplication/zip.Manifest entries (6 new pages)
Regulations— OR index of Regulation schemaRegulationDetail— OR detail withauditTrailtab (immutable evidence log surface = OR built-in)Attestations— OR index,readOnly: true(appendOnly schema)AttestationDetail— OR detail,readOnly: true, withauditTrailtabAuditPackExport— custom page wired toAuditPackExportModalVue componentCompliancedashboard — twowidget-refentries pointing atRegulation.x-openregister-widgets.coverageGridand.boardProofAudit-pack approach
Thin PHP controller (ADR-031 document-generation exception). All heavy lifting delegated to OR's
AuditTrailService.AuditPackExportModal.vuePOSTs to/api/compliance/audit/exportand triggers a client-side Blob download.Deviations from 100% declarative
Two files only, both per spec's explicit ADR-031 exceptions:
AttestationSigningGuard— HMAC-SHA256 cryptographic operation + lifecycle-guard exceptionAuditPackExportController— ZIP/document generation exception; OR has no built-in multi-format ZIP export endpointNot shipped (per design §2)
AttestationService,CoverageComputationService,AuditPackExportService,HmacKeyService,ComplianceHmacRotationJob,EvidenceLogService,ComplianceController,AttestationController,scholiq-audit-eventschema,AuditedController,ComplianceCampaignService— replaced by declarative schema extensions or consumed from OR.Test plan
lib/Settings/scholiq_register.jsonvalidates against OR schema-extension contractAttestationSigningGuard::check()— missing xAPI completion returns false; present completion returns true with non-empty signature in payloadAuditPackExportController::export()— seed 20 entries, call export, assert 4 files +manifest.event_count = 20AuditPackExportModal— select regulation + dates, click Export, assert download triggeredcomposer check:strictandnpm run lintpassing in CI🤖 Generated with Claude Code