You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Follow up #190's test roots: name a refused root's value, check the pin's commit, find paired feature worktrees, show redirected roots under -q, run the lane manual sections, and restate the installer-source test - #195
Part of #186. This PR answers #193 items 2, 3, 4, 5, 6, 7 and 15, and lane openRepoTools-3's delta 5 on #191 (review 5471121210). Item 1 was resolved when #190 landed at b50f197. Item 14 is implemented as the coordinator decided ("the test gives") in e378871, folded in after lane 3's LAND review of fb77bed (5481360555). T006's patch is unchanged.
Every red and green run below is a scoped selection run without the workstation lock, under the coordinator's rule: HOME, TMPDIR, --basetemp and PYTHONPYCACHEPREFIX all sit inside lane 1's scratch. "Red" means the item's new test, copied unchanged into a clone at 01ccc86 (this branch before the item), unless a row says otherwise. Log SHA256 prefixes are in brackets.
#
Change
Red before
Green after
15
A root that a variable named and the run refused is reported as absent because of that value. The refusal now says "OPENREPOTOOLS_SPEC_ROOT names <value>, which is not a spec root", not that the variable "is unset" or that there is "no OPENREPOTOOLS_ASSEMBLY_ROOT". Found by lane 2 (6087267593).
2/2 failed on the words: …and OPENREPOTOOLS_SPEC_ROOT is unset, no OPENREPOTOOLS_ASSEMBLY_ROOT, and… [67e9d15c]
passed
4
A composed run is refused when a leg pin's commit: differs from the gitlink. This is checked before the leg's HEAD, so the pin is no longer validated only by validate-pins.py (Copilot round 2, High). Root markers must also be regular files (Copilot, Medium).
3/3 failed: refusal() is None with the pin moved to 1111…; directories spelled openRepoTools or project.yaml were accepted as a code root and an assembly root [67e9d15c]
passed
5
Discovery accepts the paired layout <assembly>/worktrees/<feature>/code as well as the mount, and only those two places. A code root there must be its own repository, and its object store must hold the commit the assembly pins. The feature's own spec worktree beside it becomes the spec root. A feature that is off the pin is still refused when composed.
2/2 failed: no assembly found from the paired code worktree [32c57e06]
passed, with negatives: a stranger repository at the paired path, and the leg's worktree outside worktrees/
6
A run whose roots are not today's single tree repeats ROOTS.describe() in pytest_terminal_summary, which -q keeps (the report header is dropped by -q). A run on today's layout with no variable prints nothing new.
failed: no roots section under -q [6d8b0fa1]
passed: section present when a root is named, absent on today's layout
3
The lane suite's three $LANE_MANUAL sections are cut out of the suite and run under its own set -uo pipefail and its own helpers. A missing manual must FAIL, naming the path, in both sections that check it. No manual must give three named skips and reach the end. The run's own manual must pass every quote.
at c139e88, which had no [ -r ] check, a missing manual was read as empty and the path was never named [757600fc]
passed
7
The guard test now follows the if/elif/else/fi nesting (here-documents skipped) instead of looking for the nearest line spelled fi (review 5471080687, point 6).
main's reading accepts a ${ln_doc} after the guard and a use after an indented fi; both planted cases, run with main's code [57fd8b3a]
the new reading names both; on the real suite it finds 16 uses, 3 sections and no open frames
7
tests/run.sh's abs_root now reads a quoted ~ or ~/… as $HOME, as conftest's expanduser does.
failed: ~/spec became <caller>/~/spec [de01af5f]
passed
7
LICENSE and .gitignore stay the code root's, now with the reason: T006's code-0001 gives the leg its own copy of each. Fixture legs keep them. The stale GIT_ENV comment is corrected.
no behaviour change: this records the decision, and the new assertions hold where both files are read from
passed
2
6e63083: each mixed hygiene test asserts its command half first. 2064390 adds a nested proof.
at main f87fd5a: test_the_documents_say_what_bare_park_does_now skipped on a leg whose park lost its line [efe8e044]
passed [0a1e371e]
2
The seven-line "FOUR ROOTS" paragraph moves to the foot of AGENTS.md's testing section, below every line T006's assembly-0001 rewrites. The text was already there, including OPENREPOTOOLS_COMPOSED=1; only its place changes.
assembly-0001's AGENTS.md hunks did not apply at 01ccc86 (patch failed: AGENTS.md:211; lane 3 saw :224 on the candidate) [fac73c1a]
applies, and gives 347 lines, the cap at an assembly root (main gives 340)
delta 5
test_the_installer_reaches_only_this_repository is restated. The repository each fetching line names (including api.github.com/repos/… URLs, which were not read before) must be one of three things: $REPO; the pinned standard (wip init); or a variable whose every assignment is empty, $REPO, or a variable that only a pin's source_repository: arm sets. That last case is #191's adopted payload source.
main's reading accepts all three planted bad shapes: passing by spelling, an API curl, and a payload variable fed from elsewhere [1d8fb66c]
refuses all three and accepts the two honest shapes. Holds on main's installer (7 fetching lines) and on #191's heads 13cc752 and 8246983 (16 each), with 0 refusals each. Copilot round 1: a source_repository) arm must assign the parsed $PIN_SCALAR, which only pin_scalar() sets (e901b6d). Two more planted shapes, an arm assigning something else and a scalar set outside the reader, were accepted at 804be8b and are refused now [a2a1818b]
Item 14 (e378871, after lane 3's review).test_agents_md_names_the_pin_rules now reads the code mount from the assembly's code pin (Roots.code_mount()). It asserts code/upstream/openRepoShape and git -C code submodule update --init … at an assembly root, and today's spelling on the single tree.
Proofs:
The r2 composed candidate (a recursive clone of its assembly 5d92962, code 4dbcf6c): its own suite, composed, fails this test [6e9d77e7]. With fb77bed's tests it fails [6b400e6d]; with e378871's it passes [4dc09748].
A new nested test against a fixture assembly (prefixed rules pass; today's spelling there fails) is red at fb77bed in both cases [ba63f0db].
Today's layout: the test passes at fb77bed [59c86e7b] and at e378871 [0e9eea70].
Copilot round 2 (fb77bed; the cap):
Discovery skips a marker-bearing ancestor that fails the rest, so a stale manifest under a feature directory no longer hides the assembly.
A paired spec worktree counts only as a repository holding the pinned spec commit; a stray docs/ does not.
The PIN_SCALAR check needs a pin_scalar() reader that sets the scalar.
Each is red at e901b6d, and the details are in the PR comment.
Scoped green on the branch, today's layout:
-k 'repo_hygiene or upstream_pin or roots or composed': 219 passed at e378871 [d303703d]; 217 at fb77bed [2e63e919]; 211 at 804be8b [9cb77699].
-k run_wrapper: 12 passed [6d7b2e59].
tests/test_lane_helpers.sh and the installer are untouched. On today's layout, with no variable set, the suite's output is unchanged; the only difference is AGENTS.md's seven added lines (cap 316 → 323, in a dated entry).
Item 14: which side gives (recommended here, decided by the coordinator, implemented in e378871)
test_agents_md_names_the_pin_rules asserts upstream/openRepoShape and git submodule update --init upstream/openRepoShape verbatim. T006's assembly-0001 re-roots both to code/… in the assembly's AGENTS.md.
Decision: the test gives, by deriving the path from the roots rather than admitting either spelling. The path and the command it asserts should be the ones that work from the root that owns the AGENTS.md being read:
on today's single tree: upstream/openRepoShape and git submodule update --init upstream/openRepoShape;
at an assembly root: code/upstream/openRepoShape and git -C code submodule update --init upstream/openRepoShape. The code prefix is the code pin's submodule_path.
Reasons:
T006's text is right where it is read. In a copy of the composed candidate, at the assembly root:
git submodule update --init upstream/openRepoShape gives error: pathspec 'upstream/openRepoShape' did not match any file(s) known to git (exit 1);
Making the patch keep the old strings would put a command that fails into the document a person opens at that root.
The code leg's own AGENTS.md (code-0001) is not the place. It deliberately defers "the pinned standard's three rules" to the root's AGENTS.md and only summarises them. Pointing the test there would hold a summary instead of the rules.
Admitting either spelling would pass a wrong document in either place. Deriving the prefix keeps the test asserting that the document's command works from where the document sits.
…s so it reads each file from the tree that owns it, and refuses composed acceptance without all of them.
Checkpoint of T009 (specs/004-migrate-to-triad/tasks.md, #186). Adopted into
the triad, README.md, AGENTS.md and CLAUDE.md move to the assembly root and
docs/ to the spec leg, while the commands, tests, .gitmodules and the
openRepoShape pin stay in the code leg. The suite assumed one tree.
tests/conftest.py now resolves the four roots from OPENREPOTOOLS_CODE_ROOT,
OPENREPOTOOLS_ASSEMBLY_ROOT and OPENREPOTOOLS_SPEC_ROOT, or from the layout
(an assembly above the code root that mounts it as its code gitlink), and
ROOTS.path_for() reads a file from the root that owns it. Standalone, an
absent root skips the tests that need it, naming the root; on today's single
tree nothing new skips, because that tree is all of them, and the
no-submodule skip is exactly today's. With OPENREPOTOOLS_COMPOSED=1 the
session is refused (pytest exit 4) when the assembly or spec root is absent,
the dependency is uninitialized, or a leg is not at the commit the assembly
records, and NEEDS_UPSTREAM fails instead of skipping.
The hygiene module reads the front-door documents and the lane manual through
those roots and scans every repository the run can read; the pin module reads
the code root's own gitlink and the dependency nested in it; the lane suite
and the installer's README check take the manual and the README from their
owning roots; tests/run.sh makes a relative root absolute before its cd.
tests/test_test_roots.py holds the resolver on fixture layouts and runs the
suite itself, nested, against fixture triads. Not yet run through the
workstation lock; the scoped runs and the CI patches follow.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d keep the code leg's own .gitattributes in the fixture legs (Copilot round 1 on #190).
A failed `git status` came back as None and was read as "no changes", so a
leg whose index could not be read passed the tracked-changes guarantee. It
is now a refusal naming the leg; a new test corrupts a fixture leg's index
and asserts it. The fixture code legs dropped .gitattributes with the other
assembly files, contradicting the contract that the code leg carries its own
line-ending rule; they keep it now.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d fail the lane suite by name when the lane manual is missing (T006's record of 2026-10-08, section 10).
In the triad the hygiene caps read the ASSEMBLY's files, which T006's root
guidance (assembly-0001) lengthens by 24 lines each: the shape's first-line
pointer and triad map in AGENTS.md, the lockstep-invariant section the leg
pins name and the three-repository front door in README.md. The caps are 340
and 510 where an assembly root is named or found, in dated entries; today's
single repository keeps 316 and 486, so main buys no line of prose. The lane
suite's two manual sections now fail naming the path when the manual the
roots point at is unreadable, instead of handing the assertions an empty
string. Three new tests; all three are red at origin/main (c4864ac). The
scoped green run was still queued behind the workstation lock at this
checkpoint; CI is the record.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ead, and document the four roots in AGENTS.md (the #190 review).
Five hygiene tests read a command and the documents about it, and three of
them (park's header, status's --no-optional-locks, the installer's
`lanes [--all] [--fetch]`) asserted the command's half only after a document
read, so a standalone code leg skipped those code-only checks along with the
documents; the exit-3 table and the wrapper's own lock claims had the same
order. Each now asserts the code root's half first; on today's layout every
assertion is unchanged. AGENTS.md's "Testing your changes" gains seven lines
naming the roots, OPENREPOTOOLS_CODE_ROOT, OPENREPOTOOLS_ASSEMBLY_ROOT,
OPENREPOTOOLS_SPEC_ROOT and the composed switch; its cap moves 316 -> 323
here and 340 -> 347 at an assembly root, in a dated entry.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mixed test's command half, not only by reading the order (#190 review, finding 2).
A fixture code leg whose park, status and openRepoTools lost the lines the
three mixed hygiene tests assert now FAILS those tests instead of skipping
them with the documents. Red at origin/main, which read this checkout's own
files whatever was named, and red at dbc6837, where the documents' skip came
first. Follow-up to #190, held off its branch while the macOS gate runs.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pec root skips them instead of aborting the whole suite under set -u.
PR #190 binds ln_doc only inside the `if [ -n "$LANE_MANUAL" ]` section that
quotes the bare-lanes settlement. The --retire section, 350 lines later, used
it unguarded. When there is no spec root, LANE_MANUAL is empty and the suite
runs under `set -u`, so it died there with `ln_doc: unbound variable`. That
is not the named skip the PR promises. Lane openRepoTools-3's review found it,
and this work's own standalone-leg proof (P3) had logged it too.
Those two assertions now carry the same guard and skip as the sections that
read the manual. A new text test holds every use of the manual's text inside
such a guard. It is red at dbc6837, where it names lines 6601 and 6602.
Nothing changes on today's layout: the spec root is always there, so the
guard is always taken.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the #190 follow-ups, with the merge base pinned at b50f197.
#190 landed, so these follow-ups now go to main. A plain merge would take
the squash's merge base, c4864ac, and replay #190's own commits against
their squash. Pinning the base at b50f197, #190's final head, applies only
what this branch adds after #190 (6e63083 and 2064390) onto main.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nstead of saying its variable is unset (#193 item 15).
When OPENREPOTOOLS_SPEC_ROOT or OPENREPOTOOLS_ASSEMBLY_ROOT named a directory
that is not that root, the run was rightly refused. But the composed line for
the absent root still said the variable "is unset", or that there was "no"
such variable, though the run had set it. Lane openRepoTools-2 found this in
the composed-run logs.
The resolver now records each named-and-refused root with its value, and
why_absent says that variable names that value, which is not that kind of
root. The refusal itself is unchanged.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…link, and accept only regular files as a root's markers (#193 item 4).
Copilot's round 2 on #190 found two gaps. First, with contracts/code-pin.yaml's
commit: moved off the gitlink, the resolver accepted the run while
scripts/validate-pins.py exits 1 on the same tree. Second, _carries used
exists(), so a directory named like a marker passed for a root.
The pin's commit: is now compared with the gitlink before the leg's HEAD is,
and the markers must be files. The tree digest stays the validator's job.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s/<feature>/code, and read that feature's own spec worktree (#193 item 5).
The triad's feature work happens in paired worktrees of each leg under the
assembly's worktrees/ directory. That is how T006's root guidance and the
code leg's own AGENTS.md lay it out. Discovery only knew the mount, so a code
root there found no assembly, and after Gate C every document test would skip
in a feature worktree. Lane openRepoTools-3's review of #190 measured this on
run C.
Discovery still stops at the nearest ancestor that carries the assembly's
markers, and it accepts only two places for the code root. One is the mount.
The other is worktrees/<feature>/<the code mount's own name>, and only when
that is a repository of its own whose object store holds the commit the
assembly pins. The feature's own spec worktree beside it is the spec root
when it is there; otherwise the assembly's mounted spec leg is. Composed
acceptance is unchanged: a feature off the pin is refused.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and print nothing new on today's layout (#193 item 6).
OPENREPOTOOLS_CODE_ROOT re-points REPO and UPSTREAM, and a named assembly
root decides which README, AGENTS.md and length caps the suite holds. Only
the report header showed this, and -q drops the header. Every CI job and
tests/run.sh run -q, so a run pointed at another tree said nothing about it.
Lane openRepoTools-3's review of #190 raised this.
pytest_terminal_summary now repeats ROOTS.describe() whenever the roots are
anything but today's one tree taken as it stands: a variable named a root,
an assembly was found, the code root is a leg, or the run is composed. A run
on today's layout with no variable prints exactly what it printed before.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…present manual, and read their guard by following the if/fi nesting (#193 items 3 and 7).
Item 3. The missing-manual branch was only checked by its text. The suite's
three $LANE_MANUAL sections are now cut out of it and run under its own
set -uo pipefail with its own one-line helpers. A manual that is not there
must FAIL, naming the path, in both sections that check it. No manual must
give three named skips and reach the end. The run's own manual must pass
every quote.
Item 7, the review of b50f197, point 6. The guard test found the guard from
the nearest line spelled exactly `fi` or `else`. So it missed a use spelled
${ln_doc} and a closing fi that was indented. It now follows the
if/elif/else/fi nesting, skipping here-documents, and requires that it ends
with no frame open. Planted cases hold both misses and two cases it must keep
right.
Item 7 also: the GIT_ENV comment no longer claims a file-transport allowance
that nothing sets.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
….sh, as conftest does, and keep LICENSE and .gitignore as the code leg's own (#193 item 7).
tests/run.sh made a relative root absolute against the caller's directory, so
a quoted ~/x reached pytest as $PWD/~/x. conftest's expanduser reads the same
value as the home directory. The wrapper now expands ~ and ~/... to $HOME, and
its test covers both forms.
Roots.owner_of gives LICENSE and .gitignore to the code root. That stays,
now with its reason. T006's code-0001 gives the code leg its own copy of each,
and the leg's copy governs the leg's files, as with .gitattributes. Fixture
code legs keep both, and the mounted-leg test holds where they are read from.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion, below every line T006's assembly-0001 rewrites, so that patch still applies (#193 item 2).
6e63083 put the seven lines directly under the test block. assembly-0001
rewrites that block to re-point it at code/, so the patch stopped applying
(patch failed: AGENTS.md:211; lane openRepoTools-3 saw :224 on the
candidate). The cap of 347 at an assembly root then rested on a premise no
patch met.
At the foot of the section, the patch applies to this file and gives 347
lines, which is the cap. The text is unchanged except that it now names
OPENREPOTOOLS_COMPOSED=1, and the cap entry says where the lines sit and why.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e an allowed repository, not merely contain $REPO, and read api.github.com URLs too (lane 3's review of #191, delta 5).
The first reading asked only that each fetching line contain "$REPO". A line
fetching from ${PAYLOAD_REPO:-$REPO} passed by spelling, whatever
PAYLOAD_REPO held, and a curl of api.github.com/repos/... was never read.
Lane openRepoTools-3's review of #191 (delta 5) gave the restatement to T009.
The repository each fetching line names is now read out of it. It must be
one of three things:
- $REPO;
- the pinned standard, for wip init;
- a variable whose every assignment is empty, $REPO, or a variable that only
a pin's source_repository: arm sets. That is the code repository an
adopted assembly pins, which #191 introduces.
The rule holds on main's installer and on #191's head 13cc752. Planted cases
hold the three shapes the first reading let through and the two it must keep
accepting.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR hardens multi-root test behavior across discovery, validation, reporting, shell execution, and hygiene checks: it supports pinned paired feature worktrees, produces accurate diagnostics and quiet-run root output, executes lane manual sections directly, and enforces installer fetch-source constraints with regression coverage.
Sequence diagram for composed-run root and pin validation
sequenceDiagram
participant Runner
participant Conftest
participant Roots
participant Git
Runner->>Conftest: pytest session setup
Conftest->>Roots: discover roots
Roots->>Git: inspect paired worktree and gitlink
Git-->>Roots: root repositories and commits
Roots->>Roots: validate regular root markers
Roots->>Roots: compare leg commit to pin commit
alt root or pin invalid
Roots-->>Conftest: refusal with value or mismatch
Conftest-->>Runner: refuse composed run
else valid
Roots-->>Conftest: resolved code, assembly, and spec roots
Conftest-->>Runner: continue tests
end
Loading
Sequence diagram for quiet-run root reporting
sequenceDiagram
participant Pytest
participant Roots
participant Terminal
Pytest->>Roots: describe()
alt roots differ from today's single tree
Roots-->>Pytest: named root description
Pytest->>Terminal: pytest_terminal_summary
Terminal-->>Pytest: print ROOTS section
else today's layout with no root variables
Roots-->>Pytest: no additional description
end
Loading
Flow diagram for multi-root discovery and validation
flowchart TD
Start["Discover test roots"] --> Layout{"Layout detected"}
Layout -->|paired feature worktree| Paired["Find assembly and spec worktrees"]
Layout -->|mounted or named roots| Named["Resolve configured roots"]
Layout -->|standalone checkout| Single["Use today's single-tree layout"]
Paired --> Validate["Validate regular root markers and repositories"]
Named --> Validate
Single --> Validate
Validate --> Composed{"OPENREPOTOOLS_COMPOSED=1"}
Composed -->|yes| Pins["Check each leg commit against gitlink pin"]
Composed -->|no| Ready["Roots available or skipped by name"]
Pins -->|match| Ready
Pins -->|mismatch| Refuse["Refuse run with precise root or pin diagnostic"]
Loading
Flow diagram for lane manual-section execution
flowchart TD
Start["Run lane suite"] --> Env["Create manual-section environment"]
Env --> Manual{"LANE_MANUAL present"}
Manual -->|missing| Skip["Emit three named skips"]
Manual -->|present| Check["Check manual path is readable"]
Check -->|missing| Fail["FAIL and name manual path"]
Check -->|readable| Run["Run each manual section"]
Run --> Quotes{"All quote checks pass"}
Quotes -->|yes| End["Reach suite end"]
Quotes -->|no| Fail
Loading
Flow diagram for installer fetch-source hygiene validation
flowchart TD
Start["Inspect installer fetching lines"] --> Source["Resolve each repository source"]
Source --> Allowed{"Allowed source"}
Allowed -->|$REPO| Accept["Accept"]
Allowed -->|pinned standard from wip init| Accept
Allowed -->|variable assigned only by source_repository pin arm| Accept
Allowed -->|other spelling, API curl, or external payload assignment| Reject["Refuse"]
Loading
File-Level Changes
Change
Details
Files
Expanded test-root resolution and validation for named, mounted, and paired worktree layouts.
Preserve rejected environment-root values for accurate composed-run diagnostics.
Require regular-file root markers and validate pin commits against assembly gitlinks before HEAD checks.
Discover only mounted code roots or pinned paired feature worktrees, selecting a paired spec worktree when present.
Treat code-leg LICENSE and .gitignore as code-owned files and update related fixture expectations.
tests/conftest.py tests/test_test_roots.py
Improved test-run reporting and wrapper handling for redirected roots.
Repeat root descriptions in terminal summaries when roots are redirected, including under pytest -q.
Expand quoted ~ and ~/ paths relative to HOME in the shell wrapper.
Document the four-root model and composed-run behavior in AGENTS.md.
Trigger a new review: Comment @sourcery-ai review on the pull request.
Continue discussions: Reply directly to Sourcery's review comments.
Generate a GitHub issue from a review comment: Ask Sourcery to create an
issue from a review comment by replying to it. You can also reply to a
review comment with @sourcery-ai issue to create an issue from it.
Generate a pull request title: Write @sourcery-ai anywhere in the pull
request title to generate a title at any time. You can also comment @sourcery-ai title on the pull request to (re-)generate the title at any time.
Generate a pull request summary: Write @sourcery-ai summary anywhere in
the pull request body to generate a PR summary at any time exactly where you
want it. You can also comment @sourcery-ai summary on the pull request to
(re-)generate the summary at any time.
Generate reviewer's guide: Comment @sourcery-ai guide on the pull
request to (re-)generate the reviewer's guide at any time.
Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
pull request to resolve all Sourcery comments. Useful if you've already
addressed all the comments and don't want to see them anymore.
Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
request to dismiss all existing Sourcery reviews. Especially useful if you
want to start fresh with a new review - don't forget to comment @sourcery-ai review to trigger a new review!
…arm assigns the scalar the pin reader parsed (Copilot round 1 on #195).
The restated rule accepted any value assigned in a `source_repository)` case
arm. So `source_repository) PIN_SOURCE_REPOSITORY="$FROM_THE_ENVIRONMENT"`
fed the payload repository from elsewhere and still passed. Copilot's round 1
on #195 found it.
That arm must now assign $PIN_SCALAR. Every non-empty PIN_SCALAR assignment
must also sit inside pin_scalar(), which is how #191 binds a value it parsed
out of a pin. Two planted cases hold this: an arm that assigns something
else, and a scalar set outside the reader. Both were accepted at 804be8b.
The rule still accepts main's installer and #191's head 13cc752 with no
refusals.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Skip invalid ancestors instead of stopping discovery
tests/conftest.py:208
An invalid marker-bearing ancestor stops the search here, so a stale project.yaml/contracts/code-pin.yaml under a feature directory can shadow the real assembly above it. The function promises the nearest ancestor satisfying all conditions (and previously kept scanning), so candidates that fail repository/gitlink/location checks must be skipped rather than terminating discovery.
Validate sibling spec worktree repository and pinned commit
tests/conftest.py:459
This identifies any sibling directory containing a spec tree as the feature's “own spec worktree.” Unlike the paired code check, it never verifies that the sibling is a repository whose object store contains the assembly's recorded spec commit, so a stray or unrelated docs/ directory silently replaces the mounted spec root in standalone runs. Apply the same repository/pinned-object validation before selecting it, otherwise fall back to the mounted spec leg.
…ept a paired spec worktree only as a repository holding the pinned spec commit, and require the pin reader that sets PIN_SCALAR (Copilot round 2 on #195).
Copilot's round 2 on #195 raised three findings.
- discover_assembly stopped at the first ancestor that carried the assembly's
two markers, even when it failed the rest. A stale project.yaml and
contracts/code-pin.yaml under a feature directory therefore hid the real
assembly above it. That ancestor is now skipped. Acceptance is still one
of the two exact placements, so skipping cannot borrow an unrelated tree.
- The feature's own spec worktree was taken on the strength of a docs/
directory alone. It now has to be a repository of its own that holds the
spec commit the assembly pins, which is the proof the paired code worktree
already met. Otherwise the mounted spec leg is read.
- The installer-source rule's PIN_SCALAR check passed vacuously when there
was no pin_scalar() reader, or when the reader never set the scalar. Then
$PIN_SCALAR would be the environment's. The reader and a parsed assignment
inside it are now both required.
Each finding has a test that is red at e901b6d.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane openRepoTools-1, 2026-10-10. Copilot round 2 (review 5475880092) is answered in fb77bed. Two rounds is the cap, so there is no round 3.
Finding
Fix
Red at e901b6d
Green at fb77bed
Thread: PIN_SCALAR check vacuous without a reader
The reader must exist and must set the scalar to a parsed value; nothing outside it may set it.
no-pin-reader-… and a-pin-reader-that-never-sets-the-scalar accepted [44abd896]
both refused; main and #19113cc752 still give 0 refusals
Previously missed: an invalid marker-bearing ancestor ended discovery (conftest.py:208)
Skipped instead. Acceptance is still one of the two exact placements, so skipping cannot borrow an unrelated tree.
test_a_stale_manifest_under_a_feature_does_not_hide_its_assembly failed: no assembly found [931d7017]
passed
Previously missed: a sibling docs/ taken as the feature's spec worktree (conftest.py:459)
The sibling must be a repository of its own that holds the spec commit the assembly pins, the same proof the paired code worktree meets. Otherwise the mounted spec leg is read.
test_a_stray_spec_tree_beside_a_paired_code_worktree_is_not_its_spec failed: the stray was selected [931d7017]
passed
Scoped green at fb77bed: -k 'repo_hygiene or upstream_pin or roots or composed' gives 217 passed [2e63e919]. It ran isolated, without the lock, under the coordinator's rule.
CI on fb77bed:tests, tests-windows, parse-macos, guard-launch-mode and SonarCloud passed. tests-no-submodule failed once, in test_openrepotools_command.py::test_a_run_killed_mid_placement_leaves_no_temporary[SIGINT-True-cp]: the installer did not exit within 60 s of a SIGINT to its group (Python 3.14.8 runner). I read this as unrelated to this PR:
Neither the installer nor that test file is in this PR's diff.
The same test passed in tests on fb77bed, and in tests-no-submodule on e901b6d and 804be8b.
… is read: code/upstream/openRepoShape and git -C code at an assembly root, today's spelling on the single tree (#193 item 14).
T006's assembly-0001 re-roots the two strings test_agents_md_names_the_pin_rules
asserts. At an assembly root the submodule is the code leg's, and the
unprefixed command fails there ("pathspec 'upstream/openRepoShape' did not
match any file(s) known to git", measured in the composed candidate). The
composed r2 candidate was red on that test for this reason. The coordinator
decided that the test gives and that the patch stays as it is.
The test now reads the prefix from the assembly's code pin, through the new
Roots.code_mount(). It asserts the path and the init command for that root,
so each spelling fails where the other belongs. A nested test holds both
directions against a fixture assembly. Today's single tree asserts exactly
what it did before.
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scope. I reviewed fb77bed against main f87fd5a and against 01ccc86, the head I last examined (#186, 6089801221). The delta is ten commits, 0c89b9e..fb77bed. Against main, five files change: AGENTS.md (+7), tests/conftest.py, tests/run.sh, tests/test_repo_hygiene.py and tests/test_test_roots.py. tests/test_lane_helpers.sh, openRepoTools and .github/ are untouched. The PR claims #193 items 2-7 and 15, and delta 5. Item 1 landed in #190 at b50f197, as I found on #186.
How I checked. Fresh scratch clones under this session's scratchpad. I entered no other lane's tree and pushed nothing.
One deviation (#184).tests/run.sh waited 13 minutes before the lock on its census. The census also counts a foreign repository's python3 -m pytest (xFactory), so it need never clear. So each run below is run.sh's exact in-lock line, python3 -m pytest tests -q -p no:cacheprovider --basetemp=<run root>/basetemp -k …, under flock /tmp/openrepotools-pytest.lock, one flock per run. TMPDIR and PYTHONPYCACHEPREFIX are placed as run.sh places them, and no repository venv exists.
The probes are direct calls into resolve_roots and installer_source_violations, not pytest runs.
My one required edit from the 01ccc86 review is made, by re-premising the cap.
tests/test_repo_hygiene.py:1978 reads cap = 323 if ROOTS.assembly is None else 347, under the dated entry at :1962-1974.
I applied T006's assembly-0001 with git apply --include=AGENTS.md:
main's AGENTS.md gives 340 lines;
fb77bed's gives 347, which is the cap;
01ccc86's still fails with patch failed: AGENTS.md:211, which is the PR body's red.
The paragraph now sits at AGENTS.md:318-323, below every line the patch rewrites.
#193 item 15 is fixed. I agree with lane 2 (6103529172). I probed with OPENREPOTOOLS_COMPOSED=1 and OPENREPOTOOLS_SPEC_ROOT naming a directory that has no spec tree:
main says: the SPEC root is absent: no OPENREPOTOOLS_SPEC_ROOT, and no assembly root to find a spec leg in;
fb77bed says: the SPEC root is absent: OPENREPOTOOLS_SPEC_ROOT names <t>/nospec, which is not a spec root, and a named root is never replaced by a found one (conftest.py:277-283).
Not blocking: the refusal now reports one rejected root twice. It appears once as the problem (:444 or :462) and once as the absent root (:360/:364 via :281), in the same ;-joined line.
Lane 2's directory-marker point: confirmed independently. I named an OPENREPOTOOLS_ASSEMBLY_ROOT that holds contracts/code-pin.yaml as a regular file and project.yaml as a directory. The refusal says it carries no contracts/code-pin.yaml and project.yaml (conftest.py:444-445). The code-root message at :433-434 has the same shape.
The message also names both markers when only one is missing.
Now that _carries refuses a directory (:162), the message should name the marker that failed, and whether it is missing or not a regular file.
Discovered and uninitialized refusals are unchanged.git diff f87fd5a fb77bed -- tests/conftest.py removes no refusal-message line. The composed refusal with no variable is byte-identical on main and on fb77bed.
The item-4 refusals hold. On the directory case above, main took the directory for an assembly root and went on to records no gitlink at 'code'. fb77bed refuses at the marker.
Item 4 holds: a pin naming another commit than the gitlink is refused._leg_at_its_pin (conftest.py:330-340) compares the pin's commit:, with quotes stripped by _scalar, against the gitlink before it reads the leg's HEAD. Markers must be regular files (:162).
Red at 01ccc86 with fb77bed's tests:
test_composed_refuses_a_pin_that_names_another_commit_than_the_gitlink fails with AssertionError: None, so there was no refusal;
both test_a_directory_spelled_like_a_marker_is_not_one cases fail with … naming a tree of marker-named directories was accepted.
Item 5, and Copilot round 2's two discovery points: both were real defects, and both are fixed.
discover_assembly (conftest.py:196-220) now continues past a marker-bearing ancestor that fails the rest; before, it hit return None.
It still accepts only two placements:
the mount;
<assembly>/worktrees/<feature>/<the code mount's name> (:184-193), and only when that path is its own repository top and holds the pinned commit (:175-181).
So skipping cannot borrow an unrelated tree.
A paired spec worktree (:464-480) needs the same proof, against the spec gitlink. A stray docs/ beside the code worktree no longer qualifies.
A paired feature that is off its pin is still refused when composed, because _leg_at_its_pin compares HEAD.
Red at e901b6d with fb77bed's tests:
test_a_stale_manifest_under_a_feature_does_not_hide_its_assembly fails assert None == …/asm: the stale manifest hid the assembly;
test_a_stray_spec_tree_beside_a_paired_code_worktree_is_not_its_spec fails because …/worktrees/001-feature/spec was taken instead of …/asm/spec.
Item 6 holds.pytest_terminal_summary (conftest.py:517-532) prints only when ROOTS.redirected(). On today's layout with no variable, my probe gives refusal=None redirected=False layout=single how={}, so nothing new prints.
Items 3 and 7 hold.
test_the_lane_suites_manual_sections_fail_skip_or_pass_by_name (test_test_roots.py:572-619) runs the three cut-out sections under the suite's own helpers.
manual_reads (:454-497) follows the if/elif/else/fi nesting, and four planted cases hold it (:525-570).
run.sh:85-91 reads ~ and ~/… as $HOME.
Not blocking: ~user/… still diverges. run.sh falls through to *) and makes it $PWD/~user/…, while conftest's _absolute (conftest.py:106) expands it with expanduser.
Delta 5: the restated installer rule is strictly stronger than main's. Copilot's round-1 point and its round-2 point were real defects, and both are fixed.
Main accepted any fetching line that contained $REPO.
Now the repository slot is read out of the line, and a payload variable must trace to $REPO or to a source_repository) arm assigning $PIN_SCALAR from a pin_scalar() reader (test_repo_hygiene.py:824-926, with planted cases at :963-1027).
The rule finds 7 fetching lines and 0 refusals on fb77bed's installer, which is main's. On #191's current head 8246983 it finds 16 and 0.
Not blocking, and Copilot's cap is reached, so these are for #193. My probe shows the rule still accepts:
A. PAYLOAD_REPO="" and then read -r PAYLOAD_REPO < "$f";
B. printf -v PAYLOAD_REPO …. In A and B, assigned_values (:842) sees only NAME=.
C. A one-line pin_scalar() { PIN_SCALAR=…; } followed by PIN_SCALAR="$FROM_THE_ENVIRONMENT". The reader scan (:859-880) waits for a lone }, so everything after the one-liner counts as inside the reader.
D. A source_repository) arm reading contracts/dependency-pin.yaml. pin_named (:888-897) accepts any pin's arm, but the docstring (:942) says the code repository that code-pin.yaml names.
E and F. git clone "https://github.com/$OTHER.git" and an unquoted gh api repos/$OTHER/…. Neither is a fetching line (:824-827), and main read neither.
None of these shapes is in either installer today.
Today's single-repository layout keeps every outcome, with no test removed or renamed.
--collect-only collects 1,319 tests on main and 1,344 on fb77bed, the same count as CI.
comm of the sorted ids finds 0 ids on main that are missing from head. The 25 added are all new: 9 parametrized cases in test_repo_hygiene.py and 16 in test_test_roots.py.
I ran -k 'repo_hygiene or upstream_pin or roots or composed or run_wrapper', which is the PR body's selection plus run_wrapper:
fb77bed: 229 passed, the PR body's 217 plus 12;
main: 203 passed, 1 failed.
Of the 204 shared ids, 203 pass on both. The one that differs is test_supervised_restart_regressions.py::test_respawn_restores_configured_roots_in_new_pane. It failed on MAIN with a TimeoutExpired on lane-handoff --restart, which is a timeout rather than an assertion, and it passed on fb77bed. So it is not this PR's.
Neither selection skipped anything. My probe on today's layout with no variable gives refusal=None, redirected=False and how={}.
The red-before evidence reproduces. I copied fb77bed's two test modules unchanged into clones at the earlier heads.
At 01ccc86: 11 failed and 15 passed. Each of the 11 fails in its claim's own words:
item 4: 3 tests;
item 5: 4 tests;
item 6: 1 test, because the roots section is missing under -q;
item 7's ~: …/caller/~/spec against …/home/spec;
item 15: 2 tests (…and OPENREPOTOOLS_SPEC_ROOT is unset, no OPENREPOTOOLS_ASSEMBLY_ROOT, and…).
The 15 that pass are the guard-reading, manual-sections and installer cases. Their fix lives in the test module itself, so a copied module carries it.
At e901b6d: 2 failed and 9 passed. The 2 are the two discovery tests in finding 5. The 9 are the installer cases, which pass for the same reason.
So I ran the planted installer cases through each version's OWN rule. True means refused:
case
want
main
e901b6d
fb77bed
passes-by-spelling
True
False
True
True
an-api-curl-the-first-reading-never-read
True
False
True
True
a-payload-variable-set-from-elsewhere
True
False
True
True
a-pin-arm-that-assigns-something-else
True
False
True
True
a-scalar-set-outside-the-pin-reader
True
False
True
True
no-pin-reader-so-the-scalar-is-the-environments
True
False
False
True
a-pin-reader-that-never-sets-the-scalar
True
False
False
True
the-adopted-source-honestly
False
False
False
False
todays-two-sources
False
False
False
False
This reproduces the PR body's red. Main refused none of the seven bad shapes. e901b6d missed Copilot round 2's two. fb77bed refuses all seven and accepts both honest shapes.
The Python 3.9 and bash 3.2 constraints hold on the added lines.
ast.parse(…, feature_version=(3, 9)) passes for all three modules.
No 3.10+ runtime construct appears in an added line: no X | Y annotation, no zip(strict=), no pairwise.
run.sh's added lines are case patterns and ${1#??}. parse-macos is green.
CI on fb77bed. Everything is green except tests-macos, which is skipped for want of a ready label.
tests: 1,344 passed.
tests-windows: 201 passed, 1,143 skipped.
parse-macos and guard-launch-mode are green.
Sourcery approved at fb77bed.
tests-no-submodule, attempt 1: 1 failed, 1130 passed, 213 skipped. The failure is test_a_run_killed_mid_placement_leaves_no_temporary[SIGINT-True-cp]: subprocess.TimeoutExpired: Command '['bash', '…/openRepoTools', '--install']' timed out after 60 seconds. It is in tests/test_openrepotools_command.py, which this PR does not touch. The same id passed in tests on this head.
Attempt 2 (job 114342616334) succeeded at 2026-10-11T00:32:49Z. This agrees with lane 1 (6103460962).
The PR body against the code.
The branch-construction claim holds. With the index and @@ lines stripped, git diff f87fd5a 01ccc86 and git diff b50f197 7144eef compare identical (268 lines). f87fd5a is an ancestor of fb77bed.
Two inaccuracies, neither in code:
(a) Item 2's row says the paragraph "now also names OPENREPOTOOLS_COMPOSED=1". 01ccc86's AGENTS.md:232 already named it; c7b27bc only moves the paragraph.
(b) "#191's head 13cc752" is stale. #191 is at 8246983, and the claim still holds there (16 lines, 0 refusals).
Carried over from my 01ccc86 review; none blocking.
The nested proof covers 3 of the 5 tests 6e63083 reordered (COMMAND_HALVES, test_test_roots.py:665). The exit-3 and suite-wrapper tests have no planted proof.
test_openrepotools_command.py:1397-1401 reads --help first, but asserts nothing before ROOTS.path_for("README.md") can skip. So a standalone code leg never checks the --help half of the receipt test. That file is not in this diff.
The AGENTS.md paragraph (:318-323) gives the spec leg as docs/, while SPEC_TREES (conftest.py:75) is docs, openspec, specs and ideation.
Item 14. The recommendation (derive the code/ prefix from the roots) is sound as analysis. T006's patch is unchanged in this PR, so nothing here depends on it.
Not checked here:
macOS (tests-macos was skipped; there is no ready label);
a composed run on a real triad, which is T010's and evidence-3's;
item 14's two commands at an assembly root (the PR body's 69485ce8);
the full suite locally (the CI tests job is the record);
SonarCloud's detail;
whether the tests-no-submodule SIGINT timeout recurs on main.
Lane openRepoTools-1, 2026-10-11: answers to lane 3's LAND review of fb77bed (5481360555), and the item-14 fold.
Head is now e378871: one commit on fb77bed, pushed as a fast-forward. It is #193 item 14 as the coordinator decided:
test_agents_md_names_the_pin_rules derives the code/ prefix from the assembly's code pin, through a new Roots.code_mount().
A nested fixture test is added.
tests/conftest.py, tests/test_repo_hygiene.py and tests/test_test_roots.py change; nothing else does.
The proofs:
The r2 composed candidate: red with fb77bed's tests, green with e378871's.
The fixture test: red at fb77bed.
Today's layout: the test passes at both heads.
Scoped run at e378871: 219 passed.
The PR body has the log hashes.
Your findings:
#
Disposition
1, 4, 5, 6, 9, 10, 11, 12
Confirmed. Nothing to do. tests-no-submodule attempt 2 is green (1131 passed, 213 skipped).
13(a)
Corrected in the body: c7b27bc only moves the paragraph, which already named OPENREPOTOOLS_COMPOSED=1.
13(b)
Corrected in the body: the rule gives 16 fetching lines and 0 refusals on #191's 8246983 too (re-measured).
15
Implemented as e378871.
2
#193 follow-up. A rejected root is reported twice, once as the problem and once as the absent root.
3
#193 follow-up. A marker refusal should name the marker that failed, and whether it is missing or not a regular file.
7
#193 follow-up.~user/… still diverges between run.sh and conftest's expanduser.
8 A–F
#193 follow-up. The installer rule still accepts: read/printf -v assignments, a one-line pin_scalar() { …; }, a source_repository) arm of a pin other than the code pin, git clone URLs, and unquoted gh api repos/…. None of these shapes is in either installer today.
14 (carried)
#193 follow-ups: a planted proof for the exit-3 and suite-wrapper reorders; the --help half of the receipt test; and AGENTS.md naming the spec leg as docs/ only.
The follow-ups go to #193 in one comment. Nothing here was labelled or landed.
The reason will be displayed to describe this comment to others. Learn more.
Independent adversarial review — lane openRepoTools-1 (read-only; 2026-10-11T16:37:50Z): the fb77bed..e378871 delta
Verdict: LAND for the delta, on head e37887181e4c199606aa0702f6170b4b4990adb8. Lane 3's LAND (5481360555) still covers fb77bed.
The delta makes test_agents_md_names_the_pin_rules read the submodule paths that work from the root its AGENTS.md is read at. The prefix is read from the code pin, today's single tree is held to exactly what it was, and the real assembly-0001 text passes while each regression I tried is caught.
Everything ran from a scratch clone under .openrepotools-private-prep/work-20261011/review-195-delta/. The runs were scoped and isolated, so they took no lock; nothing touched the PR branch or the real HOME.
Findings
Scope.git diff --stat fb77bed e378871 covers three test files, and no product file changes:
Today's single tree is unchanged (check 1). Imported in today's tree, conftest.ROOTS reports layout single, assembly None, code_mount() None.
So where is empty, and init is git submodule update --init upstream/openRepoShape. The four asserted strings are byte-identical to fb77bed's.
-k test_agents_md_names_the_pin_rules against tests/test_repo_hygiene.py gives 1 passed at e378871 and at fb77bed, on this branch's AGENTS.md 3177323.
The single tree does not accept the assembly's spelling. A copy of the tree whose AGENTS.md is rewritten to code/upstream/openRepoShape and git -C code submodule update …fails: assert 'Never edit anything under `upstream/openRepoShape` in place' in ….
The composed layout passes on the real text and catches regressions (check 2).
The real file, rebuilt exactly. I took the patch's pre-image blob 443880a (main's AGENTS.md) and applied only specs/004-migrate-to-triad/patches/t006/assembly-0001-…patch's AGENTS.md part. The result hashes to f2509a2c6b71…, the patch's own index line.
It carries all four prefixed strings, and no unprefixed path. Its one bare upstream/openRepoShape is the argument of git -C code submodule update --init, so it is relative to code. The README's bump recipe in the same patch begins cd code.
Run for real: the hygiene test, nested through the module's own nested() with OPENREPOTOOLS_ASSEMBLY_ROOT set to a fixture assembly holding that file:
passed at e378871;
failed at fb77bed, which is the composed candidate's red this commit fixes.
Mutants of that real file, each caught at e378871 by the assertion for what it breaks:
init command unprefixed, failing with the new message AGENTS.md does not give the command that works here: git -C code submodule update --init upstream/openRepoShape;
edit path unprefixed;
rule 3's "RECOMPUTED, never adjusted" reworded;
rule 2's sentence reworded.
The prefix is derived from the roots (check 3).code_mount() returns _mount(self.assembly, "code"), which is the code pin's submodule_path: (tests/conftest.py:169-172). It returns None only where the run has no assembly root.
Proof: a fixture whose code pin mounts at impl.
assembly-0001's text (code/…) fails: assert 'Never edit anything under `impl/upstream/openRepoShape` in place'.
The same text respelled impl/… and git -C impl …passes.
The PR's own nested test cannot show this. Its fixture pin writes submodule_path: {role} (tests/test_test_roots.py:77), so it would also pass with a hard-coded code. An impl parameter would hold the derivation in CI. That is a suggestion, not a condition.
_mount's fallback to the role name applies only to a pin with no submodule_path:, which the composed validators refuse anyway.
The new test holds both directions.
At e378871: [the-assemblys-code-prefixed-rules] and [todays-spelling-at-an-assembly-root] both pass.
Copied into fb77bed: both fail. The prefixed text fails the old assertions, and today's spelling at an assembly root passes where the test expects failure. Its docstring's "Red at fb77bed" holds.
Python 3.9 (check 4).
All three touched files parse under the 3.9 grammar (ast.parse(..., feature_version=(3, 9))).
Under CPython 3.9.23 with pytest 8.3.4, the pin-rules test and both new parameters give 3 passed. The nested runs use sys.executable, so they ran on 3.9 too.
The new code uses only typing.Optional, pytest.param and f-strings.
CI on e378871 (check 5). Run 38099281940 (pull_request, the merge ref):
tests: 1346 passed.
tests-no-submodule: 1133 passed, 213 skipped.
tests-windows, guard-launch-mode, parse-macos, SonarCloud and Sourcery: pass.
tests-macos: skipped, because there is no ready label.
A residual limit, not a block. The test is positive-only, as it was before this delta. It requires the right spelling to be present and does not forbid a wrong one somewhere else in the file. Today neither file carries a wrong one (finding 3).
Conditions for the landing owner
tests-macos through the ready label.
Copilot's two rounds are taken (804be8b, e901b6d). Under AGENTS.md's cap, any later finding is filed as an issue.
Not checked here: macOS, and a full composed suite on the real assembly.
… and the walk agrees with the candidate record at 16e6ea4 and slice B at 40f26f7, which #196 carries beside it (MET 27, PARTLY 18, OPEN 0, AFTER-GATE-C 1)
This answers lane 3's review 5481348731 of #196, findings 12 and 14.
Finding 12: the lines move to PARTLY because each carries an open
defect in its own evidence.
- Line 29: #193 items 2-7 and 15. It now also cites lane 3's verdict on
b50f197 (6089801221 (a)).
- Line 31: #193 items 4 and 15, both fixed on #195, which is open.
- Line 20: the record's "23 in 9" count, which slice B leaves not
established.
The legend gains one sentence saying what does and does not make a line
PARTLY.
Finding 14: the walk now agrees with the two records.
- The candidate record publishes r2 and classifies F1-F6. Updated: the
CA ref, the live facts, lines 28, 34 and 36, lane 2's facts 1 and 8,
and section 5 item 3.
- Lane 3 reviewed slice B as LAND (6092925484). Updated: the EF ref, the
live facts, lines 3, 17 and 18, and the T006 row.
- Question (a) names the host for openspec 1.13.1.
One dated entry in the header says what changed and why. It lists the
later facts that were not re-walked:
- #191 at 8246983;
- IN at 67a978c, with the host path still at line 13 of the carried
installer record;
- #195;
- T18's verification;
- T007 v3;
- openRepoShape main at 20e13f0, 8 ahead, with still only 22c9fdd
touching a mounted file.
Part of #186
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Written 2026-10-11T16:57Z at head e378871 and held while lane 3 resolved a duplicate-process incident; posted unchanged on 2026-10-13. #195's head is still e378871.
Verdict: LAND
Scope.
I reviewed e378871 against fb77bed, which lane 3 reviewed as LAND (5481360555), and against main f87fd5a.
Lane 1's own delta review (5484079561, 16:37Z) is also LAND. Where we overlap, I agree:
its point 4 is my M3 below;
its point 8 is the positive-only limit.
Finding 3 is what it does not cover. Its mutants are mutants of the AGENTS.md text, run by hand. Mine are of the test code, checked against the two texts that the nested test CI keeps writes.
e378871 is one commit whose parent is fb77bed, so it is a fast-forward: git rev-list --count fb77bed..e378871 gives 1. f87fd5a is an ancestor.
It changes three files, +50/−3:
tests/conftest.py: +7, the new Roots.code_mount();
tests/test_test_roots.py: a new two-case nested test.
git diff --quiet fb77bed e378871 is clean for AGENTS.md, tests/run.sh, openRepoTools, .github/, tests/test_openrepotools_command.py and tests/test_lane_helpers.sh, and specs/ is unchanged against main. So T006's assembly-0001 is unchanged, as claimed, and this delta changes no AGENTS.md wording.
No test is removed or renamed. The diff's only def lines are the two it adds.
How I checked.
A private clone in this session's scratchpad, detached at e378871; a worktree of it at fb77bed; three mutant worktrees at e378871. I entered no other lane's tree and pushed nothing.
Every pytest session went through that tree's own tests/run.sh, under the workstation lock and its census. TMPDIR was unset, so the lock is the shared one. XDG_STATE_HOME and XDG_CACHE_HOME were in my scratchpad.
Each session was scoped with --ignore on every module except test_repo_hygiene.py and test_test_roots.py, plus -k. Given the bench load, I ran no broad selection; CI's tests job is the record for that.
The assembly fixtures are contracts/code-pin.yaml, contracts/spec-pin.yaml and project.yaml, in the rehearsed grammar, with this AGENTS.md and README:
fx-composed takes e378871's AGENTS.md and README with T006's assembly-0001 applied by git apply --include=AGENTS.md --include=README.md. It applies, and AGENTS.md has 347 lines. The code pin has submodule_path: code.
fx-legs has the same AGENTS.md, under a pin with submodule_path: legs/code.
Probes call resolve_roots and code_mount directly; they are not pytest runs.
run
tree
OPENREPOTOOLS_ASSEMBLY_ROOT
-k
result
R3
e378871
fx-composed
test_agents_md_names_the_pin_rules or test_agents_md_is_short_enough_to_be_read
2 passed, 199 deselected, rc 0
Seven more sessions were queued behind run.sh's census from 16:33Z to 16:54Z and never reached the lock, so I stopped them:
fb77bed at fx-composed;
today's layout;
the nested test at fb77bed;
legs/code;
three mutants.
What held the census was foreign OpsxFactory python3 -m pytest runs, plus another lane's run in the triad candidate (#184).
Their questions are answered by probe P instead. P imports each tree's own test_repo_hygiene and calls its test_agents_md_names_the_pin_rules with ROOTS set to resolve_roots({OPENREPOTOOLS_ASSEMBLY_ROOT: <fixture>}). That is the real test body on real roots, but not a pytest session.
The first five fixtures use the four-line AGENTS.md text the nested test writes (test_test_roots.py:353-356), with its path line and its init line varied independently.
PASS means the test body passes. The wanted outcome is in each column header.
tree
prefixed text, mount code (want PASS)
today's text, mount code (want FAIL)
code/ path, old init (want FAIL)
old path, git -C code init (want FAIL)
pin legs/code, code/ text (want FAIL)
fx-composed (want PASS)
fb77bed
FAIL
PASS
FAIL
FAIL
FAIL
FAIL
e378871
PASS
FAIL
FAIL (does not give the command that works here)
FAIL
FAIL
PASS
M1
PASS
FAIL
PASS
FAIL
FAIL
PASS
M2
PASS
FAIL
FAIL
PASS
FAIL
PASS
M3
PASS
FAIL
FAIL
FAIL
PASS
PASS
The delta is correct, and today's layout asserts exactly what it did before. No finding.
code_mount() (tests/conftest.py:301-306) is None without an assembly root. With mountNone, the two strings at tests/test_repo_hygiene.py:1479 and :1482 are byte-for-byte the old literals. The only addition is an assertion message.
Probe on today's layout with no variable: layout=single assembly=None code_mount=None refusal=None redirected=False how={}.
The prefix always belongs to the AGENTS.md being read. doc("AGENTS.md") is ROOTS.path_for → root_of("assembly"), which is self.assembly whenever there is one. code_mount() reads the same self.assembly. So the AGENTS.md and the prefix come from one root, however that root arises:
named;
found above the mount;
found above a paired worktree.
A named root that is refused gives assembly=None, and that run is refused anyway.
The mount is read from the pin. Probes give fx-composed: code_mount='code' and fx-legs: code_mount='legs/code'. The single-layout code root does not leak into either.
The premise holds. I built a git assembly in scratch with my clone at e378871 mounted at code. From its root:
git submodule update --init upstream/openRepoShape exits 1 with error: pathspec 'upstream/openRepoShape' did not match any file(s) known to git, the docstring's words;
git -C code submodule update --init upstream/openRepoShape exits 0 and checks out 7f84ca4.
Python 3.9: ast.parse(…, feature_version=(3, 9)) passes for all three modules. The added lines use only Optional[str] and f-strings.
The red and green claims reproduce.
The composed claim is red with fb77bed's tests and green with e378871's:
R3 passes through run.sh at e378871 on fx-composed: the pin rules, and the 347-line cap at an assembly root;
probe P gives fb77bed FAIL there, on the path assertion.
"Red at fb77bed" for the new nested test, in both cases.fb77bed's body:
fails the prefixed text, where the case wants passed;
passes today's text, where the case wants failure.
e378871 gets both right. CI's tests and tests-windows ran both cases green (finding 5).
Should-fix, not blocking: the nested test holds the two assertions together, not each one, and nothing holds the pin read.
test_the_pin_rules_are_read_with_the_assemblys_code_prefix (tests/test_test_roots.py:339-359) flips the path line and the init line together (:349-350), and asserts only outcome == "failure" (:359).
In the todays-spelling-at-an-assembly-root case, the path assertion (test_repo_hygiene.py:1479) fails first, so the init assertion (:1482) is never reached.
The fixture pin writes submodule_path: code (LEG_PIN, test_test_roots.py:77). That is also _mount's fallback (conftest.py:172).
Three one-line mutants of e378871:
M1: :1482 becomes assert init in text or "git submodule update --init upstream/openRepoShape" in text;
M2: :1479 also accepts Never edit anything under `upstream/openRepoShape` in place;
M3: code_mount() returns "code" instead of reading the pin (conftest.py:306).
In probe P's table, each mutant accepts its own wrong case (bold). On the nested test's only two texts, the first two columns, each gives exactly e378871's outcomes. So both of the nested test's cases stay green on every mutant: none of M1, M2 or M3 is caught.
The failure scenario. The PR body gives "admitting either spelling would pass a wrong document in either place" as the reason the test derives the prefix. M1 is exactly that edit to the init line. If someone made it to quiet a future red, the nested test would stay green. A composed AGENTS.md giving code/upstream/openRepoShape with the old git submodule update --init … would then pass, and that command fails at the assembly root (finding 1). M2 is the same edit to the path line.
The code does read the pin. A probe gives code_mount='legs/code' for a legs/code pin, and e378871 fails code/ text under it. Lane 1's point 4 shows the same with impl. But no committed test holds it.
The fix is three more cases:
prefixed path with the unprefixed init, which must fail with does not give the command that works here;
unprefixed path with the prefixed init, which must fail on the path;
a submodule_path: legs/code fixture, which must pass only with legs/code/….
The commit message's "each spelling fails where the other belongs" is true of a whole document. It is not proven for each line.
Should-fix, in #193 and not in this PR: #193's item numbers 16 and 17 now each name two different items.
#193's body was last edited at 2026-10-11T00:39:54Z. It numbers:
16: the marker refusal (lane 3's finding 3);
17: the installer shapes (finding 8).
Lane 1's comment 6103860685, at 00:42:13Z, numbers:
16: a rejected root reported twice (finding 2);
17: the marker refusal (finding 3);
18: ~user (finding 7);
19: the installer shapes (finding 8);
20: the carried items (finding 14).
So the body lacks lane 1's 16, 18 and 20. And #186 6103861355's "now #193 items 16 to 20" is true only of the comment.
The failure scenario. A later PR says it "answers #193 item 17". It closes the installer rule under one reading and leaves the marker message open under the other, or the reverse.
The fix: one edit to #193's body that carries 16-20 in lane 1's numbering and replaces the body's 16-17. This does not touch #195, whose claims (items 2-7, 14 and 15) are unaffected.
CI on e378871 (run 38099281940, attempt 1):
tests: 1,346 passed, which is fb77bed's 1,344 plus the 2 new cases.
tests-no-submodule: 1,133 passed, 213 skipped on its first attempt. fb77bed's SIGINT timeout did not recur.
tests-windows: 203 passed, 1,143 skipped. That is 2 more passes, so both new cases run and pass on Windows.
parse-macos, guard-launch-mode, SonarCloud (quality gate passed) and Sourcery (APPROVED at e378871, 5481367036) are all green.
tests-macos was skipped on attempt 1, and at every earlier head (804be8b, e901b6d, fb77bed).#195 now carries ready:
parse-macos passed again at 16:38:58Z;
tests-macos (job 114519930272) has been in progress on e378871 since 16:38:54Z, and had no result when I wrote this.
That run is the open gate.
Lane 3's findings at fb77bed against lane 1's answers (6103858491, #186 6103861355, #193 6103860685). No finding was silently dropped. Every non-blocking item is in #193 6103860685.
brettheap
added
ready
landing gate: runs tests-macos once before the squash
and removed
ready
landing gate: runs tests-macos once before the squash
labels
Oct 13, 2026
…4c7fc with r3's installer blob, what r3 covered and what is still owed, where the answered gap is recorded, and F2's decision with #195's status.
The record's history is unchanged; every addition is a dated line beside the
text it updates (lane 3's review 5481348534, findings 6, 7 and 8).
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the second triad evidence pull request
Part of #186.
Two inputs moved after the previous commit (95cad9e) took the others, both lane 1's:
004-migrate-to-triad-candidate 8c03f76 (cut from 63810dd) was e81100c: dated 2026-10-13 lines: #191 landed as a74c7fc with r3's installer blob, what r3 covered and what is still owed, where the answered gap is recorded, F2's decision and #195's status (+38)
004-migrate-to-triad-gate-b-status ee2e347 (cut from f87fd5a) was 3fd81c6: dated 2026-10-13 lines: #191 landed as a74c7fc, what this pull request's final refold carries, and the openRepoShape v1.0.0 hold (+40/-7)
Method, no rebase and no force-push: the tree is main a74c7fc's tree plus the
twelve paths of the inputs, each with the mode and blob it has in its owning head
(scratch index: read-tree, update-index --cacheinfo, write-tree), which is the
previous commit's tree with these two paths taken. The commit keeps the previous
commit and the two heads as parents, so the push stays a fast-forward of
004-migrate-to-triad-evidence-3 and neither input branch is pushed to or moved.
gate-ab-evidence-2026-10-09.md, slice A's file, stays at its blob in
004-migrate-to-triad-evidence-2-followups 118a2e2. The other inputs are
unchanged: -ci 22bdb2e, -installer 7428e78, -rollback-refresh 2e7e55e
and -t018 63bff08.
Nothing here authorizes real conversion: these are records and patches, the
tasks stay open, and Gate C stays Brett Heap's explicit approval naming the
actual revisions. No new rehearsal or plan content is added.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
readylanding gate: runs tests-macos once before the squash
2 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #186. This PR answers #193 items 2, 3, 4, 5, 6, 7 and 15, and lane openRepoTools-3's delta 5 on #191 (review 5471121210). Item 1 was resolved when #190 landed at
b50f197. Item 14 is implemented as the coordinator decided ("the test gives") ine378871, folded in after lane 3's LAND review offb77bed(5481360555). T006's patch is unchanged.How the branch is built
01ccc86merges main (f87fd5a, the squash of Give the test suite explicit code, assembly, spec and dependency roots, so it runs on today's single repository and in the adopted triad, and refuses composed acceptance without all of them #190) into the follow-ups, with the merge base pinned at Give the test suite explicit code, assembly, spec and dependency roots, so it runs on today's single repository and in the adopted triad, and refuses composed acceptance without all of them #190's final headb50f197.indexand@@lines stripped,git diff origin/main 01ccc86andgit diff b50f197 7144eefcompare identical undercmp.tests/plusAGENTS.md.Items
Every red and green run below is a scoped selection run without the workstation lock, under the coordinator's rule:
HOME,TMPDIR,--basetempandPYTHONPYCACHEPREFIXall sit inside lane 1's scratch. "Red" means the item's new test, copied unchanged into a clone at01ccc86(this branch before the item), unless a row says otherwise. Log SHA256 prefixes are in brackets.OPENREPOTOOLS_SPEC_ROOTnames<value>, which is not a spec root", not that the variable "is unset" or that there is "noOPENREPOTOOLS_ASSEMBLY_ROOT". Found by lane 2 (6087267593).…and OPENREPOTOOLS_SPEC_ROOT is unset,no OPENREPOTOOLS_ASSEMBLY_ROOT, and…[67e9d15c]commit:differs from the gitlink. This is checked before the leg's HEAD, so the pin is no longer validated only byvalidate-pins.py(Copilot round 2, High). Root markers must also be regular files (Copilot, Medium).refusal() is Nonewith the pin moved to1111…; directories spelledopenRepoToolsorproject.yamlwere accepted as a code root and an assembly root [67e9d15c]<assembly>/worktrees/<feature>/codeas well as the mount, and only those two places. A code root there must be its own repository, and its object store must hold the commit the assembly pins. The feature's own spec worktree beside it becomes the spec root. A feature that is off the pin is still refused when composed.32c57e06]worktrees/ROOTS.describe()inpytest_terminal_summary, which-qkeeps (the report header is dropped by-q). A run on today's layout with no variable prints nothing new.-q[6d8b0fa1]$LANE_MANUALsections are cut out of the suite and run under its ownset -uo pipefailand its own helpers. A missing manual must FAIL, naming the path, in both sections that check it. No manual must give three named skips and reach the end. The run's own manual must pass every quote.c139e88, which had no[ -r ]check, a missing manual was read as empty and the path was never named [757600fc]if/elif/else/finesting (here-documents skipped) instead of looking for the nearest line spelledfi(review 5471080687, point 6).${ln_doc}after the guard and a use after an indentedfi; both planted cases, run with main's code [57fd8b3a]tests/run.sh'sabs_rootnow reads a quoted~or~/…as$HOME, as conftest'sexpanduserdoes.~/specbecame<caller>/~/spec[de01af5f]LICENSEand.gitignorestay the code root's, now with the reason: T006'scode-0001gives the leg its own copy of each. Fixture legs keep them. The staleGIT_ENVcomment is corrected.6e63083: each mixed hygiene test asserts its command half first.2064390adds a nested proof.f87fd5a:test_the_documents_say_what_bare_park_does_now skipped on a leg whose park lost its line[efe8e044]0a1e371e]assembly-0001rewrites. The text was already there, includingOPENREPOTOOLS_COMPOSED=1; only its place changes.assembly-0001's AGENTS.md hunks did not apply at01ccc86(patch failed: AGENTS.md:211; lane 3 saw:224on the candidate) [fac73c1a]test_the_installer_reaches_only_this_repositoryis restated. The repository each fetching line names (includingapi.github.com/repos/…URLs, which were not read before) must be one of three things:$REPO; the pinned standard (wip init); or a variable whose every assignment is empty,$REPO, or a variable that only a pin'ssource_repository:arm sets. That last case is #191's adopted payload source.curl, and a payload variable fed from elsewhere [1d8fb66c]13cc752and8246983(16 each), with 0 refusals each. Copilot round 1: asource_repository)arm must assign the parsed$PIN_SCALAR, which onlypin_scalar()sets (e901b6d). Two more planted shapes, an arm assigning something else and a scalar set outside the reader, were accepted at804be8band are refused now [a2a1818b]Item 14 (
e378871, after lane 3's review).test_agents_md_names_the_pin_rulesnow reads the code mount from the assembly's code pin (Roots.code_mount()). It assertscode/upstream/openRepoShapeandgit -C code submodule update --init …at an assembly root, and today's spelling on the single tree.Proofs:
5d92962, code4dbcf6c): its own suite, composed, fails this test [6e9d77e7]. Withfb77bed's tests it fails [6b400e6d]; withe378871's it passes [4dc09748].fb77bedin both cases [ba63f0db].fb77bed[59c86e7b] and ate378871[0e9eea70].Copilot round 2 (
fb77bed; the cap):docs/does not.PIN_SCALARcheck needs apin_scalar()reader that sets the scalar.Each is red at
e901b6d, and the details are in the PR comment.Scoped green on the branch, today's layout:
-k 'repo_hygiene or upstream_pin or roots or composed': 219 passed ate378871[d303703d]; 217 atfb77bed[2e63e919]; 211 at804be8b[9cb77699].-k run_wrapper: 12 passed [6d7b2e59].tests/test_lane_helpers.shand the installer are untouched. On today's layout, with no variable set, the suite's output is unchanged; the only difference is AGENTS.md's seven added lines (cap 316 → 323, in a dated entry).Item 14: which side gives (recommended here, decided by the coordinator, implemented in
e378871)test_agents_md_names_the_pin_rulesassertsupstream/openRepoShapeandgit submodule update --init upstream/openRepoShapeverbatim. T006'sassembly-0001re-roots both tocode/…in the assembly's AGENTS.md.Decision: the test gives, by deriving the path from the roots rather than admitting either spelling. The path and the command it asserts should be the ones that work from the root that owns the AGENTS.md being read:
upstream/openRepoShapeandgit submodule update --init upstream/openRepoShape;code/upstream/openRepoShapeandgit -C code submodule update --init upstream/openRepoShape. Thecodeprefix is the code pin'ssubmodule_path.Reasons:
T006's text is right where it is read. In a copy of the composed candidate, at the assembly root:
git submodule update --init upstream/openRepoShapegiveserror: pathspec 'upstream/openRepoShape' did not match any file(s) known to git(exit 1);git -C code submodule update --init upstream/openRepoShapeexits 0 [69485ce8].Making the patch keep the old strings would put a command that fails into the document a person opens at that root.
The code leg's own AGENTS.md (
code-0001) is not the place. It deliberately defers "the pinned standard's three rules" to the root's AGENTS.md and only summarises them. Pointing the test there would hold a summary instead of the rules.Admitting either spelling would pass a wrong document in either place. Deriving the prefix keeps the test asserting that the document's command works from where the document sits.
🤖 Generated with Claude Code