Repository navigation
validate-family.py: a member row's id: that is no project id is member-id-malformed in every mode, and so is a mounted member's own project.yaml id:, with the bump that writes the row from that file (#1204) - #1273
Conversation
…git dies on read the `.gitmodules` git reads, the index's where the work tree's is gone, and every section's lines end `git add -- .gitmodules`, so a plain `git commit` after them takes the section out of HEAD (#1077) RV1069's review of #1069 (#1036) found four follow-ups, each measured on main before this change. NIT 1. `gitmodules-path-twice` read the work tree's file alone. A second section for members/IRRS staged in the index's file, the work tree's deleted, was `family ok` in every mode, and a clean clone of the commit after it failed its update and its gate. It reads the sections as git reads them now (`_sections_as_git_reads`), as `gitmodules-path-unnamed` does since #1036, and where the work tree's file is gone it names the line that puts back the file git reads first, recorded so no later finding puts it back over the hand edit. NIT 2. The value check read the work tree's file and HEAD's, never the index's. An `update = !true` staged in the index's file alone, in a named member's section, was `family ok` while git's own `submodule status` exited 128. It reads the file git reads (`_listed_as_git_reads`) and names the index's where that is the one read. Its lines put that file back, not HEAD's, and end "then stage", or "then stage and commit" where HEAD's gives the value too. Where HEAD's alone gives it, the text says to commit the index's file, with no line, so a fix staged there is kept, and `member-gitmodules-absent` then puts the file back itself (`_check_gitlinks_registered` asks `Report.put_back` alone). Git is asked in the order it was asked before, the work tree's file, then HEAD's, then the one read in its place, so `git-not-runnable` names what it named. NIT 3. Where HEAD's file held the section, `gitmodules-path-unnamed`'s lines ended "then commit .gitmodules" and staged nothing, so a plain `git commit` after them left the section in HEAD and a clone's gate failed. Every branch, in members/, outside it and in another case, prints `git add -- .gitmodules` after the keys and says "then stage and commit" there; path-twice ends "then stage and commit" too. NIT 4. A row now covers the case-variant branch where HEAD's file holds no section of the name (RV1069's probe `test_r2_stray_not_in_head`). tests/test_validate_family_index_gitmodules.py holds the new rows: each types only the printed lines, and what the text says in words, then a plain `git commit`. The older rows that pinned the old text, HEAD's put-back and #1036's control are updated to the new behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…its own, so #1111's hygiene test passes, path-twice says "back first from" the file it puts back, and two rows pin the value's write-back and path-twice's recorded put-back (#1077) Fix round 1 on #1124, from RV1124's review at 672845c. S1. `_listed_as_git_reads` opened with a bare `os.path.lexists(root / GITMODULES)`, which #1111's `test_path_guard_hygiene.py` fails by file and line. Its one caller, `_check_gitmodules_values`, asks it only where the work tree's file is gone, and `_gitmodules_put_back`, which the function calls next, asks the same `lexists` (it is on the test's allowlist), so the guard is dropped. N3. Two behaviours had no row; mutants A and B survived all 439 rows of the PR's nine files. A row for each, from RV1124's probes P10 and P7: - the value's write-back reads the file git reads, not HEAD's (A); - path-twice records its put-back, so `gitmodules-path-unnamed` after it prints none and a plain commit keeps neither `second` nor `members/Old` in HEAD (B), where the work tree's file is deleted or its deletion is staged. N4. Path-twice said "back from HEAD's, as the index holds none first": "first" sat on the wrong phrase. It says "back first from {whose}". N5. `_gitlink_out_first`'s docstring said its text ends "then commit .gitmodules"; every branch stages now, and it says which words each branch ends with. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s `.gitmodules` beside the work tree's and its stage remedies end `git add -- .gitmodules`, an index file git cannot parse is refused, and an unmerged one is read as none (#1150) RV1124's review of #1124 (pull/1124#issuecomment-6321372469) found three things outside #1124's fix round, each older than #1124 and measured again on main 961640c and on #1124's head 8dfa32b before this change: - N1: where the work tree's `.gitmodules` is on disk, the check never read the index's, which a plain `git commit` records. A value in both was taken out of the work tree's by a line that staged nothing, so the commit after it recorded the value in HEAD; a value in the index's alone was `family ok`. The check reads the index's too now, names the latter ("in the index's file alone"), and every remedy that says to stage the file ends with the line that does. - N2: an index's file with a line git cannot parse was read as nothing (`git config --blob` exits 128 there), so a value or a section before the line was never named. It is `gitmodules-unreadable` now, as HEAD's is. - N6: while a merge leaves `.gitmodules` unmerged in the index, git's submodule commands read no file, and the checks read the work tree's or HEAD's. They read none now; a value HEAD's gives says to resolve the merge without the key, then stage and commit. One finding changes: a value only the work tree's file gives, on disk, while unmerged, HEAD's clean, is no finding until the merge is resolved and staged, as git reads no file until then. Stacked on #1124 at 8dfa32b: every item also holds on main, but the code each fix changes (`_blob_listed`, `READ_HERE`, `_listed_as_git_reads`, the stage line) is #1124's. Closes #1150 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ules` and the work tree's give, with no index's file, is pinned to end with the line that stages it, as HEAD's value, not the index's, is what adds that line (#1150) Mutant M3 of 442615e (`if committed or staged:` cut to `if staged:` in `_value_taken_out`) survived all 170 rows of the three changed test files: in every row whose lines take out a value HEAD's file gives, the index's file gives it too, or the work tree holds no `.gitmodules`, so `staged` was true there anyway. The new row has the work tree's file and HEAD's give `update = !true`, and `git rm --cached` has taken `.gitmodules` out of the index. Under M3 the finding ends at the `--unset-all` line, and the plain `git commit` after it records `.gitmodules` gone from HEAD, and the gate then finds `member-gitmodules-absent`, exit 1. The row fails under M3, on #1124's head 8dfa32b and on main 961640c, and passes on 442615e. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e for: templates/family-root/scripts/validate-family.py tests/test_family_gitmodules_values.py tests/test_validate_family_index_gitmodules.py Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1208 touches validate-family.py (a docstring) and tests/test_validate_family_index_gitmodules.py (a new row); the text merges cleanly. The new row's re-pin to this branch's stage line follows in its own commit (rv1227's M1). Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ue HEAD gives where the deletion of `.gitmodules` is staged expects the stage line last and types no `git add` by hand, since this branch's lines end with it rv1227's M1 on #1227. Main's #1208 added the row while its finding ended at the `--unset-all` line; this branch's `_value_taken_out` ends every line set that says to stage the file with `git add -- .gitmodules`. Measured on the merge (git 2.43.0): the three printed lines typed in order, then a plain `git commit`, leave HEAD without the value, the index and the work tree equal to it and `git status` clean, and the gate passes; typed without the stage line, the commit exits 1 (`no changes added to commit`) and HEAD keeps the value. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and the init stop ask nothing, a value the work tree alone gives after a staging line is staged again, and another index value of the key is named, not "gives it too" rv1227's review of #1227 (issuecomment-6374575208), its M2, S1, N1, N2, N3, N4 and N5, each measured on git 2.43.0 on the branch merged with main e04826d (d95308e) before this change: - M2. With `.gitmodules` at conflict stages, git reads no file: `git submodule status` and `update --init` exit 128 at every gitlink, and a plain clone of HEAD checks tools/x out. `_sections_as_git_reads`' none made `gitlink-unnamed` say no section gave tools/x and a plain clone stopped there, and `member-uninitialized` say to write back a section the file gave. `_check_gitlinks_elsewhere` and `_init_stop_said` ask nothing while unmerged now. The hand mount's reads keep reading none, as git's own `git rm` does there: its plain `git rm` of a clone dies (`could not lookup name for submodule`, exit 128), and `--cached`, the line it prints, exits 0. - S1. `_value_taken_out` takes `again`: after a finding whose lines end `git add -- .gitmodules`, a value the work tree's file alone gives ends with that line too, and says why (`STAGED_ABOVE`). - N4. Where the index's file gives the key another value, the finding names it (`INDEX_GIVES`, `_values_said`) in place of "gives it too". - N1, N5. `_blob_paths` and `_blob_sections` say they refuse an index's file git cannot parse; `GIVEN_WHERE`'s comment says `(False, False)`. tests/test_validate_family_index_value_unmerged.py: rows for M2 (e2d, two ids; e2e), S1 (e4o1, e4o2), N4 (e4c), N2 (e1b), N3 (a real modify/delete conflict, and the sections row's on-disk id). S2, the sparse checkout, is a Limit of #1227 and a follow-up issue. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e S1 fix carries to every later finding is pinned for a third value and for a committed value the index lacks, and N4's row also for an index file giving the value and another rv1227c's independent review of #1227 (issuecomment-6722728204), its SHOULD S1, test-only. Two mutants of the S1 code survived all 181 rows of the PR's three files, and probes showed each puts a value git dies on into HEAD: K6 (`staging = committed or staged`, the stage line not carried past the next finding) and K8 (`staging = staging or staged`, a committed value's stage line not carried). The S1 row pinned only the finding right after a value that is both staged and committed. validate-family.py does not change. - test_every_later_value_the_work_tree_alone_gives_stages_again (new, rv1227c's p2a): `update = !true` staged for IRRS, then `update = !false` and `fetchRecurseSubmodules = sometimes` in IRSS's section, the work tree's file alone. The second and third findings each say `STAGED_ABOVE` and end with the stage line; typed in order, then a plain `git commit`, HEAD is as it was. K6 fails it. - test_a_committed_value_the_index_lacks_stages_the_later_ones_again (new, rv1227c's p8): IRRS `!true` committed, the index's file staged without it, the work tree's giving IRRS `!true` and IRSS `!false`. IRRS's finding is committed and not staged, and its `git add` stages IRSS's value all the same, so IRSS's ends staging again. K8 fails it. - test_another_value_the_index_s_file_gives_the_key_is_named gains an id (rv1227c's p3c, its K11): the index's file giving `!true` and `!false` beside the work tree's `!true` names both. The old id is `[another-value]` now, the new one `[this-value-and-another]`. - `staged_again` builds the later finding's text for the S1 row and the new rows, which the S1 row spelled out inline. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…mber-id-malformed` in every mode, and so is a mounted member's own `project.yaml` `id:`, with the `bump` that writes the row from that file (#1204) `PROJECT_ID_RE` was asked of the family's own id alone. A row's `id:` met the mounted `project.yaml`, compared as text, and `_check_member_ids_unique`, and neither is a pattern: under `--no-members` a row id of "irrs\n", "irrs\r", "IRRS Capital" or "-x" was `family ok`, and the holder a pre-#1166 `add` wrote for a member whose own id read "irqs\n" was `family ok` in every mode. `_check_member_ids_well_formed` asks `_is_project_id(str(id))` of every row `_check_member_ids_unique` asks, from `_check_members` before the `--no-members` return. `_check_member_identity` asks the member's own id as `family.py add` asks it, `str(id or "")`, and says no `member-id-mismatch` where either id is malformed. Both remedies are a `bump`: to the commit the row pins, or to the commit that mends the member's own file. AGENTS.md names the code; `ID_READERS` counts the new reader. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se own `id:` reads as the number 0 is refused, as `family.py add` refuses it, so the gate's `str(id or "")` reading of the member's own id is held (#1204) Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… line makes the branch `family.py` suggests for `bump`, `family/bump-member` for an id no branch word spells (#1204) Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… as an earlier one, its `id:` malformed, is `member-duplicate`'s alone, as `_check_member_ids_unique` skips it (#1204) Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideImplementationValidate member-row and mounted-manifest IDs with one whole-string project-ID check, report malformed IDs without a redundant mismatch, and give
Make
DocumentationDocument that malformed member-row IDs produce the new
TestsExercise malformed member IDs, mounted manifest IDs, remediation commands, and the shared ID-pattern reader.
Cover
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-1, 2026-11-08T20:43:46Z) Independent review r1204 of #1273 at r1204: independent review of #1273 (issue #1204) at
|
| step | base ccc2c2d2 |
head d56f2084 |
merge with main 40cb8086 |
|---|---|---|---|
validate-family.py --no-members |
exit 0, family ok |
exit 1, member-id-malformed (the row's) |
exit 1, the same |
issue's remedy, family.py remove --family-root <h> --member IRRS |
exit 0; NOTE that member's object store is left behind ... <h>/.git/modules/members/IRRS and an rm -rf line; the store is still there (True) |
the same | the same |
then family.py add --family-root <h> --member InkRouter/IRRS --at <pin> |
exit 2, REFUSED member-git-dir-cached: <h>/.git/modules/members/IRRS still holds the object store of a previously removed IRRS, and git submodule add will not write over it |
the same | the same |
add without --at |
exit 2, member-git-dir-cached |
the same | the same |
| gate after that | exit 0, family ok ... 1 member(s): the member is gone and the gate passes |
the same | the same |
PR's remedy, family.py bump --family-root <h> --member IRRS --to <pin> |
typed (base prints none): exit 0, IRRS 53946f63a693 -> 53946f63a693, committed 40008eb513cc: members/IRRS, family.yaml |
as printed: exit 0, one commit, row id: 'irrs', pin and gitlink unmoved, status clean |
as printed: exit 0, committed a12158791ab0, row id: 'irrs', gitlink unmoved |
gate after bump |
exit 0 members checked; exit 0 --no-members |
the same | the same |
CI holder (fresh clone, no member checked out): printed switch + bump |
(no finding on base) | exit 2, member-uninitialized, naming git submodule update --init members/IRRS; after it, the same bump exits 0 |
the same |
Conclusions:
- The writer's account holds on all three trees. family.py: the
removethat bothmember-duplicate-idremedies name and theaddthatbump'smember-unknownnames carry--family-root <holder>, quoted for the host, so each runs as printed (#1005) #1117 changes no part of it. - The issue's two lines do not run as printed. Following them as far as
add's refusal leaves a holder that has dropped the member and still passes the gate. - Convention:
_bump_tospells the line exactly as_lockstepdoes:{PYTHON} scripts/family.py bump --family-root {shlex.quote(str(root))} --member {shlex.quote(project)} --to .... Both remedies end withBRANCH_LEFTin the same string as their_switch_line, andtests/test_family_switch_branch_exists.pypasses on the merge (call 3).- The switch word for a malformed row id is
member, matchingfamily.py's_suggested_branch. For the member's own finding it is the row id or the project (family/bump-irssine4).
- The switch word for a malformed row id is
- README "Windows, natively" sentence: still true.
validate-family.pycalls no host quoter (shlex.quoteandshlex.joinonly).tests/test_readme_windows_quoting_sentence.pypasses on the merge with main (call 3).
Values that are not strings (brief item 3)
I parsed each spelling with the repository's own parse_yaml and asked the head's _check_member_ids_well_formed (tools/values.py):
| spelled | read as | row check | add's str(v or "") |
|---|---|---|---|
2026 |
int 2026 | ok | ok |
0 |
int 0 | ok ("0") | refused ("") |
007 |
int 7 | ok ("7") | ok |
true / false |
bool | refused (True / False) |
refused |
1.5 |
float | refused | refused |
-3 |
int | refused | refused |
[irrs] / {irrs: 1} |
list / dict | refused | refused |
"" |
str '' | refused | refused |
null / ~ / no id: |
None | absent (left to member-id-absent, members checked) |
refused |
yes, on, 0x1f, 1e3, 2026-01-01 |
str (this parser keeps them as text) | ok | ok |
.inf, 1_000, +3 |
str | refused | refused |
- The row reading,
str(), is the right one. It matches the text the identity check always compared, and it matchesfamily.py's_member_id, which rewrites a row asstr(value)and whose docstring saysid: 2026passes. - The mounted path reads exactly as
adddoes. - The two readings part only at a bare
0. That case is correct at head, but no row holds it (NIT 3). nulland noid:stay withmember-id-absent: members checked, and silent under--no-members. Rows[null]and[no-id]hold this.
The pinned copy (brief item 4)
- The import list is unchanged in the diff (
git diff ccc2c2d2 d56f2084touches nofrom repo_shape importline).COMMIT_RE,PROJECT_ID_RE,PYTHONandshlexwere already imported, so an older holder'srepo_shape.pyis enough. - I did not run
tests/test_pinned_copy_text.py; the brief excludes the pin call. ID_READERSnow counts 2fullmatchreaders invalidate-family.py: the family id and_is_project_id.- Measured: every merged tree (main, bootstrap.py, validate-family.py and siblings.py: a root or holder whose
.gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #1172, validate-family.py: a member whose.gitfile this user cannot open, or whose git directory (its own.git, or the one its gitfile names) this user cannot search, is refusedmember-unreadablenaming that part, and no longermember-commit-unresolvable, which said the pinned commit was missing and prescribed a fetch (#1221) #1246) has 2PROJECT_ID_RE.fullmatchcalls and 0.matchcalls invalidate-family.py. tests/test_value_pattern_line_break.pypasses at head (call 1a) and on the merge with main (call 3).
- Measured: every merged tree (main, bootstrap.py, validate-family.py and siblings.py: a root or holder whose
The rows (brief item 5)
New file, base validator laid into the scratch clone at d56f2084, then written back from HEAD's blob:
laid ccc2c2d2...:templates/family-root/scripts/validate-family.py work tree 4683c3cf...- base:
27 failed, 14 passed in 32.74s laid HEAD:... work tree c9a37c41... HEAD c9a37c41... EQUAL-TO-HEAD- head:
41 passed in 37.40s
| row | base | head |
|---|---|---|
test_a_row_id_the_pattern_refuses_is_found_under_no_members[line-feed, carriage-return, space, leading-dash] |
4 failed | 4 passed |
test_with_members_checked_it_is_the_one_finding_not_a_mismatch[same four] |
4 failed | 4 passed |
test_a_row_id_whose_text_is_no_id_is_found[bool, float, negative, list, mapping] |
5 failed | 5 passed |
test_the_holder_add_wrote_before_1166_is_refused_in_every_mode |
failed | passed |
test_a_mounted_member_whose_own_id_is_malformed_is_found[equal-to-its-row, row-well-formed] |
2 failed | 2 passed |
test_the_members_own_id_is_asked_as_add_asks_it |
failed | passed |
test_the_rows_remedy_runs_as_printed_from_the_tracking_branch |
failed | passed |
test_the_members_remedy_pins_the_mended_commit_as_printed |
failed | passed |
test_add_refuses_exactly_the_ids_the_holders_own_validator_finds[8 malformed] |
8 failed | 8 passed |
CONTROLS test_add_refuses_...[zero, irrs, hyphen, digit-first] |
4 passed | 4 passed |
CONTROLS test_a_well_formed_row_id_is_family_ok_under_no_members[zero, irrs] |
2 passed | 2 passed |
CONTROLS test_the_fixture_is_family_ok_in_every_mode[members, no-members, pins] |
3 passed | 3 passed |
CONTROL test_a_well_formed_row_id_that_is_not_the_members_is_still_a_mismatch |
passed | passed |
CONTROL test_a_row_id_that_reads_as_a_number_is_its_digits |
passed | passed |
CONTROL test_a_row_spelled_as_an_earlier_one_is_member_duplicates_alone |
passed | passed |
CONTROLS test_a_row_with_no_id_is_still_member_id_absent_alone[null, no-id] |
2 passed | 2 passed |
| total | 27 failed, 14 passed | 41 passed |
Every base failure is the defect:
assert 0 == 1, with thefamily okoutput (14);(True, False) == (True, True), whereaddrefuses and the gate finds nothing (8);member-id-mismatchwhere the row's finding is wanted;IndexErrorwhere the remedy row finds no finding to type.
Cases the rows miss: the <commit> fallback and a quoted holder path (NIT 2), and a bare 0 (NIT 3). All three are correct as measured, in probes e1, e3 and e2.
Mutants (brief item 6)
- All mutants ran in the scratch clone
_lane1-scratch/r1204/cloneatd56f2084, one at a time, usingtools/mutate.py. - After each one, a
finallywrote the file back from HEAD's blob. Every restore printedrestored: work tree c9a37c41009c98cf7bdfb00093fcd6179009634d HEAD c9a37c41009c98cf7bdfb00093fcd6179009634d EQUAL. - Each ran over the new file unless the table says otherwise.
Mine: 10 of 14 killed.
| id | mutant | result |
|---|---|---|
| R1 | _is_project_id: fullmatch changed to match |
KILLED, 8 failed (every line-feed row) |
| R2 | the _check_member_ids_well_formed call removed |
KILLED, 24 failed |
| R3 | the member's own id asked of the row's id | KILLED, 8 failed |
| R4 | elif source_bad: only, so a malformed row id also gets a mismatch |
KILLED, 4 failed |
| R5 | the row's text .strip()ped before it is asked |
KILLED, 10 failed |
| R6 | row check reads str(member_id or "") |
SURVIVED, 41 passed (NIT 3) |
| R7 | _bump_to leaves the holder's path unquoted (new file + test_validate_family_branch_step.py) |
SURVIVED, 64 passed (NIT 2) |
| R8 | the row's switch line words the project, not the row id | KILLED, 1 failed (..._rows_remedy_runs_as_printed_...) |
| R9 | the member's own remedy names the row's pin, not <commit> |
KILLED, 3 failed |
| R10 | the row check made under --no-members only |
KILLED, 6 failed |
| R11 | identity check's row question reads str(recorded_id or "") |
SURVIVED, 41 passed (NIT 3) |
| R12 | the COMMIT_RE fallback to <commit> removed |
SURVIVED, 41 passed (NIT 2) |
| R13 | the row check's non-mapping guard removed (new file + test_validate_family_duplicates.py + test_family_members_not_a_list.py) |
KILLED, 7 failed, all in the two older files: a traceback on a non-mapping row |
| R15 | the member's own switch word is the project, not the row id | KILLED, 1 failed (..._members_remedy_pins_the_mended_commit_...) |
The writer's M10 (a row spelled as an earlier one is asked too) checks out:
- at
d56f2084: KILLED,1 failed, 40 passed in 38.16s, bytest_a_row_spelled_as_an_earlier_one_is_member_duplicates_alone; - with
9fc09305's test file laid in: SURVIVED,40 passed in 39.73s.
The test file was then written back from HEAD's blob (18af8751..., EQUAL-TO-HEAD). The validator blob is c9a37c41 at 0ab49ee2, f2dade54, 9fc09305 and d56f2084, so the writer's M1 to M9 ran on this head's code, as the body says.
Gate calls (brief item 7), every summary line
- Probe environment: stub
ghfirst onPATH,GH_TOKEN=bogus-r1204, emptyGH_CONFIG_DIR,GIT_CONFIG_GLOBAL=/dev/null,GIT_SSH_COMMAND=false,EMAIL=r1204@openreposhape.invalid,env -u LANES_LANE,TMPDIRin scratch,PYTHONDONTWRITEBYTECODE=1. - Python 3.12.3, git 2.43.0 (
/usr/bin/git). - At most 2 of my pytest calls at once, each under 9 minutes;
df -h /workspace(93-100G free) anddf -i /(41-42%) checked before each.
| call | tree | result |
|---|---|---|
1a. the new file, tests/test_family.py, tests/test_family_add_member_id.py, tests/test_value_pattern_line_break.py |
head | 198 passed in 225.61s |
1b. #1227's tests/test_family_gitmodules_values.py, tests/test_validate_family_index_gitmodules.py, tests/test_validate_family_index_value_unmerged.py |
head | 184 passed in 297.69s (1a + 1b = 382, the collected count) |
2a. test_validate_family_{branch_step, duplicates, git_not_runnable, index_gitmodules, index_value_unmerged, member_id_pattern}.py |
head | 232 passed in 354.36s |
2b. test_validate_family_{member_path_glob, remedies_typed, typed_states, unnamed_mounts, unreadable, without_git}.py |
head | 189 passed in 244.84s (2a + 2b = 421, all 12 files) |
3. tests/test_readme_windows_quoting_sentence.py, tests/test_remove_add_remedies_family_root.py, tests/test_family_bump_to_checks.py, plus the new file, tests/test_value_pattern_line_break.py, tests/test_family_switch_branch_exists.py, tests/test_family_add_member_id.py, tests/test_agents_md_codes.py |
predicted merge with main 40cb8086 (bdb5ccb4, tree d0d49d5b) |
253 passed in 230.23s |
4. tests/test_scaffold_leg_path_collisions.py, tests/test_path_guard_hygiene.py, tests/test_no_auto_maintenance.py |
head | 283 passed, 2 skipped in 42.35s; both skips tests/test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set |
5. tests/test_agents_md_codes.py, tests/test_cli_reference.py, tests/test_repo_hygiene.py |
head | 171 passed in 47.26s |
Probes, in the scratch clone only, never committed:
test_r1204_probe_remedy.py: base3 passed in 11.81s, head3 passed in 11.75s, merge3 passed in 13.29s;test_r1204_probe_edges.py(head):4 passed in 10.91s.
The 3.14-like sitecustomize shim was not needed: the change adds no is_file, exists, is_dir or stat call. _check_member_identity's _is_a was already there, and tests/test_path_guard_hygiene.py passes (call 4).
Merges (brief item 8)
git merge-tree --write-tree d56f2084 <other>:
- current
origin/main40cb8086fb48622a1b195fc9e50ee422f036386e: exit 0, clean, treed0d49d5b0b75fdbfdb2f59ae581300c41d956c0f; - lane 1's bootstrap.py, validate-family.py and siblings.py: a root or holder whose
.gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #1172 headba81fd7d29ec19ab52c537d85e6e8df6178cfd30: exit 0, clean, tree4f157f26ea4a1a9520b44680b32b1d8ee7663a23; - lane 3's validate-family.py: a member whose
.gitfile this user cannot open, or whose git directory (its own.git, or the one its gitfile names) this user cannot search, is refusedmember-unreadablenaming that part, and no longermember-commit-unresolvable, which said the pinned commit was missing and prescribed a fetch (#1221) #1246 head0deac3a6581b308e5761c420456bac51f0fe5f11(fetchedrefs/pull/1246/head): exit 0, clean, treeff828224be5405aead14263bf9797aa4bd6a271c.
Further checks:
- validate-family.py: a member whose
.gitfile this user cannot open, or whose git directory (its own.git, or the one its gitfile names) this user cannot search, is refusedmember-unreadablenaming that part, and no longermember-commit-unresolvable, which said the pinned commit was missing and prescribed a fetch (#1221) #1246 changes_check_member_treeand adds gitfile readers, but not_check_member_identity. - In all three merged trees,
_check_member_identity(root, member, ...)has 1 call and_check_member_ids_well_formed(root, members, report)has 1 call. refs/pull/1256/headis absent on origin (git ls-remotelists 1172, 1227, 1246 and 1273 only).- Stacking:
ccc2c2d2has parents81eaf757and2d56c190, its tree is1e564383, andgit merge-tree --write-tree 81eaf757 2d56c190gives1e564383. validate-family.py: the check for values git dies on reads the index's.gitmodulesbeside the work tree's and its stage remedies endgit add -- .gitmodules, an index file git cannot parse is refused, and an unmerged one is read as none (#1150) #1227's head on origin is still2d56c190, so the stacking statement is right and no restack is needed today.
The body (brief item 9)
- Exactly one closing form,
Closes #1204(line 5).gh pr viewgivescloses: [1204], headd56f2084, basemain, draft. - The outside-v1.2.0 ruling is named.
- Every table I re-measured agrees with the body:
- the before and after table and its two quoted findings;
- 27/14 on base, 41 at head;
- the rejected-remedy measurement, which also holds on current main;
bump's "<sha> -> <sha>" commit message (Known limits; I measuredIRRS 53946f63a693 -> 53946f63a693);- call 1 is 381 at
9fc09305and 382 at head; call 2 is 421; - the merge-trees with bootstrap.py, validate-family.py and siblings.py: a root or holder whose
.gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #1172 and validate-family.py: a member whose.gitfile this user cannot open, or whose git directory (its own.git, or the one its gitfile names) this user cannot search, is refusedmember-unreadablenaming that part, and no longermember-commit-unresolvable, which said the pinned commit was missing and prescribed a fetch (#1221) #1246; - AGENTS.md at 494 lines, with line 404 at 129 characters.
- Stale or inaccurate: the main merge figure (NIT 6), and one docstring sentence that the body does not repeat (NIT 1).
What I did not cover
- The full suite, the pin call (
tests/test_pinned_copy_text.pywithtest_shape_pin_*andtest_update_shape*),tests/test_shape_doctor.py, andtests/test_template_text_placeholders.py. The brief excluded them; the writer's counts are in the body. - git 2.47.1 (no path named in my brief; NIT 7) and CPython 3.14 (bench has 3.12.3 only).
- Windows, where
PYTHONispython. - The SonarCloud and Linux CI status of the PR.
- The predicted merge with bootstrap.py, validate-family.py and siblings.py: a root or holder whose
.gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #1172 or validate-family.py: a member whose.gitfile this user cannot open, or whose git directory (its own.git, or the one its gitfile names) this user cannot search, is refusedmember-unreadablenaming that part, and no longermember-commit-unresolvable, which said the pinned commit was missing and prescribed a fetch (#1221) #1246 was not run, only merge-tree'd.
Lane: openreposhape-1 (openRepoShape-1)
…ember_ids_well_formed` says `member-pin-commit` refuses the run first where members are checked, and rows hold a pin that is no commit as `--to <commit>`, the holder's path quoted in the printed `bump`, and a bare `id: 0` as its digits (#1204) Answers NITs 1-3 of review r1204 on #1273. No behavior of validate-family.py changes; only the docstring does. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
|
(lane openRepoShape-1, 2026-11-08T21:07:50Z) Lane 1's round on r1204's review (issuecomment-6726873431):
Lane: openreposhape-1 (openRepoShape-1) |
|
(lane openRepoShape-1, 2026-11-08T21:14:54Z) Independent delta check r1204b (r1204, resumed) of #1273 at r1204: delta review of #1273 at
|
| id | mutant | d56f2084 (round 1) |
0657e396 |
|---|---|---|---|
| R6 | the row check reads str(member_id or "") |
survived, 41 passed | KILLED, 1 failed, 43 passed in 40.76s: test_a_row_id_that_reads_as_a_number_is_its_digits[zero] (assert 1 == 0 under --no-members) |
| R7 | _bump_to leaves the holder's path unquoted |
survived, 64 passed | KILLED, 1 failed, 43 passed in 41.85s: test_the_rows_remedy_runs_as_printed_from_the_tracking_branch[space-and-apostrophe] (ValueError: No closing quotation) |
| R11 | the identity check's row question reads str(recorded_id or "") |
survived, 41 passed | KILLED, 1 failed, 43 passed in 40.58s: ..._its_digits[zero] (assert 0 == 1, members checked: the mismatch was dropped) |
| R12 | the COMMIT_RE fallback to <commit> removed |
survived, 41 passed | KILLED, 1 failed, 43 passed in 40.88s: test_a_pin_that_is_no_commit_is_named_to_commit (assert ['IRRS', '--to', 'abc'] == ['IRRS', '--to', '<commit>']) |
Every restore printed restored: work tree 4e011252497e0dc276d092707b72c34117570078 HEAD 4e011252497e0dc276d092707b72c34117570078 EQUAL. With these four, my mutants stand at 14 of 14 killed.
3. The new rows assert what they claim, and the NIT 1 sentence matches probe e1.
test_a_pin_that_is_no_commit_is_named_to_commit:- Under
--no-members, the onemember-id-malformedfinding, and thebumpargv ends['IRRS', '--to', '<commit>']. - With the members checked: exit 2, stderr starts with
REFUSED member-pin-commit: IRRS:, andmember-id-malformedis absent. - That is exactly what probe
e1measured atd56f2084(exit 2,REFUSED member-pin-commit: IRRS: pin.commit is 'abc', which is not 40 hex. ..., the row's finding not printed). - It also matches the reworded docstring at
validate-family.py:1982-1984: "...--to <commit>; where members are checked,member-pin-commitrefuses the run first, and this finding follows once the pin is mended."
- Under
test_the_rows_remedy_runs_as_printed_from_the_tracking_branch[space-and-apostrophe]:- It moves the whole
on_mainholder totmp_path / "it's a holder". The members' gitfiles andcore.worktreeare relative, so the move keeps them valid. It then runs the same assertions as[plain]. - The switch's argv equals
switch_argv(holder, "bump", "irrs\n"), thebumpis typed as printed and exits 0, the row readsid: irrswith the pin unmoved, the tree is clean, and the gate is green in both modes. - R7 shows the quoting is held.
- It moves the whole
test_a_row_id_that_reads_as_a_number_is_its_digits[2026, zero]:repr(value) == str(number)proves the row parses as an int, not a string.--no-members: exit 0 and afamily ok:last line.- Members checked: exactly
FINDING member-id-mismatch: members/IRRS: project.yaml declares id 'irrs' but family.yaml records 0 for IRRS. ..., which is word for word probee2's line atd56f2084.
- Base against head for the round's rows: the base validator
4683c3cfwas laid into the clone, then HEAD's4e011252was written back (EQUAL-TO-HEAD). Base gives3 failed, 2 passed in 12.19s:- FAILED
test_a_pin_that_is_no_commit_is_named_to_commit; - FAILED
test_the_rows_remedy_runs_as_printed_from_the_tracking_branch[plain]and[space-and-apostrophe]; - PASSED
test_a_row_id_that_reads_as_a_number_is_its_digits[2026]and[zero], the controls. - At head all five pass (call D below). This matches the body's round table.
- FAILED
4. The body.
gh pr view 1273: head0657e396, draft,closes: [1204].- The live body equals
w1204/pr-body.mdminus its title line (live starts with file body: True | equal: True). The Sourcery block is gone. - Closing forms in the live body:
['Closes #1204'], the only one. - "## Review round 1 (r1204: READY, seven NITs)" against my measurements:
- the validator
3 added, 2 removedand the test file63 added, 6 removedmatch--numstat; - the blobs
c9a37c41and4e011252match; - the mutant table (R6, R11 and R12 killed by 1 row each, R7 by
[space-and-apostrophe], 43 passed beside it) matches my runs; - the base-against-head table matches check 3.
- The gate calls R1 to R5 are the writer's. I re-ran only the call below.
- the validator
- Merge-tree section ("main
27397a4b: clean, tree44248a29"; "bootstrap.py, validate-family.py and siblings.py: a root or holder whose.gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #1172's headbbe5b414... tree61067f7c"; "validate-family.py: a member whose.gitfile this user cannot open, or whose git directory (its own.git, or the one its gitfile names) this user cannot search, is refusedmember-unreadablenaming that part, and no longermember-commit-unresolvable, which said the pinned commit was missing and prescribed a fetch (#1221) #1246's head0deac3a6... tree7fdbfe6b") matches mine exactly (check 5). - The stacking statement still holds:
ls-remotegivesrefs/pull/1227/headat2d56c190,2d56c190is an ancestor of0657e396, and there is still norefs/pull/1256/head.
5. Merges. git merge-tree --write-tree 0657e396 <other>:
- current
origin/main27397a4b591ae70e56b11639671ade090fe5adef: exit 0, clean, tree44248a29ffb02bae8b711ba1d2feabc12d3973c4; - bootstrap.py, validate-family.py and siblings.py: a root or holder whose
.gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #1172's headbbe5b414eb9427ef26248bdf53fe4711ab26c3a1: exit 0, clean, tree61067f7c49d5267a34cafcea9f025b87c3fdec78; - validate-family.py: a member whose
.gitfile this user cannot open, or whose git directory (its own.git, or the one its gitfile names) this user cannot search, is refusedmember-unreadablenaming that part, and no longermember-commit-unresolvable, which said the pinned commit was missing and prescribed a fetch (#1221) #1246's head0deac3a6581b308e5761c420456bac51f0fe5f11: exit 0, clean, tree7fdbfe6bee0ab4135292b7c798028a6403e35139.
Run (probe environment, at 0657e396)
- Probe environment: stub
ghfirst onPATH,GH_TOKEN=bogus-r1204, emptyGH_CONFIG_DIR,GIT_CONFIG_GLOBAL=/dev/null,GIT_SSH_COMMAND=false,EMAIL=r1204@openreposhape.invalid,env -u LANES_LANE,TMPDIRin scratch,PYTHONDONTWRITEBYTECODE=1. - Python 3.12.3, git 2.43.0. At most 2 of my pytest calls at once.
df -h /workspaceshowed 85-86G free anddf -i /showed 42%.
| call | result |
|---|---|
D. tests/test_validate_family_member_id_pattern.py, tests/test_validate_family_branch_step.py, tests/test_family_add_member_id.py (review checkout) |
81 passed in 84.54s |
| base rows (clone, base validator laid in) | 3 failed, 2 passed in 12.19s, as expected (check 3) |
| R6, R7, R11, R12 (clone, new file) | each 1 failed, 43 passed (check 2) |
I ran nothing wider. The validator's code is unchanged (check 1), and the test change is confined to the one new file, which call D and the mutant runs cover.
Findings
- MUST: none.
- SHOULD: none.
- NIT: none new. Round 1's NITs 4 (AGENTS.md line length and scope), 5 (no init command named under
--no-members) and 7 (git 2.47.1 not run) remain, and the body's Known limits names each one.
What I did not cover
- The writer's gate calls R1 to R5 (
test_family.py, the scans, the doc trio, the template-text scans, and the predicted merge with main27397a4b). I did not re-run them; the delta is a docstring plus rows in one test file. - git 2.47.1, CPython 3.14, Windows, and the PR's CI and SonarCloud status.
Lane: openreposhape-1 (openRepoShape-1)



Stacked on #1227. This branch is stacked on #1227 (
2d56c190), merged onto main81eaf757: its first commit,ccc2c2d2, is a real merge of #1227's head into main (clean; tree1e564383, the treegit merge-tree --write-tree 81eaf757 2d56c190predicted), made before any change of this PR's, as lane 3 asked #1204 to build after #1227. That merge is the BASE every "base" below names. #1204 lands after #1227, and as the later lander it takes the refresh for lane 3's #1246 and lane 1's #1172 (and #1256, which has norefs/pull/1256/headon origin today), all of which changetemplates/family-root/scripts/validate-family.py. This PR's own change keeps out of #1227's lines (the module docstring at 64-110, the.gitmodulesreaders and_init_stop_said) and out of the loader_run_checks, where #1172 adds_refuse_an_unreadable_root.Closes #1204
Outside v1.2.0 by Brett Heap's ruling of 2026-10-21T10:32:04Z ("leave the follow-ups out of v1.2.0").
What
validate-family.pyaskedPROJECT_ID_REof one id only, the family's own. A member row'sid:met two checks, and neither is a pattern: the mountedproject.yamlcompared with it as text (member-id-mismatch), and_check_member_ids_unique. So under--no-members, the run a private family's CI falls back to, an id that ends in a line feed, holds a space or opens with-wasfamily ok; and with the members checked, so was a row that spelled the same malformed text as its member's ownproject.yaml: the row afamily.py addolder than #1166 wrote for a member whose ownid:read"irqs\n".Measured on base
ccc2c2d2and headd56f2084(the head is now0657e396, whosevalidate-family.pyhas the same code: round 1 below changes a docstring), Python 3.12.3, git 2.43.0, in the probe environment (a stubgh,GH_TOKEN=bogus, an emptyGH_CONFIG_DIR,GIT_CONFIG_GLOBAL=/dev/null,GIT_SSH_COMMAND=false), ontests/test_family.py's offline family (InkRouter, membersIRRSandIRSS; table 3 rewrites IRRS'sid: irrsline). The holder row mountsIRQSfrom a copy of its remote whoseproject.yamlreadsid: "irqs\n", with its row written as pre-#1166addwrote it (id: "irqs\n", the pin and digest those of the mount), since #1166'saddnow refuses that member.id:/ holder--no-members--no-members"irrs\n"family okmember-id-mismatchmember-id-malformedmember-id-malformedalone"irrs\r"family okmember-id-mismatchmember-id-malformedmember-id-malformedalone"IRRS Capital"family okmember-id-mismatchmember-id-malformedmember-id-malformedalone"-x"family okmember-id-mismatchmember-id-malformedmember-id-malformedalone"0"(control)family okmember-id-mismatchfamily okmember-id-mismatch(unchanged)irrs(control, the fixture)family okfamily ok"irqs\n"both sidesfamily ok, 3 members--pinsexit 0); noteproject.yaml id irqsand a raw line feed--pinsthe same)The two findings at head, the holder's path written
<holder>:(
[...]isBRANCH_LEFT, word for word, as every remedy that names aswitch -cends.)Why
The decision. The issue's two checks, under one code of their own,
member-id-malformed, asmember-bad-idisadd's; and the remedy isbumpto the commit the row pins, not the issue'sremoveand thenadd.id:, in every mode._check_member_ids_well_formed, beside_check_member_ids_unique, asksPROJECT_ID_RE.fullmatchof every row that check asks (a mapping, with aproject:, not spelled exactly as an earlier row, which ismember-duplicate's), from_check_membersbefore the--no-membersreturn: it readsfamily.yamlalone. So every row_check_member_identityreaches has been asked, and--pinsand a full run ask it too.id:, asked exactly asaddasks it._check_member_identityasksPROJECT_ID_RE.fullmatch(str(id or ""))of the mountedproject.yaml,_read_member_project's question formember-bad-id, so with the members checked the gate refuses every memberaddwould refuse, and none it would pin (the rowtest_add_refuses_exactly_the_ids_the_holders_own_validator_findsholds the two to one answer). The finding names that file as the source, asmember-id-mismatchalways did.member-id-mismatchis not said as well: it named no command, and over a malformed member it told the person to copy a malformed id into the row._is_project_idis the onefullmatchboth checks call, so the two cannot drift into two questions.fullmatch, notmatch(validate-manifest.py and validate-family.py:PROJECT_ID_RE,DATE_REandQUALIFIED_REend in$, so anid:, a date or arepository:that ends in a line break passes as well formed (#1126 follow-up) #1139):PROJECT_ID_REends in$, which matches before a trailing line feed. No import is added, so an older holderrepo_shape.pyis enough (tests/test_pinned_copy_text.py's skew rule).str(), the text the identity check always compared. A number reads as its digits, so a row a holder written before templates/assembly-root/project.yaml and the two leg pins write eight text placeholders bare (id,elected_on,visibility,tracking_branch, the two legpaths andsubmodule_paths), so a checked value YAML reads as another type (007,2026,true,null) is misread by the project or refused by the tool; quoting them reads every one back exactly and lets numeric branch names adopt #259 keeps asid: 2026passes, asfamily.py's_member_iddocstring says it does;true,1.5,-3, a list and a mapping read as text no id is, and are refused, named as the reader read them (True,['irrs'], ...).id: nulland a row with noid:staymember-id-absent's, members checked, and nothing under--no-members, as before.Rejected:
family.py removeand thenadd, runnable as printed. Measured on the base, on a hand-edited rowid: "irrs\n"(committed, holder on its work branch):remove --member IRRSexits 0 and prints its NOTE that the member's object store is left under.git/modules/members/IRRSon purpose;add --member InkRouter/IRRS --at <the pinned commit>then exits 2,REFUSED member-git-dir-cached: <holder>/.git/modules/members/IRRS still holds the object store of a previously removed IRRS, andgit submodule addwill not write over it. The two lines as printed do not run in a holder with the member checked out,addclones the member again, and without--atit moves the pin to the member's tip.bump --to <the pinned commit>, measured on the same row: exit 0, one commit, the rowid: irrs, the gitlink unmoved,validate-family.pyexit 0.bumpreads the member's ownproject.yamlat that commit and asks its id asadddoes (member-bad-id), so it repairs the row from the source and refuses where the source itself is malformed.IRRS:for the row,members/IRQS/project.yaml:for the member's own), and AGENTS.md names one code._check_member_ids_uniquedoes.id: ""is no id; it is refused (mutant M6 below).How:
templates/family-root/scripts/validate-family.py:_is_project_id,_bump_to(thebumpline, POSIX-quoted as THE LOCKSTEP RULE's) and_check_member_ids_well_formed, beside_check_member_ids_unique; one call in_check_members;_check_member_identitytakesroot(its one caller,_check_member_checkout, has it) and asks the member's own id. The module docstring's WHAT IS CHECKED gains one bullet aftermember-row's, away from validate-family.py: the check for values git dies on reads the index's.gitmodulesbeside the work tree's and its stage remedies endgit add -- .gitmodules, an index file git cannot parse is refused, and an unmerged one is read as none (#1150) #1227's lines. Each remedy's prose ends withBRANCH_LEFTin the same string as its_switch_line, astests/test_family_switch_branch_exists.py's guard reads the source. The--toof the row's remedy is the row'spin.commitwhereCOMMIT_RE.fullmatchtakes it, and<commit>otherwise.AGENTS.md: item 4 of the family section namesmember-id-malformed, on its own line, so the file stays at its 494-line cap (tests/test_repo_hygiene.py), andtests/test_agents_md_codes.pyholds the code to the tool. The passagestests/test_family_docs_one_spelling.pypins are untouched.tests/test_value_pattern_line_break.py:ID_READERScounts 2fullmatchreaders invalidate-family.py, the family's id and_is_project_id.Rows
tests/test_validate_family_member_id_pattern.pyis new, 44 rows (41 atd56f2084; round 1 below adds three). It imports the family fixture and its helpers from the files that own them (test_family,test_family_tracking_branch'son_main,test_validate_family_branch_step'sBRANCH_LEFTandswitch_argv,test_value_pattern_line_break'sID_PATTERN,a_member_whose_id_is,findingsandrewritten).ccc2c2d20657e396test_a_row_id_the_pattern_refuses_is_found_under_no_members[line-feed],[carriage-return],[space],[leading-dash](table 3)test_with_members_checked_it_is_the_one_finding_not_a_mismatch[...], the same four, members checked and--pinstest_a_row_id_whose_text_is_no_id_is_found[bool],[float],[negative],[list],[mapping]test_the_holder_add_wrote_before_1166_is_refused_in_every_modetest_a_mounted_member_whose_own_id_is_malformed_is_found[equal-to-its-row],[row-well-formed]test_the_members_own_id_is_asked_as_add_asks_it(the member's ownid: 0)test_the_rows_remedy_runs_as_printed_from_the_tracking_branch[plain],[space-and-apostrophe](round 1: the holder's path holds a space and an apostrophe)test_a_pin_that_is_no_commit_is_named_to_commit(round 1: a pin ofabcis named--to <commit>)test_the_members_remedy_pins_the_mended_commit_as_printedtest_add_refuses_exactly_the_ids_the_holders_own_validator_finds[...], 8 malformed ids[zero],[irrs],[hyphen],[digit-first]test_a_well_formed_row_id_is_family_ok_under_no_members[zero],[irrs]test_the_fixture_is_family_ok_in_every_mode[members],[no-members],[pins]test_a_well_formed_row_id_that_is_not_the_members_is_still_a_mismatchtest_a_row_id_that_reads_as_a_number_is_its_digits[2026],[zero](a bareid: 0, round 1)test_a_row_spelled_as_an_earlier_one_is_member_duplicates_alonetest_a_row_with_no_id_is_still_member_id_absent_alone[null],[no-id]To get the base column, the base's
validate-family.py(blob4683c3cf) was laid into a scratch clone at0657e396, written fromgit cat-file blob. Every base failure is the defect: nomember-id-malformed(exit 0 where the row asks 1, ormember-id-mismatchwhere it asks the row's finding), and the three remedy rows find no finding to type. The clone's file was then written back from HEAD's blob, and its blob,4e011252, equals HEAD's. (Atd56f2084the count was 27 failed, 14 passed of 41, in 27.67 s and 31.42 s.)Mutants
10 mutants, 10 killed (and the four of r1204's 14 that survived
d56f2084, R6, R7, R11 and R12, killed in round 1 below). Each ran alone in the scratch clone over the new file, and after each the file was written back from HEAD's blob and proved equal to it (c9a37c41). M1 to M9 ran at9fc09305, whosevalidate-family.pyis this head's blob; M10 survived there and is killed atd56f2084by the control row added for it.--no-membersreturn (the issue's first)source_bad = False(the issue's second)fullmatchput back tomatchin_is_project_id(the issue's third)member-id-mismatchsaid as well where an id is malformedstr(id), notadd'sstr(id or "")..._asked_as_add_asks_it)_check_member_ids_uniqueskips it[empty])--to <commit>, not the pinned commit..._rows_remedy_runs_as_printed_...)id: nulland noid:asked as text ('None')family/remove-...for abump..._rows_remedy_runs_as_printed_...)d56f2084, 1 failed (..._member_duplicates_alone); survived at9fc09305Tests
The gate, call by call, Python 3.12.3, git 2.43.0, in the probe environment, one basetemp per call, at most 2 at once. Call 1 ran at
9fc09305; every other call atd56f2084, which adds one row to the new file. Round 1 re-ran the files it touches at0657e396; those calls are in the next section.tests/test_family.py,tests/test_family_add_member_id.py,tests/test_value_pattern_line_break.py, and #1227'stests/test_family_gitmodules_values.py,tests/test_validate_family_index_gitmodules.py,tests/test_validate_family_index_value_unmerged.pytests/test_validate_family_*.py, 12 files (the new one and #1227's two included)tests/test_pinned_copy_text.pywith everytests/test_shape_pin_*.pyandtests/test_update_shape*.py(22 files, 977 ids), shard 1 of 4 (245 ids)tests/test_scaffold_leg_path_collisions.py,tests/test_path_guard_hygiene.py,tests/test_no_auto_maintenance.pytests/test_agents_md_codes.py,tests/test_cli_reference.py,tests/test_repo_hygiene.pytests/test_shape_doctor.py, shard 1 of 4 (113 ids)tests/test_shape_doctor.py, shard 2 of 4 (113 ids)tests/test_shape_doctor.py, shard 3 of 4 (113 ids)tests/test_shape_doctor.py, shard 4 of 4 (110 ids)tests/test_template_text_placeholders.py(its007andtruemember ids pass the holder's gate)dff8fd4b(treec477f34d, in the scratch clone): the new file,tests/test_family_add_member_id.py,tests/test_value_pattern_line_break.py, main's newtests/test_remove_add_remedies_family_root.py,tests/test_agents_md_codes.py,tests/test_family_switch_branch_exists.py27397a4b: 256 passed)Call 5's 13 skips, listed with
-rsin a re-run (232 passed, 13 skipped in 131.55 s), are alltests/test_update_shape_merged_away.py: "git filter-repo is not installed". The other calls ran-q, so their skips are counted, not named; each is askipiforpytest.skipof the file's own (a platform, a privilege or a tool this bench lacks), and none is in a file this PR changes. Each id list was built with--collect-onlyand checked non-empty before its call.Review round 1 (r1204: READY, seven NITs)
r1204 reviewed
d56f2084and found it READY: no MUST, no SHOULD, 27 rows failing on base and 41 passing at head, and 10 of its 14 mutants killed. Round 1 is commit0657e396on top ofd56f2084. It answers NITs 1 to 3 and changes no behavior ofvalidate-family.py: the only difference between its blob atd56f2084(c9a37c41) and at0657e396(4e011252) is the docstring of_check_member_ids_well_formed(git diff --numstat d56f2084 0657e396: the validator 3 added, 2 removed; the new test file 63 added, 6 removed).NIT 1: the docstring said
member-pin-commitis printed "beside" the row's finding, but a refusal drops the finding. Reworded: "A pin that is no 40-hex commit is named--to <commit>; where members are checked,member-pin-commitrefuses the run first, and this finding follows once the pin is mended." The first row below also holds that claim: with the members checked, the same row is exit 2,REFUSED member-pin-commit: IRRS: ..., with nomember-id-malformedin stderr.NIT 2: no row held the remedy's
<commit>fallback or the holder's quoting. Two rows intests/test_validate_family_member_id_pattern.py:test_a_pin_that_is_no_commit_is_named_to_commit(new).--no-membersover a row withid: "irrs\n"andpin.commit: "abc": exit 1, the onemember-id-malformedfinding, and the remedy's--toargument is the literal<commit>. Kills R12.test_the_rows_remedy_runs_as_printed_from_the_tracking_branchis parametrized[plain]and[space-and-apostrophe]. In the second, the holder is moved totmp_path / "it's a holder"before the hand edit, astests/test_validate_family_branch_step.pygives_switch_linethe holderit's a holder; the switch and thebumpare split byshlex, typed as printed, and thebumpexits 0 with the row'sid:written, the pin where it was, the tree clean and the gate green in every mode. Kills R7: with the path unquoted,shlex.splitraisesNo closing quotation.NIT 3: no row read a bare
id: 0, the one value where the row'sstr()andadd'sstr(id or "")part.test_a_row_id_that_reads_as_a_number_is_its_digitsis parametrized[2026]and[zero]. Each: the row reads as that number; under--no-membersit is exit 0,family ok; with the members checked (IRRS's own id isirrs) it is exit 1 and exactly one finding,member-id-mismatch: members/IRRS: project.yaml declares id 'irrs' but family.yaml records 0 for IRRS. ...(2026for the first). Kills R6 (the row check would refuse a bare0) and R11 (the identity check would pass it with no finding).The new rows, base vs head (the clone's validator laid from base
ccc2c2d2, then from HEAD, as above):test_a_pin_that_is_no_commit_is_named_to_committest_the_rows_remedy_runs_as_printed_from_the_tracking_branch[space-and-apostrophe]test_a_row_id_that_reads_as_a_number_is_its_digits[zero]Mutants, r1204's own definitions (its
tools/mutate.py), each alone in a scratch clone at0657e396; after each, the file was written back from HEAD's blob and its blob,4e011252, printed EQUAL to HEAD's:d56f2084(r1204)0657e396str(member_id or "")..._its_digits[zero]), 43 passed_bump_toleaves the holder's path unquotedtest_validate_family_branch_step.py)..._rows_remedy_runs_as_printed_...[space-and-apostrophe]), 43 passed; withtest_validate_family_branch_step.py, 1 failed, 66 passedstr(recorded_id or "")..._its_digits[zero]), 43 passedCOMMIT_REfallback to<commit>removedtest_a_pin_that_is_no_commit_is_named_to_commit:['IRRS', '--to', 'abc'] == ['IRRS', '--to', '<commit>']), 43 passedThe gate at
0657e396, Python 3.12.3, git 2.43.0, in the probe environment, one basetemp per call, at most 2 at once:tests/test_family.py,tests/test_family_add_member_id.py,tests/test_value_pattern_line_break.py,tests/test_validate_family_branch_step.pytests/test_scaffold_leg_path_collisions.py,tests/test_path_guard_hygiene.py,tests/test_no_auto_maintenance.pytests/test_agents_md_codes.py,tests/test_cli_reference.py,tests/test_repo_hygiene.pytests/test_template_text_placeholders.py,tests/test_template_values_single_pass.py,tests/test_stdout_encoding.py,tests/test_refusal_default_remediation.py,tests/test_pin_pattern_line_break.py(the docstring is template text)27397a4b(commit8cfb52f6, tree44248a29, in the scratch clone): the new file,tests/test_family_add_member_id.py,tests/test_value_pattern_line_break.py,tests/test_remove_add_remedies_family_root.py,tests/test_agents_md_codes.py,tests/test_family_switch_branch_exists.py,tests/test_readme_windows_quoting_sentence.py,tests/test_family_bump_to_checks.pyNot re-run, since this round changes a docstring and the new test file and nothing else: #1227's three test files of call 1, the pin call (calls 3 to 6) and
tests/test_shape_doctor.py(calls 9 to 12). Their results above stand ford56f2084, whose code0657e396shares.NITs 4, 5 and 7 stay as named.
--no-membersthe row's remedy says "with the member checked out" and names no command for it._lockstep'sbumpbehaves the same way, andbump's ownmember-uninitializednamesgit submodule update --initandmake bootstrap. Known limits, below.NIT 6 is refreshed below: the Merge-tree section names the current main, #1172's head and #1246's head, and call 14 is re-run on the merge with main
27397a4b(R5). The "Summary by Sourcery" block GitHub appended to the live body describes #1227's change and has no closing keyword; this file carries none of it, and it stays out of the squash message.Merge-tree
git merge-tree --write-treeof head0657e396with each head fetched on 2026-11-08 (scratch clone):27397a4b: clean, tree44248a29, exit 0. Call R5 above runs the predicted merge..gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #1172's headbbe5b414(it wasba81fd7d): clean, tree61067f7c, exit 0..gitfile this user cannot open, or whose git directory (its own.git, or the one its gitfile names) this user cannot search, is refusedmember-unreadablenaming that part, and no longermember-commit-unresolvable, which said the pinned commit was missing and prescribed a fetch (#1221) #1246's head0deac3a6(unchanged): clean, tree7fdbfe6b, exit 0.#1256 has no
refs/pull/1256/headon origin, so it has no row here. #1227's head is still2d56c190, so the stacking statement at the top holds and no restack is needed.Known limits
--no-members, a member's own malformed id behind a well-formed row is not seen: that run reads no member.addandbumprefuse such a member (member-bad-id), so only a hand edit, or anaddolder than 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, leaves one.bumprefuses one that is not (member-uninitialized, naminggit submodule update --initandmake bootstrap); a CI log under--no-membersis read by a person who then runs it where the member can be checked out. Where the row'spin.commitis no 40-hex commit, the remedy says--to <commit>.member-pin-commit, exit 2) and the row's finding follows once the pin is mended; under--no-membersthe finding stands alone and names--to <commit>. That order predates this PR (r1204's NIT 1; the docstring now says it).bumpto the commit already pinned commits as "Bump IRRS to<sha>...<sha>-><sha>" (measured,committed 832d083cac1b: members/IRRS, family.yaml): its message does not say the row's id changed.id:that reads as a positive number (id: 7) is accepted, asaddaccepts it (str(7)), though the member's ownvalidate-manifest.pyrefuses it as no string. This PR asksadd's question, unchanged.project:is skipped, as_check_member_ids_uniqueskips it;member-projectnames that row only with the members checked (read, not measured; unchanged)./usr/bin/git); the issue's git 2.47.1 was not run. Python 3.12.3 only; CI runs 3.14. The change asks nois_file,existsorstat(tests/test_path_guard_hygiene.pyis in call 7).Not in this PR
name,reference,elected_by, a leg'spath,org,tracking_branch,digest_algorithm) aremanifest okorfamily okwith a trailing line feed (#1166 follow-up) #1205, the other fields' values of a member row (repository:,path:, the pin): this PR asks only a row'sid:and, on the mounted path, the member's ownproject.yamlid:.scripts/family.py.add'smember-bad-idis the question this PR's member check copies, unchanged._run_checks, which bootstrap.py, validate-family.py and siblings.py: a root or holder whose.gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #1172 changes.Lane: openreposhape-1 (openRepoShape-1)
🤖 Generated with Claude Code