Skip to content

Foundation: image-aware scaffold, tools/new_recipe.py --mode replay-images (Refs #315) - #443

Draft
CompleteDotTech wants to merge 2 commits into
mainfrom
foundation/image-scaffold-315
Draft

CompleteDotTech wants to merge 2 commits into
mainfrom
foundation/image-scaffold-315

Conversation

@CompleteDotTech

Copy link
Copy Markdown
Contributor

Refs #315 (slice 2 of 2: the scaffolder items). No closing keyword: the docs items are a separate slice.

python tools/new_recipe.py NN --mode replay-images scaffolds a replay recipe whose requests carry images. This is a shared interface (the scaffold every multimodal recipe starts from), so it needs two independent reviews.

What changed, per item of #315

Item Disposition Where
--images or --mode replay-images Fixed, spelled --mode replay-images (see "Why --mode") tools/new_recipe.py
assets/ with generated example images and a manifest Fixed. 14 PNGs (assets/screen-<id>.png) and assets-manifest.json, written by the new folder's own build_fixtures.py, which the scaffolder runs once after writing the files tools/new_recipe.py
A build_fixtures.py using #314 Fixed. Draws with jev_cookbook.synthetic_images.login_screen, saves with canvas.write (the byte policy), writes the manifest with canvas.manifest_entry, derives each Box label from Drawn.label(); --force as in the text scaffold, --overwrite-assets accepts a changed picture tools/new_recipe.py
helpers using image_asset(root=...), image_reference, replay_key(..., assets=...) Fixed. build_request(fields) returns (state, assets); the reference sits once in the state tools/new_recipe.py
Notebook cells: one image answer up close, draw_boxes, the presence/review rule, localization_report Fixed, generated from Python source in the tool (image_notebook_cells), no .ipynb edited by hand. Not executed here (see "Not verified") tools/new_recipe.py
request_budget.json and AuditedBackend route, including the image profile of docs/backends.md Fixed. requests: 14, profile: {"images": 14, "distinct_images": 14}; setup cell is AuditedBackend(get_backend(fixtures=responses_path(), images=True), label="main"); final cell prints backend.audit_json(); README paragraph rendered from the declaration tools/new_recipe.py
side_effects.json as the template requires Fixed, the same none placeholder (with its TODO reason) as the text scaffolds tools/new_recipe.py
The comment's item: the scaffolded tests/test_build_fixtures.py must pass unchanged with assets/ present Fixed, with a finding and a decided limit (see "The test_build_fixtures.py item") tools/new_recipe.py
Passes validate, the asset check, hygiene and its own tests offline Fixed, executed (see "Checks run")
Notebook-freshness check Not run: it needs a notebook execution, which was not done on this host
Text scaffolds unchanged Fixed, executed: all 100 catalog slugs in both existing modes, before and after, trees identical
tests/test_new_recipe.py and the agreement tests cover the image variant Fixed tests/test_new_recipe.py, tests/test_template_scaffolder_agreement.py
docs/recipe-template.md links docs/vision.md The file table's assets/ row already linked vision.md on main (a search on this branch's base, not an assumption); this change adds the ## Image recipes section, which links it again, and documents the scaffold docs/recipe-template.md

Why --mode

