From 808409ba11827d51c16823ffe9a33fa182315ab3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 20:44:58 +0000 Subject: [PATCH 1/2] Fail closed when image_generation.model, size, or quality is not a string A YAML list for model overwrote the default string in image_generation_config and was str()'d into the Images API. Require non-empty YAML strings at Config.from_yaml, same as tts.model. Co-authored-by: jmjava --- milestones/README.md | 7 ++++-- milestones/image-empty-bytes.md | 4 +-- milestones/image-generation-strings.md | 35 ++++++++++++++++++++++++++ src/docgen/config.py | 7 ++++++ tests/test_config.py | 21 ++++++++++++++++ 5 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 milestones/image-generation-strings.md diff --git a/milestones/README.md b/milestones/README.md index 01ab8d1..3b084ef 100644 --- a/milestones/README.md +++ b/milestones/README.md @@ -5,10 +5,13 @@ repositories that install `docgen` and maintain their own demo bundle. The library no longer ships an in-repo dogfood; consumers are the integration test of record. -**Active:** **[image-empty-bytes.md](image-empty-bytes.md)** — -`image-generate` must not write empty PNG bytes as success. +**Active:** **[image-generation-strings.md](image-generation-strings.md)** — +`image_generation.model` / `size` / `quality` must be YAML strings at +config load. **Shipped:** +- **[image-empty-bytes.md](image-empty-bytes.md)** — + `image-generate` must not write empty PNG bytes as success (#103). - **[hint-front-matter-yaml.md](hint-front-matter-yaml.md)** — Invalid `hints/*.md` YAML front matter must fail `yaml-generate`, not skip hint `visual_map` wiring (#102). diff --git a/milestones/image-empty-bytes.md b/milestones/image-empty-bytes.md index 08779c4..978371a 100644 --- a/milestones/image-empty-bytes.md +++ b/milestones/image-empty-bytes.md @@ -1,7 +1,7 @@ # Milestone: image-generate must not write empty assets -**Status:** Active -**PR:** pending +**Status:** Shipped +**PR:** #103 **Depends on:** `milestones/hint-front-matter-yaml.md` (PR #102), `milestones/tts-empty-audio.md` (PR #99) diff --git a/milestones/image-generation-strings.md b/milestones/image-generation-strings.md new file mode 100644 index 0000000..a9da106 --- /dev/null +++ b/milestones/image-generation-strings.md @@ -0,0 +1,35 @@ +# Milestone: image_generation.model / size / quality must be strings + +**Status:** Active +**PR:** pending +**Depends on:** `milestones/image-empty-bytes.md` (PR #103), +`milestones/tts-empty-audio.md` (PR #99) + +## Problem + +`tts.model` / `tts.voice` are already non-empty YAML strings at config +load. `image_generation.model` / `size` / `quality` were not. + +`image_generation_config` does `defaults.update(block)`. A YAML **list** +for `model:` (bullet list of model names) overwrote the default string. +`generate_images_for_spec` then did `str(icfg.get("model"))`, sending +`"['gpt-image-1']"` to the Images API. + +## Goal + +Fail closed at `Config.from_yaml`. Missing keys still use the property +defaults (`gpt-image-1`, `1536x1024`, no quality). + +## Done when + +- [x] Present `image_generation.model` / `size` / `quality` must be + non-empty YAML strings. +- [x] Tests for list values of those keys. +- [ ] `ruff check src/ tests/` +- [ ] `pytest tests/` +- [ ] `docgen benchmark` (no clock change) + +## Out of scope + +- Empty mapping `image_generation: {}` remains allowed (defaults apply). +- `wizard.default_guidance` type gating is separate. diff --git a/src/docgen/config.py b/src/docgen/config.py index f85e0aa..68277ad 100644 --- a/src/docgen/config.py +++ b/src/docgen/config.py @@ -282,6 +282,13 @@ def __post_init__(self) -> None: ) if wiz.get("llm_model") is not None: require_yaml_string(wiz["llm_model"], label="wizard.llm_model", source=src) + ig = self._block("image_generation") + if ig.get("model") is not None: + require_yaml_string(ig["model"], label="image_generation.model", source=src) + if ig.get("size") is not None: + require_yaml_string(ig["size"], label="image_generation.size", source=src) + if ig.get("quality") is not None: + require_yaml_string(ig["quality"], label="image_generation.quality", source=src) ocr = self._sub_block(validation, "ocr", label="validation.ocr") if ocr.get("error_patterns") is not None: string_list_block( diff --git a/tests/test_config.py b/tests/test_config.py index c22c1f8..36f0e20 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -465,3 +465,24 @@ def test_from_yaml_mapping_av_sync_anchor_keywords_loads(tmp_path: Path) -> None cfg = Config.from_yaml(p) rows = cfg.av_sync_config["anchor_keywords"]["01"] assert rows[0]["keyword"] == "Flask" + + +def test_from_yaml_list_image_generation_model_raises(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text("image_generation:\n model:\n - gpt-image-1\n", encoding="utf-8") + with pytest.raises(ConfigError, match="image_generation.model must be a YAML string"): + Config.from_yaml(p) + + +def test_from_yaml_list_image_generation_size_raises(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text("image_generation:\n size:\n - 1536x1024\n", encoding="utf-8") + with pytest.raises(ConfigError, match="image_generation.size must be a YAML string"): + Config.from_yaml(p) + + +def test_from_yaml_list_image_generation_quality_raises(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text("image_generation:\n quality:\n - high\n", encoding="utf-8") + with pytest.raises(ConfigError, match="image_generation.quality must be a YAML string"): + Config.from_yaml(p) From a6b605dedbcd7b7166984c2b3f22714a36d982f0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 20:45:25 +0000 Subject: [PATCH 2/2] Mark image-generation-strings milestone gates as run Local ruff, pytest, and docgen benchmark are green on this branch. Co-authored-by: jmjava --- milestones/image-generation-strings.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/milestones/image-generation-strings.md b/milestones/image-generation-strings.md index a9da106..dd34669 100644 --- a/milestones/image-generation-strings.md +++ b/milestones/image-generation-strings.md @@ -25,9 +25,9 @@ defaults (`gpt-image-1`, `1536x1024`, no quality). - [x] Present `image_generation.model` / `size` / `quality` must be non-empty YAML strings. - [x] Tests for list values of those keys. -- [ ] `ruff check src/ tests/` -- [ ] `pytest tests/` -- [ ] `docgen benchmark` (no clock change) +- [x] `ruff check src/ tests/` +- [x] `pytest tests/` +- [x] `docgen benchmark` (no clock change) ## Out of scope