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.

FieldTypeDescription
id*stringKebab-case identifier, globally unique (e.g., aws-saa-c03)
name*stringHuman-readable course name
descriptionstringCourse description
estimatedHours*numberEstimated total study hours (must be positive)
version*stringSemantic version string (e.g., "2026.1")
sourceDocumentstringReference to official source (e.g., exam guide, textbook)

sections

Optional ordered list of sections. Concepts reference sections by ID.

FieldTypeDescription
id*stringSection identifier
name*stringSection display name
descriptionstringSection description
sectionExamobjectOptional section exam gate (see below)

sectionExam

Section-end certification checkpoints. Every blueprint concept must belong to the section.

FieldTypeDescription
enabledbooleanWhether the exam is active (default: true)
passingScorenumberScore to pass, 0.0-1.0 (default: 0.75)
timeLimitMinutesnumberOptional time limit in minutes
questionCountnumberTotal questions (default: 10). Must be >= sum of blueprint minQuestions
blueprintarrayArray of { conceptId, minQuestions } — each concept must be in this section
instructionsstringExam 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.

FieldTypeDescription
id*stringKebab-case identifier, unique within the course
name*stringHuman-readable concept name
sectionstringSection ID this concept belongs to
difficulty*1-101=basic definition, 5=application, 8=complex analysis, 10=multi-step
estimatedMinutes*numberEstimated study time in minutes (positive integer)
tagsstring[]Tags for filtering (e.g., "foundational", "calculation")
sourceRefstringSource reference (e.g., "NFPA 1001-2019 JPR 4.3.1")
prerequisitesstring[]Array of concept IDs that must be mastered first (max 3-4 direct)
encompassingarrayImplicit repetition edges: { concept: id, weight: 0.0-1.0 }
knowledgePointsarrayArray 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.

FieldTypeDescription
id*stringKP identifier, unique within the concept
instructionstringMarkdown instruction text (or file path ending in .md/.txt/.html)
instructionContentarrayStructured content blocks (image, video, link, callout)
workedExamplestringStep-by-step worked example text
workedExampleContentarrayStructured content blocks for the worked example
problemsarrayArray 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.

image

Screenshot or diagram

Fields: url, alt, caption?, width?

video

External demo or walkthrough

Fields: url, title, caption?

link

Supporting document or page

Fields: url, title, description?

callout

Highlighted 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.

FieldTypeDescription
id*stringProblem identifier, globally unique across the course
type*enummultiple_choice | fill_blank | true_false | ordering | matching | scenario
question*stringThe question text
optionsstring[]Answer options (for MC: 4 options, 1 correct; for ordering: 4-6 steps)
correct*string | numberCorrect answer (index for MC, text for fill_blank)
explanationstringShown after answering — should name the likely misconception
difficulty1-5Problem difficulty level (integer 1-5)

Example course YAML

aws-saa-c03.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 concept

Authoring 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 blueprint minQuestions

Run graspful validate locally before importing to catch errors early. The review gate runs additional quality checks on top of schema validation.