Skip to content

Commit 71f2921

Browse files
committed
Pin what actually keeps the probe config stable
`apply_arrival` carried a comment claiming `format-pyproject` expands inline tables back into sections, and that writing sections avoided a ping-pong. Running the formatter over a real `pyproject.toml` shows the reverse: it collapses both shapes into dotted keys under `[tool.repomatic]`. The rationale was wrong; the behaviour happens to be right. tomlrt cannot emit a dotted key at all, since assigning one produces a quoted key holding dots, so the formatter necessarily has the last word on layout. What keeps the two from rewriting each other is that the dotted shape reads back as the same nested value, so a second apply finds the label already declared and writes nothing. That is the invariant worth holding, and nothing held it. The new test applies against the formatter's real output rather than against what the applier writes, which is the version of this check that would have caught the mistaken comment in the first place.
1 parent 19512d8 commit 71f2921

2 files changed

Lines changed: 41 additions & 5 deletions

File tree

repomatic/runner_images.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -727,11 +727,17 @@ def apply_arrival(change: RunnerChange, pyproject_path: Path) -> bool:
727727
if not pyproject_path.is_file():
728728
return False
729729
doc = tomlrt.loads(pyproject_path.read_text(encoding="UTF-8"))
730-
# Seeded through `Table` rather than a bare dict: `setdefault` with a dict
731-
# writes an inline table, which `format-pyproject` then expands back into
732-
# sections. The two would rewrite each other on every push, which is the
733-
# generator/formatter ping-pong `claude.md` § Common maintenance pitfalls
734-
# warns about.
730+
# Seeded through `Table` rather than a bare dict, which `setdefault` would
731+
# turn into an inline table.
732+
#
733+
# Either way `format-pyproject` normalizes what lands here into dotted keys
734+
# under `[tool.repomatic]` (`test-matrix.variations.os = [ … ]`), and tomlrt
735+
# cannot emit that form: assigning a dotted string produces a *quoted* key
736+
# holding dots, which is a different key. So the formatter gets the last
737+
# word on layout, and the invariant that matters is not the shape written
738+
# but that re-reading the formatter's shape finds the label already there.
739+
# It does, so the two converge instead of ping-ponging, per `claude.md`
740+
# § Common maintenance pitfalls. `tests/test_runner_sync.py` pins that.
735741
node: Any = doc
736742
for key in ("tool", "repomatic", "test-matrix"):
737743
if key not in node:

tests/test_runner_sync.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,36 @@ def test_apply_arrival_writes_sections_not_inline_tables(tmp_path) -> None:
183183
assert not apply_arrival(change, pyproject), "second run should be a no-op"
184184

185185

186+
def test_apply_arrival_is_idempotent_against_the_formatter_shape(tmp_path) -> None:
187+
"""Re-reading what `format-pyproject` leaves behind finds the probe present.
188+
189+
The applier writes sections; the formatter normalizes them into dotted keys
190+
under `[tool.repomatic]`, and tomlrt cannot emit that form itself. So the
191+
formatter gets the last word on layout, and what stops the two rewriting
192+
each other on every push is this: the dotted shape reads back as the same
193+
nested value, so a second apply finds the label already there and writes
194+
nothing.
195+
196+
The fixture is the formatter's real output, captured from running
197+
`pyproject-fmt` over a freshly written probe.
198+
"""
199+
pyproject = tmp_path / "pyproject.toml"
200+
pyproject.write_text(
201+
'[project]\nname = "demo"\n\n'
202+
"[tool.repomatic]\n"
203+
'test-matrix.unstable = [ { os = "ubuntu-26.04" } ]\n'
204+
'test-matrix.variations.os = [ "ubuntu-26.04" ]\n',
205+
encoding="UTF-8",
206+
)
207+
(change,) = plan_runner_changes({}, {"ubuntu-24.04"}, [ARRIVAL], CATALOG)
208+
209+
assert not apply_arrival(change, pyproject), (
210+
"the probe is already declared in the formatter's dotted-key shape, so "
211+
"re-applying must write nothing: otherwise sync-runner-images and "
212+
"format-pyproject each undo the other, forever"
213+
)
214+
215+
186216
def test_apply_axes_retirement_moves_the_curated_tuple(tmp_path) -> None:
187217
"""The axes move as text, keeping their comments and their ordering."""
188218
axes = tmp_path / "matrix_axes.py"

0 commit comments

Comments
 (0)