Generic 'party learns skills' curriculum engine: versioned curricula, skills, prerequisites (DAG), assignments, explicit progress, and event history
| Tier | domain |
| Role | unclassified (baselined) |
| Path | crates/domain/curriculum |
| Edition | 2021 |
| Targets | domain_curriculum, pg_repository, pg_service |
| Public items | 121 across 7 modules |
| Tests | 27 |
What it is for
domain-curriculum — a generic "party learns skills" engine.
Owns curriculum structure, immutable published versions, skills, prerequisites (a DAG), assignments (keyed by learner_party_id — Animal / Person / any Party), explicit skill progress, and an append-only event history. Deliberately generic: domain specifics (dog-training cues/objects, dive skills, onboarding steps) ride in skill_type + metadata/ mastery_policy jsonb + event metadata — never in this crate. Media, comments, encounters, reservations, notifications stay outside and attach by reference (see Sprint 0.67 plan).
This crate is tenant-agnostic inside a tenant DB (one DB per tenant via infrastructure-tenant-pool); no cross-tenant references.
Capabilities
CurriculumError
Curriculum error type.
| Item |
|---|
pub enum CurriculumError |
AssignmentStatus
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub enum AssignmentStatus |
AssignmentStatus :: fn as_str(self) -> & 'static str |
AssignmentStatus :: fn from_str(s : & str) -> Result <Self, Self::Err> |
CurriculumAssignment
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub struct CurriculumAssignment |
CurriculumAssignment :: fn new(curriculum_version_id : Uuid, learner_party_id : Uuid) -> Self |
CurriculumAssignment :: fn id(& self) -> Uuid |
CurriculumAssignment :: fn created_at(& self) -> DateTime <Utc> |
CurriculumAssignment :: fn updated_at(& self) -> DateTime <Utc> |
CurriculumEvent
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub struct CurriculumEvent |
CurriculumEvent :: fn new(assignment_id : Uuid, event_type : impl Into <String>, source : EventSource) -> Self |
CurriculumEvent :: fn for_skill(mut self, skill_id : Uuid) -> Self |
CurriculumEvent :: fn id(& self) -> Uuid |
CurriculumStatus
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub enum CurriculumStatus |
CurriculumStatus :: fn as_str(self) -> & 'static str |
CurriculumStatus :: fn from_str(s : & str) -> Result <Self, Self::Err> |
CurriculumTemplate
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub struct CurriculumTemplate |
CurriculumTemplate :: fn new(code : impl Into <String>, title : impl Into <String>) -> Self |
CurriculumTemplate :: fn id(& self) -> Uuid |
CurriculumTemplate :: fn created_at(& self) -> DateTime <Utc> |
CurriculumTemplate :: fn updated_at(& self) -> DateTime <Utc> |
CurriculumTemplate :: fn deleted_at(& self) -> Option <DateTime <Utc>> |
CurriculumVersion
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub struct CurriculumVersion |
CurriculumVersion :: fn new(curriculum_id : Uuid, version_no : i32) -> Self |
CurriculumVersion :: fn id(& self) -> Uuid |
CurriculumVersion :: fn created_at(& self) -> DateTime <Utc> |
CurriculumVersion :: fn updated_at(& self) -> DateTime <Utc> |
CurriculumVersionSkill
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub struct CurriculumVersionSkill |
CurriculumVersionSkill :: fn new(curriculum_version_id : Uuid, skill_id : Uuid, sort_order : i32) -> Self |
CurriculumVersionSkill :: fn id(& self) -> Uuid |
CurriculumVersionSkill :: fn created_at(& self) -> DateTime <Utc> |
CurriculumVersionSkill :: fn updated_at(& self) -> DateTime <Utc> |
EventSource
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub enum EventSource |
EventSource :: fn as_str(self) -> & 'static str |
EventSource :: fn from_str(s : & str) -> Result <Self, Self::Err> |
ParseStatusError
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub struct ParseStatusError |
ParseStatusError :: fn fmt(& self, f : & mut std::fmt::Formatter <'_>) -> std::fmt::Result |
PrerequisiteKind
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub enum PrerequisiteKind |
PrerequisiteKind :: fn is_blocking(self) -> bool |
PrerequisiteKind :: fn as_str(self) -> & 'static str |
PrerequisiteKind :: fn from_str(s : & str) -> Result <Self, Self::Err> |
Skill
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub struct Skill |
Skill :: fn new(code : impl Into <String>, title : impl Into <String>, skill_type : impl Into <String>,) -> Self |
Skill :: fn id(& self) -> Uuid |
Skill :: fn created_at(& self) -> DateTime <Utc> |
Skill :: fn updated_at(& self) -> DateTime <Utc> |
Skill :: fn deleted_at(& self) -> Option <DateTime <Utc>> |
SkillPrerequisite
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub struct SkillPrerequisite |
SkillPrerequisite :: fn new(curriculum_version_id : Uuid, skill_id : Uuid, prerequisite_skill_id : Uuid, kind : PrerequisiteKind,) -> Self |
SkillProgress
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub struct SkillProgress |
SkillProgress :: fn new(assignment_id : Uuid, skill_id : Uuid) -> Self |
SkillProgress :: fn id(& self) -> Uuid |
SkillProgress :: fn created_at(& self) -> DateTime <Utc> |
SkillProgress :: fn updated_at(& self) -> DateTime <Utc> |
SkillProgressStatus
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub enum SkillProgressStatus |
SkillProgressStatus :: fn is_mastered(self) -> bool |
SkillProgressStatus :: fn as_str(self) -> & 'static str |
SkillProgressStatus :: fn from_str(s : & str) -> Result <Self, Self::Err> |
VersionStatus
Curriculum state vocabularies (controlled enums) and row structs.
| Item |
|---|
pub enum VersionStatus |
VersionStatus :: fn is_editable(self) -> bool |
VersionStatus :: fn label(self) -> & 'static str |
VersionStatus :: fn as_str(self) -> & 'static str |
VersionStatus :: fn from_str(s : & str) -> Result <Self, Self::Err> |
progression (other)
Pure progression logic: prerequisite-DAG validation + unlocked/next-step
| Item |
|---|
fn validate_no_cycles(edges : & (Uuid, Uuid)) -> Result <(), CurriculumError> |
fn unlocked_skills(all_skills : & Uuid, hard_prereqs : & (Uuid, Uuid), mastered : & HashSet <Uuid>,) -> Vec <Uuid> |
fn recommended_next(all_skills : & Uuid, hard_prereqs : & (Uuid, Uuid), mastered : & HashSet <Uuid>,) -> Vec <Uuid> |
AssignedCurriculum
Read model for "the curriculum a learner is assigned, with progress".
| Item |
|---|
pub struct AssignedCurriculum |
fn assemble_assigned_curriculum(assignment : CurriculumAssignment, version : CurriculumVersion, placements : Vec <CurriculumVersionSkill>, skills_by_id : & HashMap <Uuid, Skill>, progress_by_skill : & HashMap <Uuid, SkillProgress>, hard_prereqs : & (Uuid, Uuid),) -> AssignedCurriculum |
SkillView
Read model for "the curriculum a learner is assigned, with progress".
| Item |
|---|
pub struct SkillView |
CurriculumRepository:CurriculumRepository
Postgres persistence for the curriculum tables.
| Item |
|---|
pub struct CurriculumRepository |
CurriculumRepository:add
Postgres persistence for the curriculum tables.
| Item |
|---|
CurriculumRepository :: async fn add_version_skill(& self, cvs : & CurriculumVersionSkill) -> Result <()> |
CurriculumRepository :: async fn add_prerequisite(& self, p : & SkillPrerequisite) -> Result <()> |
CurriculumRepository:append
Postgres persistence for the curriculum tables.
| Item |
|---|
CurriculumRepository :: async fn append_event(& self, e : & CurriculumEvent) -> Result <()> |
CurriculumRepository:count
Postgres persistence for the curriculum tables.
| Item |
|---|
CurriculumRepository :: async fn count_version_skills(& self, version_id : Uuid) -> Result <i64> |
CurriculumRepository:create
Postgres persistence for the curriculum tables.
| Item |
|---|
CurriculumRepository :: async fn create_template(& self, t : & CurriculumTemplate) -> Result <()> |
CurriculumRepository :: async fn create_version(& self, v : & CurriculumVersion) -> Result <()> |
CurriculumRepository :: async fn create_skill(& self, s : & Skill) -> Result <()> |
CurriculumRepository :: async fn create_assignment(& self, a : & CurriculumAssignment) -> Result <()> |
CurriculumRepository:get
Postgres persistence for the curriculum tables.
| Item |
|---|
CurriculumRepository :: async fn get_template(& self, id : Uuid) -> Result <Option <CurriculumTemplate>> |
CurriculumRepository :: async fn get_version(& self, id : Uuid) -> Result <Option <CurriculumVersion>> |
CurriculumRepository :: async fn get_skill(& self, id : Uuid) -> Result <Option <Skill>> |
CurriculumRepository :: async fn get_skill_by_code(& self, code : & str) -> Result <Option <Skill>> |
CurriculumRepository :: async fn get_assignment(& self, id : Uuid) -> Result <Option <CurriculumAssignment>> |
CurriculumRepository :: async fn get_skill_progress(& self, assignment_id : Uuid, skill_id : Uuid,) -> Result <Option <SkillProgress>> |
CurriculumRepository:list
Postgres persistence for the curriculum tables.
| Item |
|---|
CurriculumRepository :: async fn list_templates(& self) -> Result <Vec <CurriculumTemplate>> |
CurriculumRepository :: async fn list_versions(& self, curriculum_id : Uuid) -> Result <Vec <CurriculumVersion>> |
CurriculumRepository :: async fn list_version_skills(& self, version_id : Uuid,) -> Result <Vec <CurriculumVersionSkill>> |
CurriculumRepository :: async fn list_prerequisites(& self, version_id : Uuid) -> Result <Vec <SkillPrerequisite>> |
CurriculumRepository :: async fn list_assignments_for_learner(& self, learner_party_id : Uuid,) -> Result <Vec <CurriculumAssignment>> |
CurriculumRepository :: async fn list_skill_progress(& self, assignment_id : Uuid) -> Result <Vec <SkillProgress>> |
CurriculumRepository :: async fn list_events(& self, assignment_id : Uuid) -> Result <Vec <CurriculumEvent>> |
CurriculumRepository:max
Postgres persistence for the curriculum tables.
| Item |
|---|
CurriculumRepository :: async fn max_version_no(& self, curriculum_id : Uuid) -> Result <Option <i32>> |
CurriculumRepository:new
Postgres persistence for the curriculum tables.
| Item |
|---|
CurriculumRepository :: fn new(pool : PgPool) -> Self |
CurriculumRepository:pool
Postgres persistence for the curriculum tables.
| Item |
|---|
CurriculumRepository :: fn pool(& self) -> & PgPool |
CurriculumRepository:remove
Postgres persistence for the curriculum tables.
| Item |
|---|
CurriculumRepository :: async fn remove_version_skill(& self, version_id : Uuid, skill_id : Uuid) -> Result <()> |
CurriculumRepository :: async fn remove_prerequisite(& self, version_id : Uuid, skill_id : Uuid, prerequisite_skill_id : Uuid,) -> Result <()> |
CurriculumRepository:set
Postgres persistence for the curriculum tables.
| Item |
|---|
CurriculumRepository :: async fn set_template_status(& self, id : Uuid, status : CurriculumStatus) -> Result <()> |
CurriculumRepository :: async fn set_version_status(& self, id : Uuid, status : VersionStatus, published_at : Option <DateTime <Utc>>,) -> Result <()> |
CurriculumRepository :: async fn set_version_skill_order(& self, version_id : Uuid, skill_id : Uuid, sort_order : i32,) -> Result <()> |
CurriculumRepository :: async fn set_assignment_status(& self, id : Uuid, status : AssignmentStatus, completed_at : Option <DateTime <Utc>>,) -> Result <()> |
CurriculumRepository:upsert
Postgres persistence for the curriculum tables.
| Item |
|---|
CurriculumRepository :: async fn upsert_skill_progress(& self, p : & SkillProgress) -> Result <()> |
rules (other)
Pure service-layer rules: version numbering + publish/assign gates.
| Item |
|---|
fn next_version_no(current_max : Option <i32>) -> i32 |
fn can_publish_version(status : VersionStatus, skill_count : usize,) -> Result <(), CurriculumError> |
fn can_assign_version(status : VersionStatus) -> Result <(), CurriculumError> |
CurriculumService:CurriculumService
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
pub struct CurriculumService |
CurriculumService:add
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: async fn add_skill(& self, version_id : Uuid, skill_id : Uuid, sort_order : i32,) -> Result <CurriculumVersionSkill> |
CurriculumService :: async fn add_prerequisite(& self, version_id : Uuid, skill_id : Uuid, prerequisite_skill_id : Uuid, kind : PrerequisiteKind,) -> Result <SkillPrerequisite> |
CurriculumService:append
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: async fn append_event(& self, assignment_id : Uuid, skill_id : Option <Uuid>, event_type : impl Into <String>, source : EventSource, actor_party_id : Option <Uuid>,) -> Result <CurriculumEvent> |
CurriculumService:archive
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: async fn archive(& self, curriculum_id : Uuid) -> Result <()> |
CurriculumService:assign
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: async fn assign(& self, version_id : Uuid, learner_party_id : Uuid, assigned_by_party_id : Option <Uuid>,) -> Result <CurriculumAssignment> |
CurriculumService:create
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: async fn create_draft(& self, code : impl Into <String>, title : impl Into <String>,) -> Result <(CurriculumTemplate, CurriculumVersion)> |
CurriculumService :: async fn create_draft_version(& self, curriculum_id : Uuid) -> Result <CurriculumVersion> |
CurriculumService :: async fn create_skill(& self, code : impl Into <String>, title : impl Into <String>, skill_type : impl Into <String>,) -> Result <Skill> |
CurriculumService:get
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: async fn get_assigned_curriculum(& self, assignment_id : Uuid) -> Result <AssignedCurriculum> |
CurriculumService:new
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: fn new(repo : CurriculumRepository) -> Self |
CurriculumService:publish
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: async fn publish_version(& self, version_id : Uuid) -> Result <CurriculumVersion> |
CurriculumService:recommended
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: async fn recommended_next_skills(& self, assignment_id : Uuid) -> Result <Vec <Uuid>> |
CurriculumService:remove
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: async fn remove_skill(& self, version_id : Uuid, skill_id : Uuid) -> Result <()> |
CurriculumService:reorder
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: async fn reorder_skills(& self, version_id : Uuid, ordered_skill_ids : & Uuid) -> Result <()> |
CurriculumService:repo
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: fn repo(& self) -> & CurriculumRepository |
CurriculumService:update
Curriculum service — the minimal API, orchestrating the pure rules
| Item |
|---|
CurriculumService :: async fn update_skill_progress(& self, assignment_id : Uuid, skill_id : Uuid, status : SkillProgressStatus, score : Option <i32>, manually_overridden : bool,) -> Result <SkillProgress> |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
domain_curriculum
errormodelsprogressionread_modelrepositoryrulesservice
flowchart TD n_domain_curriculum["domain_curriculum"] n_domain_curriculum --> n_error["error"] n_domain_curriculum --> n_models["models"] n_domain_curriculum --> n_progression["progression"] n_domain_curriculum --> n_read_model["read_model"] n_domain_curriculum --> n_repository["repository"] n_domain_curriculum --> n_rules["rules"] n_domain_curriculum --> n_service["service"]
Public surface
`error`
| Item | What it is |
|---|---|
pub enum CurriculumError | — |
`models`
| Item | What it is |
|---|---|
pub struct ParseStatusError | Parse error for the controlled-vocabulary enums. |
ParseStatusError :: fn fmt(& self, f : & mut std::fmt::Formatter <'_>) -> std::fmt::Result | — |
pub enum CurriculumStatus | Lifecycle of a curriculum template. |
CurriculumStatus :: fn as_str(self) -> & 'static str | snake_case label (matches the serde representation and the SQL value). |
CurriculumStatus :: fn from_str(s : & str) -> Result <Self, Self::Err> | — |
pub enum VersionStatus | Lifecycle of a concrete curriculum version |
VersionStatus :: fn is_editable(self) -> bool | Whether this version may still be structurally edited. |
VersionStatus :: fn label(self) -> & 'static str | snake_case label (matches the serde representation and the SQL value). |
VersionStatus :: fn as_str(self) -> & 'static str | snake_case label (matches the serde representation and the SQL value). |
VersionStatus :: fn from_str(s : & str) -> Result <Self, Self::Err> | — |
pub enum AssignmentStatus | Lifecycle of a learner's assignment to a curriculum version. |
AssignmentStatus :: fn as_str(self) -> & 'static str | snake_case label (matches the serde representation and the SQL value). |
AssignmentStatus :: fn from_str(s : & str) -> Result <Self, Self::Err> | — |
pub enum SkillProgressStatus | Explicit, stored mastery state of one skill for one assignment (principle #4). |
SkillProgressStatus :: fn is_mastered(self) -> bool | — |
SkillProgressStatus :: fn as_str(self) -> & 'static str | snake_case label (matches the serde representation and the SQL value). |
SkillProgressStatus :: fn from_str(s : & str) -> Result <Self, Self::Err> | — |
pub enum PrerequisiteKind | Strength of a prerequisite edge (principle #5). |
PrerequisiteKind :: fn is_blocking(self) -> bool | Only Hard prerequisites block unlocking (principle #5). |
PrerequisiteKind :: fn as_str(self) -> & 'static str | snake_case label (matches the serde representation and the SQL value). |
PrerequisiteKind :: fn from_str(s : & str) -> Result <Self, Self::Err> | — |
pub enum EventSource | Where a curriculum event originated (principle #9). |
EventSource :: fn as_str(self) -> & 'static str | snake_case label (matches the serde representation and the SQL value). |
EventSource :: fn from_str(s : & str) -> Result <Self, Self::Err> | — |
pub struct CurriculumTemplate | A curriculum template: the authoring root |
CurriculumTemplate :: fn new(code : impl Into <String>, title : impl Into <String>) -> Self | A new draft template with the given code + title. |
CurriculumTemplate :: fn id(& self) -> Uuid | — |
CurriculumTemplate :: fn created_at(& self) -> DateTime <Utc> | — |
CurriculumTemplate :: fn updated_at(& self) -> DateTime <Utc> | — |
CurriculumTemplate :: fn deleted_at(& self) -> Option <DateTime <Utc>> | — |
pub struct CurriculumVersion | A concrete, numbered version of a curriculum |
CurriculumVersion :: fn new(curriculum_id : Uuid, version_no : i32) -> Self | A new draft version version_no of curriculum_id. |
CurriculumVersion :: fn id(& self) -> Uuid | — |
CurriculumVersion :: fn created_at(& self) -> DateTime <Utc> | — |
CurriculumVersion :: fn updated_at(& self) -> DateTime <Utc> | — |
pub struct Skill | A reusable skill |
Skill :: fn new(code : impl Into <String>, title : impl Into <String>, skill_type : impl Into <String>,) -> Self | A new skill with the given code, title and domain skill_type. |
Skill :: fn id(& self) -> Uuid | — |
Skill :: fn created_at(& self) -> DateTime <Utc> | — |
Skill :: fn updated_at(& self) -> DateTime <Utc> | — |
Skill :: fn deleted_at(& self) -> Option <DateTime <Utc>> | — |
pub struct CurriculumVersionSkill | Placement of a skill inside a curriculum version (principle #2). |
CurriculumVersionSkill :: fn new(curriculum_version_id : Uuid, skill_id : Uuid, sort_order : i32) -> Self | Place skill_id in curriculum_version_id at sort_order (required by default). |
CurriculumVersionSkill :: fn id(& self) -> Uuid | — |
CurriculumVersionSkill :: fn created_at(& self) -> DateTime <Utc> | — |
CurriculumVersionSkill :: fn updated_at(& self) -> DateTime <Utc> | — |
pub struct SkillPrerequisite | A prerequisite edge within a version: skill_id requires prerequisite_skill_id (principle #5) |
SkillPrerequisite :: fn new(curriculum_version_id : Uuid, skill_id : Uuid, prerequisite_skill_id : Uuid, kind : PrerequisiteKind,) -> Self | — |
pub struct CurriculumAssignment | A learner's assignment to a curriculum version, keyed by learner_party_id (any Party — Animal / Person), principle #3. |
CurriculumAssignment :: fn new(curriculum_version_id : Uuid, learner_party_id : Uuid) -> Self | Assign learner_party_id to curriculum_version_id (active). |
CurriculumAssignment :: fn id(& self) -> Uuid | — |
CurriculumAssignment :: fn created_at(& self) -> DateTime <Utc> | — |
CurriculumAssignment :: fn updated_at(& self) -> DateTime <Utc> | — |
pub struct SkillProgress | Current, explicit mastery state of one skill for one assignment (principle #4). |
SkillProgress :: fn new(assignment_id : Uuid, skill_id : Uuid) -> Self | A fresh not_started progress row for skill_id under assignment_id. |
SkillProgress :: fn id(& self) -> Uuid | — |
SkillProgress :: fn created_at(& self) -> DateTime <Utc> | — |
SkillProgress :: fn updated_at(& self) -> DateTime <Utc> | — |
pub struct CurriculumEvent | An append-only curriculum event — the audit trail behind SkillProgress (principle #9) |
CurriculumEvent :: fn new(assignment_id : Uuid, event_type : impl Into <String>, source : EventSource) -> Self | A new event of event_type from source for assignment_id. |
CurriculumEvent :: fn for_skill(mut self, skill_id : Uuid) -> Self | Attach the event to a specific skill. |
CurriculumEvent :: fn id(& self) -> Uuid | — |
`progression`
| Item | What it is |
|---|---|
fn validate_no_cycles(edges : & (Uuid, Uuid)) -> Result <(), CurriculumError> | Validate that prerequisite edges form a DAG (no cycles, principle #5). |
fn unlocked_skills(all_skills : & Uuid, hard_prereqs : & (Uuid, Uuid), mastered : & HashSet <Uuid>,) -> Vec <Uuid> | Skills whose hard prerequisites are all in mastered (i.e |
fn recommended_next(all_skills : & Uuid, hard_prereqs : & (Uuid, Uuid), mastered : & HashSet <Uuid>,) -> Vec <Uuid> | Unlocked skills that are not yet mastered — the recommended next steps. |
`read_model`
| Item | What it is |
|---|---|
pub struct SkillView | One skill within an assigned curriculum: its definition, its placement in the version, the learner's explicit progress, and whether its hard prerequisites are all mastered (unlocked). |
pub struct AssignedCurriculum | The full read model: a learner's assignment + the (frozen) version, the ordered skills with progress, and the recommended next skill ids. |
fn assemble_assigned_curriculum(assignment : CurriculumAssignment, version : CurriculumVersion, placements : Vec <CurriculumVersionSkill>, skills_by_id : & HashMap <Uuid, Skill>, progress_by_skill : & HashMap <Uuid, SkillProgress>, hard_prereqs : & (Uuid, Uuid),) -> AssignedCurriculum | Assemble the read model from already-fetched rows |
`repository`
| Item | What it is |
|---|---|
pub struct CurriculumRepository | Thin Postgres repository over the eight curriculum tables |
CurriculumRepository :: fn new(pool : PgPool) -> Self | Wrap a connection pool. |
CurriculumRepository :: fn pool(& self) -> & PgPool | Borrow the underlying pool (for transactions / advanced callers). |
CurriculumRepository :: async fn create_template(& self, t : & CurriculumTemplate) -> Result <()> | — |
CurriculumRepository :: async fn get_template(& self, id : Uuid) -> Result <Option <CurriculumTemplate>> | — |
CurriculumRepository :: async fn list_templates(& self) -> Result <Vec <CurriculumTemplate>> | List non-deleted templates, newest first. |
CurriculumRepository :: async fn set_template_status(& self, id : Uuid, status : CurriculumStatus) -> Result <()> | Set a template's lifecycle status (draft → published → archived). |
CurriculumRepository :: async fn create_version(& self, v : & CurriculumVersion) -> Result <()> | — |
CurriculumRepository :: async fn get_version(& self, id : Uuid) -> Result <Option <CurriculumVersion>> | — |
CurriculumRepository :: async fn list_versions(& self, curriculum_id : Uuid) -> Result <Vec <CurriculumVersion>> | All versions of a curriculum, lowest version number first. |
CurriculumRepository :: async fn max_version_no(& self, curriculum_id : Uuid) -> Result <Option <i32>> | Highest existing version number for a curriculum (for next_version_no). |
CurriculumRepository :: async fn set_version_status(& self, id : Uuid, status : VersionStatus, published_at : Option <DateTime <Utc>>,) -> Result <()> | Update a version's status, stamping published_at when supplied. |
CurriculumRepository :: async fn create_skill(& self, s : & Skill) -> Result <()> | — |
CurriculumRepository :: async fn get_skill(& self, id : Uuid) -> Result <Option <Skill>> | — |
CurriculumRepository :: async fn get_skill_by_code(& self, code : & str) -> Result <Option <Skill>> | — |
CurriculumRepository :: async fn add_version_skill(& self, cvs : & CurriculumVersionSkill) -> Result <()> | — |
CurriculumRepository :: async fn remove_version_skill(& self, version_id : Uuid, skill_id : Uuid) -> Result <()> | — |
CurriculumRepository :: async fn list_version_skills(& self, version_id : Uuid,) -> Result <Vec <CurriculumVersionSkill>> | Skills placed in a version, ordered by sort_order then placement time. |
CurriculumRepository :: async fn count_version_skills(& self, version_id : Uuid) -> Result <i64> | — |
CurriculumRepository :: async fn set_version_skill_order(& self, version_id : Uuid, skill_id : Uuid, sort_order : i32,) -> Result <()> | Reset a placement's sort order (used by reorder). |
CurriculumRepository :: async fn add_prerequisite(& self, p : & SkillPrerequisite) -> Result <()> | Insert (or update the kind of) a prerequisite edge. |
CurriculumRepository :: async fn remove_prerequisite(& self, version_id : Uuid, skill_id : Uuid, prerequisite_skill_id : Uuid,) -> Result <()> | — |
CurriculumRepository :: async fn list_prerequisites(& self, version_id : Uuid) -> Result <Vec <SkillPrerequisite>> | All prerequisite edges in a version. |
CurriculumRepository :: async fn create_assignment(& self, a : & CurriculumAssignment) -> Result <()> | — |
CurriculumRepository :: async fn get_assignment(& self, id : Uuid) -> Result <Option <CurriculumAssignment>> | — |
CurriculumRepository :: async fn list_assignments_for_learner(& self, learner_party_id : Uuid,) -> Result <Vec <CurriculumAssignment>> | All assignments for a learner (newest first). |
CurriculumRepository :: async fn set_assignment_status(& self, id : Uuid, status : AssignmentStatus, completed_at : Option <DateTime <Utc>>,) -> Result <()> | — |
CurriculumRepository :: async fn upsert_skill_progress(& self, p : & SkillProgress) -> Result <()> | Insert or replace the progress row for (assignment_id, skill_id). |
CurriculumRepository :: async fn get_skill_progress(& self, assignment_id : Uuid, skill_id : Uuid,) -> Result <Option <SkillProgress>> | — |
CurriculumRepository :: async fn list_skill_progress(& self, assignment_id : Uuid) -> Result <Vec <SkillProgress>> | All progress rows for an assignment. |
CurriculumRepository :: async fn append_event(& self, e : & CurriculumEvent) -> Result <()> | — |
CurriculumRepository :: async fn list_events(& self, assignment_id : Uuid) -> Result <Vec <CurriculumEvent>> | Event history for an assignment, oldest first (audit order). |
`rules`
| Item | What it is |
|---|---|
fn next_version_no(current_max : Option <i32>) -> i32 | Next version number for a curriculum (1-based; first version = 1). |
fn can_publish_version(status : VersionStatus, skill_count : usize,) -> Result <(), CurriculumError> | A version may be published only if it is a Draft with at least one skill. |
fn can_assign_version(status : VersionStatus) -> Result <(), CurriculumError> | A learner may be assigned only to a Published version (principle #8). |
`service`
| Item | What it is |
|---|---|
pub struct CurriculumService | High-level curriculum operations. |
CurriculumService :: fn new(repo : CurriculumRepository) -> Self | Build a service over a repository. |
CurriculumService :: fn repo(& self) -> & CurriculumRepository | Borrow the underlying repository (for callers needing raw reads). |
CurriculumService :: async fn create_draft(& self, code : impl Into <String>, title : impl Into <String>,) -> Result <(CurriculumTemplate, CurriculumVersion)> | Create a new draft curriculum: a template plus its draft version 1. |
CurriculumService :: async fn create_draft_version(& self, curriculum_id : Uuid) -> Result <CurriculumVersion> | Start a new draft version of an existing curriculum (empty placement — the explicit migration of skills from a prior version is a separate op, principle #11) |
CurriculumService :: async fn create_skill(& self, code : impl Into <String>, title : impl Into <String>, skill_type : impl Into <String>,) -> Result <Skill> | Create a reusable skill. |
CurriculumService :: async fn add_skill(& self, version_id : Uuid, skill_id : Uuid, sort_order : i32,) -> Result <CurriculumVersionSkill> | Place a skill into a draft version at sort_order. |
CurriculumService :: async fn remove_skill(& self, version_id : Uuid, skill_id : Uuid) -> Result <()> | Remove a skill placement from a draft version. |
CurriculumService :: async fn reorder_skills(& self, version_id : Uuid, ordered_skill_ids : & Uuid) -> Result <()> | Reorder a draft version's skills: ordered_skill_idsi gets sort_order = i. |
CurriculumService :: async fn add_prerequisite(& self, version_id : Uuid, skill_id : Uuid, prerequisite_skill_id : Uuid, kind : PrerequisiteKind,) -> Result <SkillPrerequisite> | Add a prerequisite edge to a draft version, validating the whole edge set stays a DAG before persisting (principle #5). |
CurriculumService :: async fn publish_version(& self, version_id : Uuid) -> Result <CurriculumVersion> | Publish a draft version (requires ≥1 skill) |
CurriculumService :: async fn archive(& self, curriculum_id : Uuid) -> Result <()> | Archive a curriculum template (published versions remain readable but the template is no longer offered, principle #8). |
CurriculumService :: async fn assign(& self, version_id : Uuid, learner_party_id : Uuid, assigned_by_party_id : Option <Uuid>,) -> Result <CurriculumAssignment> | Assign a learner (a Party) to a published version, seeding a not_started progress row for every placed skill (principle #3/#4/#8). |
CurriculumService :: async fn update_skill_progress(& self, assignment_id : Uuid, skill_id : Uuid, status : SkillProgressStatus, score : Option <i32>, manually_overridden : bool,) -> Result <SkillProgress> | Update a learner's explicit progress on one skill |
CurriculumService :: async fn append_event(& self, assignment_id : Uuid, skill_id : Option <Uuid>, event_type : impl Into <String>, source : EventSource, actor_party_id : Option <Uuid>,) -> Result <CurriculumEvent> | Append an immutable event to an assignment's history (principle #9). |
CurriculumService :: async fn get_assigned_curriculum(& self, assignment_id : Uuid) -> Result <AssignedCurriculum> | The full assigned-curriculum read model: assignment + frozen version + ordered skills with progress + recommended next skills. |
CurriculumService :: async fn recommended_next_skills(& self, assignment_id : Uuid) -> Result <Vec <Uuid>> | The learner's recommended next skills for an assignment (unlocked and not yet mastered). |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
CurriculumError | error::CurriculumError |
CurriculumRepository | repository::CurriculumRepository |
CurriculumService | service::CurriculumService |
{AssignmentStatus,CurriculumAssignment,CurriculumEvent,CurriculumStatus,CurriculumTemplate,CurriculumVersion,CurriculumVersionSkill,EventSource,ParseStatusError,PrerequisiteKind,Skill,SkillPrerequisite,SkillProgress,SkillProgressStatus,VersionStatus,} | models::{AssignmentStatus,CurriculumAssignment,CurriculumEvent,CurriculumStatus,CurriculumTemplate,CurriculumVersion,CurriculumVersionSkill,EventSource,ParseStatusError,PrerequisiteKind,Skill,SkillPrerequisite,SkillProgress,SkillProgressStatus,VersionStatus,} |
{assemble_assigned_curriculum,AssignedCurriculum,SkillView} | read_model::{assemble_assigned_curriculum,AssignedCurriculum,SkillView} |
{can_assign_version,can_publish_version,next_version_no} | rules::{can_assign_version,can_publish_version,next_version_no} |
{recommended_next,unlocked_skills,validate_no_cycles} | progression::{recommended_next,unlocked_skills,validate_no_cycles} |
Boundary
Reaches into foundation.
Shares tier domain with 41 other crates: domain-agreements, domain-ai-report, domain-billing, domain-catalog, domain-classify, domain-comments, domain-competitive-intel, domain-contact, … (41 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | domain |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/domain/curriculum |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_domain["domain"] --> n_foundation["foundation"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `foundation-basemodels` | foundation | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
async-trait | ^0.1 | — | no | always |
chrono | ^0.4 | serde | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
sqlx | ^0.8 | runtime-tokio, postgres, chrono, uuid, json | no | always |
thiserror | ^2 | — | no | always |
uuid | ^1 | v4, serde | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
tokio | ^1 | full, test-util | no | always |
Build. None.
Depended on by. Nothing in this workspace.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR SELF["domain-curriculum"] SELF -->|runtime| n_foundation_basemodels["foundation-basemodels"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
| Feature | Enables | On by default |
|---|---|---|
postgres | — | no |
flowchart LR n_postgres["postgres"]
Targets
| Kind | Name | Source |
|---|---|---|
| lib | domain_curriculum | `src/lib.rs` |
| test | pg_repository | `tests/pg_repository.rs` |
| test | pg_service | `tests/pg_service.rs` |
Error model
| Error type | Named by |
|---|---|
CurriculumError | can_assign_version, can_publish_version, validate_no_cycles |
ParseStatusError | declared, no public signature returns it |
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | yes |
| async runtime | none detected |
| database access | yes |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 21 |
| Integration tests | 6 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
error | 1 | 0 | 0 |
models | 15 | 0 | 0 |
progression | 3 | 0 | 0 |
read_model | 3 | 0 | 0 |
repository | 1 | 0 | 0 |
rules | 3 | 0 | 0 |
service | 1 | 0 | 0 |
What the tests establish, by name:
duplicate_skill_code_is_rejected—tests/pg_repository.rsduplicate_version_no_is_rejected—tests/pg_repository.rsfull_curriculum_lifecycle_round_trips—tests/pg_repository.rsauthor_publish_assign_progress_read_flow—tests/pg_service.rscannot_publish_empty_or_assign_draft—tests/pg_service.rssecond_draft_version_increments_number—tests/pg_service.rsenum_as_str_and_fromstr_round_trip—src/models.rsevent_for_skill_attaches_skill—src/models.rsfromstr_rejects_unknown—src/models.rsnew_assignment_is_active—src/models.rsnew_progress_starts_not_started—src/models.rsnew_template_is_draft_active—src/models.rsnew_version_is_draft_unpublished—src/models.rsnew_version_skill_is_required_by_default—src/models.rsonly_draft_version_is_editable—src/models.rsonly_hard_prereqs_block—src/models.rsstatus_serializes_snake_case—src/models.rsacyclic_graph_validates—src/progression.rscycle_is_rejected—src/progression.rsrecommended_excludes_mastered—src/progression.rsself_loop_is_a_cycle—src/progression.rsunlocked_respects_hard_prereqs—src/progression.rsassembles_ordered_skills_with_unlocked_and_recommended—src/read_model.rslocked_skill_is_not_recommended—src/read_model.rsassign_requires_published—src/rules.rspublish_requires_draft_with_skills—src/rules.rsversion_numbers_start_at_one_and_increment—src/rules.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 74 | 121 |
Public modules with a //! block | 7 | 7 |
pie showData
title Public items with rustdoc
"Documented" : 74
"No rustdoc detected" : 47
Metrics
| Metric | Value |
|---|---|
| Rust source files | 8 |
| Source lines | 2233 |
| Code lines | 1806 |
| Public API items | 121 |
| Public modules | 7 |
| Tests | 27 |
| Examples | 0 |
| Cargo features | 1 |
| Direct runtime dependencies | 8 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"enum" : 7
"function" : 7
"method" : 94
"struct" : 13
pie showData
title Rust source composition
"Code" : 1806
"Blank or comment" : 427
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.