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..dd34669 --- /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. +- [x] `ruff check src/ tests/` +- [x] `pytest tests/` +- [x] `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)