data-model-entities
Use this skill whenever designing or writing database schemas, migrations, ORM models, API response shapes, or any code that defines or references the platform's core domain entities. This is the auth
SKILL.mdprimary_build_source/docs/skills/2/mnt/user-data/outputs/data-model-entities/SKILL.md
GitHubThis skill defines all 19 core domain entities for the Myelin platform, their key fields, relationships, and the data governance rules that apply to each. This is the canonical reference — any schema, migration, or model that diverges from it creates technical debt that compounds across the build.
Data Governance Rules (Apply to All Entities)
These rules apply to every entity without exception.
Tenant scoping: Every business record carries a tenantId. Entities without a tenantId are platform-level only (e.g. Tenant itself). See multi-tenant-isolation skill for enforcement.
Soft delete: Business records are never hard-deleted during normal operation. Use a deletedAt timestamp. Queries must filter WHERE deleted_at IS NULL unless explicitly auditing deletion history.
Immutable audit trail: Records that are externally significant (submissions, results, policy configs, scoring rules) are versioned — updates create a new version record rather than mutating the existing one.
Retention classes:
- Class A (10-year minimum):
SimulationStepEvent,Submission,ResultStatement,AuditLog,ConsentRecord,AgentTranscript - Class B (7-year minimum):
PeerReview,LogbookEntry,MentorSignoff,Enrollment - Class C (standard 2-year):
Notification, session and cache data
Identifiers: All primary keys are UUID v4. Tenant-scoped identifiers (e.g. learnerId in telemetry payloads) are derived from User.userId and must never be raw email addresses in externally-facing payloads.
Entity Definitions
1. Tenant
Platform-level entity. No tenantId foreign key on this entity itself.
Key fields:
tenantIdUUID PKtenantNamestringtenantSlugstring uniquepackageTierenum: PILOT | OPERATIONAL | NATIONAL_PROGRAMfeatureFlagsJSONB (seemulti-tenant-isolationskill for flag schema)compliancePolicyPackstringcompliancePolicyVersionstringdataResidencyRegionstringssoConfigJSONBretentionPolicyYearsinteger default 10statusenum: ACTIVE | SUSPENDED | OFFBOARDEDcreatedAt,updatedAt
Relationships:
- Has many
User,Programme,AuditLog
2. User
One record per human per tenant. A person with access to two tenants has two User records.
Key fields:
userIdUUID PKtenantIdUUID FK → Tenantemailstring (unique within tenant)passwordHashstring nullable (null for SSO-only accounts)ssoSubjectIdstring nullablessoMethodenum: NONE | OIDC | SAMLstatusenum: ACTIVE | PENDING_ACTIVATION | SUSPENDED | OFFBOARDEDactivatedAttimestamp nullablelastLoginAttimestamp nullabledeletedAttimestamp nullablecreatedAt,updatedAt
Relationships:
- Has one
RoleAssignment - Has many
Enrollment,ConsentRecord,SimulationAttempt,Submission,LogbookEntry,AgentTranscript
Note: Do not store PII beyond what is required for identity and contact. email is the primary identity field; do not duplicate it into telemetry payloads — use userId instead.
3. RoleAssignment
One role per user per tenant.
Key fields:
assignmentIdUUID PKtenantIdUUID FK → TenantuserIdUUID FK → Userroleenum: STUDENT | LECTURER | ADMIN | EMPLOYER_MENTOR | MINISTRY_VIEWER | PROCUREMENT_REVIEWER | REGULATOR_AUDITOR | SUPER_ADMINassignedByUUID FK → User (the admin who made the assignment)assignedAttimestamprevokedAttimestamp nullablescopeConstraintJSONB nullable (used for Ministry/Regulator to constrain which tenant IDs they may access)
Relationships:
- Belongs to
User
Note: A revoked RoleAssignment is retained for audit. Never delete role assignment records.
4. Programme
A top-level qualification or training programme. Contains one or more Module records.
Key fields:
programmeIdUUID PKtenantIdUUID FK → TenanttitlestringqualificationFrameworkstring (e.g. "QCTO", "NQF")qualificationLevelintegercoreModuleIdsUUID array (modules that are mandatory for all learners in this programme)curriculumOutcomeIdsstring array (e.g. ["FND-TT-01", "FND-IW-01"])statusenum: DRAFT | ACTIVE | ARCHIVEDversioninteger (increments on each published update)createdAt,updatedAt,deletedAt
Relationships:
- Belongs to
Tenant - Has many
Module,Enrollment
5. Module
A learning unit within a programme. Contains LearningItem records.
Key fields:
moduleIdUUID PKtenantIdUUID FK → TenantprogrammeIdUUID FK → ProgrammetitlestringmoduleTypeenum: FOUNDATION | SPECIALIZATIONprerequisiteModuleIdsUUID arraycompletionPolicyJSONB (weighted criteria, mandatory checkpoints, pass thresholds)standardsJSONB array (standards references for audit)estimatedDurationMinutesintegerstatusenum: DRAFT | ACTIVE | ARCHIVEDversionintegercreatedAt,updatedAt,deletedAt
6. LearningItem
An individual content or assessment item within a module.
Key fields:
itemIdUUID PKtenantIdUUID FK → TenantmoduleIdUUID FK → ModuleitemTypeenum: VIDEO | QUIZ | SIMULATION | PRACTICAL_TASKtitlestringcontentRefstring (URL or asset identifier)durationMinutesinteger nullableisGatedboolean (if true, must be completed before next item unlocks)scenarioIdstring nullable (for SIMULATION items, e.g. "LOTO_7STEP_v1")rubricIdUUID nullable FK → Rubric (for PRACTICAL_TASK items)orderintegercreatedAt,updatedAt,deletedAt
7. Enrollment
Tracks a learner's enrolment in a programme.
Key fields:
enrollmentIdUUID PKtenantIdUUID FK → TenantuserIdUUID FK → UserprogrammeIdUUID FK → ProgrammeenrolledAttimestampstatusenum: ACTIVE | COMPLETED | WITHDRAWN | SUSPENDEDcompletedAttimestamp nullableprogressSnapshotJSONB (cached progress state for dashboard — always recomputed from events for audit)createdAt,updatedAt
8. SimulationAttempt
A learner's attempt at a simulation scenario. One record per session.
Key fields:
attemptIdUUID PKsessionIdUUID unique (matchessessionIdin telemetry events)tenantIdUUID FK → TenantuserIdUUID FK → UserscenarioIdstringscenarioVersionstringscoringRulesVersionstringstatusenum: IN_PROGRESS | COMPLETE | ABANDONEDresetCountinteger default 0assistModeUsedbooleaninputModeenumstartedAttimestampcompletedAttimestamp nullabletotalDurationSecondsinteger nullabledemoModebooleancreatedAt,updatedAt
Relationships:
- Has many
SimulationStepEvent
9. SimulationStepEvent
Immutable event record for each assessed simulation step attempt. See simulation-telemetry-schema skill for the full payload schema. This table maps directly to that schema.
Key fields (column representation):
eventIdUUID PKsessionIdUUID FK → SimulationAttempt.sessionIdtenantIdUUID FK → TenantuserIdUUID FK → UserscenarioIdstringscenarioVersionstringscoringRulesVersionstringstepIdstringattemptNumberintegertimestamptimestampresultenum: PASS | FAILmeasuredValuenumericrequiredThresholdnumericthresholdOperatorenumtoleranceDeltanumeric nullableinputModeenumfpsAtEventnumericframeTimeMsAtEventnumericinteractionLatencyMsnumericcurriculumOutcomeIdsstring arraydemoModeboolean
Governance: append-only. No UPDATE or DELETE on this table. Retention Class A.
10. Submission
A learner's e-Portfolio evidence submission.
Key fields:
submissionIdUUID PKtenantIdUUID FK → TenantuserIdUUID FK → UsermoduleIdUUID FK → ModuleitemIdUUID FK → LearningItemversioninteger (increments on resubmission; prior versions are retained)fileRefstring (signed storage URL or object key)fileTypeenum: MP4fileSizeBytesintegermalwareScanStatusenum: PENDING | CLEAN | QUARANTINEDtranscodeStatusenum: PENDING | COMPLETE | FAILEDstatusenum: DRAFT | SUBMITTED | UNDER_REVIEW | PASSED | REWORK_REQUIREDsubmittedAttimestamp nullablereviewedAttimestamp nullablereviewedByUUID FK → User nullablereviewNotestext nullablerubricScoreJSONB nullablecreatedAt,updatedAt
Governance: versioned. On resubmission, create a new version record. Retain all versions. Retention Class A.
11. Rubric
A scoring rubric attached to a practical task or peer review.
Key fields:
rubricIdUUID PKtenantIdUUID FK → TenanttitlestringcriteriaJSONB array (each criterion: id, label, description, maxScore, required)passMarknumericversionintegerstatusenum: DRAFT | ACTIVE | ARCHIVEDcreatedAt,updatedAt
12. PeerReview
A peer review assignment and its completed evaluation.
Key fields:
reviewIdUUID PKtenantIdUUID FK → TenantsubmissionIdUUID FK → SubmissionreviewerIdUUID FK → UserrubricIdUUID FK → RubricassignedAttimestampstatusenum: ASSIGNED | IN_PROGRESS | SUBMITTED | MODERATEDcriteriaScoresJSONB nullable (map of criterion id → score)feedbackTexttext nullablesubmittedAttimestamp nullablemoderationStatusenum: PENDING | APPROVED | FLAGGED nullablemoderatedByUUID FK → User nullablemoderatedAttimestamp nullablecreatedAt,updatedAt
Constraint: reviewerId must not equal the userId of the Submission being reviewed. Enforce at database level with a CHECK constraint.
13. Placement
A learner's workplace placement record.
Key fields:
placementIdUUID PKtenantIdUUID FK → TenantuserIdUUID FK → User (learner)mentorIdUUID FK → User (employer mentor)employerNamestringstartDatedateendDatedate nullablestatusenum: ACTIVE | COMPLETED | TERMINATEDcreatedAt,updatedAt
14. LogbookEntry
A learner's workplace logbook record tied to a placement.
Key fields:
entryIdUUID PKtenantIdUUID FK → TenantuserIdUUID FK → User (learner)placementIdUUID FK → PlacementactivityDatedateactivityCategorystringdescriptiontextevidenceRefsstring array (file references)signoffStatusenum: PENDING | APPROVED | REJECTEDcreatedAt,updatedAt
Relationships:
- Has one
MentorSignoff(when signoff is requested)
15. MentorSignoff
The mentor's approval record for a logbook entry.
Key fields:
signoffIdUUID PKtenantIdUUID FK → TenantentryIdUUID FK → LogbookEntrymentorIdUUID FK → Userdecisionenum: APPROVED | REJECTEDcommentstext nullablesignedAttimestampidentityProofMethodstring (e.g. "session-authenticated")
Governance: immutable after creation. No updates. If a mentor changes their decision, a new MentorSignoff record is created and the prior one is retained.
16. ResultStatement
The formal statement of results issued to a learner.
Key fields:
resultIdUUID PKtenantIdUUID FK → TenantuserIdUUID FK → UserprogrammeIdUUID FK → ProgrammeenrollmentIdUUID FK → Enrollmentversionintegerstatusenum: ISSUED | AMENDED | REVOKEDissuedAttimestampverificationHashstring (SHA-256 of deterministic statement fields)verificationIdstring (human-readable short code for verification lookups)documentRefstring (PDF storage reference)programmeMetaJSONB (snapshot of programme metadata at time of issue)learnerMetaJSONB (snapshot of learner identity fields at time of issue — name, ID number)createdAt,updatedAt
Governance: versioned. Post-issuance amendments create a new version with status: AMENDED; prior versions are retained with their original verificationHash. Retention Class A.
17. Notification
An outbound notification record.
Key fields:
notificationIdUUID PKtenantIdUUID FK → TenantuserIdUUID FK → User (recipient)channelenum: EMAIL | SMStemplateKeystringtemplateVersionstringpayloadJSONB (template variables)statusenum: PENDING | SENT | DELIVERED | FAILEDsentAttimestamp nullabledeliveryConfirmedAttimestamp nullablefailureReasonstring nullableretryCountinteger default 0createdAt,updatedAt
18. AuditLog
An immutable record of every privileged action on the platform.
Key fields:
logIdUUID PKtenantIdUUID FK → Tenant nullable (null for Super Admin platform-level actions)actorIdUUID FK → UseractorRolestringactionstring (e.g. "ROLE_ASSIGNMENT_CREATED", "FEATURE_FLAG_ENABLED", "RESULT_ISSUED")resourceTypestring (e.g. "User", "ResultStatement", "FeatureFlag")resourceIdUUID nullablebeforeJSONB nullable (state before change)afterJSONB nullable (state after change)ipAddressstringuserAgentstringtimestamptimestamp
Governance: append-only. No UPDATE or DELETE. Retention Class A.
19. ConsentRecord
POPIA consent capture for each learner per tenant.
Key fields:
consentIdUUID PKtenantIdUUID FK → TenantuserIdUUID FK → UserconsentVersionstring (version of the consent text shown)capturedAttimestampmethodenum: EXPLICIT_CHECKBOX | SSO_DELEGATEDrevokedAttimestamp nullablerevocationReasonstring nullableipAddressstringuserAgentstring
Governance: immutable after creation. Revocation creates a revokedAt timestamp — it does not delete the record. Retention Class A.
Entity Relationship Summary
Tenant
└── User
└── RoleAssignment
└── Enrollment → Programme → Module → LearningItem
└── SimulationAttempt → SimulationStepEvent
└── Submission → PeerReview
└── Placement → LogbookEntry → MentorSignoff
└── ResultStatement
└── ConsentRecord
└── Notification
└── Rubric
└── AuditLog
What Not To Do
- Do not use raw email as a learner identifier in telemetry or external payloads — use
userId - Do not hard-delete any business record — use
deletedAt - Do not mutate a
SimulationStepEvent,AuditLog, orConsentRecordafter creation - Do not create a
ResultStatementversion by overwriting — always insert a new version row - Do not allow a
PeerReviewwherereviewerIdequals the submission'suserId - Do not store
progressSnapshotonEnrollmentas the authoritative source — it is a cache; always recompute from events for audit and reporting - Do not drop or ignore
curriculumOutcomeIdsarrays — an empty array is a constraint violation for any entity that requires outcome linkage