From dc26a8e2c64350cf45f518e0bd18675dcc0e06a7 Mon Sep 17 00:00:00 2001 From: Asterios Raptis Date: Thu, 24 Sep 2026 13:54:06 +0200 Subject: [PATCH] chore(engine): re-pin to learn-content-engine 0.28.0 and refresh the mirror The nightly currency check reported the lag (issue #37): the pin 0.27.0 differs from the tracked dist-tag latest (0.28.0). Pin bumped and the mirror refreshed from the pinned tarball with check_schema_drift.py --update, both in one commit as the script requires. Schema 1.15 to 1.16 is additive (1.16 only rewrote descriptions), and the new author lints of 0.28.0 are warnings, so no content changes; they show up in the non-blocking warning run. Closes #37 Refs astrapi69/adaptive-learner-content-template#70 Attribution trailer authorized by the repository owner's Claude Code session configuration. Co-Authored-By: Claude Opus 5.5 (1M context) --- schema/content-manifest.schema.json | 22 +++++++++++----------- schema/engine-version.txt | 2 +- schema/lesson.schema.json | 24 ++++++++++++------------ schema/quality-rules.json | 2 +- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/schema/content-manifest.schema.json b/schema/content-manifest.schema.json index f611099..dcffa5c 100644 --- a/schema/content-manifest.schema.json +++ b/schema/content-manifest.schema.json @@ -6,7 +6,7 @@ "properties": { "assets": { "default": [], - "description": "Phase 54 / v1.37.0 — optional list of binary assets (images, audio) the set bundles. Each entry declares a relative path inside ``assets/`` and the expected size in KiB. The downloader fetches every declared asset alongside the lesson JSON; the cache stores them under ``{cache_root}/.../v{version}/assets/{path}``. The manifest validator rejects assets exceeding the per-file size limit (default 500 KiB).", + "description": "Phase 54 / v1.37.0 - optional list of binary assets (images, audio) the set bundles. Each entry declares a relative path inside ``assets/`` and the expected size in KiB. The downloader fetches every declared asset alongside the lesson JSON; the cache stores them under ``{cache_root}/.../v{version}/assets/{path}``. The manifest validator rejects assets exceeding the per-file size limit (default 500 KiB).", "items": { "$ref": "#/$defs/ContentSetAsset" }, @@ -24,7 +24,7 @@ } ], "default": null, - "description": "#769 — optional set-level book block (title/author/url/asin). When present, the lesson's 'Vertiefe das Thema' section auto-inserts it as the first media item." + "description": "#769 - optional set-level book block (title/author/url/asin). When present, the lesson's 'Vertiefe das Thema' section auto-inserts it as the first media item." }, "cover_image": { "anyOf": [ @@ -76,7 +76,7 @@ "title": "Domain Label" }, "id": { - "description": "Slug-safe identifier, unique within the manifest. Convention (Phase 60 / v1.44.0): ``{target}-{level}-from-{source}`` for language sets (e.g. ``fr-a1-from-de``). Pre-v1.2 ids like ``language-fr-a1`` still load — the loader does NOT parse this, it's free-form per the EXP-005 domain-agnostic stance.", + "description": "Slug-safe identifier, unique within the manifest. Convention (Phase 60 / v1.44.0): ``{target}-{level}-from-{source}`` for language sets (e.g. ``fr-a1-from-de``). Pre-v1.2 ids like ``language-fr-a1`` still load - the loader does NOT parse this, it's free-form per the EXP-005 domain-agnostic stance.", "maxLength": 120, "minLength": 1, "title": "Id", @@ -107,12 +107,12 @@ } ], "default": null, - "description": "Phase 60 / v1.44.0 — repo-relative directory where the set's own ``manifest.yaml`` + ``lessons/`` + ``assets/`` live. Enables the source-language tree (e.g. ``sets/de/fr-a1`` for a French-for-German set while the id stays the flat slug ``fr-a1-from-de``). When omitted the loader falls back to the legacy ``sets/{id}`` convention. No leading/trailing slash, no ``..`` segments.", + "description": "Phase 60 / v1.44.0 - repo-relative directory where the set's own ``manifest.yaml`` + ``lessons/`` + ``assets/`` live. Enables the source-language tree (e.g. ``sets/de/fr-a1`` for a French-for-German set while the id stays the flat slug ``fr-a1-from-de``). When omitted the loader falls back to the legacy ``sets/{id}`` convention. No leading/trailing slash, no ``..`` segments.", "title": "Path" }, "source_language": { "default": "en", - "description": "BCP-47 code of the language the learner ALREADY SPEAKS — the language the card ``back`` fields, notes and theory text are written in. A 'French A1 for German speakers' set has ``target_language: fr`` + ``source_language: de``. Defaults to ``en`` for pre-v1.2 content (the pilot sets were authored with English explanations).", + "description": "BCP-47 code of the language the learner ALREADY SPEAKS - the language the card ``back`` fields, notes and theory text are written in. A 'French A1 for German speakers' set has ``target_language: fr`` + ``source_language: de``. Defaults to ``en`` for pre-v1.2 content (the pilot sets were authored with English explanations).", "title": "Source Language", "type": "string" }, @@ -149,7 +149,7 @@ } ], "default": null, - "description": "Phase 60 / v1.44.0 — optional title in the TARGET language (e.g. 'Français A1' for a French set). Shown as a secondary native-script label alongside ``title``. The community-share validator requires it for shareable sets; bundled/legacy sets may omit it.", + "description": "Phase 60 / v1.44.0 - optional title in the TARGET language (e.g. 'Français A1' for a French set). Shown as a secondary native-script label alongside ``title``. The community-share validator requires it for shareable sets; bundled/legacy sets may omit it.", "title": "Title Native" }, "version": { @@ -259,17 +259,17 @@ }, "ContentSetAsset": { "additionalProperties": false, - "description": "One bundled binary asset (image, audio) declared in the\nset manifest (Phase 54 / v1.37.0).\n\nThe declaration drives:\n - the downloader (fetch_asset per ``path`` alongside the\n lesson JSON)\n - the cache writer (store under ``assets/{path}``)\n - the size validator (reject ``size_kb > MAX_ASSET_SIZE_KB``)\n\nOptional everywhere — sets without any assets simply\nomit the ``assets`` list. The current PictureChoice\ncomponent falls back to text-only when the resolver\ncan't produce a blob URL, so authored content without\nassets stays playable.", + "description": "One bundled binary asset (image, audio) declared in the\nset manifest (Phase 54 / v1.37.0).\n\nThe declaration drives:\n - the downloader (fetch_asset per ``path`` alongside the\n lesson JSON)\n - the cache writer (store under ``assets/{path}``)\n - the size validator (reject ``size_kb > MAX_ASSET_SIZE_KB``)\n\nOptional everywhere - sets without any assets simply\nomit the ``assets`` list. The current PictureChoice\ncomponent falls back to text-only when the resolver\ncan't produce a blob URL, so authored content without\nassets stays playable.", "properties": { "path": { - "description": "Relative path inside the set's ``assets/`` directory. Example: ``img/sunrise.png`` resolves to ``{cache_root}/.../assets/img/sunrise.png``. No leading slash, no ``..`` segments — the path is appended to a Path() and any upward navigation would escape the cache isolation.", + "description": "Relative path inside the set's ``assets/`` directory. Example: ``img/sunrise.png`` resolves to ``{cache_root}/.../assets/img/sunrise.png``. No leading slash, no ``..`` segments - the path is appended to a Path() and any upward navigation would escape the cache isolation.", "maxLength": 300, "minLength": 1, "title": "Path", "type": "string" }, "size_kb": { - "description": "Declared file size in KiB (used by the validator + the downloader's progress reporting). The downloader rejects assets whose actual byte length exceeds ``size_kb * 1024`` by more than 10 percent — keeps content authors honest.", + "description": "Declared file size in KiB (used by the validator + the downloader's progress reporting). The downloader rejects assets whose actual byte length exceeds ``size_kb * 1024`` by more than 10 percent - keeps content authors honest.", "maximum": 500, "minimum": 1, "title": "Size Kb", @@ -284,7 +284,7 @@ "type": "object" }, "ContentSetBook": { - "description": "#769 — optional set-level book block (manifest ``sets[].book``).\n\nSurfaced to the lesson's \"Vertiefe das Thema\" section as the first\nmedia item. ``extra=\"ignore\"`` tolerates future fields (e.g. ``isbn``,\n``year``) the media card doesn't consume.", + "description": "#769 - optional set-level book block (manifest ``sets[].book``).\n\nSurfaced to the lesson's \"Vertiefe das Thema\" section as the first\nmedia item. ``extra=\"ignore\"`` tolerates future fields (e.g. ``isbn``,\n``year``) the media card doesn't consume.", "properties": { "asin": { "anyOf": [ @@ -481,5 +481,5 @@ ], "title": "ContentManifest", "type": "object", - "x-schema-version": "1.15" + "x-schema-version": "1.16" } diff --git a/schema/engine-version.txt b/schema/engine-version.txt index 1b58cc1..697f087 100644 --- a/schema/engine-version.txt +++ b/schema/engine-version.txt @@ -1 +1 @@ -0.27.0 +0.28.0 diff --git a/schema/lesson.schema.json b/schema/lesson.schema.json index 83fb1a8..6c6e9d6 100644 --- a/schema/lesson.schema.json +++ b/schema/lesson.schema.json @@ -2,7 +2,7 @@ "$defs": { "Card": { "additionalProperties": false, - "description": "The smallest learnable unit (Phase 43 / 2B-lesson).\n\nA card carries a single term / concept / fact in a single\ndirection. SRS (Phase 46) tracks one card at a time;\nindividual exercises reference cards by id so a single\n'Bonjour = Hello' card can drive a matching exercise, a\nfree-text drill, and a summary review without\nduplication.\n\nConvention: ``card.id`` is unique within the lesson, not\nglobally. Cross-lesson card sharing happens via a\nseparate ``shared/`` directory inside the set (P-111\nterritory — not yet implemented).", + "description": "The smallest learnable unit (Phase 43 / 2B-lesson).\n\nA card carries a single term / concept / fact in a single\ndirection. SRS (Phase 46) tracks one card at a time;\nindividual exercises reference cards by id so a single\n'Bonjour = Hello' card can drive a matching exercise, a\nfree-text drill, and a summary review without\nduplication.\n\nConvention: ``card.id`` is unique within the lesson, not\nglobally. Cross-lesson card sharing happens via a\nseparate ``shared/`` directory inside the set (P-111\nterritory - not yet implemented).", "properties": { "audio": { "anyOf": [ @@ -164,7 +164,7 @@ } ], "default": null, - "description": "Optional Markdown footnote shown after the user answers. Pronunciation tips, etymology, false-friend warnings — anything that helps long-term retention.", + "description": "Optional Markdown footnote shown after the user answers. Pronunciation tips, etymology, false-friend warnings - anything that helps long-term retention.", "title": "Notes" }, "tags": { @@ -205,7 +205,7 @@ }, "CardTokenRole": { "additionalProperties": false, - "description": "One ``token → role`` annotation on a card.\n\nPhase 52I / v1.35.0 / P-130. The cloze generator looks up\nits target blank by matching ``token`` against the\n``ElementError.element_key`` — when a role is present, the\ngenerator can pick a same-role distractor pool instead of\na position-based heuristic.\n\nThe ``token`` is a verbatim slice of the card's ``front``;\nno whitespace normalisation, so authors can annotate even\nsub-word morphemes (an accent-bearing letter, an article\ncontraction) if a future generator needs it.", + "description": "One ``token → role`` annotation on a card.\n\nPhase 52I / v1.35.0 / P-130. The cloze generator looks up\nits target blank by matching ``token`` against the\n``ElementError.element_key`` - when a role is present, the\ngenerator can pick a same-role distractor pool instead of\na position-based heuristic.\n\nThe ``token`` is a verbatim slice of the card's ``front``;\nno whitespace normalisation, so authors can annotate even\nsub-word morphemes (an accent-bearing letter, an article\ncontraction) if a future generator needs it.", "properties": { "role": { "$ref": "#/$defs/TokenRole", @@ -228,7 +228,7 @@ }, "ClozeBlank": { "additionalProperties": false, - "description": "One blank inside a cloze exercise's ``sentence`` (Phase 52D /\nv1.35.0 / P-127).\n\nMarker-based convention: the sentence carries visible ``___``\ntokens; ``blanks[i]`` provides the metadata for the i-th\nmarker (left-to-right). The validator enforces\n``sentence.count(\"___\") == len(blanks)`` so the i↔i mapping\nis unambiguous at render time.\n\n``accept`` carries the per-blank canonical + acceptable\nvariants — the renderer reuses FreeText's ``isFreeTextCorrect``\nmatcher (NFC-normalised + Levenshtein <= 1) so authors only\nneed to enumerate semantic variants (gendered article,\ncapitalisation, et cetera), not typos.", + "description": "One blank inside a cloze exercise's ``sentence`` (Phase 52D /\nv1.35.0 / P-127).\n\nMarker-based convention: the sentence carries visible ``___``\ntokens; ``blanks[i]`` provides the metadata for the i-th\nmarker (left-to-right). The validator enforces\n``sentence.count(\"___\") == len(blanks)`` so the i↔i mapping\nis unambiguous at render time.\n\n``accept`` carries the per-blank canonical + acceptable\nvariants - the renderer reuses FreeText's ``isFreeTextCorrect``\nmatcher (NFC-normalised + Levenshtein <= 1) so authors only\nneed to enumerate semantic variants (gendered article,\ncapitalisation, et cetera), not typos.", "properties": { "accept": { "description": "Accepted answers for this blank. First entry is the canonical (shown after a wrong attempt). Same shape as FREE_TEXT.accept.", @@ -598,7 +598,7 @@ "type": "object" }, "ExerciseType": { - "description": "Closed enum of exercise types the loader knows about.\n\nEXP-001 + EXP-006: the four base types ship in Phase 43-45.\nPhase 52D / v1.35.0 added CLOZE (fill-in-the-blank with\n``___`` markers) — see the schema_version bump in\n``models.py``. Adding a sixth type (ordering, drag-image-\npair, etc.) requires a minor schema_version bump and a new\nenum value plus its renderer.", + "description": "Closed enum of exercise types the loader knows about.\n\nEXP-001 + EXP-006: the four base types ship in Phase 43-45.\nPhase 52D / v1.35.0 added CLOZE (fill-in-the-blank with\n``___`` markers) - see the schema_version bump in\n``models.py``. Adding a sixth type (ordering, drag-image-\npair, etc.) requires a minor schema_version bump and a new\nenum value plus its renderer.", "enum": [ "matching", "picture_choice", @@ -665,7 +665,7 @@ }, "InlineExample": { "additionalProperties": false, - "description": "One inline worked example on a theory step or exercise (schema v1.5).\n\nAn inline example carries REAL content the learner reads in place —\na sample sentence (language lessons) or a code snippet with syntax\nhighlighting (programming lessons). This is DISTINCT from\n``LessonStep.example_url`` (#139 / schema v1.4), which links OUT to an\nexternal illustration: ``example_url`` is the LINK variant,\n``examples`` is the INLINE-CONTENT variant. The two are complementary\nand may coexist on the same theory step.\n\nWhen ``language`` is set, ``content`` is treated as source code in\nthat language and rendered as a syntax-highlighted block (the same\n``CodeBlock`` the theory Markdown + code cards use); when it is\nabsent, ``content`` is plain text. Additive + optional, so content\nwithout ``examples`` validates unchanged.", + "description": "One inline worked example on a theory step or exercise (schema v1.5).\n\nAn inline example carries REAL content the learner reads in place - a sample sentence (language lessons) or a code snippet with syntax\nhighlighting (programming lessons). This is DISTINCT from\n``LessonStep.example_url`` (#139 / schema v1.4), which links OUT to an\nexternal illustration: ``example_url`` is the LINK variant,\n``examples`` is the INLINE-CONTENT variant. The two are complementary\nand may coexist on the same theory step.\n\nWhen ``language`` is set, ``content`` is treated as source code in\nthat language and rendered as a syntax-highlighted block (the same\n``CodeBlock`` the theory Markdown + code cards use); when it is\nabsent, ``content`` is plain text. Additive + optional, so content\nwithout ``examples`` validates unchanged.", "properties": { "content": { "description": "The example's content. Plain text (e.g. a sample sentence) when ``language`` is absent; source code in ``language`` when it is set.", @@ -711,7 +711,7 @@ }, "LessonResource": { "additionalProperties": false, - "description": "One lesson-level supplementary-media entry (EXP-029 / MED-05).\n\nMirrors a ``media.yaml`` resource minus ``domain`` (inherited\nfrom the parent set). Surfaced in the \"Vertiefe das Thema\"\nsection after the lesson summary. Optional + additive, so\npre-EXP-029 lessons load unchanged. Added to the authoritative\nschema (EXP-039) so the JSON-Schema / generated TS types cover\nit — previously this shape lived only in the frontend\n``ContentLessonResource`` interface, and a lesson carrying\n``resources`` was rejected by ``extra=\"forbid\"`` here.", + "description": "One lesson-level supplementary-media entry (EXP-029 / MED-05).\n\nMirrors a ``media.yaml`` resource minus ``domain`` (inherited\nfrom the parent set). Surfaced in the \"Vertiefe das Thema\"\nsection after the lesson summary. Optional + additive, so\npre-EXP-029 lessons load unchanged. Added to the authoritative\nschema (EXP-039) so the JSON-Schema / generated TS types cover\nit - previously this shape lived only in the frontend\n``ContentLessonResource`` interface, and a lesson carrying\n``resources`` was rejected by ``extra=\"forbid\"`` here.", "properties": { "author": { "anyOf": [ @@ -907,7 +907,7 @@ } ], "default": null, - "description": "THEORY: optional inline worked examples rendered under the step body (schema v1.5, additive). DISTINCT from ``example_url``: that links OUT to an external illustration, ``examples`` carries the example content INLINE (a sample sentence, or a syntax-highlighted code snippet — see ``InlineExample.language``). The two may coexist on one step. Additive + optional; steps without ``examples`` validate unchanged.", + "description": "THEORY: optional inline worked examples rendered under the step body (schema v1.5, additive). DISTINCT from ``example_url``: that links OUT to an external illustration, ``examples`` carries the example content INLINE (a sample sentence, or a syntax-highlighted code snippet - see ``InlineExample.language``). The two may coexist on one step. Additive + optional; steps without ``examples`` validate unchanged.", "title": "Examples" }, "exercise": { @@ -1124,7 +1124,7 @@ "type": "string" }, "TokenRole": { - "description": "Closed enum of grammatical roles a card token can carry.\n\nPhase 52I / v1.35.0 / P-130. Annotates individual tokens\ninside a card's ``front`` so the v1.35.0+ cloze generator\ncan pick a semantically-meaningful blank instead of a\nposition-based one. Optional field on Card — old content\nwithout token_roles still validates and the generator\nfalls back to a positional heuristic.\n\nClosed enum to keep author input disciplined. Adding a\nrole (e.g. ``pronoun``, ``conjunction``, ``auxiliary``)\nis a minor schema_version bump — extending an open enum\nsilently would let typos masquerade as valid roles and\nthe generator would skip them without warning.", + "description": "Closed enum of grammatical roles a card token can carry.\n\nPhase 52I / v1.35.0 / P-130. Annotates individual tokens\ninside a card's ``front`` so the v1.35.0+ cloze generator\ncan pick a semantically-meaningful blank instead of a\nposition-based one. Optional field on Card - old content\nwithout token_roles still validates and the generator\nfalls back to a positional heuristic.\n\nClosed enum to keep author input disciplined. Adding a\nrole (e.g. ``pronoun``, ``conjunction``, ``auxiliary``)\nis a minor schema_version bump - extending an open enum\nsilently would let typos masquerade as valid roles and\nthe generator would skip them without warning.", "enum": [ "article", "verb", @@ -1141,7 +1141,7 @@ "$id": "https://astrapi69.github.io/learn-content-engine/schema/lesson.schema.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, - "description": "One lesson in a content set (Phase 43 / 2B-lesson).\n\nA lesson is the unit a user works through end-to-end —\ntypically 5-15 minutes of content. The viewer (Phase 44)\nwalks the steps in order; SRS (Phase 46) tracks the\ncards referenced by each exercise.\n\nReferential integrity: every ``card_id`` referenced by\nany exercise step MUST exist in the lesson's ``cards``\nlist. Enforced by the model validator so the viewer can\ntrust the references later.", + "description": "One lesson in a content set (Phase 43 / 2B-lesson).\n\nA lesson is the unit a user works through end-to-end - typically 5-15 minutes of content. The viewer (Phase 44)\nwalks the steps in order; SRS (Phase 46) tracks the\ncards referenced by each exercise.\n\nReferential integrity: every ``card_id`` referenced by\nany exercise step MUST exist in the lesson's ``cards``\nlist. Enforced by the model validator so the viewer can\ntrust the references later.", "properties": { "cards": { "default": [], @@ -1277,7 +1277,7 @@ } ], "default": null, - "description": "Optional BCP-47 code of the language taught (Phase 60 / v1.44.0). Mirrors the parent set's ``target_language``; lets an exported standalone lesson carry its own pair. Absent on pre-v1.2 lessons — the parent set is authoritative.", + "description": "Optional BCP-47 code of the language taught (Phase 60 / v1.44.0). Mirrors the parent set's ``target_language``; lets an exported standalone lesson carry its own pair. Absent on pre-v1.2 lessons - the parent set is authoritative.", "title": "Target Language" }, "title": { @@ -1323,5 +1323,5 @@ ], "title": "Lesson", "type": "object", - "x-schema-version": "1.15" + "x-schema-version": "1.16" } diff --git a/schema/quality-rules.json b/schema/quality-rules.json index 1bc94c4..6c2ec90 100644 --- a/schema/quality-rules.json +++ b/schema/quality-rules.json @@ -1,5 +1,5 @@ { - "$schema-version": "1.15", + "$schema-version": "1.16", "_comment": "GENERATED from scripts/generate_lesson_schema.py (EXP-039). Do not edit. Shared quality minimums for the content quality gate (app + content repo).", "rules": { "minExerciseTypes": 2,