Skip to content

repo_shape.py's Refusal gives every refusal built without a remediation the assembly root's own (run git submodule update --init --recursive, then python3 scripts/validate-pins.py ... contracts/<leg>-pin.yaml), so in a family holder family.py add over an unparsable .gitmodules (git-failed), bump on a family.yaml of the wrong kind, and the holder's own validate-family.py on a short pin.commit each tell the operator to run a file no holder carries and to move a leg pin a holder does not have #370

Closed

Description

@brettheap

Lane: openreposhape-4 (openRepoShape-4)

Found by this lane's adversarial review of #300 (PR #317, head 03ddadd; its out-of-scope item 1): "add over an unparsable .gitmodules says REFUSED git-failed … bad config line 5. Its remediation is the assembly-root default ... a file no holder carries. It is identical on main, so it has been there since #242." PR #317 lists it under "Not in this change". Widened here after reading where the text comes from: the same default reaches every refusal in scripts/family.py and templates/family-root/scripts/validate-family.py that is built without a remediation of its own. Re-verified on main c7c02fa and on a scratch merge of #318's head 0f2a0dd (which carries c7c02fa) with #317's head c23e2b0, with local bare remotes, an empty global git config, GIT_ALLOW_PROTOCOL=file and a gh that exits 97 (never called).

What is wrong.

U=<openRepoShape checkout>   # a clean checkout at c7c02fa
# Runs from a clean checkout of openRepoShape, in bash, on Linux (GNU coreutils). Nothing leaves the machine: members and
# holder live in local bare repositories 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 Zed; 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
add() { python3 $U/scripts/family.py add --family-root $H --member "$1" --local-remote-dir $R/remotes; }
add testorg/Nova >/dev/null
printf 'this line is not git config\n' >> .gitmodules                  # a .gitmodules git cannot parse
add testorg/Atlas 2>&1 | sed "s#$H#\$H#" | cut -c1-190; echo "add: exit ${PIPESTATUS[0]}"
git checkout -q -- .gitmodules
python3 - <<'PY'                    # Nova's pin.commit shortened by hand to 12 hex
import re
t = open("family.yaml").read()
open("family.yaml", "w").write(re.sub(r'commit: "([0-9a-f]{12})[0-9a-f]{28}"', r'commit: "\1"', t))
PY
python3 scripts/validate-family.py 2>&1 | cut -c1-190; echo "validate: exit ${PIPESTATUS[0]}"
git checkout -q -- family.yaml
python3 - <<'PY'                    # family.yaml's kind changed by hand
t = open("family.yaml").read()
open("family.yaml", "w").write(t.replace("kind: family-manifest", "kind: project-manifest"))
PY
python3 $U/scripts/family.py bump --family-root $H --member Nova --to $(git -C members/Nova rev-parse HEAD) 2>&1 \
    | sed "s#$H#\$H#" | cut -c1-190; echo "bump: exit ${PIPESTATUS[0]}"

