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. There is no fixed cap on KPs per concept. Fully-authored concepts should have as many KPs as the staircase requires, often around 3-5, 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[] | At least two distinct options for choice, scenario, or ordering problems. Matching uses left|right pairs. |
| correct* | varies by problem type | Answer contract for the selected type. See the requirements below. |
| explanation | string | Shown after answering — should name the likely misconception |
| difficulty | 1-5 | Problem difficulty level (integer 1-5) |
- Multiple choice and scenario: Set a zero-based integer index within the options. For four options, valid answers are 0 through 3.
- True or false: Set true or false, as a boolean or a string.
- Fill blank: Set nonempty text, a number, or an object with an answer and optional alternatives.
- Ordering: List every option text once in the correct order, or supply comma-separated indices that include every option once.
- Matching: Use left|right option pairs. Map every left label to an offered right label, or provide a full comma-separated index order.
Example course YAML
This excerpt illustrates the schema. It needs complete teaching content and practice variants before publication. Review the source facts and run validation and the review gate before importing a finished course.
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
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 as many KPs as the staircase requires, often around 3-5. 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.