From 66a06b3c8c197d878a0721082ddd52c354926a75 Mon Sep 17 00:00:00 2001 From: Asterios Raptis Date: Thu, 1 Oct 2026 15:20:27 +0200 Subject: [PATCH 1/2] chore(engine): pin 0.36.0 and refresh the schema mirror Schema 1.19: free_text gains case_sensitive (engine#242). This repository's gate at 0.36.0: engine-validate: 36 lesson(s), 5 manifest(s) checked - 0 file(s) with errors Co-Authored-By: Claude Opus 5.5 (1M context) --- schema/content-manifest.schema.json | 2 +- schema/engine-version.txt | 2 +- schema/lesson.schema.json | 12 +++++++++--- schema/quality-rules.json | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/schema/content-manifest.schema.json b/schema/content-manifest.schema.json index 5274bd2..683c06b 100644 --- a/schema/content-manifest.schema.json +++ b/schema/content-manifest.schema.json @@ -481,5 +481,5 @@ ], "title": "ContentManifest", "type": "object", - "x-schema-version": "1.18" + "x-schema-version": "1.19" } diff --git a/schema/engine-version.txt b/schema/engine-version.txt index 7b52f5e..93d4c1e 100644 --- a/schema/engine-version.txt +++ b/schema/engine-version.txt @@ -1 +1 @@ -0.35.0 +0.36.0 diff --git a/schema/lesson.schema.json b/schema/lesson.schema.json index db2297c..8a74738 100644 --- a/schema/lesson.schema.json +++ b/schema/lesson.schema.json @@ -304,7 +304,7 @@ } ], "default": null, - "description": "FREE_TEXT: list of accepted answers. Exact-match first, Levenshtein-tolerant fallback in the renderer. The first entry is the canonical answer shown after a wrong attempt. CLOZE ``multiselect`` (#1195) reuses this field with a mode-specific meaning: EVERY entry is a correct option (not just the first), rendered as a checkbox group with ``distractors`` and graded by exact-set match; the two lists must be disjoint.", + "description": "FREE_TEXT: list of accepted answers. Exact-match first, Levenshtein-tolerant fallback in the renderer; case is not an error unless ``case_sensitive`` is true. The first entry is the canonical answer shown after a wrong attempt. CLOZE ``multiselect`` (#1195) reuses this field with a mode-specific meaning: EVERY entry is a correct option (not just the first), rendered as a checkbox group with ``distractors`` and graded by exact-set match; the two lists must be disjoint.", "title": "Accept" }, "accept_orderings": { @@ -352,6 +352,12 @@ "title": "Card Ids", "type": "array" }, + "case_sensitive": { + "default": false, + "description": "FREE_TEXT: when true, an answer must match an ``accept`` entry in case (``I am Anna`` is right, ``i am Anna`` is wrong); when false (the default) case is not an error. Declare it only where the exercise teaches case, such as capitalisation. The rules follow it: ``accept`` and ``distractors`` are compared in case only when it is true (``E-FREETEXT-DISJOINT``). Schema 1.19, engine#242. Ignored by the other exercise types.", + "title": "Case Sensitive", + "type": "boolean" + }, "cloze_mode": { "anyOf": [ { @@ -384,7 +390,7 @@ }, "distractors": { "default": [], - "description": "Content-only fallback distractors. The exercise renderer picks from this pool when no AI provider is configured (EXP-005 / P-114 dual mode). When AI is available, the AI generator may use the pool as a seed for harder distractors.", + "description": "Content-only fallback distractors. The exercise renderer picks from this pool when no AI provider is configured (EXP-005 / P-114 dual mode). When AI is available, the AI generator may use the pool as a seed for harder distractors. FREE_TEXT: no entry may also be an accepted answer (``E-FREETEXT-DISJOINT``, compared after trimming, in case only when ``case_sensitive`` is true).", "items": { "type": "string" }, @@ -1334,5 +1340,5 @@ ], "title": "Lesson", "type": "object", - "x-schema-version": "1.18" + "x-schema-version": "1.19" } diff --git a/schema/quality-rules.json b/schema/quality-rules.json index a7dee44..ea4184d 100644 --- a/schema/quality-rules.json +++ b/schema/quality-rules.json @@ -1,5 +1,5 @@ { - "$schema-version": "1.18", + "$schema-version": "1.19", "_comment": "Shared quality minimums, canonical in learn-content-engine and applied by its validateLessonQuality (engine#185). A lesson's purpose lifts some of them: bridge the exercise and exercise-type minimums, quiz the exercise-type minimum. Consumers read these numbers from the pinned engine release instead of keeping a copy.", "rules": { "minExerciseTypes": 2, From 379554e05730e9c6fcb71ae306b18506c42d643a Mon Sep 17 00:00:00 2001 From: Asterios Raptis Date: Thu, 1 Oct 2026 15:20:27 +0200 Subject: [PATCH 2/2] docs(format): case is not an error unless the exercise declares it Template-owned file copied from adaptive-learner-content-template#98. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/LESSON-FORMAT.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/LESSON-FORMAT.md b/docs/LESSON-FORMAT.md index af75502..8de4917 100644 --- a/docs/LESSON-FORMAT.md +++ b/docs/LESSON-FORMAT.md @@ -172,6 +172,13 @@ right = card `back`). Requires non-empty `card_ids` and forbids an explicit "direction": "source_to_target" } ``` +Case is not an error. An exercise that teaches case (capitalisation) +declares `"case_sensitive": true` (schema 1.19), and the app then grades +it in case. No distractor may also be an accepted answer +(`E-FREETEXT-DISJOINT`); they are compared without case unless the +exercise declares `case_sensitive`, so `i am Anna` can be a distractor +for `I am Anna` only there. + ### cloze ```jsonc { "type": "cloze", "prompt": "…", "card_ids": [ … ],