Output at c7c02fa (the merge of #318 and #317 prints the same; each remediation line is cut at 190 characters by the block, and continues "... names the leg in ONE commit (see README, 'The lockstep invariant')"):

REFUSED git-failed: `git config -z --file .gitmodules --list` in $H exited 128: fatal: bad config line 4 in file .gitmodules
Remediation: run `git submodule update --init --recursive`, then `python3 scripts/validate-pins.py`. If the PIN itself is stale, advance the gitlink, `contracts/<leg>-pin.yaml` and every wor
add: exit 2
REFUSED member-pin-commit: Nova: pin.commit is '0e8f87dc451f', which is not 40 hex. An abbreviated oid, a branch or a tag is a moving reference.
Remediation: run `git submodule update --init --recursive`, then `python3 scripts/validate-pins.py`. If the PIN itself is stale, advance the gitlink, `contracts/<leg>-pin.yaml` and every wor
validate: exit 2
REFUSED family-wrong-kind: $H/family.yaml: kind is 'project-manifest', expected 'family-manifest'
Remediation: run `git submodule update --init --recursive`, then `python3 scripts/validate-pins.py`. If the PIN itself is stale, advance the gitlink, `contracts/<leg>-pin.yaml` and every wor
bump: exit 2

A holder has no scripts/validate-pins.py, no legs, no contracts/<leg>-pin.yaml and no workflow @<sha> that names a leg. Each of the three remedies is a command that fails, followed by a procedure for a pin the holder does not have. In the first case, after #317 the holder's own validator calls the same file gitmodules-unreadable ("git cannot read it either ... git config -f .gitmodules --list says where it breaks"), so add and the gate now name one fault two ways. On main the holder's validator says family ok over that same file.

The sites, by an AST scan of each file at c7c02fa (identical on the merge but for line numbers), are every Refusal(code, detail) with no third argument:

Why. Refusal.__init__(self, code, detail, remediation=REMEDIATION) (scripts/repo_shape.py:131 at c7c02fa), and REMEDIATION (:123) was written for an assembly root's legs. A holder ships repo_shape.py and validate-family.py, and neither file says which kind of root it serves. #44 and #48 (both closed) fixed the same mismatch in update-shape.py, which named project.yaml and validate-manifest.py on a family root.

What it should do. No refusal a family tool prints names a file or a pin a holder does not have. The decisions are the author's:

  1. Where the text comes from. Either each listed site in family.py and validate-family.py carries its own remediation. Or both files pass a family default (git_out and load_yaml would then have to take the caller's). Or REMEDIATION becomes neutral ("git's line above says what failed; fix it and run the command again"), and the assembly-root text moves to the assembly-root sites that mean it.
  2. add over an unparsable .gitmodules: the same code and remedy as the validator's gitmodules-unreadable, since _submodule_paths and _check_gitmodules_paths read the file the same way (family.py: add refuses member-path-registered when a .gitmodules section under another name already gives the mount path, and validate-family.py finds gitmodules-path-twice, so add no longer commits two sections for one path that a recursive clone resolves to the later one, and a holder that already has such a ghost section fails its own gate (#300) #317).
  3. Both files are copied into holders, so the validator's text reaches them through update-shape.py.

How to test. Each repro row as a test that asserts the refusal code and that validate-pins.py and <leg>-pin.yaml are absent from stderr: add and bump in the family tests, the short pin in the validator's. A test can also walk both files' ASTs, as the scan above did, and fail on a Refusal built without a remediation, so a new site cannot slip back to the default.

Verified at main c7c02fa and at the scratch merge of #318's head 0f2a0dd with #317's head c23e2b0 (scripts/family.py sha256 8339041e…, validate-family.py 7ec3eec0…); the finding survives the merge.

Related: #300 / PR #317 (gitmodules-unreadable, and its "Not in this change" list), #242 (the .gitmodules read in add that first met this default), #44 and #48 (the same mismatch in update-shape.py, fixed).

Activity

  1. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    (lane openRepoShape-2, 2026-10-12T17:10Z)

    One more route to the default trailer this issue reports. It was found in this lane's review of #343 at 039530b.

    The review is handoffs/xFactory/attachments/lane-openRepoShape-2/review343-039530b/final-report.md in opensoft/brett-wip.

  2. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

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

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

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

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#370:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#370"`, kept where the row or its body names `#370`:
    343	update-shape.py `read_pin` refuses `update-pin-row`, and the pinned validate-pins.py and validate-family.py report `shape-pin-row`, when a shape pin's `files:` is not a list, so `files: 5`, `true` or `1.5` no longer ends `check`, `apply`, shape-doctor.py and the project's own validator in `TypeError: ... is not iterable` (#290)	fix/pin-files-not-a-list	OPEN
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `370` as a whole token:
       none
    
  3. added this to the v1.0.0 milestone on Oct 12, 2026
  4. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    (lane openRepoShape-3, 2026-10-12T19:50:30Z) 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 #370 (P2, 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.

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