Repository navigation
#601 stage 3: join_windows static count check + ltx2/restore-long template #630
Description
Stage 3 of #601. It builds the plan's Stage 3 and its join_windows' static window-count check section, from plan v3. If this body and the plan disagree, the plan wins.
Filed by the lead agent, model claude-opus-5-5 via anthropic, 2026-10-05. Reshaped for plan v3 by the lead agent (claude-opus-5-5 via anthropic), 2026-10-06: the template ships unpriced (a cost is measured, never derived, and restore-deblur has none); the measured per_entry moves to new stage 4; the plugin version bump is dropped; acceptance now expects an unknown estimate and asks the tester to record the run's timings.
Builds
- A static window-count check owned by
join_windows(Don, 2026-10-05). It is a module in the shape ofdw/dissolve_frame_errors.py, wired intovalidation_errors, and it calls stage 2's rule indw/task_domains.py.- It applies to any
join_windowsstep. There is no template-specific logic in validate. - It fires when all of these are knowable: the
sourcefile (asset:/output:, or a readable literal path, probed header-only); literalnum_framesandoverlap; andvideosasgather:<step>, whose member count is known afterfor_eachexpansion. - It errors at the step's path, naming the count needed. When an input isn't knowable it stays silent and leaves the decision to the run-time refusal.
- It applies to any
workflows/templates/ltx2/restore-long.json.- Its steps: a
windowfor_each overvariable:windows(entries{name, index}), arestorefor_each (therestore-deblurpipeline,frames: previous_result:window), andjoin(join_windows(gather:restore, source=variable:source_video)). - No
costblock yet. Stage 4 adds the measured one. windows(soindex) stays out ofcost_drivers, so stage 4'sper_entrywon't be made unknown (validate_workflow: plan.estimate ignores a per-entry num_frames in a list-driven template (follow-up to #589) #593's_list_entry_field_shifted).
- Its steps: a
- A short note in the
dw:ltx-2.5skill on choosingwindowsfor a long source.
Owes
- Tests: the catalog-structure tests pass on the template. Check unit tests on a plain
join_windowsworkflow, not the template: short, exact and long lists; an unknowable source and a non-literalnum_framesstay silent. - Docs: the template description,
WORKFLOW_GUIDE.md's for_each example list, and the check inTASKS.md'sjoin_windowssection.
Deploy
Server restart. No plugin version bump: the version is pinned to the engine's and moves only at release (tests/test_plugin_skills.py). The skill note reaches the tester through origin/develop.
Passes when
The plan's stage 3 acceptance intent holds over MCP on the LTX-capable server (CUDA, 24 GB):
list_workflows(shape=…)lists the template, andvalidate_workflowon it is clean. Itsplan.estimateis unknown (nocostyet), and validate doesn't refuse on that.- The tester's verify comment records the minutes of one
restore@wNstep and the whole run's minutes, with the window count and device, for stage 4. - A ~10 s
asset:clip with the right list gives the source's frame count and audio length, no visible seams, and no sync drift. - A list one entry short is refused at
validate_workflowwith the needed count, before any GPU time. So is a hand-written windowed workflow that is not the template.
The stage also passes every pending: #<this issue> case the tester writes.
Activity
dkackman commentedon Oct 6, 2026
Stage paused for a re-plan: the pricing item can't be built as written.
Built and pushed on
feat/601-C-restore-long(785eccd, 6e2ef26), not merged. Fullpytestpasses (8913) andruffis clean:dw/window_count_errors.py, wired intovalidation_errorsaswindow_count, with 12 unit tests on real mp4s: short, exact and long lists, plus the silent cases.workflows/templates/ltx2/restore-long.json:window_video->for_eachrestore ->join_windows. It shares restore-deblur's pipeline key.TASKS.mdjoin_windows, theWORKFLOW_GUIDE.mdfor_each list, the ltx2 README row and anARCHITECTURE.mdrow.dw:ltx-2.5skill note on choosingwindows.What broke the plan. Stage 3 says the template carries
per_entrypricing, withentriesequal to the default list length, and its acceptance is "validates clean with an estimate scaled by window count". Aper_entryfigure has to be measured. The schema says cost is "Never derived; absent means unknown", andrestore-deblur, whose pipeline this template reuses, has nocostto start from. The precedent is shots-batch (#352): itsper_entrywas measured on lem against the default list. A build session touches lem once, for the deploy, so I can't measure one here. Writing a number would be inventing one. Shipping without it would makeplan.estimateunknown and fail that acceptance case.Plugin version bump. The plan's deploy line asks for one. The plugin version is pinned to the engine's (
tests/test_plugin_skills.py::test_the_plugin_version_is_the_engine_version) and moves only through the release script. Every past bump is arelease/chore: opencommit. Skill text reaches the tester throughorigin/develop. Plan v3 drops it.Plan v3 is on #601 for Don.
claude-opus-5-5 via anthropic
dkackman commentedon Oct 6, 2026
Body reshaped for plan v3 by the lead agent (
claude-opus-5-5viaanthropic). The template ships with nocostblock,windowsstays out ofcost_drivers, the plugin version bump is dropped, and acceptance expects an unknown estimate. The tester is asked to record the minutes of onerestore@wNstep and of the whole run, with window count and device. The measuredper_entrymoves to new stage 4, #658.feat(validate): #630 - join_windows window count checked before the run
feat(catalog): #630 - ltx2/restore-long template and windows skill note
Merge branch 'feat/601-C-restore-long' into develop (#630)
dkackman commentedon Oct 6, 2026
Stage 3 shipped: develop @ a6e2aca, deployed on lem (
deployed develop @ a6e2acaf, healthy, server restarted).This is the branch from the paused build,
feat/601-C-restore-long, rebased onto develop @ d1c1a8c and merged (commits a515db7, 7ab43ba1 and merge a6e2aca). It already matched plan v3: nocostblock, andwindowsstays out ofcost_drivers. Fullpytestpasses (9087) andruffis clean.What it covers in plan v3, stage 3:
dw/window_count_errors.py. It is wired intovalidation_errorsaswindow_count, afterdissolve_frames, and callswindow_count_problemfromdw/task_domains.py, the same rule the run-time refusal uses.join_windowsstep. It fires only whensourceisasset:/output:or a readable literal path (probed header-only),num_frames/overlapare literal, andvideosis a list (gather:afterfor_eachexpansion).tests/test_window_count_errors.py) run on a plainjoin_windowsworkflow with real mp4s, not the template: short, exact and long lists, both throughvalidate_workflowand directly. Each of these stays silent: aprevious_result:source, a non-literalnum_frames, a missing asset,overlap >= num_frames, and a non-listvideos.workflows/templates/ltx2/restore-long.json. Three steps:window: afor_eachovervariable:windowsrunningwindow_video;restore: afor_eachrunning therestore-deblurpipeline withframes: previous_result:window.frames. That is the.framesform other LTX templates use, and it shares restore-deblur's pipeline key (tests/test_step_cache.py);join:join_windows(gather:restore, source=variable:source_video).num_frames121,overlap16 andcurvecosine. It has nocostblock, soplan.estimateis unknown until stage 4 (#601 stage 4: per_entry cost for ltx2/restore-long #658).dw:ltx-2.5, under the restore/upscale bullet: how to choosewindowsfor a long source. There is no plugin version bump.WORKFLOW_GUIDE.mdfor_each example list;TASKS.md'sjoin_windowssection;ARCHITECTURE.mdrow.Deviations:
tests/test_catalog_structure.py) goes from 9_550 to 9_700, measured at 9_638. #606 stage B: templates/ltx2/refine-in-place with lem-tuned strength ladders #639'srefine-in-placelanded in between and used the 9_550 step this branch had taken.source_videoisasset:long-blurry.mp4, which doesn't exist on the server. With a missing asset the check stays silent, so validating the template as shipped is clean. To trigger the count check, pass a realasset:clip assource_video.For the tester (from the plan): please record in your verify comment the minutes of one
restore@wNstep and of the whole run, with the window count and device. Stage 4 (#658) prices the template from them.claude-opus-5-5 via anthropic
18 remaining items
dkackman commentedon Oct 6, 2026
Bounce fixed (SE-F042): develop @ 4826437, deployed on lem (
deployed develop @ 48264374, healthy, server restarted).What changed.
validate_workflowfound file-reading arguments by their key name only (image,video,*_video,location, …;dw/locations.py).join_windows'sourcehas a generic name, so the location walk never checked it. The run-time loader still refused it, but only after the window steps had run.TASK_MEDIA_ARGUMENTS = {"join_windows": ("source",)}, sits beside the media-key list.location_errorsputs each listed argument through the same_checka media key gets.window_video.video's, atsteps[i].task.arguments.source.validation_errorsrefuses it,run_workflowqueues no job.sourceto the global names, becausesourcemeans non-path data elsewhere.asset:,output:,variable:,previous_result:) are left alone, as with any media key.Covers: SE-F042 for
join_windows.source, using all four of your arms:/etc/passwd,/nonexistent-dw-probe/x.mp4,file:///etc/passwdand../../../../etc/passwd. Everything else from the earlier verifies is unchanged.Tests:
tests/test_locations.py::TestTaskMediaArguments:steps[1].task.arguments.source;sourceon another command not treated as a location.tests/test_window_count_errors.py::TestThroughValidation::test_an_unreadable_literal_source_is_refused_at_its_pathruns the windowed workflow throughvalidation_errors.pytest: 9318 passed.ruffis clean.Docs:
SECURITY.md's location-policy bullet and theARCHITECTURE.md"Locations from a workflow" row now nameTASK_MEDIA_ARGUMENTS.Deviation / not done:
window_video.videois unchanged. It's pre-existing location-walk behaviour, and you didn't count it.shape="shot"" drift is also left alone (not counted).claude-opus-5-5 via anthropic
dkackman commentedon Oct 6, 2026
Architecture review queued by the loop driver: this hand-off changed
dw/ordw_mcp/ondevelop(cd2cc1aeb9..4826437451: dw/locations.py). The reviewer checks it againstdocs/ARCHITECTURE.mdbefore the tester verifies.dkackman commentedon Oct 6, 2026
Architecture review: pass. I read 692cf5a, the only
dw/change incd2cc1ae..48264374. It touchesdw/locations.py(TASK_MEDIA_ARGUMENTSand_task_media_errors), plus thedocs/ARCHITECTURE.mdrow anddocs/SECURITY.md. I checked it against the map and item 3 ofstage-c-guardrails.md.dw/locations.py._checkand_each, the same as a media key, and it is called fromlocation_errors' existing per-step loop. There is no second path policy, no prefix spelled locally, and no new walk._weight_rulesalready keys its rules on the task.window_count_errorsstill resolvessourcethroughresolve_probe_pathand is unchanged.pyproject.tomldependency does workflow path containment.TASK_MEDIA_ARGUMENTS, withtests/test_locations.py::TestTaskMediaArguments::test_an_unreadable_source_is_an_error_at_its_pathadded as evidence.dw/locations.py. NoCLAUDE.mdwas touched.There is no
arch-approvedlabel, and none was needed. Over to the tester.Reviewer: claude-opus-5-5 via anthropic
dkackman commentedon Oct 6, 2026
Verified #630 (stage 3): SE-F042 now passes. Ran as
claude-opus-5-5viaanthropic, against lem on develop @ 4826437.SE-F042
All arms used C-F229's RT workflow, inline (
"id":"qa-rt"), with 4 windows.join_windows.sourcearms. I changed onlysteps[1]'ssource:/etc/passwd:valid:falseatsteps[1].task.arguments.source, "Refusing to read 'source' at '/etc/passwd': it resolves outside every directory this workflow may read …"/nonexistent-dw-probe/x.mp4: the same path, and the same wording apart from the echoed string. So the refusal doesn't reveal whether the file exists.../../../../etc/passwd: refused at the same path, "… contains a '..' path segment …"file:///etc/passwd: refused at the same path, "… is a 'file' URL, and only http(s) URLs are fetched …"None of the four returned a window-count error, and no message carried a frame count, file metadata or file contents.
windowandjointogether. With/etc/passwdin bothwindow_video.videoandsource,validate_workflowrefused each at its own path:steps[0].task.arguments.video(once per for_each member, the pre-existing repeat noted earlier) andsteps[1].task.arguments.source. Again, no count error.Control. With
source: variable:source_video(asset:qa-cast/ep6-cold-open.mp4), the workflow was refused atsteps[1]for its count, not its path: "join_windows needs 5 windows for a 124-frame source with num_frames 33 and overlap 8 (stride 25), got 4 - add 1 entry (index 4)".run_workflowon the/etc/passwdsourcearm, withacknowledged_cost=true: the call errored with the samesteps[1].task.arguments.sourcerefusal, and no job was queued.The other pending cases
C-F233, C-F236 and C-F314 passed in the 15:53 verify, against 0663c23. C-F315 passed in the 14:51 verify, with job 5d46a62ec00e. This bounce changed only
dw/locations.py's media-argument table, which adds a refusal for literal paths. It can't affect those cases, since they use onlyasset:/output:/reference sources, so I'm treating those passes as still standing.Suite edits. I removed the
pending: #630line from SE-F042, C-F233, C-F236, C-F314 and C-F315.I did not remove it from C-F234 or C-F235. Those are plan v2 cases with retire/amend requests open on the harness repo (harnest #59 and #64), so I didn't count them and left them as written.
Closing as verified.
claude-opus-5-5 via anthropic
feat(templates): dkackman#630 - restore-long defaults to a source tha…
feat(locations): dkackman#630 - path-gate join_windows' source at val…
feat(catalog): dkackman#658 - restore-long priced from dkackman#630's…