Repository navigation
update-shape.py: update-accept-local-missing-name asks a copy an earlier apply kept, read unchanged, as a module a written copy imports from and as an importer of a written one, so a plain apply that stops either on its import is refused (#1082) - #1133
brettheap wants to merge 4 commits into
Conversation
… apply writes against its module as the run leaves it, so a copy an earlier apply kept, read `unchanged`, is asked too, and no `siblings.py` stopping on its import is copied (#1082) #536 built the guard's set of modules from the rows `--accept-local` names in this run, so a copy an earlier apply kept, which reads `unchanged` once the upstream stops changing it, was never asked. The next apply wrote an importer beside it unasked. Measured offline: a holder cut at 6476c17 that applied 2a119f2 keeping its own bootstrap.py was then written main's siblings.py, with its `untraced` import (#1075), by `apply --at 6da757c`. That apply exited 0 and committed, and `siblings.py --help` stopped on an ImportError. The same held for #1023's `member_make_environment` with #1023 landed in two halves. Each written .py copy is now asked against its module as the run leaves it: a copy this run keeps (`(kept)`), a copy an earlier run kept, whose pin records the project's own bytes (`(kept by an earlier run)`), or the upstream's own bytes at the target (`(the upstream's)`). The code stays `update-accept-local-missing-name`, and a refusal whose every line is one this run's --accept-local makes prints what it printed. The guard still runs before `confirm` and before any write. An importer this run does not write is still not asked, but for a kept one against a module this run writes (#604). So a copy an earlier run kept that imports a name the written module drops is a known limit, named in the docstring. Closes #1082 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR closes a cross-run safety gap in Sequence diagram for apply missing-name refusalsequenceDiagram
participant Apply as update-shape.py apply
participant Rows as Copy rows
participant Validator as Missing-name validator
participant Files as Copies as left
Apply->>Rows: _written_copies
Apply->>Files: _copies_as_left
Files-->>Validator: Module ownership and bytes
Apply->>Validator: _written_copies_lacking
Validator->>Files: _names_as_left
Files-->>Validator: Names defined by module
alt Missing imported name
Validator-->>Apply: _missing_name_refusal
Apply-->>Apply: Nothing written
else Names agree
Validator-->>Apply: Continue to confirm and write
end
Flow diagram for cross-run missing-name validationflowchart TD
A["apply starts"] --> B["Build copies as left"]
B --> C["Classify module bytes"]
C --> C1["Current --accept-local"]
C --> C2["Earlier-run kept"]
C --> C3["Upstream target"]
C1 --> D["Check written importers"]
C2 --> D
C3 --> D
D --> E{"Imported names all defined?"}
E -->|Yes| F["Continue to confirm and write"]
E -->|No| G["Refuse before any write"]
G --> H["Explain module ownership and remediation"]
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Independent review of #1133 (#1082) at The change does what #1082's Verify section asks. Both two-step cases are now refused before anything is written, the one-step refusal prints exactly what main prints, and no valid run I tried is refused. There are two SHOULDs: a real gap the PR documents but that is still inside the issue's Goal, and one claim of unchanged words that no row pins. Neither blocks a draft. FindingsSHOULD 1: the known limit is a real gap inside #1082's Goal, so it needs a follow-up issue before this lands.
SHOULD 2: nothing pins the opening of a
NIT: the exits row does not check the row state the remedy promises.
NIT, the coordinator's #1111 check: there are none.
Checked and true
Could not verify
Checklist candidates
Scratch left
Lane: openreposhape-5 (openRepoShape-5) |
…rlier apply kept, read `unchanged`, as an importer too, so a plain apply that drops a name it imports is refused before any write, naming who kept it and giving it its exits (#1082) Fix round 1 of #1133, after rv1133's review. #604 asked only a copy this run's --accept-local keeps against a module this run writes, so the head left a known limit inside #1082's Goal. Measured by the reviewer offline: a holder whose `apply --accept-local scripts/siblings.py` kept its own siblings.py, edited to take `told_past` from bootstrap.py, read `unchanged` against a target whose bootstrap.py renamed it. A plain `apply` exited 0, copied bootstrap.py, committed, and `siblings.py --help` stopped on an ImportError. Main's tool and the head's behaved the same. The rows read `kept by an earlier run` as a module are now asked as importers too, beside the kept ones. `_names_dropped` still limits the refusal to names this run takes away, so a copy an earlier run kept whose names all stay still applies. Its line reads `(written) no longer defines X, which Y (kept by an earlier run) imports from it`; the general opening now fits every kind of line ("a copy imports a name from a copy beside it whose bytes, as this run leaves them, do not define it, and this run writes one of the two"); and the remedy gives that copy its two exits, `--accept-local` on it once changed, or the upstream's bytes by `git show`. A refusal whose every line is one this run's --accept-local makes still prints what main prints, and a #604 row now pins its opening. The code stays `update-accept-local-missing-name`. The known-limit paragraph and the row that pinned the limit are replaced by rows that hold the refusal, its controls and its exits; the exits rows also check the state `check` reads after each exit. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Re-check of #1133 (#1082) for lane openRepoShape-5 Verdict: READY at FindingsNIT 1: nothing pins how a refusal in both directions at once opens.
NIT 2: the Merges section predates #1166.
NIT 3: the title names only the first round's direction.
Checked and trueEach finding is met. I ran the reviewer's own scripts, copied into my folder, with the head's tool and with main's.
Rows fail where the body says. I ran the head's test file with the other tools in scratch clones:
Mutants. Each was applied alone in a scratch clone, then restored by checkout with its sha256 checked (
N3, N8, N9 and R2 fail the same counts the body gives. What the round could have broken. I found nothing.
Gates and runs, all through
Merges and body claims.
Could not verify
Scratch left
Lane: openreposhape-5 (openRepoShape-5) |
…ts the refusal opens with `KEPT_LEAD`, as a refusal of this run's own `--accept-local` always has, so an opening that falls back to the general words there fails it (#1082) rc1133's NIT 1 on #1133: update-shape.py:4561 gives a refusal whose lines are all ones this run's `--accept-local` makes the opening `KEPT_LEAD`, and `test_both_directions_in_one_run_are_one_refusal` (a `(kept) lacks` line plus a `(written) no longer defines` line) pinned its lines, its clause and its remedy but not its opening. rc1133's mutant O1, `lead = (KEPT_LEAD if len(sides) == 1 and sides <= {KEPT_MODULE, WRITTEN_MODULE} else LEFT_LEAD)`, survived the 185 rows. The row now asserts `startswith(KEPT_LEAD)`, and its docstring says why the opening is that one. Tests only; update-shape.py is untouched. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
update-accept-local-missing-name asks each copy an apply writes against its module as the run leaves it, so a copy an earlier apply kept, read unchanged, is asked too, and no siblings.py stopping on its import is copied (#1082)update-accept-local-missing-name asks a copy an earlier apply kept, read unchanged, as a module a written copy imports from and as an importer of a written one, so a plain apply that stops either on its import is refused (#1082)
|
Re-check of #1133 (#1082) NIT round for lane openRepoShape-5 READY at Checked and trueNIT 1: the
NIT 3: the title is true
NIT 2: the Merges row for #1166
Body and commit
Not measured
Lane: openreposhape-5 (openRepoShape-5) |



Goal
#1082:
update-shape.py applyrefuses, before any write, every run that would leave a copy importing a name from a copy that does not define it, wherever the kept copy came from: this run's--accept-local, or an earlier run's.Fix round 1 (
53791e45, on a merge of main038dafba) answers rv1133's review: its SHOULD 1 (the known limit was inside this Goal) is fixed here rather than filed, and its SHOULD 2 and NIT are taken.What was measured on main, before the change
All probes are offline.
ghis a stub that exits 97,GIT_SSH_COMMAND=false,GIT_CONFIG_GLOBAL=/dev/null, and git is 2.43.0. The tool is main'supdate-shape.py(sha256ca47f90e, bloba7ae7505, the same at2a119f28,6da757cb,4ed7c7bfand038dafba). Scripts and logs are in lane 5's scratchv118/w1082/(tools/probe1023.sh,tools/probe1075.sh,probe-*) and, for fix round 1,v120/f1133/(tools/knownlimit.sh,tools/probe1075.sh,limit-*,probe-*1075).#1075's
untraced, on main's own history. #1075 has now landed, so main itself holds the case.6476c170has abootstrap.pywith nountraced. Its own edit to that file is committed.apply --at 2a119f28 --accept-local scripts/bootstrap.py --branch shape/firstexits 0. That was main before the family root's siblings.py: with a token in use, the clone and the fetch that carry it run in bootstrap.py'suntraced()environment, somake siblingsandfamily.py siblingswrite the token to no trace the caller asks git for (#1055) #1075, and itssiblings.pytakes nountraced.check --at 6da757cbreadsunchanged scripts/bootstrap.pyandupstream-changed scripts/siblings.py. the family root's siblings.py: with a token in use, the clone and the fetch that carry it run in bootstrap.py'suntraced()environment, somake siblingsandfamily.py siblingswrite the token to no trace the caller asks git for (#1055) #1075 changedsiblings.pyalone, andbootstrap.pyis blob3c1060d3at both commits.apply --at 6da757cb --branch shape/secondexits 0, printscopied scripts/siblings.py, and commits.scripts/siblings.py --helpexits 1 withImportError: cannot import name 'untraced' from 'bootstrap'.apply --at 6da757cb --accept-local scripts/bootstrap.py, exits 2 withscripts/bootstrap.py (kept) lacks untraced, which scripts/siblings.py imports from it. The holder is unchanged.#1023's
member_make_environment. A holder is cut atf1946d04, the parent of #1023's9e154d87. Itsbootstrap.pyhas nomember_make_environment, and itssiblings.pydoes not name it. Its own edit tobootstrap.pyis committed.MAKEFLAGSas it is, so it runs its jobs together again; every other make still gets no jobserver and no-j(#987) #1023 defined the name inbootstrap.pyand added it tosiblings.py's import in one commit.git log -S member_make_environmentover the two files names9e154d87alone.check --at <main>readsboth scripts/bootstrap.py.apply --at <main>isupdate-conflict.apply --at <main> --accept-local scripts/bootstrap.pyis refused already:scripts/bootstrap.py (kept) lacks member_make_environment, untraced, which scripts/siblings.py imports from itat6da757cb, andlacks member_make_environmentat2a119f28. The holder is unchanged.MAKEFLAGSas it is, so it runs its jobs together again; every other make still gets no jobserver and no-j(#987) #1023 landed in two halves, as the family root's bootstrap.py: git's update ended by a signal fails the run by name, and B4 with a token in use is told from fetches redone without a trace, so the token reaches no file (#998, #996) #1045 and the family root's siblings.py: with a token in use, the clone and the fetch that carry it run in bootstrap.py'suntraced()environment, somake siblingsandfamily.py siblingswrite the token to no trace the caller asks git for (#1055) #1075 landeduntraced, it does reach it. T1 is a commit on main withsiblings.pyas it was atf1946d04. That is7c695d71on2a119f28, and3d5a7edaon6da757cb.apply --at T1 --accept-local scripts/bootstrap.py --branch shape/firstexits 0.check --at <main>readsunchanged scripts/bootstrap.pyandupstream-changed scripts/siblings.py.apply --at <main> --branch shape/secondexits 0, copiessiblings.py, and commits.scripts/siblings.py --helpthen exits 1 withImportError: cannot import name 'member_make_environment' from 'bootstrap'.2a119f28and6da757cb.The reverse direction: a copy an earlier run kept, as the importer (rv1133's SHOULD 1). rv1133 measured it with main's tool and with
aa7ddf59's; fix round 1 measured it again with main's (limit-m/).4ed7c7bf. Its own edit tosiblings.pyaddsfrom bootstrap import told_past, which the upstream'ssiblings.pydoes not import, and is committed.siblings.py --helpstill exits 0.apply --at U1 --accept-local scripts/siblings.py --branch shape/firstexits 0. U1 is main plus an unrelated change tovalidate-family.py.told_pastinbootstrap.pyalone.check --at U2readsupstream-changed scripts/bootstrap.pyandunchanged scripts/siblings.py.apply --at U2 --branch shape/second, with no--accept-local, exits 0, printscopied scripts/bootstrap.py, and commits.siblings.py --helpthen exits 1 withImportError: cannot import name 'told_past' from 'bootstrap'.--accept-local scripts/siblings.py, was already refused by update-shape.pyapply --accept-local: a kept.pycopy that imports a name the run's newrepo_shape.pydropped passes, andmake bootstrapthen dies on an ImportError #604 withscripts/bootstrap.py (written) no longer defines told_past, which scripts/siblings.py (kept) imports from it.What changed
update-shape.py,_refuse_kept_copies_missing_names(#536, #604):.pycopy is asked against its module as this run leaves it, whoever kept it. The reading is_copies_as_left,_written_copies_lackingand_names_as_left. The module may be:--accept-local: as before, the line says(kept);unchangedrow whose pin records the project's own bytes, which is a copy an earlier run kept that the upstream has not changed since. The line says(kept by an earlier run);upstream-changedrow or an--addfile),already-at-target, orunchangedwith the target's bytes. The line says(the upstream's). No shipped copy meets this today: the reader rows oftests/test_update_shape_kept_helper.pyfind every name each shipped importer takes defined beside it. So it refuses only a target whose own copies disagree.apply --accept-local: a kept.pycopy that imports a name the run's newrepo_shape.pydropped passes, andmake bootstrapthen dies on an ImportError #604 asked only a copy this run's--accept-localkeeps against a module this run writes (_kept_copies_importing_dropped). The rows_copies_as_leftreads askept by an earlier runare now passed beside the kept ones, under a new side,WRITTEN_UNDER_EARLIER.scripts/bootstrap.py (written) no longer defines told_past, which scripts/siblings.py (kept by an earlier run) imports from it.KEPT_IMPORTERSmaps each(written)side to its importer's word, so a(kept)importer's line is unchanged._names_droppedstill limits the refusal to names this run takes away: a name the module's bytes on disk define and its written bytes do not. A copy an earlier run kept whose names all stay, or that takes a name the module lacked before too, still applies.unchangedcopy holding the upstream's own bytes is still not asked as an importer: that pair is two of the target's own copies.if not kept: returnis gone. A run with no--accept-localis asked too. The guard still stands afterupdate-conflict,update-local-driftandupdate-rewrite-symlink, and beforeconfirmand any write.--accept-localmakes.(kept)or(written)-under-(kept)refusal prints byte for byte what main prints. In fix round 1, with the head53791e45:cmpof the masked one-step logs gives 1312 bytes for the family root's siblings.py: with a token in use, the clone and the fetch that carry it run in bootstrap.py'suntraced()environment, somake siblingsandfamily.py siblingswrite the token to no trace the caller asks git for (#1055) #1075's(kept)refusal and 1331 bytes for thetold_past(written)refusal, both identical to main's tool. A update-shape.pyapply --accept-local: a kept.pycopy that imports a name the run's newrepo_shape.pydropped passes, andmake bootstrapthen dies on an ImportError #604 row now pins that opening (SHOULD 2).(written)line under a copy an earlier run kept:a copy imports a name from a copy beside it whose bytes, as this run leaves them, do not define it, and this run writes one of the two.checkreads itunchanged, then gives two exits. As a module that lacks a name: merge the upstream's version by hand so it defines every name its line names. As an importer of a name the written module drops (fix round 1): change it so it no longer takes any name its(written) no longer definesline names. Then commit, and run again with--accept-local on each copy an earlier run kept that you changed, whose row then reads locally-modified. Or write the upstream's bytes over it (the remedy's owngit showline names that copy), commit, and run again. All four exits are tested end to end, and each checks the statecheckthen reads:locally-modifiedafter the first,already-at-targetafter the second.run this again with an --at whose copies agree.KEPT_LEAD, the code and every(kept)line are as main has them.tests/test_update_shape_kept_helper.py:earlier_standard, models the family root's bootstrap.py and siblings.py: under GNU make 4.4's named-pipe jobserver, a member's make that says it is 4.4 or later is handed the holder'sMAKEFLAGSas it is, so it runs its jobs together again; every other make still gets no jobserver and no-j(#987) #1023 in two halves in the suite's own clone, from A's bytes, with no historical commit:before-1023: the holder'sbootstrap.pyspellsmember_make_environmentby the family root's bootstrap.py and siblings.py: a member's make inherits no jobserver from the holder'smake -jN, somake -j2 bootstrap,validate,parkandresumeno longer warnjobserver unavailableof a rule the person does not own (#888) #950'smember_make_env, and itssiblings.pydefines its own;bootstrap-1023: A'sbootstrap.pyback;siblings-no-name: the control;kept-no-nametodropping_standard, a branch offkept-basethat changesvalidate-family.pyand adds or takes away no name. A holder cut atkept-basekeeps its ownbootstrap.py, which importsDROPPED_NAMEfromrepo_shape.py, through an earlier apply there (kept_by_an_earlier_apply, shared withkept_earlier);droppedis the next apply's target. This is rv1133'stold_pastscenario in the file's own fixtures.refusal()gainsleft=andat_target=, andassert_stops_on_the_importgainsscript=.assert_the_kept_copy_starts_as_beforeandassert_the_exit_appliesare shared by the--accept-localrows and the earlier-kept rows.andoror(205 asserts, checked on the syntax tree).untraced()environment, somake siblingsandfamily.py siblingswrite the token to no trace the caller asks git for (#1055) #1075's docstring line (six names) is kept as main has it.Fix round 1, after rv1133's review
git merge origin/main(038dafba) was clean: merge commit7aee5efa, tree27a2785e, the same asgit merge-tree --write-tree aa7ddf59 038dafba. The fix is one new commit on it,53791e45. Nothing was amended, rebased or forced.test_an_earlier_kept_copy_is_asked_when_the_next_apply_drops_its_name: end to end,checkreads the keptbootstrap.pyunchangedandrepo_shape.pyupstream-changed; the plain apply todroppedis refused with nothing written, one detail line, the general opening, and that copy's exits, and none of the other kinds' exits. Its premise is measured: the keptbootstrap.pybesidedropped'srepo_shape.pystops on the import.test_an_earlier_kept_copy_importing_what_this_run_keeps_applies(2 controls): a namerepo_shape.pylacked before too, and one the writtenrepo_shape.pystill defines. Both apply with no--accept-local, and the kept copy starts after the run exactly where it started before.test_either_exit_for_an_earlier_kept_importer_applies(2): each exit the remedy gives that copy, with the statecheckreads after it; the apply then writesrepo_shape.py, andbootstrap.pystarts.test_an_importer_an_earlier_run_kept_is_asked_against_a_written_module(2, in process): the line, the opening, the exits, and theagainwording alone and beside a copy this run keeps.test_an_importer_holding_the_targets_bytes_is_not_askedreplacestest_an_importer_this_run_leaves_alone_is_not_asked, whose second param pinned the limit.limit-h/): the second apply exits 2 withscripts/bootstrap.py (written) no longer defines told_past, which scripts/siblings.py (kept by an earlier run) imports from it, the holder is unchanged, andsiblings.py --helpexits 0. The one-step, first-apply andchecklogs match main's tool once paths and shas are masked. the family root's siblings.py: with a token in use, the clone and the fetch that carry it run in bootstrap.py'suntraced()environment, somake siblingsandfamily.py siblingswrite the token to no trace the caller asks git for (#1055) #1075's probe (probe-h1075/) is refused at its second apply too, with(kept by an earlier run) lacks untraced.test_keeping_a_copy_that_imports_a_name_the_run_drops_is_refused, a update-shape.pyapply --accept-local: a kept.pycopy that imports a name the run's newrepo_shape.pydropped passes, andmake bootstrapthen dies on an ImportError #604 end-to-end(written)row, now assertsstartswith(KEPT_LEAD). rv1133's R2 is killed (below).test_either_exit_the_remedy_names_appliesand the new one) check thatcheckreadslocally-modifiedafter the change exit andalready-at-targetafter the drop exit. The first exit's id now says the edit was changed by hand, as the remedy'schangeddoes.Rows that fail on main
The new test file (185 rows) was run against main's
update-shape.py(038dafba, sha256ca47f90e, in a scratch clone): 17 failed, 168 passed (base-main.log). Againstaa7ddf59's: 13 failed, 172 passed (base-aa7.log). With the change: 185 passed.aa7ddf59test_an_earlier_kept_copy_is_asked_when_the_next_apply_writes_its_importerassert 0 == 2: the second apply copiessiblings.pytest_with_branch_an_earlier_kept_copy_is_refused_before_any_branchassert 0 == 2test_either_exit_the_remedy_names_applies(2 params)assert 0 == 2: the apply the remedy follows is not refusedtest_a_written_copy_is_asked_against_its_module_as_this_run_leaves_it(4 params)test_each_kind_of_module_is_given_its_own_exit(2 params)merged, notchangedtest_every_kind_of_line_in_one_run_is_one_refusal(2 params)(kept)linetest_an_earlier_kept_copy_is_asked_when_the_next_apply_drops_its_nameassert 0 == 2: the plain apply copiesrepo_shape.pytest_either_exit_for_an_earlier_kept_importer_applies(2 params)assert 0 == 2: the apply the remedy follows is not refusedtest_an_importer_an_earlier_run_kept_is_asked_against_a_written_module(2 params)(kept)lineThese pass on main and after: the one-step row (
test_the_one_step_run_is_refused_as_it_was, which pins the old opening), the #604 row that now pinsKEPT_LEAD, the controls (test_an_earlier_kept_copy_that_defines_every_name_applies, the two new earlier-kept importer controls, the four module-defines-every-name controls), andtest_an_importer_holding_the_targets_bytes_is_not_asked.Mutants
Each was applied alone in a scratch clone, run on the test file, and restored by checkout with its sha256 checked.
Fix round 1, at
53791e45(185 rows):_kept_copies_importing_dropped({}), asaa7ddf59had it(kept)in its line(written)-under-(kept)sideKEPT_LEADopening kept for a(written)line under a copy an earlier run keptunchangedwhy and the--accept-local on each copy an earlier run keptexit given to an earlier-kept module onlygit show(written)-under-(kept)line onlyunchangedrow asked as an importer, the target's own bytes tootest_an_importer_holding_the_targets_bytes_is_not_askeda copy this run writes or keeps imports ...)(written)-only refusal given the general opening (sides <= {KEPT_MODULE})test_keeping_a_copy_that_imports_a_name_the_run_drops_is_refused(SHOULD 2); it survived ataa7ddf59keptset restored_write_copiesNo survivor. Every mutant was restored by checkout with its sha256 checked (
5b386f8b), and each clone's status was clean at the end (mutants1.out,mutants2.out,mut-*.log).The first round, at
7b447602(179 rows):keptset restored (left = dict.fromkeys(kept, KEPT_MODULE))unchangedrows asked as importers (against the modules this run writes) but the written importers asked against the oldkeptset only_write_copies(refused after a write)copied(kept)--accept-local on each copy an earlier run keptexit droppedunchangedsentence droppedRuns
All runs went through lane 5's
run9.sh, with the stubgh,GIT_SSH_COMMAND=false,GIT_CONFIG_GLOBAL=/dev/nullandEMAILexported. No more than two ran at once. The full suite was not run.Fix round 1, at
53791e45(eachSTARTline records the head anddirty=0; logs inv120/f1133/):tests/test_update_shape_kept_helper.pyown243.logown247.logtests/test_update_shape.py,tests/test_update_shape_messages.py,tests/test_update_shape_paste_lines.pyrunC.logtests/test_update_shape_apply_lines_and_headers.py,tests/test_update_shape_merged_away.py,tests/test_update_shape_rewrite_symlink.py,tests/test_update_shape_landing.pyrunD.logtests/test_update_shape_add_not_a_directory.pyrunF.logtests/test_path_guard_hygiene.pyand the trio (tests/test_repo_hygiene.py,tests/test_agents_md_codes.py,tests/test_cli_reference.py)runE.logNo failure and no skip in any run at this head. The two runs of the new file went through
run9.shon 2026-10-22 at 13:31Z, the next three on 2026-10-22 from 15:16Z to 15:21Z, andrunF.logat 15:23Z; eachSTARTline readshead=53791e45...anddirty=0. Of the 19tests/test_update_shape*.pyfiles, these 9 ran at this head. The other 10 did not run at this head: all 10 ran at the first round's head7b447602in shard A or B (below), over the first round'supdate-shape.py, which this round's commit changed. The full suite was not run.The first round, at
7b447602(treee0e2d19e, the same tree asaa7ddf59): the new file 179 passed on git 2.43.0 and on git 2.47.1; shard A (29 files) 2031 passed, 5 skipped; shard B (29 files) 1814 passed, 5 skipped; the trio 170 passed. No failure in any run.Merges
Read at
53791e45, after a re-fetch of main (origin/mainis still038dafba, the second parent of the branch's merge commit7aee5efa):git merge-tree --write-tree origin/main HEADexits 0 and writes treebfa8c1d4, which isHEAD's own tree: the branch holds038dafbaas an ancestor, so the merge adds nothing.7aee5efa(parentsaa7ddf59and038dafba) has tree27a2785e, the same tree asgit merge-tree --write-tree aa7ddf59 038dafba, which exits 0. It was clean.gh pr list --state open --limit 100 --json number,headRefName,headRefOid,files. None but this one touchesupdate-shape.pyortests/test_update_shape_kept_helper.py, so there is no other head to merge against. The list isopen-prs-d1.jsoninv120/f1133/.git diff --name-only origin/main HEADnames those two files and no other.NIT round, after rc1133's re-check
rc1133 re-checked
53791e45(#1133 issuecomment-6347273216): READY, with three NITs. This round takes all three, in one new commit,a7bfee1b, on53791e45. It was pushed as a fast-forward of the branch; nothing was amended, rebased or forced. The commit changestests/test_update_shape_kept_helper.pyalone (+7, -4).update-shape.pyis53791e45's, sha2565b386f8b, so the fix and every measurement in the sections above are unchanged._missing_name_refusalopens withKEPT_LEADwhere every line is one this run's--accept-localmakes. A run with a(kept) lacksline and a(written) no longer definesline is such a run, buttest_both_directions_in_one_run_are_one_refusalpinned its lines, its clause and its remedy, not its opening. rc1133's mutant O1 (lead = (KEPT_LEAD if len(sides) == 1 and sides <= {KEPT_MODULE, WRITTEN_MODULE} else LEFT_LEAD)) therefore survived the 185 rows. The row now assertsresult.stderr.startswith(KEPT_LEAD), result.stderr, right afterassert_refused_writing_nothing, and its docstring says why the opening is that one.id:, a date or arepository:ending in a line feed is refused by name, and a pin'ssource_repository:that is no path orowner/reponame is named as the pin's (#1139) #1166. Its row is in "Merges ata7bfee1b" below, and it replaces the Merges section's "Open pull requests" line.Mutant O1, before and after
Applied alone in this worktree and restored by checkout, sha256
5b386f8bchecked, status clean afterwards. The mutant is rc1133's (mutate_rc.py, copied unchanged).53791e45's, without the new asserto1-before-row.log). rc1133 measured the whole file: 185 passeda7bfee1b's, with the new asserto1-after-file.log). The failure istest_both_directions_in_one_run_are_one_refusalat the new assert: the opening reada copy imports a name from a copy beside it whose bytes, as this run leaves them, do not define it, ...No other mutant was needed: the round adds no behaviour and no new row, and the earlier rounds' 14 + 10 mutants stand at an unchanged
update-shape.py.Rows that fail on main
None, and that is measured, not assumed. The new assert pins an opening the head keeps exactly as main prints it, so it cannot fail on main. rc1133 had that only by reading, so it is measured here: the row, new assert included, run with main's
update-shape.py(origin/main961640c6, sha256ca47f90e) swapped into the worktree, gives 1 passed (main-row.log); the file was restored to5b386f8bafterwards. The assert guards that opening against a later regression like O1, as SHOULD 2's assert guards the one-step opening against R2.Runs at
a7bfee1bAll through lane 5's
run9.sh, eachSTARTline athead=a7bfee1b...anddirty=0, with the stubghandEMAILexported; two ran at once, and the full suite was not run. No failure and no skip. Logs are in lane 5's scratchv124/n1133/.tests/test_update_shape_kept_helper.pyandtests/test_path_guard_hygiene.pyown243.logtests/test_update_shape_kept_helper.pyown247.logThe commit changes no function, no path guard and no refusal text, so no other test file names anything it changed. The test file still has no assert that joins conditions with
andoror(206 asserts, checked on the syntax tree), no non-ASCII byte, and its widest line is 79 characters.Merges at
a7bfee1bRead after a re-fetch of main:
origin/mainis961640c6(#1155, which addsdocs/release-notes/v1.2.0.mdand nothing else). It touches neither of this PR's files.git merge-tree --write-tree origin/main HEADexits 0 and writes tree98765a8b. The branch does not hold961640c6, and the predicted merge differs fromHEADbydocs/release-notes/v1.2.0.mdalone.id:, a date or arepository:ending in a line feed is refused by name, and a pin'ssource_repository:that is no path orowner/reponame is named as the pin's (#1139) #1166 (fix/1139-anchored-patterns, head2593952c95, opened 2026-10-22T17:31:57Z) touchesupdate-shape.py, at lines well apart from_missing_name_refusal(rc1133's reading), and does not touch the test file.git merge-tree --write-tree a7bfee1b 2593952c9519c4b96de295657635fc711f75c695exits 0 and writes tree67858a0b. Against53791e45the same command writes37498506, rc1133's tree, which is reproduced. rc1133's tests on the predicted merge of53791e45and validate-manifest.py, validate-family.py, family.py and update-shape.py: anid:, a date or arepository:ending in a line feed is refused by name, and a pin'ssource_repository:that is no path orowner/reponame is named as the pin's (#1139) #1166 (c5a98365: this file,test_value_pattern_line_break.py,test_update_shape_unreadable.py,test_path_guard_hygiene.py) were 311 passed; they were not re-run ata7bfee1b, which changes one assert and a docstring in a test file validate-manifest.py, validate-family.py, family.py and update-shape.py: anid:, a date or arepository:ending in a line feed is refused by name, and a pin'ssource_repository:that is no path orowner/reponame is named as the pin's (#1139) #1166 does not touch.gh pr list --state open --limit 100 --json number,headRefName,headRefOid,files. Besides this one, only validate-manifest.py, validate-family.py, family.py and update-shape.py: anid:, a date or arepository:ending in a line feed is refused by name, and a pin'ssource_repository:that is no path orowner/reponame is named as the pin's (#1139) #1166 touchesupdate-shape.py, and none but this one touchestests/test_update_shape_kept_helper.py, so no other head needs a merge-tree. The list isopen-prs.json.Closes #1082
Claim: #1082 (comment)
Lane: openreposhape-5 (openRepoShape-5)
🤖 Generated with Claude Code
Summary by Sourcery
Extend apply-time import validation to detect and explain missing-name disagreements involving copies retained by earlier runs, preventing broken updates before any changes are written.
Bug Fixes:
update-shape.py applyfrom writing changes that would leave copies kept by earlier applies with missing imported names.Enhancements:
--accept-local.Tests: