From bf60ea20ab233b8918d6663f852e6b14337524de Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 01:13:03 +0000 Subject: [PATCH 1/2] Fail closed when scene-spec layout gaps coerce bools to 1.0 first_row_title_buff / row_gap / column_gap still went through float(), so true became 1.0 and looked like a valid gap. Require YAML numbers when present; missing keys keep compile defaults. Co-authored-by: jmjava --- milestones/README.md | 7 +++-- milestones/scene-spec-bool-numerics.md | 2 +- milestones/scene-spec-layout-gaps.md | 38 ++++++++++++++++++++++++++ src/docgen/scene_spec.py | 9 ++++++ tests/test_scene_spec.py | 17 ++++++++++++ 5 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 milestones/scene-spec-layout-gaps.md diff --git a/milestones/README.md b/milestones/README.md index 1948bc3..084a0e3 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:** **[scene-spec-bool-numerics.md](scene-spec-bool-numerics.md)** — -scene-spec `wait_word` / `run_time` / sizes must not coerce YAML bools. +**Active:** **[scene-spec-layout-gaps.md](scene-spec-layout-gaps.md)** — +scene-spec layout gaps must not coerce YAML bools to 1.0. **Shipped:** +- **[scene-spec-bool-numerics.md](scene-spec-bool-numerics.md)** — + scene-spec `wait_word` / `run_time` / sizes must not coerce YAML bools + (#147). - **[grok-stt-start-end.md](grok-stt-start-end.md)** — Grok STT word/segment `start` / `end` must be JSON numbers, not bools (#146). diff --git a/milestones/scene-spec-bool-numerics.md b/milestones/scene-spec-bool-numerics.md index 650dbaf..80b5e93 100644 --- a/milestones/scene-spec-bool-numerics.md +++ b/milestones/scene-spec-bool-numerics.md @@ -1,6 +1,6 @@ # Milestone: scene-spec numerics must not coerce bools -**Status:** Active +**Status:** Shipped **PR:** [#147](https://github.com/jmjava/documentation-generator/pull/147) **Depends on:** `milestones/grok-stt-start-end.md` (PR #146), `milestones/visual-beats-numeric.md` (PR #120) diff --git a/milestones/scene-spec-layout-gaps.md b/milestones/scene-spec-layout-gaps.md new file mode 100644 index 0000000..383a0f4 --- /dev/null +++ b/milestones/scene-spec-layout-gaps.md @@ -0,0 +1,38 @@ +# Milestone: scene-spec layout gaps must be YAML numbers + +**Status:** Active +**PR:** (this PR) +**Depends on:** `milestones/scene-spec-bool-numerics.md` (PR #147) + +## Problem + +PR #147 typed `wait_word` / `run_time` / sizes / `page_transition_run_time`. +Layout gaps still go through ``float()`` at compile: + +```python +first_row_title_buff = float(layout.get("first_row_title_buff", 0.5)) +row_gap = float(layout.get("row_gap", 0.6)) +column_gap = float(layout.get("column_gap", 0.8)) +``` + +`bool` is a subclass of `int`: `first_row_title_buff: true` becomes +**1.0** (looks like a valid gap). A string raises at compile, not at +`validate_scene_spec`. + +## Goal + +Present `layout.first_row_title_buff` / `row_gap` / `column_gap` must +be YAML numbers (not bool). Missing keys keep compile defaults. + +## Done when + +- [ ] Bool / string layout gaps raise `SceneSpecError` +- [ ] Missing gaps still validate (defaults at compile) +- [ ] `ruff check src/ tests/` +- [ ] `pytest tests/` +- [ ] `docgen benchmark` (no clock change; meets baseline) + +## Out of scope + +- Range floors (`MIN_TITLE_ROW_BUFF`) stay density warnings +- Issue #56 diff --git a/src/docgen/scene_spec.py b/src/docgen/scene_spec.py index f87f897..8ceb1a5 100644 --- a/src/docgen/scene_spec.py +++ b/src/docgen/scene_spec.py @@ -1839,6 +1839,15 @@ def validate_scene_spec(data: dict[str, Any], *, path_label: str = "spec") -> No raise SceneSpecError( f"{path_label}: layout.dwell_run_time must be a number in (0, 3] if set" ) + for gap_key in ("first_row_title_buff", "row_gap", "column_gap"): + if gap_key not in layout or layout.get(gap_key) is None: + continue + gap_val = layout[gap_key] + if not _is_yaml_number(gap_val): + raise SceneSpecError( + f"{path_label}: layout.{gap_key} must be a YAML number, not " + f"{type(gap_val).__name__} ({gap_val!r})" + ) if has_rows: rows = data["rows"] diff --git a/tests/test_scene_spec.py b/tests/test_scene_spec.py index 5fd2a77..e0fb80c 100644 --- a/tests/test_scene_spec.py +++ b/tests/test_scene_spec.py @@ -469,6 +469,23 @@ def test_validate_rejects_bool_page_transition_run_time() -> None: validate_scene_spec(spec) +def test_validate_rejects_bool_first_row_title_buff() -> None: + spec = _spec_with(layout={"first_row_title_buff": True}) + with pytest.raises(SceneSpecError, match="first_row_title_buff must be a YAML number"): + validate_scene_spec(spec) + + +def test_validate_rejects_string_row_gap() -> None: + spec = _spec_with(layout={"row_gap": "0.6"}) + with pytest.raises(SceneSpecError, match="row_gap must be a YAML number"): + validate_scene_spec(spec) + + +def test_validate_accepts_numeric_layout_gaps() -> None: + spec = _spec_with(layout={"first_row_title_buff": 0.5, "row_gap": 0.6, "column_gap": 0.8}) + validate_scene_spec(spec) + + def test_validate_rejects_wait_word_and_wait_segment_together() -> None: with pytest.raises(SceneSpecError, match="at most one"): validate_scene_spec( From 7c50401d66a0f341a11cc13d64af3680ff574bbb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 01:13:52 +0000 Subject: [PATCH 2/2] Record PR #148 and local gate results for scene-spec-layout-gaps ruff green; pytest 821 passed, 1 skipped; docgen benchmark meets baseline. Co-authored-by: jmjava --- milestones/scene-spec-layout-gaps.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/milestones/scene-spec-layout-gaps.md b/milestones/scene-spec-layout-gaps.md index 383a0f4..e401b36 100644 --- a/milestones/scene-spec-layout-gaps.md +++ b/milestones/scene-spec-layout-gaps.md @@ -1,7 +1,7 @@ # Milestone: scene-spec layout gaps must be YAML numbers **Status:** Active -**PR:** (this PR) +**PR:** [#148](https://github.com/jmjava/documentation-generator/pull/148) **Depends on:** `milestones/scene-spec-bool-numerics.md` (PR #147) ## Problem @@ -26,11 +26,11 @@ be YAML numbers (not bool). Missing keys keep compile defaults. ## Done when -- [ ] Bool / string layout gaps raise `SceneSpecError` -- [ ] Missing gaps still validate (defaults at compile) -- [ ] `ruff check src/ tests/` -- [ ] `pytest tests/` -- [ ] `docgen benchmark` (no clock change; meets baseline) +- [x] Bool / string layout gaps raise `SceneSpecError` +- [x] Missing gaps still validate (defaults at compile) +- [x] `ruff check src/ tests/` +- [x] `pytest tests/` (821 passed, 1 skipped) +- [x] `docgen benchmark` (no clock change; meets baseline) ## Out of scope