fix(schema): DOJ-4007 — relax lesson.required to unblock free-form / legacy content#22
fix(schema): DOJ-4007 — relax lesson.required to unblock free-form / legacy content#22lapc506 wants to merge 1 commit into
Conversation
…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 SummaryThis PR relaxes the course lesson schema to support legacy free-form lessons. The main changes are:
Confidence Score: 2/5These issues should be fixed before merging.
|
| 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
| "anyOf": [ | ||
| { "required": ["body"] }, | ||
| { "required": ["context", "concept", "build", "ship"] } | ||
| ], |
There was a problem hiding this comment.
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.
| "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.| @@ -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." | |||
There was a problem hiding this comment.
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.| @@ -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." | |||
There was a problem hiding this comment.
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.| @@ -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." | |||
There was a problem hiding this comment.
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.| "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." |
There was a problem hiding this comment.
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.
Summary
Relaxes
lesson.requiredincourse.schema.jsonfrom 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.requiredshrinks from[id, order, slug, title, context, concept, build, ship, reflect]→[id, order, slug, title](only metadata stays required)bodyfield accepts free-form markdown as alternative shape (minLength 50)anyOfenforces AT LEAST one of the two authoring shapes (cannot ship an empty lesson)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
Lessoninterface (src/types/course.ts:53-64) does NOT reference any of the 5 IDT bucketsLessonDraftalready exposes genericcontent?: stringmapping tobodylesson.context|concept|build|ship|reflectacrosssrc/+supabase/Verification
scripts/ci/check_json_schemas.pypasses (3 schema files valid)Follow-up (separate work, tracked under DOJ-4007)
bodyonlyai-image-generation-real-projectscourse (Fernanda already started on uuid6e706026-7d89-4e5c-bebc-53fe442289ad)Refs
Created by Claude Code on behalf of @andres
🤖 Generated with Claude Code