Skip to content

fix(schema): DOJ-4007 — relax lesson.required to unblock free-form / legacy content#22

Open
lapc506 wants to merge 1 commit into
mainfrom
andres/doj-4007-relax-lesson-schema
Open

fix(schema): DOJ-4007 — relax lesson.required to unblock free-form / legacy content#22
lapc506 wants to merge 1 commit into
mainfrom
andres/doj-4007-relax-lesson-schema

Conversation

@lapc506
Copy link
Copy Markdown
Collaborator

@lapc506 lapc506 commented May 18, 2026

Summary

Relaxes lesson.required in course.schema.json from 9 → 4 fields so the 27 existing dojo-academy text classes (formato libre) can pass schema validation without ~3-4 hours of manual retrofitting per lesson (Fernanda's audit 2026-05-15, Slack thread).

Same systemic "schema rigidity" failure mode that DemoLab hit with Articulate Rise's open-ended question rejection — this PR fixes it at the schema layer so all downstream tooling stays compatible.

What changes

  • lesson.required shrinks from [id, order, slug, title, context, concept, build, ship, reflect][id, order, slug, title] (only metadata stays required)
  • 5 pedagogical buckets become optional but strongly recommended — descriptions now lead with "(Recommended)"
  • New body field accepts free-form markdown as alternative shape (minLength 50)
  • anyOf enforces AT LEAST one of the two authoring shapes (cannot ship an empty lesson)
  • Lesson description documents the two shapes + downstream consumer contract
"anyOf": [
  { "required": ["body"] },
  { "required": ["context", "concept", "build", "ship"] }
]

Why this approach (Opción C — free-form fallback)

Andrés' directive (2026-05-18): the schema must serve content authors, not the other way around. Retrofitting 27 lessons manually is a poor trade against a 10-line schema change that unblocks all of them immediately + enables external imports (Articulate Rise, Notion).

Pedagogical structure remains the IDT default for NEW lessons — writer / reviewer agents keep emitting the 5-bucket frame. Legacy + external content now has a valid path.

dojo-os compatibility — verified

  • Lesson interface (src/types/course.ts:53-64) does NOT reference any of the 5 IDT buckets
  • LessonDraft already exposes generic content?: string mapping to body
  • Zero matches for lesson.context|concept|build|ship|reflect across src/ + supabase/
  • No follow-up code change required in dojo-os

Verification

  • scripts/ci/check_json_schemas.py passes (3 schema files valid)
  • IDT consumer agents (course-visualizer, changelog-generator, references/course-iteration-guide) reference 5-bucket for visualization/changelog only, NOT validation
  • CI green on first push

Follow-up (separate work, tracked under DOJ-4007)

  • IDT writer/reviewer agents may want to emit WARNING on lessons with body only
  • Staging seed of ai-image-generation-real-projects course (Fernanda already started on uuid 6e706026-7d89-4e5c-bebc-53fe442289ad)

Refs

  • DOJ-4007 — Phase 3 content backflow to staging (parent)
  • Slack #C08PNJTDR4P 2026-05-15 — Fernanda audit thread
  • Slack DM Fernanda 2026-05-15 18:25 — concrete blocker writeup

Created by Claude Code on behalf of @andres

🤖 Generated with Claude Code

…legacy content

The original `lesson` schema required all 5 IDT pedagogical buckets
(context, concept, build, ship, reflect) as mandatory fields. This blocked
the staging seed of the 27 existing dojo-academy text classes (Fernanda's
audit, 2026-05-15 #C08PNJTDR4P) and matched the same rigidity that
DemoLab hit with Articulate Rise's open-ended question rejection — a
systemic "schema rigidity" failure mode.

## What changes

`lesson.required` shrinks from 9 → 4 fields (only metadata: id, order,
slug, title). The 5 pedagogical buckets become OPTIONAL but
strongly-recommended (their descriptions now lead with "(Recommended)").
A new `body` field accepts free-form markdown as an alternative shape.

`anyOf` enforces that a lesson MUST provide AT LEAST one of the two
authoring shapes — either the 5-bucket structured fields OR `body` — so
empty lessons still fail validation:

```jsonc
"anyOf": [
  { "required": ["body"] },
  { "required": ["context", "concept", "build", "ship"] }
]
```

The description on the lesson definition documents the two shapes and the
contract for downstream consumers (dojo-os ingestion): MUST accept both
shapes without branching — render the structured fields when present,
fall back to `body` otherwise.

## Why opcion C (free-form fallback, IDT recommended)

Andrés' directive (2026-05-18): the schema must serve content authors, not
the other way around. Retrofitting 27 legacy text classes (~3-4 hours of
manual work per Fernanda's estimate) is a poor trade against a 10-line
schema change that unblocks them all immediately. Pedagogical structure
remains the IDT default for NEW lessons — the toolkit's writer / reviewer
agents continue to emit the 5-bucket frame. Legacy content + external
imports (Articulate Rise, Notion, etc.) now have a valid path.

## dojo-os compatibility (verified)

dojo-os `Lesson` interface in `src/types/course.ts:53-64` does NOT
reference any of the 5 IDT buckets. `LessonDraft` already exposes a
generic `content?: string` mapping to `body`. Zero matches for
`lesson.context|concept|build|ship|reflect` across `src/` + `supabase/`.
No follow-up code change required in dojo-os.

## Verification

- `scripts/ci/check_json_schemas.py` passes (JSON parse clean — 3 schema
  files valid).
- IDT consumer agents (`course-visualizer.md`, `changelog-generator.md`,
  `references/course-iteration-guide.md`) reference the 5-bucket fields
  for visualization / changelog summary purposes only; they don't validate
  the shape and continue to work for lessons that DO use the structured
  fields.

## Follow-up (separate work)

- IDT writer/reviewer agents may want to emit WARNING (not error) on
  lessons that ship with `body` only, recommending migration to the
  5-bucket frame when the content allows. Tracked under DOJ-4007 alongside
  the staging seed work.

Refs: DOJ-4007 (Phase 3 — content backflow to staging)

Created by Claude Code on behalf of @andres

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 18, 2026

Greptile Summary

This PR relaxes the course lesson schema to support legacy free-form lessons. The main changes are:

  • Lesson metadata is the only always-required set.
  • A new body markdown field can satisfy lesson content requirements.
  • Structured lesson buckets are marked as recommended instead of globally required.
  • anyOf requires either body or the structured bucket shape.

Confidence Score: 2/5

These issues should be fixed before merging.

  • The schema accepts body-only lessons, but slide rendering and audit workflows still assume five structured fields.

  • Structured lessons can now omit reflect, even though the schema text and consumers still treat it as part of the five-bucket shape.

  • Visualization and changelog tooling need small follow-up updates so free-form lessons are displayed and versioned correctly.

  • assets/schemas/course.schema.json

  • agents/slides-renderer.md

  • assets/templates/marp-lesson.md.tmpl

  • skills/course-audit/SKILL.md

  • agents/course-visualizer.md

  • agents/changelog-generator.md

Important Files Changed

Filename Overview
assets/schemas/course.schema.json Relaxes lesson validation and adds free-form body support, but downstream contracts are not fully updated.
Prompt To Fix All With AI
Fix the following 5 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 5
assets/schemas/course.schema.json:193-196
**Require reflect too**

The structured branch now validates lessons that have `context`, `concept`, `build`, and `ship` but no `reflect`. The schema description still defines the recommended IDT shape as all five buckets, and consumers such as the audit and slide renderer still read `lesson.reflect`. A structured lesson without reflections can now pass schema validation and then fail later quality gates or render an empty reflection slide.

```suggestion
      "anyOf": [
        { "required": ["body"] },
        { "required": ["context", "concept", "build", "ship", "reflect"] }
      ],
```

### Issue 2 of 5
assets/schemas/course.schema.json:193-208
**Body breaks slide rendering**

This new `body` shape validates lessons that do not have `context`, `concept`, `build`, `ship`, or `reflect`, but `/slides-preview` still renders only those five fields into `assets/templates/marp-lesson.md.tmpl`. A legacy body-only lesson can now pass schema validation and then produce blank or undefined Marp sections, or be skipped because the renderer treats every structured section as empty. The schema change needs a matching body fallback in the slide renderer/template before this shape is accepted as a valid lesson.

### Issue 3 of 5
assets/schemas/course.schema.json:193-208
**Audit still fails body**

Body-only lessons now pass schema validation, but `/course-audit` still treats missing `context`, `concept`, `build`, `ship`, and `reflect` as framework failures after validation. That means the legacy lessons this schema is unblocking can immediately fail the next documented course quality workflow. Either the audit needs a `body` path that reports missing buckets as warnings, or the schema should not accept body-only lessons as fully valid for downstream tooling yet.

### Issue 4 of 5
assets/schemas/course.schema.json:193-208
**Visualizer misreads body**

The new free-form shape leaves the visualizer without the section strings it uses for the CONTEXT→CONCEPT→BUILD→SHIP→REFLECT distribution. A valid body-only lesson can be shown with bogus baseline percentages, zero-length sections, or underconstructed BUILD warnings even though the lesson is intentionally free-form. The visualizer needs an explicit body-only display path, such as a “Free-form body” row that skips five-bucket percentage checks.

### Issue 5 of 5
assets/schemas/course.schema.json:205-208
**Classify body edits**

Adding `body` makes it possible for the main instructional content of a lesson to live outside the fields that `changelog-generator` classifies today. A course revision that rewrites a body-only lesson can pass through `/course-revise` or `/course-diff` without the same PATCH classification currently given to `context`, `concept`, `build`, `ship`, and `reflect` rewrites. The changelog rules should include `body` additions, removals, and rewrites so free-form lessons are versioned consistently.

Reviews (1): Last reviewed commit: "fix(schema): DOJ-4007 — relax lesson.req..." | Re-trigger Greptile

Comment on lines +193 to +196
"anyOf": [
{ "required": ["body"] },
{ "required": ["context", "concept", "build", "ship"] }
],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Require reflect too

The structured branch now validates lessons that have context, concept, build, and ship but no reflect. The schema description still defines the recommended IDT shape as all five buckets, and consumers such as the audit and slide renderer still read lesson.reflect. A structured lesson without reflections can now pass schema validation and then fail later quality gates or render an empty reflection slide.

Suggested change
"anyOf": [
{ "required": ["body"] },
{ "required": ["context", "concept", "build", "ship"] }
],
"anyOf": [
{ "required": ["body"] },
{ "required": ["context", "concept", "build", "ship", "reflect"] }
],
Prompt To Fix With AI
This is a comment left during a code review.
Path: assets/schemas/course.schema.json
Line: 193-196

Comment:
**Require reflect too**

The structured branch now validates lessons that have `context`, `concept`, `build`, and `ship` but no `reflect`. The schema description still defines the recommended IDT shape as all five buckets, and consumers such as the audit and slide renderer still read `lesson.reflect`. A structured lesson without reflections can now pass schema validation and then fail later quality gates or render an empty reflection slide.

```suggestion
      "anyOf": [
        { "required": ["body"] },
        { "required": ["context", "concept", "build", "ship", "reflect"] }
      ],
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 193 to +208
@@ -197,31 +202,36 @@
"slug": { "type": "string" },
"title": { "type": "string" },
"estimated_minutes": { "type": "integer", "minimum": 5 },
"body": {
"type": "string",
"minLength": 50,
"description": "Free-form markdown body — alternative to the 5-bucket structured fields. Use for legacy / open-ended content or content imported from external authoring tools (Articulate Rise, Notion exports, etc.). IDT tooling emits a WARNING (not an error) when a lesson uses `body` instead of the structured fields, recommending migration to the 5-bucket frame when the content allows."
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Body breaks slide rendering

This new body shape validates lessons that do not have context, concept, build, ship, or reflect, but /slides-preview still renders only those five fields into assets/templates/marp-lesson.md.tmpl. A legacy body-only lesson can now pass schema validation and then produce blank or undefined Marp sections, or be skipped because the renderer treats every structured section as empty. The schema change needs a matching body fallback in the slide renderer/template before this shape is accepted as a valid lesson.

Prompt To Fix With AI
This is a comment left during a code review.
Path: assets/schemas/course.schema.json
Line: 193-208

Comment:
**Body breaks slide rendering**

This new `body` shape validates lessons that do not have `context`, `concept`, `build`, `ship`, or `reflect`, but `/slides-preview` still renders only those five fields into `assets/templates/marp-lesson.md.tmpl`. A legacy body-only lesson can now pass schema validation and then produce blank or undefined Marp sections, or be skipped because the renderer treats every structured section as empty. The schema change needs a matching body fallback in the slide renderer/template before this shape is accepted as a valid lesson.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 193 to +208
@@ -197,31 +202,36 @@
"slug": { "type": "string" },
"title": { "type": "string" },
"estimated_minutes": { "type": "integer", "minimum": 5 },
"body": {
"type": "string",
"minLength": 50,
"description": "Free-form markdown body — alternative to the 5-bucket structured fields. Use for legacy / open-ended content or content imported from external authoring tools (Articulate Rise, Notion exports, etc.). IDT tooling emits a WARNING (not an error) when a lesson uses `body` instead of the structured fields, recommending migration to the 5-bucket frame when the content allows."
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Audit still fails body

Body-only lessons now pass schema validation, but /course-audit still treats missing context, concept, build, ship, and reflect as framework failures after validation. That means the legacy lessons this schema is unblocking can immediately fail the next documented course quality workflow. Either the audit needs a body path that reports missing buckets as warnings, or the schema should not accept body-only lessons as fully valid for downstream tooling yet.

Prompt To Fix With AI
This is a comment left during a code review.
Path: assets/schemas/course.schema.json
Line: 193-208

Comment:
**Audit still fails body**

Body-only lessons now pass schema validation, but `/course-audit` still treats missing `context`, `concept`, `build`, `ship`, and `reflect` as framework failures after validation. That means the legacy lessons this schema is unblocking can immediately fail the next documented course quality workflow. Either the audit needs a `body` path that reports missing buckets as warnings, or the schema should not accept body-only lessons as fully valid for downstream tooling yet.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 193 to +208
@@ -197,31 +202,36 @@
"slug": { "type": "string" },
"title": { "type": "string" },
"estimated_minutes": { "type": "integer", "minimum": 5 },
"body": {
"type": "string",
"minLength": 50,
"description": "Free-form markdown body — alternative to the 5-bucket structured fields. Use for legacy / open-ended content or content imported from external authoring tools (Articulate Rise, Notion exports, etc.). IDT tooling emits a WARNING (not an error) when a lesson uses `body` instead of the structured fields, recommending migration to the 5-bucket frame when the content allows."
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Visualizer misreads body

The new free-form shape leaves the visualizer without the section strings it uses for the CONTEXT→CONCEPT→BUILD→SHIP→REFLECT distribution. A valid body-only lesson can be shown with bogus baseline percentages, zero-length sections, or underconstructed BUILD warnings even though the lesson is intentionally free-form. The visualizer needs an explicit body-only display path, such as a “Free-form body” row that skips five-bucket percentage checks.

Prompt To Fix With AI
This is a comment left during a code review.
Path: assets/schemas/course.schema.json
Line: 193-208

Comment:
**Visualizer misreads body**

The new free-form shape leaves the visualizer without the section strings it uses for the CONTEXT→CONCEPT→BUILD→SHIP→REFLECT distribution. A valid body-only lesson can be shown with bogus baseline percentages, zero-length sections, or underconstructed BUILD warnings even though the lesson is intentionally free-form. The visualizer needs an explicit body-only display path, such as a “Free-form body” row that skips five-bucket percentage checks.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +205 to +208
"body": {
"type": "string",
"minLength": 50,
"description": "Free-form markdown body — alternative to the 5-bucket structured fields. Use for legacy / open-ended content or content imported from external authoring tools (Articulate Rise, Notion exports, etc.). IDT tooling emits a WARNING (not an error) when a lesson uses `body` instead of the structured fields, recommending migration to the 5-bucket frame when the content allows."
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Classify body edits

Adding body makes it possible for the main instructional content of a lesson to live outside the fields that changelog-generator classifies today. A course revision that rewrites a body-only lesson can pass through /course-revise or /course-diff without the same PATCH classification currently given to context, concept, build, ship, and reflect rewrites. The changelog rules should include body additions, removals, and rewrites so free-form lessons are versioned consistently.

Prompt To Fix With AI
This is a comment left during a code review.
Path: assets/schemas/course.schema.json
Line: 205-208

Comment:
**Classify body edits**

Adding `body` makes it possible for the main instructional content of a lesson to live outside the fields that `changelog-generator` classifies today. A course revision that rewrites a body-only lesson can pass through `/course-revise` or `/course-diff` without the same PATCH classification currently given to `context`, `concept`, `build`, `ship`, and `reflect` rewrites. The changelog rules should include `body` additions, removals, and rewrites so free-form lessons are versioned consistently.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant