Skip to content

family.py bump --to an annotated tag's object id, which is 40 hex and passes COMMIT_RE, writes the TAG's id into the member's pin.commit while git checkout puts the commit the tag points at in the gitlink, so bump exits 0 and commits a holder whose own validate-family.py reports member-gitlink-mismatch, exit 1, where add --at pins the commit the same tag names #405

Closed

Description

@brettheap

Lane: openreposhape-4 (openRepoShape-4)

Found by this lane's adversarial review of #326 part 1 (PR #345, head 875d802), the first item of its "Out of scope (named, not fixed)": "bump --to <a 40-hex annotated TAG object id> exits 0 and commits a row whose commit: is the TAG oid while the gitlink records the peeled commit." #345 landed as 3ae0109 without it, by design: it previews the real run, and "the dry run previews it faithfully". Re-verified on main 3ae0109, with local bare remotes, an empty global git config, GIT_ALLOW_PROTOCOL=file and a gh that exits 97 (never called). Not claimed: macOS and Windows, which were not run. Nothing below depends on the platform.

What is wrong.

U=<openRepoShape checkout>   # a clean checkout at 3ae0109
# Runs in bash on Linux (GNU coreutils). Nothing leaves the machine: every repository is a local bare one under $R, git
# reads an empty global config, GIT_ALLOW_PROTOCOL=file makes any accidental https/ssh clone fail, and a `gh` that exits
# 97 stands first on PATH.
R=$(mktemp -d); mkdir $R/bin; printf '#!/bin/sh\nexit 97\n' > $R/bin/gh; chmod +x $R/bin/gh; touch $R/gitconfig
export PATH=$R/bin:$PATH GIT_CONFIG_GLOBAL=$R/gitconfig GIT_CONFIG_NOSYSTEM=1 GIT_ALLOW_PROTOCOL=file \
       GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t.invalid GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t.invalid
for P in Atlas Nova; do
  python3 $U/scaffold-project.py --org testorg --project $P --elected-by 'Test Human' --elected-on 2026-10-12 \
      --local-remote-dir $R/remotes --work-dir $R/work >/dev/null
done
python3 $U/scripts/family.py init --org testorg --family Fam --created-by 'Test Human' --created-on 2026-10-12 \
    --local-remote-dir $R/fam --work-dir $R/h >/dev/null 2>&1
H=$R/h/Fam; cd $H
python3 $U/scripts/family.py add --family-root $H --member testorg/Atlas --local-remote-dir $R/remotes >/dev/null
# Atlas releases: one commit, an ANNOTATED tag on it, both pushed
(cd $R/work/Atlas && echo moved > NOTES.md && git add NOTES.md && git commit -qm 'moved on' \
   && git tag -a v2 -m 'release 2' && git push -q origin main v2)
TAG=$(git -C $R/work/Atlas rev-parse v2); PEELED=$(git -C $R/work/Atlas rev-parse 'v2^{commit}')
echo "--to $TAG is a $(git -C $R/work/Atlas cat-file -t $TAG) of commit $PEELED"
python3 $U/scripts/family.py bump --family-root $H --member Atlas --to $TAG --dry-run | grep -E '^(commit|gitlink) '
python3 $U/scripts/family.py bump --family-root $H --member Atlas --to $TAG | grep -E 'committed'; echo "bump: exit ${PIPESTATUS[0]}"
echo "row pin.commit:   $(grep -A6 'project: Atlas' family.yaml | grep 'commit:' | tr -d ' "' | cut -d: -f2)"
echo "HEAD's gitlink:   $(git rev-parse HEAD:members/Atlas)"
python3 scripts/validate-family.py 2>&1 | grep -E '^(FINDING|REFUSED|family ok)' | cut -c1-150; echo "validate-family.py: exit ${PIPESTATUS[0]}"
python3 $U/scripts/family.py bump --family-root $H --member Atlas --to $PEELED | grep -E 'committed'; echo "bump --to the peeled commit: exit ${PIPESTATUS[0]}"
python3 scripts/validate-family.py 2>&1 | grep -E '^(FINDING|REFUSED|family ok)' | cut -c1-150; echo "validate-family.py: exit ${PIPESTATUS[0]}"

Output at 3ae0109:

--to 1f17cb89971400096727e55bf50190f51b027379 is a tag of commit 4d4cac88f24035b15d8fe8c170044add893c44e2
commit       f5222ce5b704 -> 1f17cb899714
gitlink      members/Atlas f5222ce5b704 -> 4d4cac88f240
  committed fdbba21ccea6: members/Atlas, family.yaml
bump: exit 0
row pin.commit:   1f17cb89971400096727e55bf50190f51b027379
HEAD's gitlink:   4d4cac88f24035b15d8fe8c170044add893c44e2
FINDING member-gitlink-mismatch: members/Atlas: gitlink 4d4cac88f24035b15d8fe8c170044add893c44e2 != family.yaml pin 1f17cb89971400096727e55bf50190f51b
validate-family.py: exit 1
  committed 3eda2c05e571: members/Atlas, family.yaml
bump --to the peeled commit: exit 0
family ok: Fam (fam), 1 member(s)
validate-family.py: exit 0

The commit bump made is the one its pull request carries, and the holder's own gate, which CI runs, is red on it. Nothing in the run says the id was a tag. The dry run's two lines show the split, but only to a reader who compares the two ids. A second bump --to the peeled commit repairs it (the last four lines).

Why. add was made to peel in #236. _probe_commit (:1457) resolves --at with ^{commit}: "an annotated tag's own sha resolves to the commit it tags, which is what checking it out pinned before". tests/test_family_add_refusals.py::test_an_at_that_is_a_tag_of_a_commit_pins_the_commit_it_tags holds it. bump kept the older shape: the argument is the pin, and the checkout is trusted to agree. For a commit's id they agree. For a tag's id the checkout peels and the row does not.

bump-leg.py also writes the tag's id into contracts/<role>-pin.yaml when given one (probed on 3ae0109 with a tag on a spec leg's commit). Its own validate-pins.py then goes red before the commit, so it rolls back and exits 2, bump-leg-validators-red. That is a refusal, but it comes from its gate and does not name the tag. family.py bump has no gate before its commit.

What it should do. A bump given a tag's id either pins the commit the tag names, as add --at does, or refuses the tag by name before anything moves. The decisions are the author's:

  1. Peel or refuse.
  2. Where. Resolve --to once, in the member after the fetch (rev-parse --verify <to>^{commit}). Use that one id for the checkout, the digest, the row, the commit message and the nothing to do test, in the real run and in _bump_dry_run, which already computes resolved.
  3. bump-leg.py. The same question for its --to, decided once for both tools.

How to test. Build a tag with tests/test_family_add_refusals.py's tagged_remote, then run bump --to <the tag's id>:

  • Peel: exit 0, pin.commit and HEAD's gitlink both the peeled commit, and validate-family.py exit 0.
  • Refuse: exit 2, with the holder's snapshot unchanged (HEAD, ls-files -s -z, family.yaml's bytes, the member's HEAD).
  • Under --dry-run: the commit and gitlink lines name one commit, or the dry run refuses as the real run does.

Verified at main 3ae0109 (scripts/family.py sha256 bf059598…).

Related: #326 / PR #345 (the review that named it; bump --dry-run), #236 (add --at peels a tag), #365 (bump's rollback, in progress in this lane, in the same function; independent of this), #407 (the same review's other real-run item, in remove).

Activity

  1. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-3, session 43f4ac63-0116-41de-977d-afc23cdf7fdf@Eagle, 2026-10-12T19:28:42Z, for #405

    Logged in lanes/log/openRepoShape-3.md at ddf158af969302dc222c6ee99e2ab02134e89efd.

    The three reads (lane-collision-protocol Rule 1):

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#405:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#405"`, kept where the row or its body names `#405`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `405` as a whole token:
       none
    
  2. added this to the v1.0.0 milestone on Oct 12, 2026
  3. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    (lane openRepoShape-3, 2026-10-12T19:50:57Z) A v1.0.0 scope notice. Brett Heap scoped openRepoShape's first release today: every procedure in AGENTS.md works end to end for ordinary repositories on Linux, macOS and Windows. The tag waits for every in-scope issue to close and a full test run to pass on all three OSes on the release commit. This issue is #405 (P1, parent #459): in scope, in the v1.0.0 milestone. Nothing changes in how it is worked. Known-limit issues stay open, without the milestone, under their area's parent.

  4. added 2 commits that reference this issue on Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions