Course Schema
Courses are defined as YAML files with three top-level keys: course, sections, and concepts. The schema is validated with Zod at import time.
Top-level structure
course:
id: string # kebab-case, globally unique
name: string
description: string
estimatedHours: number
version: string # e.g., "2026.1"
sourceDocument: string # official source reference
sections: # optional, ordered list
- id: string
name: string
description: string
sectionExam: ... # optional exam gate
concepts: # required, array of concept objects
- id: string
name: string
...course
Top-level metadata about the course.
| Field | Type | Description |
|---|---|---|
| id* | string | Kebab-case identifier, globally unique (e.g., aws-saa-c03) |
| name* | string | Human-readable course name |
| description | string | Course description |
| estimatedHours* | number | Estimated total study hours (must be positive) |
| version* | string | Semantic version string (e.g., "2026.1") |
| sourceDocument | string | Reference to official source (e.g., exam guide, textbook) |
sections
Optional ordered list of sections. Concepts reference sections by ID.
| Field | Type | Description |
|---|---|---|
| id* | string | Section identifier |
| name* | string | Section display name |
| description | string | Section description |
| sectionExam | object | Optional section exam gate (see below) |
sectionExam
Section-end certification checkpoints. Every blueprint concept must belong to the section.
| Field | Type | Description |
|---|---|---|
| enabled | boolean | Whether the exam is active (default: true) |
| passingScore | number | Score to pass, 0.0-1.0 (default: 0.75) |
| timeLimitMinutes | number | Optional time limit in minutes |
| questionCount | number | Total questions (default: 10). Must be >= sum of blueprint minQuestions |
| blueprint | array | Array of { conceptId, minQuestions } — each concept must be in this section |
| instructions | string | Exam instructions shown to learner |
concepts
A concept represents one teachable idea that can be tested independently. Too broad and students get stuck. Too narrow and the graph becomes unwieldy.
| Field | Type | Description |
|---|---|---|
| id* | string | Kebab-case identifier, unique within the course |
| name* | string | Human-readable concept name |
| section | string | Section ID this concept belongs to |
| difficulty* | 1-10 | 1=basic definition, 5=application, 8=complex analysis, 10=multi-step |
| estimatedMinutes* | number | Estimated study time in minutes (positive integer) |
| tags | string[] | Tags for filtering (e.g., "foundational", "calculation") |
| sourceRef | string | Source reference (e.g., "NFPA 1001-2019 JPR 4.3.1") |
| prerequisites | string[] | Array of concept IDs that must be mastered first (max 3-4 direct) |
| encompassing | array | Implicit repetition edges: { concept: id, weight: 0.0-1.0 } |
| knowledgePoints | array | Array of KP objects (see below). Empty = graph stub |
knowledgePoints
Each knowledge point teaches one load-bearing move, distinction, or case. Fully-authored concepts should have 2-4 KPs with progressive difficulty. Each KP follows: instruction → content → worked example → practice.
| Field | Type | Description |
|---|---|---|
| id* | string | KP identifier, unique within the concept |
| instruction | string | Markdown instruction text (or file path ending in .md/.txt/.html) |
| instructionContent | array | Structured content blocks (image, video, link, callout) |
| workedExample | string | Step-by-step worked example text |
| workedExampleContent | array | Structured content blocks for the worked example |
| problems | array | Array of problem objects (see below). Need 3+ per KP |
Content blocks
Used in instructionContent and workedExampleContent for media and references. The type field determines the variant.
imageScreenshot or diagram
Fields: url, alt, caption?, width?
videoExternal demo or walkthrough
Fields: url, title, caption?
linkSupporting document or page
Fields: url, title, description?
calloutHighlighted note
Fields: title, body
problems
Practice problems attached to each knowledge point. The adaptive engine needs at least 3 problems per KP. Two consecutive correct answers passes a KP.
| Field | Type | Description |
|---|---|---|
| id* | string | Problem identifier, globally unique across the course |
| type* | enum | multiple_choice | fill_blank | true_false | ordering | matching | scenario |
| question* | string | The question text |
| options | string[] | Answer options (for MC: 4 options, 1 correct; for ordering: 4-6 steps) |
| correct* | string | number | Correct answer (index for MC, text for fill_blank) |
| explanation | string | Shown after answering — should name the likely misconception |
| difficulty | 1-5 | Problem difficulty level (integer 1-5) |
Example course YAML
course:
id: aws-saa-c03
name: AWS Solutions Architect Associate
description: Comprehensive prep for the AWS SAA-C03 exam
estimatedHours: 40
version: "2026.1"
sourceDocument: "AWS SAA-C03 Exam Guide"
sections:
- id: foundations
name: Cloud Foundations
description: Core AWS concepts and global infrastructure
- id: networking
name: Networking
description: VPC, subnets, routing, and connectivity
sectionExam:
enabled: true
passingScore: 0.80
questionCount: 10
blueprint:
- conceptId: vpc-basics
minQuestions: 3
- conceptId: subnet-design
minQuestions: 2
concepts:
- id: shared-responsibility
name: AWS Shared Responsibility Model
section: foundations
difficulty: 3
estimatedMinutes: 20
tags: [foundational, security]
sourceRef: "SAA-C03 Domain 1"
prerequisites: []
knowledgePoints:
- id: shared-responsibility-kp1
instruction: |
AWS uses a shared responsibility model. AWS manages
security OF the cloud (hardware, networking, facilities).
You manage security IN the cloud (data, IAM, encryption).
workedExample: |
Scenario: A company stores PII in S3. Who is responsible
for encrypting that data? Answer: The customer — S3
encryption is the customer's responsibility.
problems:
- id: sr-kp1-p1
type: multiple_choice
question: "Who is responsible for patching the underlying EC2 hypervisor?"
options:
- "The customer"
- "AWS"
- "Shared equally"
- "The VPC owner"
correct: 1
explanation: "AWS manages the hypervisor. Customers manage the OS and applications running on EC2."
difficulty: 2
- id: sr-kp1-p2
type: multiple_choice
question: "A company needs to encrypt S3 objects at rest. Whose responsibility is this?"
options:
- "AWS — they manage S3 infrastructure"
- "The customer — data encryption is their responsibility"
- "S3 encrypts everything by default, no action needed"
- "The VPC security group controls this"
correct: 1
explanation: "While S3 now encrypts by default with SSE-S3, managing encryption keys and choosing encryption methods is the customer's responsibility."
difficulty: 3
- id: sr-kp1-p3
type: scenario
question: "Your auditor asks who manages physical security at an AWS data center. What do you tell them?"
options:
- "We manage it through security groups"
- "AWS manages all physical data center security"
- "It depends on the region"
- "Physical security is a shared responsibility"
correct: 1
explanation: "Physical security is entirely AWS's responsibility — it's security OF the cloud."
difficulty: 4
- id: vpc-basics
name: VPC Fundamentals
section: networking
difficulty: 4
estimatedMinutes: 30
tags: [networking, foundational]
sourceRef: "SAA-C03 Domain 2"
prerequisites:
- shared-responsibility
knowledgePoints: [] # stub — needs graspful fill conceptAuthoring guidelines
Concept granularity
A concept = one teachable idea that can be tested independently. Good: "Forms of Co-Ownership (Joint Tenancy, Tenancy in Common, TBE)". Too broad: "All of Property Law". Too narrow: "Definition of Joint Tenancy" (make this a KP instead).
Prerequisites
Max 3-4 direct prerequisites per concept (working memory limit). Only list direct prerequisites — transitive ones are inferred. If A→B→C, don't add A→C explicitly.
Knowledge points
Fully-authored concepts should have 2-4 KPs with progressive difficulty. Each KP teaches one load-bearing move. Difficulty should rise one small step at a time — learners should feel like climbing a staircase, not jumping a gap.
Problem quality
- Multiple choice: 4 options, 1 correct. Distractors should be plausible.
- Fill blank: Accept reasonable variations (the system normalizes).
- Ordering: 4-6 steps max. Every step must be necessary.
- Scenario: Audio description + follow-up. Best for complex application.
- Explanations should name the likely misconception, not just reveal the answer.
Encompassing weights
- 1.0: Fully exercises the target as a subskill
- 0.5-0.7: Substantially exercises the target
- 0.2-0.4: Partially exercises the target
- < 0.2: Probably not worth listing
Progress-safe evolution
After a course has active learners, keep course.id, section id, concept id, and KP id stable across revisions. Safe changes: refine instruction, add worked examples, revise problems, add new concepts. Unsafe: renaming slugs in place. Use --archiveMissing when retiring content.
Validation rules
The importer validates the following at import time:
- No cycles in the prerequisite graph
- All referenced concept IDs exist
- Encompassing weights are 0.0-1.0
- Every authored concept has at least 1 KP
- Every authored KP has at least 2 problems
- Section exam blueprint concepts belong to the section
- Section exam
questionCount≥ sum of blueprintminQuestions
Run graspful validate locally before importing to catch errors early. The review gate runs additional quality checks on top of schema validation.