diff --git a/.dev/STATE.md b/.dev/STATE.md index 68cd9cc..9894c66 100644 --- a/.dev/STATE.md +++ b/.dev/STATE.md @@ -7,13 +7,23 @@ Roadmap detail lives in the work-plan tracker **#257**, not here (PLAN.md predat ## In flight -- **v0.29.1 release cut 2026-09-21** (payload #315 `cd41558`) — patch: the Malayalam editor's - round-3 answers (detail in the next entry). Nothing in it is a feature; the one behavioural - correction is the `-ഉം` comma rule, where v0.29.0 had shipped the wrong guess. §4a gate - status is recorded on the release PR. After the floating tags move, round 4 (`numpy`, - lecture-python-programming.ml#23) is regenerated at `@v0` — the editor has been asked to hold - off until then. W1 (#259) still targets v0.30.0. -- **ml round-3 answers encoded (2026-09-21, #315 merged as `cd41558`; released by v0.29.1)** — the +- **v0.29.1 is tagged but NOT released (2026-09-21)** — its §4a gate came back 83/84: scenario 17 + on the `.ml` lane failed twice (the model wrapped a plain `## Exercises` list in + `{exercise-start}`; structural parity refused the file). `v0` = `v0.29` = `a6fda54` (v0.29.0) + still; no GitHub release. Measured at twelve draws per arm the defect is **latent since + v0.28.0** (5/12 at v0.29.0) and aggravated by the #315 rules (11/12); one scope sentence on the + exercise rule takes it to 0/24, and the action path delivers 2/2. The fix ships as + **v0.29.2**. Log: `.dev/log/2026-09-21-v0291-gate-scenario17.md`. +- **v0.29.1 release PR merged and tagged 2026-09-21 — gate failed, NOT released (entry above)** + (release PR #316 `7fe78a5`; payload #315 `cd41558`) — patch: the Malayalam editor's round-3 + answers (detail in the next entry). Nothing in it is a feature; the one behavioural + correction is the `-ഉം` comma rule, where v0.29.0 had shipped the wrong guess. §4a tally + (83/84) is on #316; CHANGELOG marks the version `[YANKED]`. Its contents ship in v0.29.2; + **once v0.29.2's floating tags move**, round 4 (`numpy`, lecture-python-programming.ml#23) is + regenerated at `@v0` — the editor has been asked to hold off until then. W1 (#259) still + targets v0.30.0. +- **ml round-3 answers encoded (2026-09-21, #315 merged as `cd41558`; tagged in v0.29.1, ships in + v0.29.2)** — the editor answered lecture-python-programming.ml#22 on 2026-09-19. `ml` rules 27 → 28, glossary v0.7.0. Two answers overturned a guess: the comma between `-ഉം` items is **always** there (v0.29.0 shipped "phrases only"), and *draw* is not a light verb (left unencoded — both forms diff --git a/.dev/log/2026-09-21-v0291-gate-scenario17.md b/.dev/log/2026-09-21-v0291-gate-scenario17.md new file mode 100644 index 0000000..9961d89 --- /dev/null +++ b/.dev/log/2026-09-21-v0291-gate-scenario17.md @@ -0,0 +1,24 @@ +# 2026-09-21 — v0.29.1's §4a gate fails on the `.ml` lane; a latent priming defect, fixed + +**What happened**: v0.29.1 (release PR #316, `7fe78a5`) was tagged and gated on the tag. 83 of 84 sync runs succeeded; 28/28 PRs delivered per lane. The one failure was **scenario 17 (`new-document-toc`) on `.ml`**, and a re-run of the failed job against the same tagged bytes failed identically. The floating tags were **not** moved and no GitHub release was published: `v0` = `v0.29` = `a6fda54` (v0.29.0) throughout. + +**The defect**: the fixture `game-theory.md` writes its exercises as a plain `## Exercises` heading and a numbered list — no directives. The model wrapped them in `{exercise-start}` … `{exercise-end}`; the structural-parity guard refused the file (directives 3 → 5; `verbatim-directive block sequence differs: source [none] vs output [exercise-start]`), so the sync PR carried a `_toc.yml` entry for a lecture it did not deliver (the #222 / #289 class, and what W1 #259 is for). The primer is the `ml` exercise-verbatim rule, which names every exercise directive at length and never says they must already be in the source. + +**Measured, because two draws mislead** — CLI `init -f game-theory.md` on the fixture, twelve draws per arm, refusals by the parity guard: + +| Rules | Glossary | Refused | +|---|---|---| +| v0.29.0 | v0.6.0 | 5 / 12 | +| v0.29.0 | v0.7.0 | 8 / 12 | +| v0.29.1 | v0.6.0 | 11 / 12 | +| v0.29.1 | v0.7.0 | 11 / 12 | +| v0.29.1 + scope sentence (first wording) | v0.7.0 | 0 / 12 | +| v0.29.1 + scope sentence (final wording) | v0.7.0 | 0 / 12 | + +The first six draws per arm read 1/6 against 5/6 and looked like a clean regression; the next six moved the baseline to 5/12. **The defect is latent since the rule arrived in v0.28.0** — the gates of v0.28.0, v0.28.1 and v0.29.0 each passed scenario 17 on a single draw with a ~40% chance of failing — and the #315 rules aggravate it to ~90%. Why longer comma and plural rules should do that is not known; the crossing puts it in the rules rather than the glossary, and that is all the data says. + +**The fix**: one sentence on the exercise rule — it concerns directives the source ALREADY contains; never add a directive, fence or label the source does not have; a plain `## Exercises` heading and list keeps its English text (the editor's ruling covers all exercise content) and its source structure. A first wording told the model to *translate* the list; it did not (0 of 19 written draws at either version put Malayalam there), so the instruction was changed to match the behaviour the ruling wants. Still 28 rules. Action path, local bundle, on the fixture PR that failed (test-translation-sync#907): delivered 2 of 2 (test-translation-sync.ml#221, #222). + +**Lessons**: (1) a single-draw gate cannot see a 40% defect — it passed three releases; scenario-level retries or N draws on the new-document scenarios are worth a look under #260 / W1. (2) Six draws per arm was not enough either: it gave the right direction and the wrong story. (3) A rule that names directives primes the model to emit them; say "already in the source" wherever a rule lists markup. + +**Next**: PR → merge → **v0.29.2** (v0.29.1 stays a tag that was never released; the CHANGELOG says so) → §4a gate on the new tag → floating tags → smoke → release → regenerate round 4 at `@v0`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 83c14fe..78c4e17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.29.1] - 2026-09-21 +### Fixed + +- **`ml`: the exercise-verbatim rule no longer primes the model to invent exercise directives** (caught by v0.29.1's §4a gate, which is why v0.29.1 was tagged but never released: no floating tag moved, no GitHub release). Harness scenario 17 (`new-document-toc`) failed twice on the `.ml` lane — the fixture writes its exercises as a plain `## Exercises` heading and a numbered list, the model wrapped them in `{exercise-start}` … `{exercise-end}`, and the structural-parity guard correctly refused the file (directives 3 → 5), leaving the sync PR with a `_toc.yml` entry for a lecture it did not deliver. Measured on that fixture, twelve draws per arm: **5/12 refused at v0.29.0** — the defect is latent since the rule arrived in v0.28.0, and four gates passed it on single lucky draws — **11/12 at v0.29.1** (the #315 rules aggravate it; crossing rules and glossary puts it in the rules), and **0/24** once the rule says it concerns only directives the source already contains. Behaviour is otherwise unchanged: a plain exercises section keeps its English text, as the editor's ruling requires, and its source structure. + +## [0.29.1] - 2026-09-21 [YANKED] + +> **Tagged, never released.** The §4a gate on the `v0.29.1` tag came back 83/84 — harness scenario 17 failed on the `.ml` lane (see the `Fixed` entry above) — so no floating tag was moved and no GitHub release was published: `@v0` and `@v0.29` never pointed here. The tag exists and the entries below describe it accurately, but do not pin it; its contents ship in the release above. ### Changed diff --git a/dist-action/index.js b/dist-action/index.js index 90630bf..8b1684d 100644 --- a/dist-action/index.js +++ b/dist-action/index.js @@ -26493,7 +26493,7 @@ var LANGUAGE_CONFIGS = { 'Choose the verb form by what the English means, not by its surface tense: a state that results from a completed action takes the perfect-stative participle -\u0D24\u0D3F\u0D30\u0D3F\u0D15\u0D4D\u0D15\u0D41\u0D28\u0D4D\u0D28, not the bare past participle (design \u0D1A\u0D46\u0D2F\u0D4D\u0D24\u0D3F\u0D30\u0D3F\u0D15\u0D4D\u0D15\u0D41\u0D28\u0D4D\u0D28 library, overlay \u0D1A\u0D46\u0D2F\u0D4D\u0D24\u0D3F\u0D30\u0D3F\u0D15\u0D4D\u0D15\u0D41\u0D28\u0D4D\u0D28 density lines, Indent \u0D1A\u0D46\u0D2F\u0D4D\u0D24\u0D3F\u0D30\u0D3F\u0D15\u0D4D\u0D15\u0D41\u0D28\u0D4D\u0D28 code \u2014 not design \u0D1A\u0D46\u0D2F\u0D4D\u0D24, overlay \u0D1A\u0D46\u0D2F\u0D4D\u0D24); something the lecture has just done takes the perfect -\u0D07\u0D1F\u0D4D\u0D1F\u0D41\u0D23\u0D4D\u0D1F\u0D4D ("we\'ve also used `alpha`" \u2192 `alpha` \u0D09\u0D2A\u0D2F\u0D4B\u0D17\u0D3F\u0D1A\u0D4D\u0D1A\u0D3F\u0D1F\u0D4D\u0D1F\u0D41\u0D23\u0D4D\u0D1F\u0D4D, not \u0D09\u0D2A\u0D2F\u0D4B\u0D17\u0D3F\u0D1A\u0D4D\u0D1A\u0D41); English "by doing X" / "using X you can \u2026" / "we can find \u2026 by printing" states a condition and takes -\u0D06\u0D7D (`ax.legend(loc=\'upper center\')` \u0D09\u0D2A\u0D2F\u0D4B\u0D17\u0D3F\u0D1A\u0D4D\u0D1A\u0D3E\u0D7D legend-\u0D28\u0D4D\u0D31\u0D46 \u0D38\u0D4D\u0D25\u0D3E\u0D28\u0D02 \u0D2E\u0D3E\u0D31\u0D4D\u0D31\u0D3E\u0D02; \u2026 print \u0D1A\u0D46\u0D2F\u0D4D\u0D24\u0D3E\u0D7D, \u2026 \u0D12\u0D30\u0D41 list \u0D28\u0D2E\u0D41\u0D15\u0D4D\u0D15\u0D4D \u0D15\u0D3E\u0D23\u0D3E\u0D02), not the conjunctive participle \u0D09\u0D2A\u0D2F\u0D4B\u0D17\u0D3F\u0D1A\u0D4D\u0D1A\u0D4D / \u0D1A\u0D46\u0D2F\u0D4D\u0D24\u0D4D; and a hedge such as "perhaps" (\u0D12\u0D30\u0D41\u0D2A\u0D15\u0D4D\u0D37\u0D47) agrees with the possibility ending -\u0D0F\u0D15\u0D4D\u0D15\u0D3E\u0D02 (\u0D09\u0D23\u0D4D\u0D1F\u0D3E\u0D2F\u0D47\u0D15\u0D4D\u0D15\u0D3E\u0D02), not the plain future \u0D09\u0D23\u0D4D\u0D1F\u0D3E\u0D15\u0D41\u0D02', `Render English idiom, metaphor and coined jargon by its plain meaning in classroom Malayalam, never word-for-word: "written to help MATLAB refugees find a ready home" \u2192 MATLAB \u0D09\u0D2A\u0D2F\u0D4B\u0D17\u0D3F\u0D1A\u0D4D\u0D1A\u0D3F\u0D30\u0D41\u0D28\u0D4D\u0D28\u0D35\u0D7C\u0D15\u0D4D\u0D15\u0D4D \u0D0E\u0D33\u0D41\u0D2A\u0D4D\u0D2A\u0D24\u0D4D\u0D24\u0D3F\u0D7D \u0D09\u0D2A\u0D2F\u0D4B\u0D17\u0D3F\u0D15\u0D4D\u0D15\u0D3E\u0D28\u0D3E\u0D15\u0D41\u0D28\u0D4D\u0D28 \u0D24\u0D30\u0D24\u0D4D\u0D24\u0D3F\u0D32\u0D3E\u0D23\u0D4D \u0D07\u0D24\u0D4D \u0D0E\u0D34\u0D41\u0D24\u0D3F\u0D2F\u0D3F\u0D30\u0D3F\u0D15\u0D4D\u0D15\u0D41\u0D28\u0D4D\u0D28\u0D24\u0D4D (no refugees, no home); "see if you can follow what's going on" \u2192 \u0D0E\u0D28\u0D4D\u0D24\u0D3E\u0D23\u0D4D \u0D28\u0D1F\u0D15\u0D4D\u0D15\u0D41\u0D28\u0D4D\u0D28\u0D24\u0D46\u0D28\u0D4D\u0D28\u0D4D \u0D28\u0D3F\u0D19\u0D4D\u0D19\u0D7E\u0D15\u0D4D\u0D15\u0D4D \u0D2E\u0D28\u0D38\u0D4D\u0D38\u0D3F\u0D32\u0D3E\u0D15\u0D4D\u0D15\u0D3E\u0D7B \u0D38\u0D3E\u0D27\u0D3F\u0D15\u0D4D\u0D15\u0D41\u0D28\u0D4D\u0D28\u0D41\u0D23\u0D4D\u0D1F\u0D4B \u0D0E\u0D28\u0D4D\u0D28\u0D4D \u0D28\u0D4B\u0D15\u0D4D\u0D15\u0D41\u0D15 (to understand \u2014 never \u0D2A\u0D3F\u0D28\u0D4D\u0D24\u0D41\u0D1F\u0D30\u0D41\u0D15, which is to chase); "fine-grained control over all aspects of presentation" \u2192 presentation-\u0D28\u0D4D\u0D31\u0D46 \u0D13\u0D30\u0D4B \u0D1A\u0D46\u0D31\u0D3F\u0D2F \u0D15\u0D3E\u0D30\u0D4D\u0D2F\u0D35\u0D41\u0D02 \u0D35\u0D30\u0D46 control \u0D1A\u0D46\u0D2F\u0D4D\u0D2F\u0D3E\u0D7B \u0D15\u0D34\u0D3F\u0D2F\u0D41\u0D28\u0D4D\u0D28\u0D41; "un-Pythonic" \u2192 Python-\u0D28\u0D4D\u0D31\u0D46 \u0D38\u0D3E\u0D27\u0D3E\u0D30\u0D23 \u0D36\u0D48\u0D32\u0D3F\u0D2F\u0D4B\u0D1F\u0D4D \u0D2A\u0D4A\u0D30\u0D41\u0D24\u0D4D\u0D24\u0D2A\u0D4D\u0D2A\u0D46\u0D1F\u0D3E\u0D24\u0D4D\u0D24`, 'A list of pointers to outside resources \u2014 the bullets of a "Further Reading" or "References" style section, each a link plus a short description \u2014 stays wholly in English, copied byte-for-byte from the source including its line wrapping ("* The [Matplotlib gallery](\u2026) provides many examples." and "* [Seaborn](\u2026) facilitates common statistics plots in Matplotlib." stay exactly so); the editor returned every such bullet to English on lecture-python-programming.ml#13 (decision record D-2026-09-18-ml-further-reading-lists-stay-english), so never treat an all-English further-reading list as under-translation', - 'Every exercise-related directive \u2014 {exercise}, {exercise-start} \u2026 {exercise-end}, {hint}, {solution}, {solution-start} \u2026 {solution-end} \u2014 stays wholly in English, copied byte-for-byte from the source: the fence line, its title argument and options, and every prose line inside, including pure programming instructions ("Rewrite the `factorial()` function using recursion." stays exactly so). This is the editor of record\'s standing ruling until he has decided each exercise individually (2026-09-01, decision record D-2026-09-03-ml-all-exercise-content-stays-english; it subsumes the earlier math-heavy Hint/Solution ruling of 2026-08-17). The engine restores these blocks from the source mechanically after translation, so do not spend effort rephrasing anything inside them, and never treat an all-English exercise block as under-translation', + 'Every exercise-related directive \u2014 {exercise}, {exercise-start} \u2026 {exercise-end}, {hint}, {solution}, {solution-start} \u2026 {solution-end} \u2014 stays wholly in English, copied byte-for-byte from the source: the fence line, its title argument and options, and every prose line inside, including pure programming instructions ("Rewrite the `factorial()` function using recursion." stays exactly so). This is the editor of record\'s standing ruling until he has decided each exercise individually (2026-09-01, decision record D-2026-09-03-ml-all-exercise-content-stays-english; it subsumes the earlier math-heavy Hint/Solution ruling of 2026-08-17). The engine restores these blocks from the source mechanically after translation, so do not spend effort rephrasing anything inside them, and never treat an all-English exercise block as under-translation. This rule is about directives the source ALREADY contains \u2014 never add an {exercise}, {exercise-start}, {solution} or any other directive, fence or label that the source does not have: where the source writes its exercises as a plain "## Exercises" heading followed by a numbered list, keep that heading and list as they are \u2014 the text in English like all exercise content, the structure exactly as the source has it, with no directive wrapped around it', "Never translate comments inside code blocks or code cells \u2014 every code comment stays exactly as written in the English source", `Keep section headings in their original English form, byte-identical including possessives and punctuation \u2014 do not translate them and never attach a Malayalam case-suffix to a possessive inside a heading (e.g. "## Overview" stays "## Overview"; "### Matplotlib's Split Personality" stays exactly so, never "Matplotlib-\u0D2F\u0D41\u0D1F\u0D46 Split Personality")`, "Keep proper names (economists, researchers, institutions) in English/Latin script \u2014 do not transliterate them", diff --git a/src/__tests__/language-config.test.ts b/src/__tests__/language-config.test.ts index df23641..19921ad 100644 --- a/src/__tests__/language-config.test.ts +++ b/src/__tests__/language-config.test.ts @@ -319,6 +319,27 @@ describe('Language Configuration', () => { }); }); + // v0.29.1's §4a gate (2026-09-21): scenario 17 on the .ml lane failed twice + // — the model wrapped the fixture's plain "## Exercises" list in + // {exercise-start}, and structural parity refused the file. Measured on that + // fixture: 5/12 refusals at v0.29.0, 11/12 at v0.29.1, 0/24 with this scope + // sentence. The verbatim rule must speak only of directives the source has. + describe('Malayalam exercise rule is scoped to existing directives', () => { + const rules = getLanguageConfig('ml').additionalRules; + const exerciseRule = rules.find((r) => r.startsWith('Every exercise-related directive')); + + it('forbids adding a directive the source does not have', () => { + expect(exerciseRule).toBeDefined(); + expect(exerciseRule).toContain('directives the source ALREADY contains'); + expect(exerciseRule).toContain('never add an {exercise}, {exercise-start}, {solution}'); + expect(exerciseRule).toContain('with no directive wrapped around it'); + }); + + it('stays one rule — the scope is part of the verbatim rule, not a 29th', () => { + expect(rules).toHaveLength(28); + }); + }); + describe('Malayalam round-2 rules (lecture-python-programming.ml#7)', () => { const rules = getLanguageConfig('ml').additionalRules.join('\n'); diff --git a/src/language-config.ts b/src/language-config.ts index 5afaf3d..d141db8 100644 --- a/src/language-config.ts +++ b/src/language-config.ts @@ -139,7 +139,7 @@ export const LANGUAGE_CONFIGS: Record = { 'Choose the verb form by what the English means, not by its surface tense: a state that results from a completed action takes the perfect-stative participle -തിരിക്കുന്ന, not the bare past participle (design ചെയ്തിരിക്കുന്ന library, overlay ചെയ്തിരിക്കുന്ന density lines, Indent ചെയ്തിരിക്കുന്ന code — not design ചെയ്ത, overlay ചെയ്ത); something the lecture has just done takes the perfect -ഇട്ടുണ്ട് ("we\'ve also used `alpha`" → `alpha` ഉപയോഗിച്ചിട്ടുണ്ട്, not ഉപയോഗിച്ചു); English "by doing X" / "using X you can …" / "we can find … by printing" states a condition and takes -ആൽ (`ax.legend(loc=\'upper center\')` ഉപയോഗിച്ചാൽ legend-ന്റെ സ്ഥാനം മാറ്റാം; … print ചെയ്താൽ, … ഒരു list നമുക്ക് കാണാം), not the conjunctive participle ഉപയോഗിച്ച് / ചെയ്ത്; and a hedge such as "perhaps" (ഒരുപക്ഷേ) agrees with the possibility ending -ഏക്കാം (ഉണ്ടായേക്കാം), not the plain future ഉണ്ടാകും', 'Render English idiom, metaphor and coined jargon by its plain meaning in classroom Malayalam, never word-for-word: "written to help MATLAB refugees find a ready home" → MATLAB ഉപയോഗിച്ചിരുന്നവർക്ക് എളുപ്പത്തിൽ ഉപയോഗിക്കാനാകുന്ന തരത്തിലാണ് ഇത് എഴുതിയിരിക്കുന്നത് (no refugees, no home); "see if you can follow what\'s going on" → എന്താണ് നടക്കുന്നതെന്ന് നിങ്ങൾക്ക് മനസ്സിലാക്കാൻ സാധിക്കുന്നുണ്ടോ എന്ന് നോക്കുക (to understand — never പിന്തുടരുക, which is to chase); "fine-grained control over all aspects of presentation" → presentation-ന്റെ ഓരോ ചെറിയ കാര്യവും വരെ control ചെയ്യാൻ കഴിയുന്നു; "un-Pythonic" → Python-ന്റെ സാധാരണ ശൈലിയോട് പൊരുത്തപ്പെടാത്ത', 'A list of pointers to outside resources — the bullets of a "Further Reading" or "References" style section, each a link plus a short description — stays wholly in English, copied byte-for-byte from the source including its line wrapping ("* The [Matplotlib gallery](…) provides many examples." and "* [Seaborn](…) facilitates common statistics plots in Matplotlib." stay exactly so); the editor returned every such bullet to English on lecture-python-programming.ml#13 (decision record D-2026-09-18-ml-further-reading-lists-stay-english), so never treat an all-English further-reading list as under-translation', - 'Every exercise-related directive — {exercise}, {exercise-start} … {exercise-end}, {hint}, {solution}, {solution-start} … {solution-end} — stays wholly in English, copied byte-for-byte from the source: the fence line, its title argument and options, and every prose line inside, including pure programming instructions ("Rewrite the `factorial()` function using recursion." stays exactly so). This is the editor of record\'s standing ruling until he has decided each exercise individually (2026-09-01, decision record D-2026-09-03-ml-all-exercise-content-stays-english; it subsumes the earlier math-heavy Hint/Solution ruling of 2026-08-17). The engine restores these blocks from the source mechanically after translation, so do not spend effort rephrasing anything inside them, and never treat an all-English exercise block as under-translation', + 'Every exercise-related directive — {exercise}, {exercise-start} … {exercise-end}, {hint}, {solution}, {solution-start} … {solution-end} — stays wholly in English, copied byte-for-byte from the source: the fence line, its title argument and options, and every prose line inside, including pure programming instructions ("Rewrite the `factorial()` function using recursion." stays exactly so). This is the editor of record\'s standing ruling until he has decided each exercise individually (2026-09-01, decision record D-2026-09-03-ml-all-exercise-content-stays-english; it subsumes the earlier math-heavy Hint/Solution ruling of 2026-08-17). The engine restores these blocks from the source mechanically after translation, so do not spend effort rephrasing anything inside them, and never treat an all-English exercise block as under-translation. This rule is about directives the source ALREADY contains — never add an {exercise}, {exercise-start}, {solution} or any other directive, fence or label that the source does not have: where the source writes its exercises as a plain "## Exercises" heading followed by a numbered list, keep that heading and list as they are — the text in English like all exercise content, the structure exactly as the source has it, with no directive wrapped around it', 'Never translate comments inside code blocks or code cells — every code comment stays exactly as written in the English source', 'Keep section headings in their original English form, byte-identical including possessives and punctuation — do not translate them and never attach a Malayalam case-suffix to a possessive inside a heading (e.g. "## Overview" stays "## Overview"; "### Matplotlib\'s Split Personality" stays exactly so, never "Matplotlib-യുടെ Split Personality")', 'Keep proper names (economists, researchers, institutions) in English/Latin script — do not transliterate them',