fix(sync): converge generated files in one run and release them when their tool is off - #341
Merged
Merged
Conversation
When a region inside a generated file (such as a template's justfile recipes) stopped being declared, the generated writer skipped the whole file for that run and left the region step to retract it, so a changed generated text only landed one sync later and sync --check failed once right after a real sync. The writer now retracts omitted regions itself before merging: unedited ones are removed and cut from the whole-file baseline, deleted ones are forgotten, and settled ones are taken or kept. A region kept with its edit is the user's, so it sits out the three-way merge and is appended after it. Only a region still waiting for its decision holds the file.
Turning just or Docker off left justfile and Dockerfile behind, owned forever: their writers return early when the tool is off, the same hole #340 closed for structured documents. An owned generated file that no tool generates and no declared region targets is now released the way a seed is: deleted when its text matches the whole-file baseline, forgotten when already deleted, and otherwise a retracted conflict for the whole file, whose local choice keeps it as the user's and desired deletes it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary & Motivation
This PR fixes two leftover problems in how generated text files (
justfile,Dockerfile) are reconciled. Both live inReconciliation._write_generated.sync --checkfailed once in CI right after a real sync. feat(templates): typed template options that gate content through requires #338 noted this as a known limitation.justor Docker off made_write_justfile/_write_docker_artifactsreturn early, sojustfileandDockerfilestayed on disk and in the lock forever. This is the same hole feat(sync): retract structured documents nothing declares any more #340 closed for structured documents.Planning & Key Decisions
The generated writer retracts omitted regions itself. It no longer skips the file. It runs the region retraction (
append_marker_blockswith no payloads and the omitted regions' baselines) over the local text first, then merges:Region ownership leaves the record, so the region step later finds nothing left to do, and the file converges in one run.
A region kept with
localsits out the merge. It's the user's now, so it must not be owned text in the baseline. But if it stays in the local text during the diff3 merge, it sits right after the last generated line. When that line changes, the adjacent hunks overlap and create a spurious conflict; the first version of this change hit exactly that in a test. So the writer detaches the kept regions before the merge and appends them afterwards, the way a new region is appended.Only a pending decision holds the file. While an edited omitted region has no resolution yet, the writer leaves the file alone and the region step reports the
retractedconflict, as before. The file regenerates in the run that settles it. Regenerating around an open decision would have meant writing the region into the result while also reporting it. A target where Protostar owns only regions (theregionspolicy) is still left to the region step.Cutting a region from the baseline doesn't compare it. The whole-file baseline and a region's own baseline can drift apart: a region can update on its own while the whole-file merge conflicts. So removing a region from the text baseline is now
appends.cut_regions, which removes by marker. It no longer runs the three-way region merge against a baseline that might not match. The region step's existing baseline cut uses it too.Undeclared generated files are released the way seeds are, at file level.
EnvironmentManifest.generated_files()lists what a tool generates this run;target_filesnow builds on it.Reconciliation._release_undeclared_generatedtakes each ownedtext-policy record that no tool generates and no declared region targets:retractedconflict for the whole file, wherelocalkeeps it as the user's anddesireddeletes it.Unlike #340's structured documents, generated text has no units to keep apart. A justfile reduced to hunks around the user's edits would be a fragment, so the decision stays whole.
A file that still receives a declared region stays declared. If a template appends to the justfile without
requires = "just", the generated text stays while that region is declared, matching #340's "declared while a producer contributes to it". Retracting only the generated text and turning the file into a regions-only file would have been a policy change of its own.Architectural Invariants
_write_generatedsettles omitted regions before it merges. Only an unsettled edited region defers regeneration.textrecord outsidegenerated_files()and the declared region targets is deleted when unedited, forgotten when gone, and otherwise one file-levelretracteddecision.Testing
test_generated_execution.py:retractedconflict with no editslocalordesiredregenerates in that run, with the kept region appended and later runs asking nothingtest_lifecycle.py, withjustswitched off:localordesiredkeeps it as the user's or deletes itapply_generatedharness now declares the path it generates, because it writes through a patched writer without turning a tool on.