--mode is the existing option that selects a variant of the scaffold, so the image variant is one more value of it. A separate --images flag could be combined with --mode scripted; no value of --mode can ask for an image recipe that is not a replay recipe. MODES keeps its two text values (the tests and the recipe guard iterate over it, and the guard's scaffolder.MODES loop is unchanged); ALL_MODES adds replay-images and is what --mode and scaffold() accept.

What the image scaffold is

A working example, not a skeleton: invented sign-in windows (login_screen), two questions (present, a Noul: is an error banner shown; location, a Box: where), 6 validation, 7 test and 1 demo example, a rule locate that holds whatever the model answers, and stored answers that are synthetic and deliberately imperfect. TODO markers say what to replace. tools/new_recipe.py itself stays standard library only; the one step that draws the images needs the installed package and Pillow, and if it fails the folder is removed and the command exits 1 (tested).

The test_build_fixtures.py item

The comment predicted FileNotFoundError on assets/<file> for the text variant. Executed on this scaffold, that is not what happens: this build_fixtures.py writes the images before it reads them, so the text variant (which copies only the two scripts) passes in the environment that wrote the committed files. What it cannot survive is a different PNG encoder: the committed bytes are part of every replay key that carries a picture, and a rerun that draws the pictures afresh disagrees with the committed fixtures although every pixel is the same. Executed table (a sitecustomize that re-encodes every PNG with compress_level=1, same pixels, different bytes, and says the environment is not canonical):

Test file run Encoder Result
image variant (copies assets/) the one that wrote the committed files 4 passed
image variant the other encoder 4 passed
text variant (build_fixtures_test_text(recipe)) the one that wrote the committed files 3 passed
text variant the other encoder 1 failed (test_the_generator_reproduces_the_committed_fixtures), 2 passed

So the scaffold emits a variant, build_fixtures_test_text(recipe, "replay-images"), that also copies assets/ and adds two checks (images and manifest come back unchanged; a changed picture is refused, not rewritten). build_fixtures_test_text(recipe) with one argument still returns the text variant. A test pins the encoder contrast (and a mutation that drops the copy is caught).

Decided limit, for the owner of the recipe guard: tests/test_recipe_guard.py (open PR #365, so not touched here) compares every replay recipe's tests/test_build_fixtures.py byte for byte with build_fixtures_test_text(recipe), one argument. A published image recipe would be compared with the text variant and fail that check. The one-line follow-up there is to pass "replay-images" when the recipe has assets/.

Checks run (executed on this host, Windows 11, Python 3.14.3)

  • Text scaffolds byte-unchanged. Every catalog slug (100) scaffolded in --mode replay and --mode scripted from origin/main's tools/new_recipe.py and from this branch's, diff -r on the output trees: identical (800 and 700 files).
  • The image scaffold, offline, no hand edit (rank 77 in a temp folder): python -m jev_cookbook.fixtures validate -> fixtures valid (mode replay); tools/check_recipe_assets.py on a git tree holding it -> passed (26 files); tools/check_hygiene.py on its 26 files -> passed; ruff check and ruff format --check (from the repo root, as CI runs them, ranks 61, 77 and 100) -> passed; its own pytest tests -> 15 passed.
  • Request audit. The real tests/request_audit.audit_recipe over the scaffold with the audit line produced by replaying the notebook's decide() calls in plain Python (the demo example, then validation, then test; not a kernel run): returns 14, which equals the declared count, the two image quantities and the rendered README paragraph. Mutations of the declaration (one request fewer, no profile, images or distinct_images off by one, a README paragraph that is not the rendered one) each fail.
  • The numbers the notebook will print, worked in plain Python against the scaffold's own fixtures and by hand: presence threshold 0.28 (lowest confidence at which every validation call is right; v06 is the one wrong call, at 0.16), box threshold 0.86; test report: 7 examples, 4 present (2 located, 1 missed, 1 in review) and 3 absent (1 false positive, 1 correct absent, 1 in review), mean IoU over located present targets 0.9025390625 (denominator 2), over all present targets 0.45126953125 (denominator 4), hit rate 2/4. The stored-answers test finds two confidently wrong test answers (t05, t07).
  • locate, input -> outcome, executed: p 0.90, box 0.90, thresholds 0.30/0.80 -> located; p 0.10 -> absent; p 0.40 (presence confidence 0.20 < 0.30) -> review; p 0.80 with threshold 0.70 (confidence 0.60) -> review, with 0.60 -> located; p exactly 0.5 -> absent; box confidence None -> review; 0.50 < 0.80 -> review; 0.80 == 0.80 -> located; a threshold outside [0, 1] -> ValueError. A grid of 101 probabilities by 5 thresholds shows a review is never an absence.
  • images=True offline: replay with and without it gives identical results for all 14 examples (executed), so the option changes nothing offline. That it enables the images header in a live run is from docs/backends.md (read, not executed).
  • Focused tests: tests/test_new_recipe.py (the 70 tests that do not reach the notebook executor, 85 items with parametrization) and tests/test_template_scaffolder_agreement.py (20 items), plus test_run_mode_sentence, test_no_private_shared_names, test_recipe_sources, test_docs_examples, test_docs_ruff_version, test_check_recipe_assets and the scaffolder-dependent tests of test_template.py and test_recipe_guard.py: 618 passed, 7 skipped (-rs: 6 in test_run_mode_sentence for its closed legacy list, 1 in test_recipe_guard for a scripted recipe, which has no test_build_fixtures.py to guard). The collected ids were listed and none of the 51 tests that reach the executor was among them. A second run of neighbouring modules (test_pytest_config, test_catalog_shape, test_fixtures_assets, test_synthetic_images, the docs and asset-checker modules): 1121 passed, 16 skipped (-rs: no symbolic links on this platform, POSIX file modes, and the byte pins, which run only under the pinned Pillow 12.3.0 with a classic zlib).
  • Fails before, passes after. The new and changed tests against a copy of origin/main's tools/new_recipe.py (with the module's ALL_MODES and IMAGE_MODE references made tolerant so it collects): 5 failed, 18 errors (the fixture cannot scaffold the mode) and 5 passed (the text-mode regression guards, which are meant to pass on both); the agreement module: 3 failed, 17 passed on main, 20 passed here. The image rows of the measured-cell comparison pass on main too, because main's generator treats any mode that is not scripted as replay.
  • Mutation checks (a one-line change to the final tool, then the named tests): 18 of 18 caught: profile dropped, distinct_images off by one, a row dropped, the folder kept after a failed drawing step, the drawing step skipped, assets= dropped from a decide, images=True dropped, selection dropped, assets/ no longer copied by the test, a changed picture always overwritten, 0.5 treated as a yes, labels typed instead of drawn, the text variant replaced by the image one, the image test_build_fixtures.py not written, MODES growing the image mode, an images mention in the text scaffold, the rectangle gate > instead of >=, the demo example labelled.
  • ruff check and ruff format --check on the three changed Python files, tools/check_hygiene.py (635 files), tools/check_recipe_assets.py (462 files), tools/render_catalog.py --check, git diff --check: all clean. ruff is 0.15.8 locally; pyproject.toml pins 0.17.2.

Not verified

  • No notebook was executed, so the new notebook's cells (state, answer-boxes, python-rule, evaluation, evaluation-review) are verified by parsing (ast), ruff, a plain-Python replay of their logic, and calls to the same APIs, not by running them. The new test_a_fresh_image_scaffold_passes_the_request_audit_with_no_hand_edit executes the notebook with a kernel, like its text counterpart, and has not been run; neither has any of the 51 existing tests that reach the executor.
  • The notebook-freshness check (needs an execution), full pytest, ruff 0.17.2, the Python 3.10 leg, and the byte pins under the notebook constraints (Pillow here is 13.0.0 with a zlib-ng build, not the pinned 12.3.0, so the PNG bytes this host writes are not the canonical ones; the committed ones of a real recipe must be recreated under the pin, which the README TODO says).
  • A live run: no provider call was made.

Deferred (recorded, not silently dropped)

Prior work

Searched issues and pull requests before drafting (gh api search/issues for the scaffolder, replay-images, image scaffold, test_build_fixtures and assets; the contracts below were read). No earlier change adds an image path to the scaffolder.

Generated with Claude Code

CompleteDotTech and others added 2 commits October 31, 2026 18:33
…mages (Refs #315)

Slice 2 of #315: the scaffolder items only.

`python tools/new_recipe.py NN --mode replay-images` scaffolds a replay recipe
whose requests carry images. After writing the replay files it runs the new
folder's own build_fixtures.py once, which draws 14 example images with
jev_cookbook.synthetic_images, writes assets/ and assets-manifest.json
(origin generated, licence, attestations) and a complete fixtures/ whose Box
labels are derived from the drawn geometry. helpers.py loads each image with
image_asset(path, root=ROOT), derives image_reference and keys the replay with
replay_key(state, questions, assets=assets). The notebook cells are generated
from Python source in the tool: one image answer up close, draw_boxes, the
presence/review rule (locate) and localization_report. request_budget.json
declares the 14 requests and the two image quantities of docs/backends.md, so
the scaffold passes the request audit with no hand edit.

tests/test_build_fixtures.py for an image recipe is a variant that also copies
assets/ into the temporary folder it rebuilds in, so the committed picture
bytes (part of every replay key that carries a picture) are compared, not
regenerated. build_fixtures_test_text(recipe) still returns the text variant;
build_fixtures_test_text(recipe, "replay-images") returns the image one.

The text scaffolds are byte-unchanged: all 100 catalog slugs in both existing
modes were scaffolded before and after and the trees compared. MODES keeps its
two text values; ALL_MODES adds the image mode.

Tests: tests/test_new_recipe.py and the template/scaffolder agreement tests
cover the image variant. Deferred to other slices: README/backends/fixtures
links, the experimental label, docs/live.md, the docs-link test, a frame
variant, and the recipe guard's byte comparison of test_build_fixtures.py for
a recipe that has assets/.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…clearly invented (Refs #315)

Reading the product names that login_screen draws for the first set of seeds
showed two that match real brands (MAFEX and ROMER), and several short ones
that could. The manifest of an image recipe attests real_brand false, and
docs/synthetic-images.md asks the author to read every drawn name, so the
example uses other seeds: v04, t02, t03, t04, t06, t07 and d01 change
(14 -> 17, 22 -> 32, 23 -> 34, 24 -> 35, 26 -> 48, 27 -> 54, 31 -> 60), giving
longer names that were read by eye (this is not a trademark search).

Only the drawing seed changes. The screen states, the gold boxes, the stored
answers and every number the notebook prints are unchanged, so the tests'
hand-computed values still hold; the one test that edits a row by its text
follows the new seed of t07.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant