From 0ad7cb1f2d2e5f8f15d8e2052f03c0b4284dad3d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 07:11:44 +0000 Subject: [PATCH] chore(engine): pin 0.37.0; the root entry owns the set description Following learn-content-engine#246 (maintainer decision 2026-10-09): the root manifest's entry owns a set's description, every other field lives in both manifests and must be identical. description removed from 4 set manifest(s); readers of the set manifest inherit it from the root entry (engine inheritFromRootEntry; the search index already falls back to the root). Pin 0.37.0 with the template's runner (validateManifestPair for every set manifest) and LESSON-FORMAT.md, byte-identical to template main. Measured after the change: 0 MANIFEST-ENTRY-MISMATCH, 0 W-LESSON-COUNT-CLAIM, engine gate 0 errors. Refs astrapi69/learn-content-engine#246 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01J2P2V9WkY1XCdqtE2d4ag6 --- docs/LESSON-FORMAT.md | 20 ++++++++++++---- schema/engine-version.txt | 2 +- scripts/validate_with_engine.mjs | 26 +++++++++++++++++++-- sets/de/python-basics/manifest.yaml | 4 ---- sets/de/react-19/manifest.yaml | 9 ------- sets/de/react-fortgeschritten/manifest.yaml | 7 ------ sets/de/react-grundlagen/manifest.yaml | 7 ------ 7 files changed, 41 insertions(+), 34 deletions(-) diff --git a/docs/LESSON-FORMAT.md b/docs/LESSON-FORMAT.md index 8de4917..8ced8e8 100644 --- a/docs/LESSON-FORMAT.md +++ b/docs/LESSON-FORMAT.md @@ -291,8 +291,6 @@ sets: version: '1.0.0' lesson_count: 1 domain: language - description: >- - … metadata: author: your-handle license: CC-BY-SA-4.0 @@ -300,8 +298,22 @@ metadata: - 01-greetings.json ``` -Then add the same set block to the **root** `manifest.yaml` under `sets:`, and -add a row to the README table (keeping the totals line in sync). +Then add the same set block to the **root** `manifest.yaml` under `sets:`, plus +the set's `description`, and add a row to the README table (keeping the totals +line in sync). + +A set is described twice, so each field has one home: + +- The **root** entry carries the `description`. Write it there only; a reader + of the set manifest inherits it, so a second copy can only drift. +- The **set manifest** carries the lesson list (`metadata.lessons`). +- Every other field appears in both and must be **identical**: `title`, + `title_native`, `tags`, `visibility`, `version`, `lesson_count` and the + rest. `make lint` compares the two entries of every set + (`validateManifestPair`, `MANIFEST-ENTRY-MISMATCH`). +- Leave the lesson count out of the `title` and `description` prose, or keep + it equal to `lesson_count`: a digit count that differs is flagged + (`W-LESSON-COUNT-CLAIM`). Run `python scripts/validate_content.py` until it prints `All N set(s) passed validation.` diff --git a/schema/engine-version.txt b/schema/engine-version.txt index 93d4c1e..0f1a7df 100644 --- a/schema/engine-version.txt +++ b/schema/engine-version.txt @@ -1 +1 @@ -0.36.0 +0.37.0 diff --git a/scripts/validate_with_engine.mjs b/scripts/validate_with_engine.mjs index 78bc21f..6b56d71 100644 --- a/scripts/validate_with_engine.mjs +++ b/scripts/validate_with_engine.mjs @@ -31,7 +31,7 @@ * validated instead of refused - `make lint-warnings` used to shell out to the * bare CLI (no registry) and died on ext content (content-test#71). */ -import { validateLesson, validateLessonQuality, validateManifest } from "learn-content-engine"; +import { validateLesson, validateLessonQuality, validateManifest, validateManifestPair } from "learn-content-engine"; import { readFileSync, readdirSync, statSync } from "node:fs"; import { join, relative } from "node:path"; import { parse as parseYaml } from "yaml"; @@ -283,6 +283,21 @@ function selfTest() { console.log("self-test OK: the source language reaches the card-back script lint"); } + // Root and set manifest (learn-content-engine#246): a field both carry + // with different values is surfaced, as a warning or an error depending + // on the pinned engine's tier. + const setEntry = { id: "s1", title: "Set", target_language: "es", level: "A1", version: "1.0.0", lesson_count: 1 }; + const pair = validateManifestPair( + { name: "Root", sets: [{ ...setEntry, description: "Root text" }] }, + { name: "Set", sets: [{ ...setEntry, description: "Set text" }] }, + ); + if (![...pair.errors, ...pair.warnings].some((issue) => issue.id.endsWith("-MANIFEST-ENTRY-MISMATCH"))) { + failures++; + console.error("SELF-TEST FAIL: a root/set manifest mismatch must be surfaced (MANIFEST-ENTRY-MISMATCH)"); + } else { + console.log("self-test OK: a root/set manifest mismatch is surfaced"); + } + if (failures) return 1; console.log(`\nSelf-test passed: the gate rejects all bad-lesson classes, applies the quality minimums, gates the extension tier, and surfaces author warnings.`); return 0; @@ -325,8 +340,15 @@ function validateAll(repoRoot, { showWarnings = false } = {}) { if (showWarnings && res.warnings.length) warned.push({ file: rel, warnings: res.warnings }); } else if (rel.endsWith("manifest.yaml")) { manifests += 1; - const res = validateManifest(parseYaml(readFileSync(file, "utf8"))); + const setManifest = parseYaml(readFileSync(file, "utf8")); + const res = validateManifest(setManifest); if (!res.valid) report(rel, res.errors); + // The set is described twice, here and in the root manifest; the two + // must agree (learn-content-engine#246). The engine's tier decides: + // a warning in 0.37.x, an error from the release that raises it. + const pair = validateManifestPair(rootManifest, setManifest); + if (!pair.valid) report(rel, pair.errors); + if (showWarnings && pair.warnings.length) warned.push({ file: rel, warnings: pair.warnings }); // Manifest warnings were collected for lessons but DROPPED here, so the // set-level ordering gate (learn-content-engine#110, W-SET-ORDER-*) // reached this runner and reported nothing. A connected gate that diff --git a/sets/de/python-basics/manifest.yaml b/sets/de/python-basics/manifest.yaml index bb70d0c..89b4896 100644 --- a/sets/de/python-basics/manifest.yaml +++ b/sets/de/python-basics/manifest.yaml @@ -33,10 +33,6 @@ sets: path: sets/de/python-basics version: '1.0.0' lesson_count: 15 - description: >- - Fünfzehn Einführungslektionen Python, auf Deutsch erklärt, - vom ersten print() bis zu List Comprehensions, mit - lauffähigen Code-Beispielen und erwarteter Ausgabe. tags: - beginner - programming diff --git a/sets/de/react-19/manifest.yaml b/sets/de/react-19/manifest.yaml index 1657428..09b5fa1 100644 --- a/sets/de/react-19/manifest.yaml +++ b/sets/de/react-19/manifest.yaml @@ -43,15 +43,6 @@ sets: path: sets/de/react-19 version: '1.0.0' lesson_count: 8 - description: >- - React 19 an lauffähigem Code: pro Lektion eine Komponente, acht - Übungen darauf. Deckt 19.0 (Actions, use, ref als Prop, Server - Components), 19.2 (gebündelte Suspense-Enthüllung, useEffectEvent, - Activity) und 19.3 (ViewTransition, Fragment Refs) ab. Übungstypen - mit Schwerpunkt auf Code lesen: reading-comprehension, - error-correction, parsons, dazu ordering, categorization, - multiple_choice, matching, cloze mit echtem Trägersatz und eine - benotete Abschlussprüfung. tags: - programming - react diff --git a/sets/de/react-fortgeschritten/manifest.yaml b/sets/de/react-fortgeschritten/manifest.yaml index 6aabf7d..7acf9ec 100644 --- a/sets/de/react-fortgeschritten/manifest.yaml +++ b/sets/de/react-fortgeschritten/manifest.yaml @@ -32,13 +32,6 @@ sets: path: sets/de/react-fortgeschritten version: '1.0.1' lesson_count: 8 - description: >- - Teil 2 der React-Reihe (Fortgeschritten, B1): acht Lektionen mit - überwiegend Multiple-Choice, Single-Answer als cloze select-mode und - „Wähle alle zutreffenden" als cloze multiselect, plus matching und - kurze Lückentext-Items. Themen: useEffect, Datenfetching, useContext, - useRef, Performance, Listen/Keys, Formulare, Komposition/Patterns. - Distraktoren aus klassischen React-Irrtümern. React-18, Hooks-first. tags: - react - javascript diff --git a/sets/de/react-grundlagen/manifest.yaml b/sets/de/react-grundlagen/manifest.yaml index 733b122..fc6ab80 100644 --- a/sets/de/react-grundlagen/manifest.yaml +++ b/sets/de/react-grundlagen/manifest.yaml @@ -27,13 +27,6 @@ sets: path: sets/de/react-grundlagen version: '1.0.1' lesson_count: 5 - description: >- - React-Grundlagen auf Deutsch für Einsteiger: fünf Lektionen mit - insgesamt 31 Karten, Theorieschritten und überwiegend - Multiple-Choice-Übungen (picture_choice mit plausiblen - Distraktoren) plus wenigen Lückentext-Items (cloze) zu React als - Library, Komponenten, JSX, Props, State/useState, Events und - Grundprinzipien. tags: - react - javascript