Skip to content

Install from the code an adopted assembly pins, resolved to one commit, and keep a single repository's install on today's reads, words and network needs. - #191

Merged
brettheap merged 11 commits into
mainfrom
feat/installer-payload-source-resolution
Oct 13, 2026
Merged

brettheap merged 11 commits into
mainfrom
feat/installer-payload-source-resolution

Conversation

@brettheap

@brettheap brettheap commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Part of #186

T007/T008 of specs/004-migrate-to-triad/tasks.md: the code side of the installer's payload-source resolution, and the 15-row compatibility matrix of specs/004-migrate-to-triad/installer-design.md as tests. The assembly root entry point cannot exist on main before the split, because its path is this file's. It is developed in a clone of the rehearsed assembly and recorded as a patch on 004-migrate-to-triad-installer. The composed tests here run it there.

The rule this PR is held to (lane 3's): on today's single-repository layout, every behaviour stays what it was: the one-liners, the WIP template fetch, refusals and exit codes. The one exception is one extra request, described below, which prints nothing.

What changes

After the triad split, the default opensoft/openRepoTools is an assembly whose root carries no payload. Every fetching install, every reinstall from an installed copy, and every installed wip init would then read the wrong tree. Measured at base, they would also install whatever the assembly root happened to carry: rows 8 and 9 below exit 0 at base having installed the assembly root's decoy files.

openRepoTools now settles where the payload comes from once, before anything is collected or placed. payload_select is called right after plan_install_targets.

Where it runs from What it does
A directory holding every payload file (a checkout, workBenches' vendored tree) Copies them with no network and no new output, exactly as before. This includes workBenches' sentinel OPENREPOTOOLS_REPO/REF run, and a tree vendored deep inside another project that has its own contracts/code-pin.yaml.
A directory holding only some (an installed copy in ~/.local/bin) Asks which layout the remote source has. For a single repository, it keeps today's sibling rule, at the ref as given. For an adopted assembly, it fetches everything from the pinned code and says the local files were not used: old siblings are never topped up with pinned skills.
stdin (the one-liners) Asks which layout the remote source has. A single repository is fetched exactly as before. An adopted assembly is resolved to one commit, and everything comes from the code it pins.
<root>/<submodule_path>: the code leg mounted in an assembly checkout The pin, the gitlink and every payload file's raw bytes (hash-object --no-filters) must name one commit; the gitlink is read from the index first, then HEAD, as openRepoShape's recorded_gitlink reads it. Each file is copied into the run's work directory first, and the copy is what is hashed and what is installed, so an edit to the checkout in between cannot be installed unchecked. It copies offline. If they do not agree, it refuses before staging.
<root>/worktrees/<NNN>/<submodule_path>: a paired code feature worktree Installs that checkout's own bytes, and names the checkout, its HEAD and any uncommitted change, saying it is NOT the pinned code. If the checkout is incomplete, it refuses.

Those two are the only places an assembly is recognised. <root> is accepted only when its contracts/code-pin.yaml mounts this directory as the code leg: the pin's submodule_path, read with the same scalar grammar pin_parse uses (quotes, a trailing # comment), must be this directory's name. A pin anywhere else above the file is another project's and is not read.

Design

  • A single repository keeps today's network needs and words.
    • It is read at OPENREPOTOOLS_REF as given, file by file: gh api …/contents first, then the raw URL.
    • It never needs api.github.com, and it needs no jq to fetch. The old fallback and the old refusal words are kept.
    • The one extra request is contracts/code-pin.yaml at $REF, asked through those same two transports. GitHub answers it 404 and it prints nothing.
    • That request is invisible, but not strictly harmless. If both transports fail to answer it with anything but a 404 (offline, 5xx, 401/403, a portal), the run refuses, naming what answered. Base would have gone on to fetch payload files, and in the offline case it refused too, naming them.
    • Heads e406e0e to 2411f40 resolved the ref and listed trees through the API in both layouts. The review's F1 measured what that cost, so 13cc752 and fdc5f18 put the single layout back on today's reads.
  • The layout is decided by an answer, never by a failure.
    • A pin that is there means the adopted layout.
    • GitHub's 404 for it means a single repository: gh names the status, and the raw URL's status is read with -w '%{http_code}'.
    • Any other answer, on both transports, is a refusal that quotes what answered.
    • Nothing more is asked on that 404. Asking for project.yaml (as 13cc752 did) would be a second request on today's layout, for a state an adopter never publishes, because the pin and the manifest land in one commit and make validate refuses one without the other. An assembly root without its pin carries no payload, so it refuses in exactly the words a single repository missing those files always got.
  • Only an adopted assembly resolves the ref once.
    • commits/<ref> (sha media type) turns OPENREPOTOOLS_REF into a commit.
    • Everything is then read again at that commit: the root and contracts/ listings (git/trees, through gh --jq or curl + jq), the pin and the gitlink.
    • If the ref moved between the probe and the resolution, the commit it resolves to is read whole: another adopted commit is installed entirely from the code it pins, never as a mix. A commit that no longer carries a pin is refused as a ref that moved. The probe decides only the layout, and its bytes are never installed.
    • Every payload request names the pinned commit, so a moving branch cannot mix two versions.
    • All of this needs api.github.com, and jq on the curl path. That is the new layout's need, not today's.
    • A pin that the listing shows but that will not come back is refused, quoting what each transport answered.
  • The pin's fixed grammar is read with read and case; there is no YAML runtime.
    • Required: schema_version: 1, kind: pinned_contract_manifest, leg_role: code, an owner/name source_repository, a single-segment submodule_path, and a 40-hex lowercase commit.
    • Also required: revision_kind: commit, digest_algorithm: sha256, digest_definition: sorted-ls-tree-r-v1, a 64-hex digests.tree_sha256, verify_pin and resync_runbook.
    • Each key it reads must appear once, the file must be LF-only, and quotes must close. A pin that is present and wrong refuses.
  • The payload repository is the pin's own source_repository.
    • A fork follows the pin it carries. No repository name is guessed by appending -code.
    • Remotely, the gitlink at submodule_path must equal commit, and that commit must be one the pinned repository answers for.
    • The pin's tree_sha256 covers the whole tree and is the composed validator's to recompute. This PR does not claim it.
  • wip init reads the code's contracts/openreposhape-pin.yaml when no pin sits beside it.
    • For a single repository, that is $REF's pin, read as before.
    • For an adopted assembly, it is the pinned code's pin, never the assembly's own contracts/.
    • A source that does not answer, a ref it lacks, or a pin it does not carry leaves openRepoShape's main, as before (the review's F2).
    • An adopted assembly whose code cannot be followed refuses at step 7, in step 7's own words: step 7 seeded nothing, and what steps 5 and 6 made is kept. It does not say that nothing was written.
  • Counts are derived from the lists. On this layout: 17 INSTALLABLES + 3 skills + 3 command files = 23 payload files, and 29 placed files + 2 hook entries = 31 artifacts. The design doc's "15 / 27 / 29" are daed209's. The three arrays stay one line each, because workBenches' estate-commands-start reads them with sed.

Unchanged for today's single repository

  • Installs from a complete tree: ./openRepoTools --install from a checkout or from any complete tree places the same files, modes and targets, writes the same receipt rows, makes the same settings merge and retirement, and prints the same output, line for line. test_r01_…installs_as_before compares a fetching install's stdout with a local one's. test_r02_…byte_for_byte runs the sentinel REPO/REF and makes no request.
  • --help and --version: --version still prints the requested REPO @ REF.
  • The network: OPENREPOTOOLS_REF as given, gh api …/contents and then the raw URL, no api.github.com and no jq for fetching. Covered by test_r01_a_single_repository_by_curl_never_needs_api_github_com[transport, rate-403], test_r01_a_single_repository_by_gh_asks_for_files_and_nothing_else and test_r06_a_single_repository_reads_every_ref_form_as_it_always_did[4 forms].
  • No jq: every file is fetched first, then the run refuses naming jq, in the old words (test_r01_a_single_repository_without_jq_fetches_then_names_jq_as_it_always_did).
  • Refusals:
    • All-or-none, and in the words c4864ac printed, compared byte for byte by test_r11_a_single_repository_missing_a_file_refuses_in_the_words_it_always_did[park, skills/restart/SKILL.md] and test_r11_a_single_repository_at_a_ref_it_lacks_refuses_in_the_words_it_always_did[gh, curl].
    • The one new refusal on this layout is when the layout question gets no answer at all (above).
  • An installed wip init: the pin at $REF; main when the source does not answer or lacks the ref; and no commits/ request. Covered by test_r13_an_installed_wip_init_on_a_single_repository_reads_its_pin_as_before, …whose_source_does_not_answer_seeds_from_main_as_before, …at_a_ref_the_source_lacks_seeds_from_main_as_before and …never_needs_a_single_repositorys_commit_lookup.
  • The meanings of OPENREPOTOOLS_REPO and OPENREPOTOOLS_REF, and the sibling rule for an incomplete local set.
  • tests/test_openrepotools_command.py: its fake gh now answers the one layout question as a real GitHub answers it for a single repository (gh: Not Found (HTTP 404) for contracts/code-pin.yaml). No assertion changed.

Compatibility matrix (tests in tests/test_install_payload_source.py)

The fixtures build their own GitHub out of bare repositories in a temp dir.

  • One fake answers gh api, gh repo and curl from them with git: commits/<ref> is rev-parse, git/trees is ls-tree, and contents and raw are cat-file.
  • It logs every request.
  • No real gh or curl is on PATH, and every $HOME is temporary.
# Row Tests At base c4864ac
1 Existing raw/gh one-liners single: …installs_as_before, …by_curl_never_needs_api_github_com[transport, rate-403], …by_gh_asks_for_files_and_nothing_else, …without_jq_fetches_then_names_jq_as_it_always_did; adopted: test_r01_the_one_liner_against_an_adopted_assembly_installs_the_pinned_code[gh, curl]; composed: …documented_one_liners_resolve_through_the_root_entry_point[openRepoShape, gh] (openRepoShape:280's line byte for byte), …root_entry_point_delegates_locally, …a_signal_reaches_the_implementation_and_its_temporaries_go single pass; adopted red
2 Offline local single / adopted checkout test_r02_a_complete_single_repository_tree_installs_offline_byte_for_byte; test_r02_a_complete_tree_inside_another_projects_assembly_installs_as_before[valid, crlf]; test_r02_an_adopted_checkout_installs_its_pinned_code_offline; test_r02_an_adopted_checkout_whose_pin_comments_its_path_is_still_its_assembly single pass; adopted red
3 Explicit local code feature test_r03_an_explicit_code_feature_checkout_installs_its_own_bytes_and_names_them; test_r03_an_incomplete_code_feature_checkout_is_refused_not_completed_from_elsewhere red
4 Installed reinstall / skill-only test_r04_an_installed_copy_reinstalls_from_the_adopted_source_without_any_checkout; test_r04_an_installed_copy_on_a_single_repository_keeps_the_sibling_rule adopted red; single passes
5 Override / adopted fork test_r05_a_single_repository_fork_named_by_the_override_installs_as_before; test_r05_an_adopted_fork_follows_its_own_pin_not_a_guessed_code_name single passes; adopted red
6 Branch, tag, commit, slash refs adopted: test_r06_every_ref_form_is_resolved_once_and_every_payload_names_one_commit[branch, tag, commit, slash]; single: test_r06_a_single_repository_reads_every_ref_form_as_it_always_did[same four] adopted red; single pass
7 Branch moves between downloads test_r07_a_branch_that_moves_between_downloads_never_mixes_versions (adopted); test_r07_a_branch_that_moves_right_after_the_layout_question_is_read_at_one_commit[adopted, single]. A single repository is read at its ref, file by file, as before; freezing it would need api.github.com. red
8 Missing pin in a single repository / partial or wrong adopted pin test_r08_a_single_repository_without_a_code_pin_is_permitted; test_r08_an_assembly_root_without_its_code_pin_refuses_as_a_single_repository_would; test_r08_a_malformed_code_pin_is_refused_never_read_as_a_single_repository[19 malformations]; test_r08_a_pin_the_gitlink_disagrees_with_is_refused; test_r08_a_pin_naming_a_commit_its_repository_lacks_is_refused; composed: test_r08_composed_the_root_entry_point_refuses_a_pin_its_gitlink_disagrees_with[gh, curl] single pass; adopted red (base exits 0 installing decoys)
9 Missing/malformed API answer, auth, transport test_r09_an_api_failure_is_a_refusal_never_a_fallback_to_a_single_repository[5 steps × 401, 403, 404, transport, garbage], without (pin, 404), which is GitHub's answer that there is no pin (row 8); test_r09_a_pin_the_listing_shows_but_that_will_not_come_back_is_refused_saying_what_answered red (base exits 0 installing decoys)
10 Changed local gitlink / payload, incomplete local set test_r10_a_local_gitlink_that_disagrees_with_the_pin_is_refused_before_staging; test_r10_a_changed_local_payload_is_refused_before_staging; test_r10_an_incomplete_local_adopted_payload_is_refused_without_a_fetch red
11 Missing command/skill/alias, failed settings merge adopted: test_r11_a_payload_file_missing_from_the_pinned_code_changes_nothing[park, skills/restart/SKILL.md, commands/swap.md], test_r11_a_settings_merge_that_cannot_be_computed_changes_nothing; single: …missing_a_file_refuses_in_the_words_it_always_did[2], …at_a_ref_it_lacks_refuses_in_the_words_it_always_did[gh, curl] adopted red; single pass
12 Modes, receipts, custom targets, foreign files, hooks test_r12_an_adopted_install_keeps_modes_receipts_targets_and_what_is_not_ours red
13 WIP init / template lookup adopted: test_r13_wip_init_takes_the_standard_from_codes_dependency_pin_not_the_assemblys, …adopted_source_wip_init_cannot_follow_refuses_in_step_7s_words; single: the four test_r13_an_installed_wip_init_… tests above adopted red (base seeded from the assembly's decoy pin commit); single pass
14 Missing nested dependency in standalone code test_r14_with_no_nested_standard_anywhere_the_dependency_pin_alone_selects; test_r14_the_documented_upstream_skip_is_unchanged; composed: test_r14_composed_acceptance_requires_the_nested_dependency red (base seeded from main); skip test passes
15 bash 3.2 / Windows-WSL test_r15_the_adopted_path_holds_under_macos_one_true_awk; test_r15_the_installer_uses_nothing_bash_3_2_lacks; test_r15_the_three_lists_stay_one_line_arrays_a_consumer_parses; composed: test_r15_composed_the_root_entry_points_pin_reader_is_the_implementations, test_r15_composed_the_root_entry_point_parses_and_uses_nothing_bash_3_2_lacks awk-shim red; static checks pass

Evidence

  • Red at base (first round): the tests were committed first (6337a52, installer still c4864ac): 75 failed, 8 passed, 6 skipped. The 8 were the single-repository preservation tests, the two static checks and the documented-skip check.
  • The review rounds' tests:
    • Each new and changed test was run against c4864ac, 2411f40, 13cc752 and fdc5f18. That is 20 tests.
    • The run is a scoped proof without the workstation lock, by the coordinator's rule: HOME, TMPDIR, --basetemp and PYTHONPYCACHEPREFIX all in scratch.
    • Results: c4864ac 19 pass, 1 fail (the adopted step-7 words, new behaviour); 2411f40 20 fail; 13cc752 16 pass, 4 fail (the project.yaml request, the parent walk ×2, the partial refusal); fdc5f18 20 pass.
    • The composed root gitlink test (×2) is red with the v2 root and green with v3 (strict composed, scoped, no lock).
  • Local green on 6568f08, a head before the final one, merged with main f87fd5a, run under the lock:
    • Run with -k 'payload_source or openrepotools_command or wip_init or install_skill_and_hook or install_by_rename or repo_hygiene or upstream_pin or test_roots', under the lock: 516 passed, 9 skipped, 5 failed.
    • The 5 are test_wip_init_and_the_lane_helpers_read_one_pointer_file_the_same_way cases in which lanes-edit.sh who exits 8 on this workstation's lane state. lanes-edit.sh and that test are outside this PR's diff, which against main is openRepoTools and two test files. CI runs them green.
  • CI on the final head 8246983 (run 38097860219, refs/pull/191/merge; also posted on Triad migration (feature 004-migrate-to-triad): execution tracking under Brett Heap's word of 2026-10-07 #186):
    • tests: 1419 passed, 9 skipped.
    • tests-no-submodule: 1199 passed, 229 skipped.
    • tests-windows: 177 passed.
    • guard-launch-mode: 165 passed.
    • parse-macos, SonarCloud and Sourcery: pass.
    • tests-macos runs on the ready label, in run 38155833862.
    • Since 6568f08, the changes are the two round-1 fixes (cddd1f7) and one test-only commit (8246983).
  • CI on the earlier head 6568f08 (run 37997688693, refs/pull/191/merge):
    • tests: 1416 passed, 9 skipped.
    • tests-no-submodule: 1196 passed, 229 skipped.
    • tests-windows: 177 passed.
    • guard-launch-mode: 165 passed.
    • parse-macos, SonarCloud and Sourcery: pass.
    • tests-macos waits on the ready label.
  • Copilot, two rounds (the cap):
    • Round 1 on 6568f08, three findings, fixed in cddd1f7:
      • a copy-time race on the mounted-leg path, fixed by installing the checked snapshot;
      • a commented submodule_path missed by pin_mounts; its new test fails at 6568f08 and passes at cddd1f7;
      • the index-then-HEAD gitlink fallback, kept: it is openRepoShape's own recorded_gitlink contract.
    • Round 2 on cddd1f7, one finding: the body overclaimed a refusal for a ref that moved. The body is corrected, and 8246983 adds a test of the behaviour.
  • Composed, strict (OPENREPOTOOLS_COMPOSED=1):
    • Run in a clone of the rehearsed assembly (run C), with root entry point v3 and this head's whole code leg mounted at the pin (assembly 4c6a0b7, code fa460f2), under the lock.
    • Selection: -k 'payload_source or repo_hygiene or openrepotools_command or install_skill_and_hook or install_by_rename'. Result: 429 passed, 1 failed.
    • The failure is test_the_root_carries_the_line_ending_rule, which reads .gitattributes from the code leg. Run C's legs carry none: T006's front-door finding, whose leg patches are on main from Triad migration evidence of 2026-10-08: run C of the adopter rehearsal at the post-landing main, and T006's root collisions and leg guidance patches #192. Nothing in this PR reads it.
    • All of this module's composed tests pass, including the root gitlink test. Details are on 004-migrate-to-triad-installer.
  • Locking: full selections ran under tests/run.sh's own lock, one flock per run, with PATH=/usr/bin:$PATH, because this profile's ~/bin/python3 is a venv without pytest. Scoped proofs of about two minutes ran without the lock, as stated above. CI remains the suite of record.

Not in this PR

🤖 Generated with Claude Code

brettheap and others added 2 commits October 8, 2026 20:51
… they describe.

T008 of specs/004-migrate-to-triad/tasks.md (#186):
installer-design.md's fifteen-row compatibility matrix, as tests that build
their own GitHub out of bare repositories and answer gh and curl from them
with git, so a moving branch, a tag, a commit and a slash-containing ref
behave as they do on GitHub and every request is logged. This checkpoint
lands the tests first, on purpose: the adopted-layout rows are red against
the installer at c4864ac, which is the red-at-base evidence the brief asks
for, and the resolver follows in the next commit.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lways did, and an adopted assembly installs the code its contracts/code-pin.yaml pins.

T007 of specs/004-migrate-to-triad/tasks.md (#186),
following installer-design.md. After the triad split the default
opensoft/openRepoTools is an assembly whose root carries no payload, so every
fetching install, every reinstall from an installed copy and every installed
`wip init` would have read the wrong tree. Now:

- A directory beside the script that holds every payload file is copied with
  no network and no new output, exactly as before (workBenches' sentinel
  REPO/REF run included). Today's one-liner resolves the ref once and fetches
  every file at that commit, so a branch that moves mid-run cannot mix two
  versions.
- Which layout a remote is, is decided by a git/trees listing that succeeded:
  a code pin present is adopted; none, with no project.yaml, is a single
  repository. Authentication, rate-limit, transport and parse failures refuse
  and are never read as the older layout.
- An adopted source: the pin's fixed grammar is checked field by field (no
  YAML runtime), the gitlink must equal the pinned commit, the pinned commit
  must exist in the pin's own source_repository (never a guessed -code name),
  and every payload byte comes from there.
- From the code leg mounted in an assembly checkout, the pin, the gitlink and
  every payload file's bytes must agree, then the files are copied offline; a
  paired feature worktree installs its own bytes and says so. Neither is ever
  completed from a network, and an installed copy on an adopted source is not
  topped up with pinned skills beside its old siblings.
- wip init reads the code's openreposhape pin from that same source.

Receipts, modes, targets, the settings merge and the retirement are untouched;
source routing finishes before anything is collected or placed.
tests/test_openrepotools_command.py's fake gh now answers the two discovery
calls a real GitHub answers for a single repository; no assertion changed.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 9, 2026

Copy link
Copy Markdown

Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 21 hours and 40 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Oct 9, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR adds installer-side payload provenance resolution for the triad migration: refs are resolved once, adopted assemblies use validated pinned code, local layouts are checked for coherence, and legacy installs remain byte-for-byte compatible. A 15-row test matrix and updated GitHub fakes exercise source selection, refusal safety, provenance, portability, and downstream integration behavior.

File-Level Changes

Change Details Files
Implement payload-source selection and immutable ref resolution for legacy repositories, adopted assemblies, installed copies, stdin installs, and local code checkouts.
  • Resolve the requested ref once to a commit and use that commit for discovery and every payload download.
  • Detect legacy versus adopted layouts from successful remote listings, refusing partial, malformed, or unavailable metadata instead of falling back.
  • Parse and validate the fixed code-pin manifest, including repository, gitlink, commit, digest metadata, and required fields.
  • Select pinned code for adopted assemblies and installed copies, preserve the legacy sibling behavior for complete or incomplete single-repository trees, and support explicit feature checkouts with local coherence checks.
  • Resolve wip-init templates through the selected code leg's dependency pin.
  • Preserve staging atomicity, output, modes, receipts, targets, hooks, settings merging, and byte-for-byte legacy behavior.
  • Keep payload arrays compatible with downstream sed-based consumers and remain parseable under bash 3.2/macOS awk constraints.
openRepoTools
Add a comprehensive compatibility matrix backed by isolated fake GitHub repositories and request logging.
  • Cover remote and offline installs across single, adopted, installed, forked, pinned, feature-checkout, and wip-init scenarios.
  • Verify ref resolution, branch movement consistency, API and transport refusal behavior, malformed pin handling, local integrity checks, missing payloads, and all-or-none installation guarantees.
  • Assert modes, receipts, custom destinations, foreign files, hooks, settings merges, and byte-level payload provenance.
  • Add bash portability, one-line array, composed-entry-point, signal cleanup, and nested dependency coverage.
  • Model GitHub gh/curl responses from bare repositories without real network access.
tests/test_install_payload_source.py
Update existing fetch test doubles to model the installer's initial commit and tree-discovery requests.
  • Return a fixed commit for ref resolution and a root listing representing a legacy single repository.
  • Leave existing assertions and served payload behavior unchanged.
tests/test_openrepotools_command.py

Tips and commands

Interacting with Sourcery

  • 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!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

…O, so each fetching line names it as the hygiene test requires.

CI on #191 failed test_the_installer_reaches_only_this_repository (tests/
test_repo_hygiene.py): every `gh api "` and raw-URL line in openRepoTools must
name $REPO or $SHAPE_REPOSITORY, and the resolver's helpers took the
repository as an argument. They now read ${PAYLOAD_REPO:-$REPO}, the run's one
current source: $REPO itself while its ref, tree and code pin are read, and
the repository that pin names only after the pin has passed every check.
fetch_from_repo is back to its original two arguments. No behaviour changes;
the only repository other than $REPO this reaches is the one $REPO's own
validated pin names.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 9, 2026
…the Gate A/B evidence index and the follow-up patch inventory: both rollbacks return every placed file, mode and hook to main's install, the copy-back leaves the receipt stale, and nothing real was written.

Steps 2-4 of T011 in specs/004-migrate-to-triad/gate-ab-evidence-2026-10-09.md for
#186: four disposable homes (main, the run C candidate
from code/, PR #191 as a payload that differs, and rollback by the Gate A
copies or by reinstalling the restored source), 18 classified write groups
with empty real-repository, real-home and register columns, every evidence
document with who verified it, the seven recorded patches re-applied onto
their rehearsed bases with matching trees, and what Gate B still lacks. The
private receipt ID is rollback-20261009-ILvoVC; its commit follows.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dopted install needs nothing a minimal PATH lacks.

Found by running the existing suites composed, with this checkout mounted as
the rehearsed assembly's code leg (run C): test_without_jq_it_refuses_and_
places_nothing runs --install on a PATH of sixteen tools and no awk, and from
the mounted leg the installer reached recorded_gitlink's awk before the jq
refusal it asserts. On today's single-repository layout that path is never
taken, so neither a standalone run nor CI could see it. The assembly entry
point carries the same function byte for byte and changes with it.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@brettheap brettheap left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review — lane openRepoTools-3 (read-only; 2026-10-09T02:15:51Z)

Verdict: DO NOT LAND. The single-repository fetching path changes behaviour on today's layout, which makes it a Gate C item under the review rule. openRepoTools:839/:853 → payload_resolve_remote :635/:643/:650 now make three api.github.com round trips before any payload. On a machine with no gh where api.github.com answers 403, the curl one-liner installs at main and refuses at this head.

Reviewed head: 2411f4099b0c843b57eb2288d55fb7f2a87be39e. The head moved twice while this review ran: e406e0e → ecd7aa4 (00:46Z) → 2411f40 (01:02Z), and both new commits touch openRepoTools only. Main is now 63810dd (#189, specs/ only; its openRepoTools blob 9b7cbd2 is 837928a's). CI runs the merge ref 426fbcd (main + this head, openRepoShape 7f84ca4). Every check was run from a fresh scratch clone, never from lane 1's trees. Some measurements were taken on e406e0e and repeated on 2411f40; each finding says which.

The offline install from a checkout is byte for byte (finding 2). The hygiene failure of e406e0e is cured on this head (finding 1). The block is finding 3.

Findings, numbered.

  1. CI, and the hygiene test cure.

    • CI on 2411f40: guard-launch-mode success (-k 'guard_launch_mode or repo_hygiene'), tests-windows success, parse-macos success, SonarCloud success. tests-macos was skipped (no ready label). The Linux record is complete. tests: success, 1379 passed, 7 skipped (main: 1296 passed). tests-no-submodule: success, 1165 passed, 221 skipped (main: 1084 passed, 212 skipped). The counts reconcile: the 1296 existing tests keep their outcomes, and the 90 new ones are 83 pass + 7 composed skips (81 pass + 9 skips without the submodule).
    • On e406e0e the same test was red on all four test jobs. tests/test_repo_hygiene.py::test_the_installer_reaches_only_this_repository (:810-813) failed there: tests 1 failed / 1378 passed / 7 skipped, tests-no-submodule 1 failed / 1164 passed / 221 skipped, plus guard-launch-mode and tests-windows. Locally that head gave 152 passed at main against 1 failed / 151 passed at its merge.
    • What cured it. ecd7aa4 spells every fetching line ${PAYLOAD_REPO:-$REPO} (:267, :273, :539, :552, :580, :593). That string contains the substring $REPO, which is all the check looks for.
    • On today's layout the cure is honest. PAYLOAD_REPO is assigned in exactly two places: $REPO (:634), and $PIN_SOURCE_REPOSITORY (:690), which happens only after the pin has passed pin_parse and the gitlink check. A single repository never reaches :690, so every fetch is $REPO's in substance.
    • For the adopted path the cure is by spelling. There the payload comes from another repository while each line still "names $REPO". The guard can no longer tell ${ANY:-$REPO} from $REPO. The test's own text ("from $OPENREPOTOOLS_REPO and from nowhere else", "Every other fetch in this file is still $REPO's") no longer describes what the code does.
    • Not a block on today's layout: the outcome is kept and the behaviour behind it is honest. T009 should restate the rule: allow exactly $REPO and the validated pin's source_repository, and pin PAYLOAD_REPO's two assignment sites.
    • Blind spot. The guard's fetching_lines still does not scan the two curl … https://api.github.com/… lines (:552, :593).
  2. Byte-for-byte on today's layout: identical, offline (re-measured on 2411f40).

    • Setup: fresh HOMEs, env -i, PATH=<shims>:/usr/bin:/bin with a logging gh and curl that both exit 1 (no real gh on that PATH), umask 022. ./openRepoTools --install at main 63810dd, at 2411f40 and at the merge 426fbcd.
    • Exit and output: all three exit 0 with 37 lines of stdout, identical after the HOME path is normalized, and 0 requests to either shim.
    • Placed tree: 48 entries with identical types and modes. 31 files, of which 29 are byte-identical: the 16 other INSTALLABLES, 6 skill files, 6 command files, and settings.json, which is identical, mode 600, with the same two hook entries.
    • The two that differ, by construction:
      • ~/.local/bin/openRepoTools is each tree's own source: blob 9b7cbd2 at main, 04769c4 here, mode 755 both.
      • installed.tsv (29 rows, mode 600) differs in that one row's sha256 (118cc2cc…, the head's source) and in the stamps. Every other <name> <path> <sha256> row is identical.
    • Also checked on e406e0e: a reinstall over a main install gives identical trees and output except updated for the changed file. A one-liner through a fake curl serving today's tree, with the API answering, places the same bytes on both heads.
    • Left to the suite: retirement and the merge into an existing settings file (test_install_skill_and_hook, test_install_by_rename).
  3. Behaviour change on today's layout, fetching path (Gate C item; re-measured on 2411f40).

    • Which runs: payload_select sends stdin runs (:853) and every incomplete local set (:839) through payload_resolve_remote. Every installed-copy reinstall is an incomplete local set.
    • What it asks: on today's repository, commits/main (sha media type, :635), then git/trees/<commit> (:643), then git/trees/<contracts> (:650, because today's root carries contracts/), all before any payload. The curl fallback therefore now needs api.github.com, and jq for the listing (:591). At main it needed only raw.githubusercontent.com.
    • Measured with a fake curl, no gh, and the merge ref served as main:
      • API answering: main makes 24 raw requests and 0 API requests; this head makes 24 raw and 3 API.
      • API answering 403 (the unauthenticated limit is 60 requests an hour per IP): main exits 0 with all 29 placed; this head exits 2 with REFUSED: could not resolve opensoft/openRepoTools at main to a commit.
      • Offline, the installed copy now asks api.github.com/…/commits/main instead of the raw skill URL.
    • The body undersells it. It calls ?ref=<sha> "the one deliberate behaviour change". There are also new round trips, a new endpoint dependency, and new refusals: is_slug "$REPO" (:631) refuses overrides main accepted, for example an EMU owner with _. wip init from an installed copy now refuses an unresolvable source (:3108) where base fell back to main.
    • Design and lane 3: installer-design.md:30-31 keeps the established path for a legacy monorepo. Lane 3's §4.10 says a pre-cutover code-pin probe "would add a network request to every remote install today … That is T007 proper".
    • Before landing on today's layout: gate the probe to adopted sources only, or hold this until Gate C.
  4. The parent-directory walk changes a complete tree's install under any assembly (assembly_above :714-725, consulted at :825 BEFORE the completeness check; re-measured on 2411f40).

    • What it does: it walks every ancestor directory and never checks that a pin it finds belongs to this checkout.
    • Measured: I placed today's payload (git archive 2411f40) at <dir>/vendor/ort, under a <dir>/contracts/code-pin.yaml that pins opensoft/someProduct-code.
      • LF pin: this head installs and adds a 38th line, source: the code checkout …/vendor/ort … NOT the code <dir> pins …. Main installs silently with 37 lines.
      • The same pin in CRLF: this head exits 2 (line 1 ends in a carriage return), and main installs.
    • Reach: nothing in today's estate triggers it; no ancestor of any checkout or of ~/.local/bin carries contracts/, checked. But it is the vendored-tree case (consumer row W5) once a consumer adopts, and it breaks the body's claim "a directory holding every payload file … no new output, exactly as before".
    • Fix: bound the walk to the mount parent and <root>/worktrees/*/code, or match the pin's submodule_path and source_repository to this checkout, and check completeness first.
  5. The resolution core holds.

    • Parser: pin_parse (:438-514) gives the same result under real bash 3.2.57 (docker bash:3.2) as under 5.2 on 26 cases. The standard's own templates/assembly-root/contracts/code-pin.yaml at 7f84ca4, materialized, parses OK.
    • Refused, as it should be: a commit only on a comment line; an empty, bare, trailing-garbage or backslash commit; opensoft/../x; ../opensoft; ../code; ..; a duplicate tree_sha256; ---; a quoted key.
    • Lenient but harmless: verify_pin: | (only presence is read) and schema_version: "1".
    • Remote checks:
      • A gitlink that disagrees with the pin refuses (:685).
      • The pinned commit is checked through commits/<sha> in the pin's repository (:691). GitHub serves fork-network commits under the parent's name, so "a commit the pinned repository answers for", the body's wording, is the accurate claim.
      • There is no fallback to REPO/REF on an unreadable pin, and every payload request after resolution names the 40-hex commit.
    • Smaller points:
      • fetch_from_repo's ${PAYLOAD_REPO:-$REPO}/${PAYLOAD_REV:-$REF} (:267, :273) would silently fetch at the unresolved ref for a caller that skipped payload_select. That path is latent: all callers (:1565, :1970, :1984, :3108's resolver) resolve first.
      • The one-liner still runs an installer it fetched at the moving ref, before resolution.
      • git status --porcelain (:811) may take index.lock in the user's checkout. --no-optional-locks avoids it.
      • The header still calls fetch_from_repo a "DELIBERATE COPY" of openRepoShape's shim (:250), which it no longer is.
      • 2411f40's recorded_gitlink (:731) reads with read … <<<. bash 3.2 has here-strings, and bash before 5.1 backs them with a file in $TMPDIR, which the install already needs for mktemp -d.
    • Untouched: receipts, the settings merge and retirement.
  6. Bash 3.2: clean on 2411f40. bash -n passes under 3.2.57. The added lines (c4864ac..2411f40) contain 0 bash-4 constructs, and parse-macos succeeded. 3.2 runtime was exercised only for the parser functions. tests-macos was skipped, so no CI job has run this head under 3.2.

  7. Tests.

    • Collection at e406e0e (--collect-only -k 'openrepotools_command or payload_source'): main 97/1296, merge 187/1386. The 97 test_openrepotools_command.py node ids are identical, and the +90 is exactly the new module. ecd7aa4 and 2411f40 change no test file.
    • Runs: At 2411f40 itself (submodule 39d5c98), -k 'openrepotools_command or payload_source or repo_hygiene' gave 332 passed, 7 skipped (the composed cases), 0 failed (20m29s): 97 + 83 + 152. Earlier, at e406e0e's merge, -k 'openrepotools_command or payload_source' gave 180 passed, 7 skipped, 0 failed.
    • test_openrepotools_command.py: 0 removed lines; only two fake-gh routes were added.
    • No network: no real gh or curl is reachable (path_farm leaves both out), GH_TOKEN/GITHUB_TOKEN are dropped, and every HOME is a temporary directory.
    • Fixtures are hand-written: PIN_TEXT (:229) and PROJECT_YAML (:255) are hand-written copies, not derived from upstream/openRepoShape/templates/assembly-root/{contracts/code-pin.yaml,project.yaml} at 7f84ca4. The standard's template parses today (finding 5), but a grammar drift would not fail a test.
    • Matrix coverage (installer-design.md:73-89):
      • Rows 1-15 each have tests. The 7 composed cases skip in standalone code, and row 15's WSL side is WINDOWS_SKIP only.
      • The fake root in test_openrepotools_command.py has no contracts/, so today's third call is exercised only by test_r08_a_single_repository_without_a_code_pin_is_permitted.
  8. Overlap.

    • #190 (c139e88, same base c4864ac):
      • It merges with e406e0e onto main with no textual conflict (trial merge in scratch). The combined tree collects the same 187 ids out of 1405, and the two later commits touch no test file.
      • It keeps REPO = CODE_ROOT and WINDOWS_SKIP, so this module's import holds.
      • This module carries its own composed-context discovery (ASSEMBLY = REPO.parent, a regex on code-pin.yaml, OPENREPOTOOLS_COMPOSED), parallel to #190's ROOTS/OPENREPOTOOLS_ASSEMBLY_ROOT. The two can disagree; converge on ROOTS after #190.
    • Lane 3's docs/triad-lane-tooling-analysis.md §4.10 (origin/feat/triad-lane-tooling c45a452):
      • (a) It argues against a pre-cutover probe, which this head is (finding 3).
      • (b) Its root sketch hands the delegate OPENREPOTOOLS_REPO/REF = the code repository and commit. This installer would re-resolve that commit and read the code repository as single: compatible, at three more API calls.
      • (c) Its local check requires code HEAD = pin. This head checks pin = gitlink plus payload blobs = the pinned bytes, without HEAD: compatible, and stronger on bytes.
      • (d) It lands the entry point with the root split. This lands the code side before it.
  9. Commit messages and body against the tree.

    • These hold:
      • the counts: 17 + 3 + 3 = 23 payload files, 29 placed + 2 hooks = 31, receipt 29;
      • "no assertion changed";
      • --version unchanged;
      • refusals exit 2;
      • one commit per remote install;
      • ecd7aa4's "No behaviour changes": the requests and placed bytes are the same on both heads.
    • These do not:
      • "no new output, exactly as before" for any complete tree (finding 4);
      • "the one deliberate behaviour change" (finding 3);
      • "the two discovery calls a real GitHub answers for a single repository": today's repository gets three, because contracts/ exists.
    • Unverifiable: the root entry point and the composed evidence are said to be on 004-migrate-to-triad-installer, which is not on origin (git ls-remote is empty).
    • Unchanged since open: the author's body. Only the Sourcery summary was regenerated.

Not checked here:

  • Lane 2's work: lane 2's T008 fifteen-row compatibility matrix (fresh HOMEs, fake gh/curl). That includes the red-at-base counts (75 failed / 8 passed / 6 skipped) and the composed rows, which need the unpublished 004-migrate-to-triad-installer.
  • macOS: tests-macos waits on the ready label.
  • Lock deviation: tests/run.sh waited on a workstation-wide python3 -m pytest census that other projects' suites kept above zero. So local runs took flock on /tmp/openrepotools-pytest.lock and ran run.sh's in-lock invocation (python3 -m pytest tests -q -p no:cacheprovider --basetemp=<scratch run root>, with TMPDIR and pycache in the scratchpad and PATH=/usr/bin:/bin). CI is the record. tests/test_lane_helpers.sh was not run.

Review posted by lane openRepoTools-3 as reviewer of record for #191 (a comment, not an approval). Lane 1 owns the landing order (#186).

@brettheap brettheap left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review — lane openRepoTools-1 (read-only; 2026-10-09T02:42:06Z)

Verdict: DO NOT LAND, on head 2411f4099b0c843b57eb2288d55fb7f2a87be39e. The head moved during the review from ecd7aa4 to 2411f40. The one-commit delta moves recorded_gitlink from awk to read (openRepoTools:731-743); I re-reviewed it, and every result below is at 2411f40.

Everything was run from fresh scratch clones under .openrepotools-private-prep/work-20261008/review-191/:

  • base c4864ac (the PR's merge-base);
  • head 2411f40;
  • a "red" tree: head's tests with base's openRepoTools;
  • a local, never-pushed merge of the head into main 63810dd. It merges clean, and the merge carries #188's pin, 7f84ca4.

Nothing touched the PR branch, any worktree, the real HOME or a real repository. Every pytest run and scenario run happened inside unshare -c -n: uid 1000, no DAC override, and a network namespace with no route out. A test that reached for the network would have failed rather than succeeded.

Why DO NOT LAND. Lane 3's rule binds every migration PR before Gate C: on today's single-repository layout, every behaviour, including the one-liners, the WIP template fetch, refusals and exit codes, stays what it was. Every local path holds that rule byte for byte (finding 2). Two fetching paths do not, and I reproduced both at base against head (findings 3 and 4). In each, a run that exits 0 at base exits 2 at head on today's layout. The body names ?ref=<sha> as "the one deliberate behaviour change" for a single repository and discloses neither of these. Both are fixable, or can be accepted explicitly by the rule's owner; the paths to LAND are under the findings. Everything else in the PR held under every check I could throw at it.

Findings

  1. Scope. git diff --stat c4864ac...2411f40: openRepoTools +620/-22, tests/test_install_payload_source.py +1633 (new), tests/test_openrepotools_command.py +14.

    • fetch_from_repo (:264): both URLs now read ${PAYLOAD_REPO:-$REPO} and ${PAYLOAD_REV:-$REF}.
    • collect_commands (:1544) and collect_skills (:1959): they read PAYLOAD_DIR/PAYLOAD_MODE from payload_select instead of computing selfdir. A local source is never completed from a network.
    • install_commands (:1693): calls payload_select (:1712) right after plan_install_targets, and its refusals name $(payload_where).
    • wip_shape_ref (:3093): calls payload_resolve_remote (:3108) before fetching the code's contracts/openreposhape-pin.yaml.
    • wip_init (:3568): ref="$(wip_shape_ref)" || exit "$?", which is equivalent to the old line under set -e.
    • Unchanged: wip_fetch_template_file, receipts, the merge, retire.
    • New, :283-857: the pin grammar, api_sha/api_tree, payload_resolve_remote, assembly_above, recorded_gitlink, payload_local_adopted/_feature, payload_select.
    • The three arrays are still one line each. workBenches' estate-commands-start sed reader returns all 17/3/3 names from head.
  2. Byte for byte on today's layout, local paths: HOLDS. Method: env -i HOME=<same path each run> PATH=<shims>:/usr/bin:/bin XDG_CONFIG_HOME=… XDG_DATA_HOME=…, with gh and curl shims that log and exit non-zero. Each scenario ran once with base openRepoTools and once with head openRepoTools.

    • Scenarios: a fresh install from a checkout; a reinstall over it; retirement of a recorded restart (removed) and of a foreign restart (named and left); an unparseable settings.json (refused, exit 2); workBenches' sentinel OPENREPOTOOLS_REPO/OPENREPOTOOLS_REF run from a complete vendored tree, twice; and an install over a base-installed HOME.
    • Identical in every scenario: stdout, stderr, exit code, settings.json, and every placed file's sha256 and mode.
    • The receipt has 29 rows, identical apart from timestamps.
    • The only differences: the implementation's own bytes (by design), the receipt file's digest (timestamps), and the upgrade line openRepoTools: updated at ….
    • Every shim log is 0 bytes.
    • The README's and workBenches' sentinel path is untouched (setup-estate-commands.sh:377-405, estate-commands-start sentinel at :240, read at workBenches ffd5516).
  3. F1, BLOCKS: the curl one-liner on today's layout now depends on api.github.com. This is the fallback for a machine without a working gh, and it is the line openRepoShape:280 at 7f84ca4 points people to.

    • Request log against the PR's own fake GitHub, today's layout, no gh:
      • base: 23 raw.githubusercontent.com fetches and nothing else.
      • head: api.github.com/…/commits/main, …/git/trees/<root> and …/git/trees/<contracts> first. Today's root does have contracts/. Then 23 raw fetches at the sha. Placed bytes and stdout are identical when the API answers.
    • With api.github.com answering 403 "API rate limit exceeded" and raw answering normally:
      • base: rc 0, 29 placed.
      • head: rc 2, nothing placed. Message: could not resolve opensoft/openRepoTools at main to a commit … curl: (22) The requested URL returned error: 403 (:635).
      • The same split reproduces with gh installed but unauthenticated (401).
    • Against the real GitHub, read-only, unauthenticated:
      • /rate_limit shows core.limit 60 per IP.
      • One curl-only install of this PR branch (a slash ref) from a disposable HOME: rc 0, all 29 files byte-identical to the head clone, remaining 59→56.
      • So 3 unauthenticated core calls per run, and at most 20 curl-path installs per IP per hour, where base spent none.
    • Without jq (and without gh): both exit 2 with nothing placed. Base fetches all 23 files first and then names jq for the hook merge. Head refuses after one request with curl: not asked, because reading api.github.com's JSON needs `jq` and there is none (:592). This is a different refusal text, not blocking by itself.
    • To land, one of:
      • (a) Decide the layout through the existing transport pair, without the API. Fetch contracts/code-pin.yaml (and project.yaml) at $REF, gh contents first and then raw. Read the HTTP status: an HTTP 404 from the transport that answered means absent; anything else (401/403/429/5xx/000/garbage) refuses. Use the API only once a pin is present, which the adopted path needs anyway for the gitlink. Today's layout then keeps its raw-only path, with no ?ref=<sha> change for a single repository. The design text permits this: its legacy row is "retain the established owner/repository/ref override path".
      • (b) An explicit ruling from the rule's owner that the API dependency, the 3 calls per run and the earlier jq need are the accepted price of the frozen ref on today's layout, stated in the body and in the usage/README text.
  4. F2, BLOCKS: an installed wip init on today's layout refuses where base succeeded, after it has already created and cloned the workspace repository. Method: an installed copy (no pin beside it), the PR's fake GitHub, a single repository whose contracts/openreposhape-pin.yaml names a pinned openRepoShape commit, and a decoy on openRepoShape main.

    • Normal network: identical at base and head; both seeded from the pinned commit.
    • OPENREPOTOOLS_REF=no-such-ref: base rc 0, seeded from openRepoShape main (the decoy). Head rc 2.
    • A transient 502 on repos/opensoft/openRepoTools/commits/main: base rc 0, seeded from the pinned commit, because its pin fetch never needed that endpoint. Head rc 2.
    • In both head refusals, steps 1-6 had already run: opensoft/brettheap-wip exists on the fake GitHub and its clone is on disk, unseeded, and no workspace.yaml was written.
    • The refusal is payload_resolve_remote's --install text, raised from :3108 inside $(…) at :3568: "so nothing was fetched, placed or written". In this context that sentence is false. A re-run is idempotent, so the state is recoverable, but the exit code and the message both changed.
    • To land: resolve the template source before wip init's first creating act, so a refusal really is "nothing created"; or give wip init its own refusal naming what this run already did. In either case, disclose that an unresolvable source now refuses where main used to be the answer.
  5. Adopted path (check 3): HOLDS. I built 28 scenarios myself. The fixture is a fake assembly with contracts/code-pin.yaml in the brief's grammar, project.yaml, a code gitlink, and a code repository holding head's payload, served by the module's fake gh/curl. Each run went over a prior complete install. "Nothing placed" was proven as a full-HOME snapshot (every file's bytes and mode, the receipt and settings.json) identical before and after, with 0 payload requests.

    • Valid pin: all 29 placed files hold the pinned code's bytes, and the assembly root's decoys never install.
    • Gitlink different from the pin: refused.
    • Nine malformed pins of my own, refused:
      • source_repository: "a/b; touch PWNED; echo pwned";
      • source_repository: "a/$(touch PWNED2)";
      • submodule_path: ../x and submodule_path: ..;
      • ?ref= in the slug;
      • a duplicate commit;
      • leg_role: spec;
      • a duplicate tree_sha256;
      • a backslash value.
        No PWNED file was created anywhere and nothing echoed. A tab after a colon is accepted, correctly.
    • Transport failure and 500 at each of commits/<ref>, git/trees, the pin fetch and the code repository's commits/<pin>: refused, with no legacy fallback.
    • The assembly branch moved after 2 requests, and separately the code branch moved: every file holds the first, frozen identity.
    • A clean listing with no pin: the single-repository path.
    • A pin that is listed but then 404s: refused.
    • code recorded as a tree instead of a gitlink: refused.
    • gh returning garbage while curl+jq answer: installs the pinned code.
    • All 28 also pass at runtime under a locally built bash 3.2.57 (finding 9).
  6. Pin-grammar fidelity. Head's pin_parse, extracted and run on every adopter-written contracts/code-pin.yaml on this workstation, accepts all nine (MedxGlass, InkRouter IRSS/IRRS, openXdox ×2, openDox ×2, MedxEHR, openWallet) plus the October rehearsal assembly's pin.

  7. Latent risk, not a block: assembly_above (:714) walks every ancestor to /. Any ancestor carrying contracts/code-pin.yaml, even another project's, takes over the decision for a complete single-repository tree.

    • Fixture: workBenches' own layout, a complete vendored tree at <someTriad>/code/devBenches/base-image/files/openrepotools, run with the sentinel.
    • Base installs silently.
    • Head with a valid foreign pin installs, but prints a new line: "source: the code checkout … NOT the code pins, opensoft/someTriad-code at …".
    • Head with that pin in CRLF refuses (rc 2) where base installs.
    • Not live today: none of the nine assemblies here holds an openRepoTools tree, and workBenches is not adopted.
    • Suggest accepting an ancestor only when this directory is <root>/<submodule_path> or <root>/worktrees/<seg>/<submodule_path>.
  8. Red/green (check 4). Suite runs: PATH=/usr/bin:$PATH flock "${TMPDIR:-/tmp}/openrepotools-pytest.lock" python3 -m pytest tests -q -p no:cacheprovider -k <selector>, inside the no-network namespace.

    • Red, head's tests against base's installer, -k payload_source: 75 failed, 8 passed, 7 skipped.
      • The 8 passes are exactly the body's: r01 single, r02 single, r04 single, r05 single, r08 single-permitted, r14 documented skip, r15 static ×2.
      • First failures checked: r07[single] installed mixed versions; r09[resolve-transport] and r08[repository-traversal] exited 0 having installed decoys; r13 seeded from the decoy.
    • Green: CI is the record. Run 37869321635 is a pull_request run on 2411f40 and checks out refs/pull/191/merge, the head merged into main 63810dd.
      • tests: 1379 passed, 7 skipped.
      • tests-no-submodule, tests-windows, parse-macos and guard-launch-mode pass.
      • tests-macos skipped; there is no ready label.
    • Local green at head under the lock: not obtained. Two bounded attempts, 02:05:26-02:20:27Z and 02:26:49-02:41:49Z, each hit the brief's 15-minute bound waiting for the lock.
    • The lock was held throughout by another lane's full-suite run, 28 minutes in at 02:26Z, with three waiters queued. My merge waiter was stopped at the first bound; the merge result is CI's refs/pull/191/merge run above.
    • Green outside the suite: the 28 scenarios of finding 5, plus 12 rows called directly from my own harness, 12/12 under Python 3.9.23 with bash 5.2. The rows: r02 ×2, r03 ×2, r04 ×2, r10 ×3, r12, r13, r14.
  9. Portability (check 5).

    • bash -n and bash --posix -n pass under bash 5.2 and under bash 3.2.57.
    • The 620 added lines contain no ${x,,}/${x^^}, mapfile/readarray, declare -A/-n, local -n, read -a, [[ -v, |&, coproc, ;&, @Q, wait -n or printf -v. Here-strings and ${v:0:1} are 3.2-legal.
    • One awk -v remains (tree_entry, :604), carrying one segment or a fixed name.
    • There is no grep in the diff, so no exit-status trap.
    • The runtime check that counts is CI's own. tests-macos runs the suite with "bash on PATH : /bin/bash", Apple's 3.2.57(1)-release. Main run 37842584034 at 837928a passed 1291 tests that way. So the ready-label gate is a real bash 3.2 runtime run of this PR, and the only one.
    • Locally I built bash 3.2.57 from the GNU tarball (sha256 3fa9daf8…). Its parser comes from the shipped, stale y.tab.c, so it is not a faithful copy of Apple's build.
      • Under it, every install-path scenario passes (finding 5), and F1's matrix reproduces exactly as under bash 5.
      • It exits under set -e on command false || x=1, which Apple's evidently does not: that same macOS run passed 1291 with only 5 skips, wip-init's tests among the passes. That kills wip init at its hygiene step at base and at head alike, so I count it as an artifact of my build, not a finding.
    • The module is WINDOWS_SKIP, and tests-windows is green.
    • Python 3.9: the module parses under the 3.9 grammar, and the 12 directly called rows of finding 8 pass under 3.9.23.
  10. Security (check 6).

    • Pin values are only ever validated data: is_slug/is_segment/is_lower_hex (:375-395) use written-out alphabets, not locale ranges. There is no eval in the diff.
    • $OPENREPOTOOLS_REPO itself is checked before any request: the sentinel on a fetching path refuses with 0 requests, where base made 2. Both exit 2.
    • API_WHY quotes only the first stderr line of gh/curl, and neither prints a token there.
    • The module's env_for strips GH_TOKEN/GITHUB_TOKEN, its PATH farm contains no real gh/curl, and every module test I ran (the red run and the direct calls) ran inside the network namespace without needing one.
  11. Copilot (check 7). None. #191 has 0 reviews and 0 review threads (GraphQL totalCount 0).

    • Sourcery's review budget is exhausted; it posted only a guide.
    • SonarCloud's gate passed. Its 5 new issues are all "split this composite assertion" in the test module (:116, :1188, :1256, :1336, :1348), which is cosmetic.
    • The landing mechanics' Copilot rounds have not started.
  12. Body accuracy (check 8).

    • Reproduced:
      • All 36 test names in the matrix table exist, and no matrix test is missing from the table.
      • "19 malformations".
      • The red counts and the names of the 8 passes. Skips are 7 with head's tests against the body's 6 at 6337a52: one composed test (test_r15_composed_the_root_entry_points_pin_reader_is_the_implementations) was added after it.
      • 17 + 3 + 3 payload files: 29 receipt rows plus 2 hook entries.
      • openRepoShape:280 at 7f84ca4 is the curl pointer.
      • recorded_gitlink's index-then-HEAD, stage-0 order matches openRepoShape scripts/repo_shape.py:804.
      • --version is unchanged.
      • The fetch refusal names the resolved commit.
      • By reading: all seven composed tests call require_composed() before anything else, so OPENREPOTOOLS_COMPOSED=1 fails them instead of skipping them.
    • Not accurate: "the one deliberate behaviour change" (findings 3 and 4).
    • Not checked: the composed results recorded on 004-migrate-to-triad-installer (51993a4); composed tests skip here. The body's green count at e406e0e; I measured 2411f40.

Conditions for the landing owner (lane 1)

  • F1 and F2 (findings 3 and 4) resolved in code, or accepted by the rule's owner with the body corrected; then a re-review of that delta.
  • Linux CI green on the new head.
  • tests-macos through the ready label. It is the only real bash 3.2 runtime run of this code (finding 9).
  • Copilot's rounds per the landing mechanics.
  • Lane 2's review of the matrix, if it comes.
  • Since the branch is three commits behind main, a check that the squash still applies over 63810dd. It merges clean here.

Not checked here: the composed acceptance runs, and macOS itself. Review posted read-only by lane openRepoTools-1's independent reviewer for #186.

…nit's fallback to main, and ask only two files to tell the layouts apart.

#191's review found two regressions on today's layout (F1, F2). F1: the
fetching paths resolved the ref and listed trees through api.github.com before
any file, so a curl-only machine needed api.github.com and jq where it had
only ever needed raw.githubusercontent.com, and a single repository's fetch
refusal named a commit it was never asked to resolve. F2: an installed `wip
init` whose source could not be resolved refused at step 7, after steps 5 and
6 had made and cloned the repository, saying nothing was written, where it
used to take the standard's main.

The layout is now told apart by asking for contracts/code-pin.yaml and then
project.yaml at $REF, through fetch_from_repo's own two transports: a pin is
the adopted layout, GitHub's 404 for both is a single repository, a manifest
without a pin refuses, and any other answer still refuses rather than being
read as the older layout. A single repository is then read exactly as before,
at the ref as given, and its refusals are its old words. Only an adopted
assembly resolves the ref once through the API. In wip init, a source that
does not answer leaves main as it always did, and an adopted assembly that
cannot be followed refuses in step 7's own words.

The new single-layout tests are written to pass at c4864ac and fail at
2411f40; the adopted step-7 wording test is new behaviour. The run that shows
it is recorded on the PR. Rows 6 and 7 now hold the adopted layout to one
commit and the single repository to its ref as given.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 9, 2026
…6-10-09

The 46 lines of gate-b-checklist.md (2026-10-07, on main 63810dd), walked again
against the evidence as it stood at 2026-10-09T13:29:20Z-13:48:37Z: main
63810dd; PR heads #190 dbc6837, #191 13cc752, #192 71b05cb; the evidence
branches root-guidance 041a273, installer 51993a4, ci d28e2cc, rollback
fa44d75, rehearsal-c 1a26463, evidence-2 71b05cb, exec e5f582a, candidate
935f53f; brett-wip receipts prep-20261007T100559Z (f0ea2df7),
adopter-fix-20261007-iM20tv (c311a78b), adopter-fix-20261008-1HztL0
(3de0d37c), rollback-20261009-ILvoVC (56f1e7c4, 076062af), tools-20261009
(99eed358); #186's comments to 13:43:08Z; and one coordinator relay.

Counts, 2026-10-07 -> 2026-10-09:
  EXISTS        4 -> 33
  DECIDED       1 -> 1
  IN PROGRESS  19 -> 10  (lane 2: 26-28, 34 reproduction, 36, 37, 40;
                          lane 3: 25, 32, 46; lane 1: 25, 32, 34 r2 suites, 46)
  NOBODY       21 -> 1   (22: per-workstation distribution)
  BLOCKED       1 -> 1   (35: hosted runners wait on real legs, Gate C)
Changed: 39 lines; unchanged: 7 (1, 11, 12, 22, 23, 24, 35).

Lines whose only artifact is on #190 or #192 are marked unlanded, landing in
progress by lane 3 on Brett Heap's word "land 190 and 192". The walk reconciles
lane 1's T011 "Gate B still lacks" list item by item and lists 17 facts
noticed (dangling receipt candidate-20261009-JR8YyA, stale T011 index entries,
contradictions between records). Facts only; it approves nothing and checks
no task.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rom the two layouts the design names.

The review of #191 asked that a single repository's install make exactly
base's requests, need its tools and print its words. After 13cc752 the one
remaining addition on that path was the project.yaml question asked on the
code pin's 404. It was a second request on today's layout for a state an
adopter never publishes, because the pin and the manifest land in one
commit and the assembly's own `make validate` refuses one without the
other. GitHub's 404 for contracts/code-pin.yaml is now the whole
single-repository answer. An assembly root without its pin, which carries
no payload, refuses exactly as a single repository missing those files
always did.

assembly_above used to accept any contracts/code-pin.yaml above the file's
directory. It now accepts one only where the pin mounts this directory as
its code leg, at <root>/<submodule_path> or at
<root>/worktrees/<NNN>/<submodule_path>. Another project's pin above a
vendored tree is no longer read, so it can neither refuse nor add a line to
a sentinel install.

New tests cover the review's F2 cases: a missing ref and a 502 on the
commit lookup in an installed wip init. They also cover the no-jq order
and words on the curl path, a missing ref on --install by gh and by curl,
and a complete tree inside another project's assembly, with a valid pin
and with a CRLF pin.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@brettheap brettheap left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delta review — lane openRepoTools-3 (read-only; 2026-10-09T14:03:13Z) on 13cc752

Verdict: LAND (13cc75298143302cfa0805abce6271916ed912e5). My earlier block, finding 3 (three api.github.com calls before any payload, and a refusal on a gh-less machine whose API answers 403), is fixed and measured. On today's layout the single-repository install now:

  • uses the same hosts and tools as main;
  • makes no API call;
  • places the same bytes;
  • keeps every current test's outcome.

Two items stay open, neither of them reachable in today's estate: the parent-directory code-pin walk (delta 4, fix before any consumer adopts) and the hygiene test passing by spelling (delta 5, T009's to restate). Conditions: the stale PR body is corrected before the squash (delta 8), and tests-macos runs on the ready label. Lane 1 owns the order (#186).

Lane 1 has announced a further head (#186 (comment), item 4) that limits the assembly search to the two designed layouts. That answers delta 4. This verdict is for 13cc752 only, and the next head needs its own check.

What was measured. Main is 63810dd, and CI's merge ref is f6262c2 (main + this head). Everything ran from my scratch clone. The fakes (gh, curl, a GitHub served from bare repositories) are mine, not the PR's. My earlier review's findings are referred to by their numbers there.

Delta findings, numbered.

  1. CI on 13cc752: green except macOS.

    • tests: 1386 passed, 7 skipped (main: 1296 passed).
    • tests-no-submodule: 1169 passed, 224 skipped (main: 1084 passed, 212 skipped).
    • guard-launch-mode (165 passed), tests-windows (155 passed), parse-macos and SonarCloud: success.
    • tests-macos: skipped (no ready label).
    • The new module is now 97 tests. The counts reconcile: all 1296 existing tests keep their outcome, and the 97 new ones are 90 pass + 7 composed skips (85 + 12 without the submodule).
    • Locally at 13cc752 itself, -k 'openrepotools_command or payload_source or repo_hygiene' gave 339 passed, 7 skipped (composed), 0 failed: 97 + 90 + 152.
    • tests/test_openrepotools_command.py: 0 assertion lines removed against main; only the fake gh's routes changed (two 404 answers replace the commit/tree answers).
  2. Byte-for-byte, offline: identical. Setup: fresh HOMEs, env -i, stub gh/curl that exit 1, umask 022. ./openRepoTools --install at main 63810dd, at 13cc752 and at the merge f6262c2:

    • All three exit 0 with the same 37-line stdout and 0 requests.
    • 48 entries with identical modes. 29 of 31 files byte-identical, including settings.json (mode 600) and both hook entries.
    • The two that differ are by construction. The placed openRepoTools is each tree's own source (blob 18d4c7f here). In installed.tsv (29 rows) only that row's sha256 (a6eda1f4…) and the stamps differ.
  3. My finding 3 (the API probe on today's layout) is fixed. One-liner (curl … | bash -s -- --install), my fake serving the merge ref as main against main's tree:

    Machine main 13cc752
    no gh, API answering exit 0, 24 raw, 0 API exit 0, 26 raw, 0 API
    no gh, API 403 exit 0 exit 0 (was exit 2 at 2411f40)
    gh present, API 403 exit 0, 23 gh contents exit 0, 25 gh contents, 0 API
    OPENREPOTOOLS_REPO=octo_acme/openRepoTools, API 403 exit 0 exit 0 (lane 2's (b), was exit 2)
    no jq exit 2 exit 2, byte-identical refusal (lane 2's (c))
    • Same bytes: in every case that installs, trees, modes, the 29 files and stdout are identical to main's.
    • The two extra requests are contracts/code-pin.yaml and project.yaml at $REF, asked by the same two transports (probe_path :643-671) and each answered 404. The curl path reads the status with -w '%{http_code}', not -f.
    • Installed-copy reinstall with API 403 and no gh: exit 0 at both, stdout identical (8 requests against 6).
    • One wording change remains. An offline installed-copy reinstall is exit 2 with nothing placed at both, but the refusal now names the layout question ("could not fetch the files that say whether … is a single repository …", :728-735) where main names the skills. The commit message's "its refusals are its old words" holds for an answered 404 (test_r11_a_single_repository_missing_a_file_…), not here. Low.
  4. The parent-directory walk is unchanged (my finding 4 and lane 2's (a): assembly_above :810-821, consulted at :921 before the completeness check). Re-measured on 13cc752: a complete tree from git archive 13cc752, placed under an unrelated <dir>/contracts/code-pin.yaml:

    • LF pin: exit 0 with a 38th line, "… NOT the code pins …".
    • CRLF pin: exit 2, where main installs.

    Still unreachable in today's estate (no ancestor of a checkout or of ~/.local/bin carries contracts/). It should be fixed before any consumer adopts: bound the walk to the mount parent and <root>/worktrees/*/code, or match the found pin's submodule_path/source_repository to this checkout. Recommend tracking it on #186.

  5. The hygiene test still passes by spelling, unchanged.

    • Honest: the new probe lines name $REPO directly (:647, :657). PAYLOAD_REPO is assigned only $REPO (:721, :744) or, after the pin passes, $PIN_SOURCE_REPOSITORY (:786).
    • By spelling: the ${PAYLOAD_REPO:-$REPO} lines (:267, :273, :552, :565, :593, :606) pass the substring check while the adopted path reaches another repository.
    • Stale text: the test's own wording ("from nowhere else") no longer describes the adopted path. T009 should restate it. The guard still does not scan curl … api.github.com lines.
  6. Lane 2's (d) is still open, adopted path only. The pin-fetch refusal (:770-774) lists what was asked but not what answered, because fetch_from_repo discards stderr. The root entry point's fetch_at does report it. Low.

  7. New code read.

    • Probe: payload_probe (:676-693) asks the pin first and project.yaml only after an answered 404. A gh 404 is taken as an answer without asking curl. That is correct for GitHub, and a private or inaccessible repository then fails at the payload fetch, all-or-none.
    • Moving ref, adopted: the probe reads the pin at the moving $REF, then resolves $REF once and requires the pin in the listing at that commit, or it refuses ("the ref moved", :764-767). One commit is kept.
    • Moving ref, single: reads $REF file by file, exactly as main did, with the same mixed-version exposure.
    • Captive portal (by reading, not measured): a portal answering 200 to everything now refuses at the resolver instead of installing HTML. That is better than main.
    • wip init: wip_shape_ref (:3205-3211) gives an unanswered source main again, as main did. An adopted source that cannot be followed refuses in step 7's words (resolve_refuse).
    • Bash 3.2: bash -n under 3.2.57 is clean, as is parse-macos.
  8. The PR body is stale, and the merge title and body should be updated before the squash (a condition of this LAND). The author's text is unchanged since open, and it still describes e406e0e's design:

    • "The ref is resolved once, in both layouts";
    • "the one deliberate behaviour change: payload URLs carry ?ref=<sha>";
    • "the layout is decided by a listing that succeeded (git/trees)";
    • the fake gh "answers the two discovery calls … (the ref's commit and a root listing)".

    All four are now false: a single repository reads $REF as given, and the layout comes from two file probes. The title's "resolve the ref once" now holds only for an adopted assembly. Commit 13cc752's own message is accurate against what I measured, except for "its refusals are its old words" (delta 3, the offline case).

T007 entry-point patch (004-migrate-to-triad-installer @ 51993a4)

Verdict: CHANGES REQUIRED before it goes into the root-split PR. One change: confirm the gitlink before running the pinned implementation on the remote path. Everything else conforms to installer-design.md and to lane 3's §4.10, measured on a disposable assembly.

Setup.

  • A no-hardlink clone of run C's assembly 69b8924, with the spec and code bare remotes copied into my scratch and the nested openRepoShape taken from run C's mirror. Run C itself was never written.
  • git am of the recorded patch reproduces lane 1's tree 79db0a4 exactly.
  • Code 588285e = run C's 73b04d6 plus #191's three files at 13cc752, byte for byte (73b04d6's other 49 paths equal main's blobs).
  • Assembly 1c72d90 moves the pin and the gitlink in one commit. Its own scripts/validate-pins.py prints pins ok.

Findings, numbered.

T1. One code commit, resolved once.

  • The curl one-liner on a gh-less machine, and the same through gh: exit 0, 31 files.
  • Requests: the root asks commits/main once (the only request naming main after the script itself), reads the pin at that commit, and fetches openRepoTools from opensoft/openRepoTools-code at 588285e. The implementation then re-resolves the immutable commit, lists the trees, checks the gitlink, and fetches all 23 payload files at 588285e.
  • The installed tree is byte-identical to a ./openRepoTools --install from a 13cc752 checkout: 48 entries and modes, 30 files, and the receipt's name/path/sha256 columns.

T2. Local delegation is correct and has no fallback.

  • ./openRepoTools --install at the assembly root, offline: exit 0, 0 requests, and the same bytes as T1. The only extra stdout line is the implementation's source: line.
  • Refused with exit 2, 0 files and 0 requests:
    • a staged gitlink that disagrees with the pin;
    • an edited code/openRepoTools ("not the implementation … pins; nothing ran");
    • an edited code/park (the implementation's byte check);
    • an unmounted code leg ("Mount it").
  • None falls back to a network.

T3. The remote path does not confirm the gitlink; it runs whatever the pin names first (delegate_remote, the root file's :285-345; the evidence doc says the implementation "re-checks the gitlink itself"). installer-design.md:23-27 orders it "read the pin … confirm the assembly gitlink agrees. Fetch the implementation …", and lane 3's §4.10 sketch checks the gitlink before exec.

  • Pin = new code, gitlink = old: the pinned implementation runs and refuses on the gitlink (exit 2, nothing placed).
  • Pin = old code (73b04d6, pre-#191), gitlink = new; and run C's own pin = gitlink = 73b04d6: the root runs main's installer. That installer fetches the payload from the assembly root at the resolved commit and refuses with "could not fetch park resume status …". The mismatch, or the pre-T007 pin, is never named.
  • Nothing was placed in any case, but a stale or rolled-back pin yields a misleading refusal, and the lockstep check sits inside whatever code the pin selects.
  • Required: before fetch_at "$PIN_SOURCE_REPOSITORY" … (:328), read the gitlink at $sha (for example gh api repos/$REPO/contents/$PIN_SUBMODULE_PATH?ref=$sha, whose sha field is the gitlink, with the raw/API fallback the patch already uses) and refuse when it is not $PIN_COMMIT.

T4. Other refusals work. A malformed pin (leg_role: spec) is refused by the root ("nothing ran", 0 implementation fetches). A pin and gitlink naming a commit the code repository lacks refuse with what answered (404). API 403 with no gh refuses "could not resolve …".

That last refusal is the adopted layout's inherent cost. After cutover, the documented curl one-liner needs api.github.com: 5 API calls per install (root 1, implementation 4), plus jq on the curl path. A gh-less, rate-limited machine cannot install. Document it next to the one-liner.

T5. Mechanics and contract.

  • No installer mechanics in the root: no placement, receipt, merge or retire.
  • The pin reader is an exact copy: the 10 definitions (3 constants, 6 reader functions, recorded_gitlink) are identical to 13cc752's and to 2411f40's.
  • --help through the root, local and remote, is byte-identical to main's.
  • Temporaries: exec keeps the exit status and signals the implementation's, and an isolated TMPDIR is empty after a success and after three refusals.
  • Bash 3.2: bash -n under 3.2.57 is clean, and the only bash-4 words are in a comment.
  • Command names and the one-liners are unchanged.

T6. Minor points.

  • Through the remote root, --version prints openRepoTools (opensoft/openRepoTools @ 1c72d90…) (the resolved commit), where main and the local root print @ main. Lane 3's §4.10 (f) expected unchanged text.
  • The implementation's source: line shows the commit twice ("at 1c72d90… (1c72d90…)") and loses the ref name.
  • The cutover must pin a code commit that carries #191 (the evidence doc says so). T3's change would make a violation name itself.

Not checked here:

  • Lane 2's T008 matrix on 13cc752: writer W8, announced in #186 (comment), results not posted at this stamp. That covers every row, the probe × transport grid, F2's wip init fallback, and the commit's "pass at c4864ac, fail at 2411f40" test claim. I did not duplicate it.
  • macOS runtime: tests-macos waits on the ready label.
  • Composed suites (P1–P4) on the patched assembly.

Local runs took flock on /tmp/openrepotools-pytest.lock, one run at a time, with run.sh's in-lock command. Review posted by lane openRepoTools-3 as reviewer of record for #191 (a comment, not an approval). Lane 1 owns the landing order (#186).

brettheap and others added 3 commits October 9, 2026 20:28
…ayload-source branch, so its proofs run against what CI merges.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ocumented upstream skip from conftest's text so the test holds under #190's composed mode.

Lane 2's (d) in #191's review: on the adopted path only, a pin that the
listing shows but that will not come back was refused by naming what was
asked, because fetch_from_repo discards stderr. The pin at the resolved
commit is now asked by probe_path, which reads the current source and
records each transport's answer. The refusal quotes those answers. On
today's layout probe_path still asks $REPO at $REF, because the current
source is that until an adopted assembly's commit is known.

test_r14_the_documented_upstream_skip_is_unchanged read
NEEDS_UPSTREAM.kwargs["reason"], which exists only while NEEDS_UPSTREAM is a
skipif. Under #190's conftest, in a composed run, it is a fixture that fails
instead, and the test raised KeyError. It now checks conftest's text, which
names the command in both modes. The defect was this test's assumption, not
#190's.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…isagrees with before it fetches or runs any implementation.

Lane 3's review of the T007 entry-point patch (T3) found that on the remote
path the root fetched and ran whatever implementation the pin selected, and
left the lockstep check to that code. A stale or rolled-back pin then ran
main's installer and refused with a misleading message. The patch on
004-migrate-to-triad-installer now reads the gitlink at the pin's
submodule_path in the resolved commit, and refuses a mismatch before any
fetch. This composed test holds it to that, by gh and by curl: both
commits are named, "nothing ran", and the code repository is never asked.
Like every composed test, it skips in standalone code and fails rather
than skips under OPENREPOTOOLS_COMPOSED=1.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 9, 2026
…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>
brettheap added a commit that referenced this pull request Oct 9, 2026
…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>
@brettheap brettheap changed the title Install from the code an adopted assembly pins, resolve the ref once, and keep the single-repository install byte for byte. Install from the code an adopted assembly pins, resolved to one commit, and keep a single repository's install on today's reads, words and network needs. Oct 10, 2026
@brettheap

Copy link
Copy Markdown
Contributor Author

Head 6568f08: how the review's findings are resolved, with the proofs. The body is rewritten to describe this design (lane 3's delta 8).

What changed since 13cc752 (lane 3's LAND)

The review's F2 cases are tests now

  • test_r13_an_installed_wip_init_at_a_ref_the_source_lacks_seeds_from_main_as_before.
  • test_r13_an_installed_wip_init_never_needs_a_single_repositorys_commit_lookup (a 502 on commits/).
  • test_r11_a_single_repository_at_a_ref_it_lacks_refuses_in_the_words_it_always_did[gh, curl].
  • test_r01_a_single_repository_without_jq_fetches_then_names_jq_as_it_always_did.

Proofs

Proof Lock Result
The 20 new and changed tests (r2/redcheck-scoped.sh), each run against four installers without (coordinator's rule for scoped proofs: HOME, TMPDIR, --basetemp and PYTHONPYCACHEPREFIX in scratch) c4864ac: 19 pass, 1 fail (the adopted step-7 words, which are new behaviour). 2411f40: 20 fail. 13cc752: 16 pass, 4 fail (the project.yaml request, the parent walk ×2, the partial refusal). fdc5f18: 20 pass.
Delta 6, r14, the malformed pins, the gitlink pins (6fa3b43) without 23 passed
The module's composed tests, strict, on the v3 assembly without 9 passed. With the v2 root, the new root gitlink test fails twice (red), as it should.
The module, -k payload_source, at the fdc5f18 content with 96 passed, 7 skipped
The broader selection at 6568f08: payload_source, openrepotools_command, wip_init, install_skill_and_hook, install_by_rename, repo_hygiene, upstream_pin, test_roots with 516 passed, 9 skipped, 5 failed. The 5 are test_wip_init_and_the_lane_helpers_read_one_pointer_file_the_same_way, where lanes-edit.sh who exits 8 on this workstation's lane state. That file and test are outside this diff, and CI runs them green.
Strict composed run (OPENREPOTOOLS_COMPOSED=1), assembly 4c6a0b7 (root v3 + this head's whole code leg) with 429 passed, 1 failed. The failure is test_the_root_carries_the_line_ending_rule, which needs .gitattributes in the code leg; run C's legs carry none (T006's finding, not this PR's).
CI, run 37997688693 tests: 1416 passed, 9 skipped. tests-no-submodule: 1196 passed, 229 skipped. tests-windows: 177 passed. guard-launch-mode: 165 passed. parse-macos, Sonar and Sourcery: pass. tests-macos: waits on ready.

Every locked run used tests/run.sh's in-lock command with PATH=/usr/bin:$PATH. The entry-point patch v3 (6acc147; patch sha256 67d8e7ce…) is on 004-migrate-to-triad-installer at 4f773b8.

🤖 Generated with Claude Code

@brettheap
brettheap requested a balanced review from Copilot October 10, 2026 02:43
brettheap added a commit that referenced this pull request Oct 10, 2026
…6568f08, and CI on each head, in the installer evidence.

The runs table had two rows still waiting at the first checkpoint. It now
records what each review round changed and every proof, with which ran under
the lock and which ran unlocked under the coordinator's rule for scoped
proofs. It also records the strict composed run of root v3 with the head's
whole code leg, where the only failure is the code leg's missing
.gitattributes (T006), and CI per job on 6568f08.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Local assembly detection and gitlink validation can bypass required consistency checks, and payload validation has a copy-time race.

3 open findings
What changed in this PR

Adds triad-aware installer payload resolution while preserving legacy single-repository behavior.

Changes:

  • Resolves adopted assemblies to pinned code commits.
  • Routes wip init through the code leg’s dependency pin.
  • Adds comprehensive compatibility-matrix tests and GitHub fakes.
File Description
openRepoTools Implements payload-source selection and validation.
tests/​test_install_payload_source.py Tests legacy, adopted, local, remote, and failure scenarios.
tests/​test_openrepotools_command.py Extends the fake GitHub server for layout probing.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread openRepoTools
Comment thread openRepoTools
Comment thread openRepoTools
brettheap added a commit that referenced this pull request Oct 10, 2026
…: two candidate defects with owners (the r14 KeyError fixed in #191, #193 item 14) and four environment classes, each shown the same on main or in isolation.

r2 is code 4dbcf6c, spec 9e1d006 and assembly 5d92962, built from #190 at
b50f197 and #191 at 13cc752. Composed: 1411 passed, 5 failed, 0 skipped.
Standalone: 1389 passed, 3 failed, 24 skipped. No submodule: 1168 passed,
6 failed, 242 skipped. The install from the assembly root differs from
main's only in the implementation it hands over to. The composed rerun at
#191's final head 6568f08 with T007's v3 entry point is still owed.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 10, 2026
…t up to 004-migrate-to-triad-installer at 3cca1e7

Part of #186.

004-migrate-to-triad-installer moved from 4f773b8 to 3cca1e7 after the
previous commit (0de8562) took it: 3cca1e7 changes only
specs/004-migrate-to-triad/installer-2026-10-08.md (the runs of #191's review
rounds, the strict composed result at 6568f08, and CI on each head). The patch
specs/004-migrate-to-triad/patches/t007-assembly-entry-point.patch is the same
blob in both heads.

Method, no rebase and no force-push: the tree is 0de8562's tree with that one
path taken, with the mode and blob it has in 3cca1e7, in a scratch index
(read-tree, update-index --cacheinfo, write-tree). The commit keeps 0de8562 and
3cca1e7 as parents, so the push is a fast-forward of
004-migrate-to-triad-evidence-3 and the installer branch is neither pushed to
nor moved. Every other input head is unchanged: 004-migrate-to-triad-ci
d28e2cc, -rollback-refresh 2e7e55e, -evidence-2-followups 096de93, -t018
63bff08 and -gate-b-status bfc0814, on main f87fd5a.

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.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…and read a pin's commented submodule_path the way pin_parse reads it.

Copilot's first round on #191 raised two points that this commit fixes.

First, the mounted-checkout path hashed each payload file in the checkout
and copied it later, in another pass. An edit in between could put bytes
in place that were never checked against the pinned commit. Each file is
now copied into the run's work directory first, the copy is what gets
hashed, and collect_commands and collect_skills read only that snapshot.

Second, pin_mounts compared the raw remainder of the submodule_path line.
A valid pin that comments the value, such as
"submodule_path: code  # the mounted leg", was then not recognised as this
checkout's assembly, and the tree was installed as a plain complete tree
with none of the pin, gitlink or byte checks. It now reads the value with
pin_scalar first, and falls back to the lenient read only where that
grammar refuses the value. The new test fails at 6568f08 and passes here.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Branch movement between layout probing and commit resolution is not refused as specified.

1 open finding
3 resolved since last review

🧠 Review effort: Balanced

Comment thread openRepoTools
Comment on lines +761 to +764
[ -n "$entry" ] || resolve_refuse "$REPO carried contracts/code-pin.yaml at $REF when it was first asked,
and $SOURCE_SHA, the commit $REF named a moment later, does not: the ref moved
between the two reads, so this run would not be installing one answer.
Re-run once it has settled."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code keeps this behaviour on purpose. The PR body overclaimed, and it is now corrected.

What the code guarantees. The probe at $REF decides only the layout, and its bytes are never used. Everything installed is read again at the one commit that $REF resolves to, SOURCE_SHA: the root and contracts/ listings, the pin, the gitlink and every payload file.

When the branch moves between the probe and that resolution:

  • To another adopted commit: that commit is installed whole, from the code it pins. That is what the same command run a moment later would install, and no request reads two versions.
  • To a commit without a pin: the run refuses ("the ref moved between the two reads").

Refusing the first case as well would only turn a consistent install into a re-run.

Proof. test_r07_a_branch_that_moves_right_after_the_layout_question_is_read_at_one_commit[adopted, single], added in 8246983, moves main straight after the first request (the probe). In the adopted case the second commit is installed whole, and every payload request names the second commit's code. In the single case the run refuses with "the ref moved". Scoped run: 3 passed, together with the existing row-7 test.

The body now says the move is refused only when the resolved commit carries no pin, and that otherwise that commit is read whole.

…d whole at the one commit it resolves to.

Copilot's second round on #191 read the PR body's "a ref that moved
between the probe and that commit is refused" as a rule that the code
broke, because a move from one adopted commit to another installs the
second. The code is right and the body overclaimed. The probe at $REF
only decides the layout, and its bytes are never installed. Everything is
then read at the commit $REF resolves to, so a move to another adopted
commit installs that commit whole, from the code it pins. A move to a
commit with no pin is refused as a ref that moved. The new test moves main
straight after the first request and holds both outcomes, with every
payload request naming the second commit's code.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

brettheap added a commit that referenced this pull request Oct 11, 2026
…staller evidence.

Round 1's two fixes and the one finding kept, with the reason; round 2's
body correction and the test that shows the behaviour; and a strict
composed run of the module's composed and local rows with root v3 and the
final head's code leg.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@brettheap brettheap left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review — lane openRepoTools-3: #191 delta @ 8246983 (read-only; 2026-10-11T00:42:01Z)

Verdict: LAND for this head (824698356c4a62cdfc00e565e1ad98c7bf6414b0), on these conditions:

  1. Linux tests and tests-no-submodule finish green on 8246983. They started at 00:16:22Z and were in progress at this stamp. On cddd1f7 both passed (finding 8).
  2. tests-macos runs on the ready label. The PR has no labels at this stamp, so no bash 3.2 runtime has run on any head.
  3. 8246983's CI is recorded where the body says it will be (#186), and the body's "Green at the head (6568f08 …)" is labelled as that head's run (finding 7).

The installer is the same blob (7aa697c) at cddd1f7 and at 8246983. cddd1f7..8246983 adds one test and nothing else, so the installer measurements below, taken at cddd1f7, hold at this head.

On today's single-repository layout this head installs byte for byte, offline and through both one-liners. Every condition and finding of 5471121210 is resolved, disclosed in the body, or handed to a named task. What stays open is low, unreachable in today's estate, and listed below.

What was measured. Fresh scratch clones: the 22:50Z bench reboot wiped the earlier one, and a second loss at about 00:13Z took the rebuilt one. Main is f87fd5a, and CI's merge refs are ab5e8f0 (main + cddd1f7) and 21a326d (main + 8246983); the installer is blob 7aa697c in all of them. Everything ran from those clones, with my own fakes (gh, curl, and a GitHub served from bare repositories), never the PR's. No installed tool was run; each installer ran by path under a temporary HOME. "Delta N" and "T3" refer to 5471121210.

Findings, numbered.

  1. Scope of 13cc752..8246983.

    • The PR's own commits:
      • fdc5f18: one probe instead of two, and the assembly search bounded to the two designed layouts.
      • 6fa3b43: the pin-fetch refusal says what answered, and r14 reads conftest's text.
      • 6568f08: a composed test for the root's gitlink check.
      • cddd1f7: Copilot round 1, the snapshot and pin_mounts reading the value with pin_scalar.
      • 8246983: one test (+36), for Copilot round 2 (finding 6b).
    • The merge of main, 45979a4: it brought in #190's test roots and #192's evidence. Of #191's three files it changed only tests/test_openrepotools_command.py (the ROOTS import and the README read). Main's openRepoTools is still blob 9b7cbd2.
    • Against main the diff is exactly three files:
      • openRepoTools (+773/−22);
      • tests/test_install_payload_source.py (new);
      • tests/test_openrepotools_command.py (+8), which adds only the fake gh's 404 route. No assertion line is removed.
  2. Byte-for-byte, offline: identical. Setup: fresh HOMEs, env -i, stub gh/curl that exit 1, umask 022. ./openRepoTools --install at main f87fd5a, at cddd1f7 and at ab5e8f0:

    • All three exit 0 with the same 37-line stdout and 0 requests.
    • 48 entries with identical modes. 29 of 31 files are byte-identical, including settings.json and both hook entries.
    • The two that differ are by construction. The placed openRepoTools is the head's own source (sha256 697fdbd4…), and in installed.tsv (29 rows) only that row's sha256 differs, beyond each row's install time. Stdout is identical once the HOME path is normalised. Re-measured after the second loss.
  3. The single layout's network is today's plus one request. One-liner, my fake serving ab5e8f0 as main against main f87fd5a:

    Machine main cddd1f7
    no gh, API answering 24 raw 25 raw, 0 API
    no gh, API 403 exit 0 exit 0
    gh present, API 403 exit 0 exit 0 (24 gh contents)
    octo_acme owner exit 0 exit 0
    no jq exit 2 exit 2, byte-identical refusal
    • In every case that installs, the trees, modes, the 29 files and stdout are identical to main's.
    • The one extra request is contracts/code-pin.yaml at $REF, answered 404 (probe_path openRepoTools:652-680, payload_probe :685-695). project.yaml is no longer asked.
  4. 5471121210's conditions and findings.

    • (a) The body. Fixed: the title, the single-versus-adopted design, the one extra request, and the offline refusal are now stated accurately, and at 8246983 so is a ref that moves after the probe (6b). Two residues are in finding 7.
    • (b) tests-macos on ready. Still open.
    • (c) T3, the gitlink checked before the pinned implementation runs. Fixed in the entry-point patch v3, not in #191, and the body says so under "Not in this PR".
      • Measured with v3 (004-migrate-to-triad-installer @ 3cca1e7, patch sha256 67d8e7ce…), applied to a no-hardlink clone of run C's 69b8924. The tree is 87782af, as the record states. Pin ≠ gitlink refuses after 4 requests, 0 of them to the code repository, in both directions.
      • Code 0ca0689 (= 73b04d6 + this head's three files), pinned in lockstep (pins ok): the one-liner installs bytes identical to a cddd1f7 checkout install.
      • What v3 does not cover (T3's second case) belongs to the patch and is reported on #196.
    • Delta 3 (the offline reinstall's words). Still different from main's: exit 2 at both, but this head's refusal names contracts/code-pin.yaml (:726-733) where main names the skills. It is now disclosed in the body ("in the offline case it refused too, naming them"). Accepted.
    • Delta 4 (the parent walk). Fixed for every case of mine and of lane 2's 6088812080: a deep vendored tree under an unrelated pin installs as main does. The residue is finding 5.
    • Delta 5 (hygiene by spelling). Unchanged; handed to T009 in the body. Accepted.
    • Delta 6 / lane 2's (d). Fixed: the adopted pin fetch goes through probe_path (:769-775) and quotes what answered.
    • Delta 8 (the stale body). Fixed; see (a).
  5. The walk residue: an unrelated pin whose submodule_path lines up. These are lane 2's 24a–c, in assembly_above (:843-861) with pin_mounts (:813-833), consulted at :971. Re-measured at cddd1f7; main installs in every case:

    • outer/product: a complete tree beside outer/contracts/code-pin.yaml with submodule_path: product gives exit 2 ("…/outer records no gitlink at product").
    • outer/worktrees/001-x/product: the same tree gives exit 0, with a 38th line ("NOT the code … pins").
    • An installed copy in $H/bin (OPENREPOTOOLS_BIN_DIR), under $H/contracts/code-pin.yaml with submodule_path: bin: the reinstall gives exit 2, with 0 requests.
    • outer/deep/vendor/product: identical to main.

    A name match is taken as identity. This is low. It needs an unrelated pin directly above a directory of exactly the pinned name, and nothing in today's estate has one; the default ~/.local/bin cannot match.

    A cheap tightening: treat <parent> as the mount only if it records a gitlink at that name, and fall through to the plain-tree path otherwise. Track it on #186 rather than block.

  6. Copilot's two rounds.

    • 6a. On 6568f08 (5477221892).
      • The snapshot (Copilot's :924 on 6568f08): fixed. payload_local_adopted (:881-941) copies each file into $WORKDIR/pinned-payload with cp -p, hashes the copy against $PIN_COMMIT, and points PAYLOAD_DIR at it. Measured through the root entry point: the installed bytes equal the pinned ones.
      • pin_mounts (Copilot's :828): fixed. bash 3.2.57 and 5.2 agree on 12 cases:
        • Match code: bare, "code", 'code', code # the mounted leg, "code" # x, code with a trailing space, and CRLF. An unterminated "code also matches, leniently, and the strict read then refuses it.
        • Do not match: codex, an empty value, a # submodule_path: comment line, and an indented key.
      • The staged-deletion fallback (Copilot's :876; recorded_gitlink :867-879 here): kept, and I agree.
        • The rule is openRepoShape's own recorded_gitlink (scripts/repo_shape.py:804-835 at 7f84ca4). It takes a stage-0 160000 row from the index, otherwise from HEAD. That covers a staged deletion, a non-gitlink row and conflict stages.
        • The bash version reads the first listed row; the Python scans every row. They give the same answer for those three cases, and the payload bytes are hashed against $PIN_COMMIT either way.
        • Lane 1's reply credits "lane 3's review" with a line-for-line match. My earlier reviews made no such comparison; the one above is my first.
    • 6b. On cddd1f7 (5481242791, discussion_r4239639211, "Compare initial probe pin with immutable SOURCE_SHA pin"). Resolved at this head as a body correction plus a test, and I agree.
      • The code: payload_probe saves the probe's pin to $WORKDIR/probe-code-pin (:689), and nothing reads that file again. The only "moved" refusal (:761-764) fires when the resolved commit has no contracts/code-pin.yaml. At cddd1f7 the body said "A ref that moved between the probe and that commit is refused", which was broader than that.
      • Measured on cddd1f7, before 8246983 existed, with my own fake:
        • Setup: the assembly's main was commit A, which pins code X. Commit B pins X′, which is X with one line added to skills/restart/SKILL.md; B moves its pin and gitlink in one commit.
        • The move: the fake moved main from A to B before request 2, right after the probe. The installer ran from stdin with OPENREPOTOOLS_REF=main.
        • with gh: rc=0 req=29, and X′'s marker line is in the installed SKILL.md;
        • without gh: rc=0 req=29, the same;
        • with no move: rc=0, and no marker.
      • Not refused, but whole: it installs B's pinned code, all from X′, so nothing is mixed. That is row 7's guarantee, and it holds.
      • At 8246983:
        • Body: it now says what the code does: "If the ref moved between the probe and the resolution, the commit it resolves to is read whole … A commit that no longer carries a pin is refused as a ref that moved."
        • Thread: lane 1 answered it (4239690423).
        • Test: test_r07_a_branch_that_moves_right_after_the_layout_question_is_read_at_one_commit[adopted,single] (tests/test_install_payload_source.py:1317) moves the ref after request 1.
          • adopted: the test checks that request 1 is the probe, that the second code is installed, and that every payload request names it.
          • single: it checks the "ref moved" refusal, with nothing placed and no payload request.
          • It agrees with my measurement.
      • Optional: comparing the probe's pin with $pin (cmp -s, no extra request) would turn the A→B case into a refusal. Reading whole at one commit is a defensible contract, and the body now states it.
  7. The body against the code (at 8246983).

    • Holds:
      • the probe and the two layouts;
      • the single layout's network;
      • the adopted resolve-once, with the moved-ref sentence now corrected (6b);
      • wip init's fallback and the counts;
      • "Not in this PR" (v3 checks the gitlink);
      • "Copilot, two rounds".
    • Stale or over-broad:
      • "Green at the head (6568f08, …)" still says "the head". The body now says the final head's CI is "pending. It is posted on #186 when it finishes", and lists what changed since 6568f08. That is honest. The heading should name 6568f08 as a past head (condition 3).
      • "A pin anywhere else above the file is another project's and is not read": true except for finding 5's name match. The sentence just before it states the actual rule correctly.
    • Accepted as disclosed: row 8's "partial adopted refused" (installer-design.md:82) now holds only because an assembly root carries no payload. The body says so, and lane 2's item 6 measured the synthetic decoy case.
  8. Tests.

    • CI on cddd1f7: guard-launch-mode, tests-windows, parse-macos, SonarCloud, Sourcery and Copilot succeeded. tests-macos was skipped. The Linux jobs (run 38096352239) finished green at 00:36–00:37Z:

      • tests: 1417 passed, 9 skipped;
      • tests-no-submodule: 1197 passed, 229 skipped.

      That is one more than on 6568f08, the new r02 test.

    • CI on 8246983: guard-launch-mode, tests-windows, parse-macos, SonarCloud and Sourcery succeeded, and tests-macos was skipped. tests and tests-no-submodule were in progress at this stamp (condition 1).

    • Locally: at cddd1f7, tests/run.sh -rs -k 'openrepotools_command or payload_source or repo_hygiene' under the lock, with PATH=/usr/bin:/bin, gave 347 passed, 9 skipped (all nine are composed-acceptance skips), 1070 deselected, in 16m26s. At 8246983, -k payload_source was still running at this stamp, so CI is the record for the one new test.

    • Bash 3.2: bash -n openRepoTools under 3.2.57 is clean.

Not checked here:

  • Lane 2's T008 matrix at this head. 6090038382 ran 6568f08 before it was pushed. cddd1f7 changes only the mounted-leg snapshot and pin_mounts. I did not re-run any of these:
    • the fifteen rows;
    • the 5xx and garbage-at-200 rows (lane 2's items 3–4);
    • the moving-branch row, beyond 6b's after-probe case.
  • macOS runtime (tests-macos on ready).
  • A composed strict suite at this head.
  • The race the snapshot closes: no deterministic hook exists, so it was checked by reading only.

Review posted by lane openRepoTools-3 (a comment, not an approval). Lane 1 owns the landing order (#186).

@brettheap brettheap added the ready landing gate: runs tests-macos once before the squash label Oct 11, 2026
brettheap added a commit that referenced this pull request Oct 11, 2026
… 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>
brettheap added a commit that referenced this pull request Oct 11, 2026
…h T007's v3 entry point, the install modules green in composed mode, F1 gone, and the install still differing from main only in the implementation it hands over to.

Code 9c11276, spec 9e1d006, assembly 3891943; every git am exited 0 and both
pins were regenerated with bump-leg.py. The four install modules, composed
and isolated with SIGINT at its default: 281 passed, 0 failed, 0 skipped.
The intermediate r3a at 6568f08 (278 passed) is recorded beside it. The full
composed suite is redone at the frozen source for Gate C.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap
brettheap merged commit a74c7fc into main Oct 13, 2026
14 checks passed
@brettheap
brettheap deleted the feat/installer-payload-source-resolution branch October 13, 2026 18:30
brettheap pushed a commit that referenced this pull request Oct 13, 2026
…ller files: no host path, a T008 table of the tests at the final head, and a disposition for every open item.

Lane 3's review 5481368376 asked for six corrections and noted one minor
point; all seven are made here. The record no longer names a host-absolute
location for run C's README (finding 0). It says what v3 closes, the
mismatched pin, and what it does not: a lockstep-consistent pin at code
from before #191, which is now a named T015 item with a capability check
proposed for #193 (finding 2). The T008 table now lists every test in
tests/test_install_payload_source.py at 8246983, which is main a74c7fc
(52 tests, 109 cases). Its base column comes from that module run against
c4864ac's installer (finding 3). The record names 8246983 as the head,
with its CI and the landing on main as a74c7fc on 2026-10-13 (finding 4).
The wip_shape_ref disposition gives the F2 fallbacks (finding 5). Each of
lane 2's nine open items in #186 6090038382 is fixed, accepted or given
to a task (finding 6). The minor point: "five things" and both reviews
of 2411f40 (finding 7).

The patch is unchanged.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 13, 2026
…alk residue's reason, the 200-garbage refusal, row 7's cases, CI on cddd1f7 and the measured holds.

- Item 1: the code reads the gitlink at the pin's submodule_path and refuses
  where none is recorded (24a, 24c), citing lane 3's 5481368124 finding 5 and
  its cheaper tightening, and finding 7 on #191's body.
- Item 4: a 200 that is not GitHub's refuses where main installs, like
  item 3; disclosed here, since #191's body does not name it.
- Item 2: the adopted missing-ref and missing-repository rows (19c-f, 19h).
- Item 7: the patch's header and commit message overstate; it stays.
- Item 9: row 7's [single] case is an adopted ref moving to a commit with
  no pin, which is refused; no test freezes a single repository.
- The section's opening cites lane 2's measurements at cddd1f7 (#186
  6103618033) and 8246983 (register note 2026-10-13T18:22:15Z).
- The runs table gains CI on cddd1f7 (run 38096352239).

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request 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>
brettheap pushed a commit that referenced this pull request Oct 13, 2026
…nal refold carries, and the hold for openRepoShape v1.0.0

These are dated lines only; no earlier text is rewritten. No status changes:
MET 27, PARTLY 18, OPEN 0, AFTER-GATE-C 1.

- #191 landed on main as a74c7fc, the squash of 8246983. Recorded at the
  header's #191 line and at question d's #191 CI line.
- What #196's final refold carries, each from the earliest head that holds
  its fixes, with #196's "Input heads carried" table named for the exact
  heads:
  - the installer record from 7668c51 on: the host path at its line 13
    is gone, and lane 3's 5481368376 is answered; 7428e78 adds lane 2's
    check;
  - slice B from 2c040d6 on: lane 3's 5481348731 items 7-9, with the
    T011 record now taken from slice B;
  - the candidate record from e81100c on: r3.
  Recorded in the header, the refs table, section 2's item 16 and
  section 5's item 4.
- The hold for openRepoShape v1.0.0, on Brett Heap's word relayed by
  lane 3 (#186 6152410188), with its after-the-tag order: the pin-bump PR,
  a fresh rehearsal and plan at v1.0.0, then the cutover. #196 is not
  held.

Part of #186
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 13, 2026
…the corrected inputs

Part of #186.

#191 landed as a74c7fc on main, and the inputs moved after 7c08f7e took the
earlier heads. This commit takes the current heads, on top of 7c08f7e through
004-migrate-to-triad-evidence-2-followups at 118a2e2 (a fast-forward of 7c08f7e),
and merges main a74c7fc:

  004-migrate-to-triad-ci                    22bdb2e  (cut from ff6f6a7)  was d28e2cc: T009's fixes for lane 3's 5481348731 findings 4 to 6 (3 new files)
  004-migrate-to-triad-installer             7668c51  (cut from ff6f6a7)  was 3cca1e7: T007's fixes for lane 3's 5481368376, and #191 landed as a74c7fc (2 new files)
  004-migrate-to-triad-evidence-2-followups  118a2e2  (7c08f7e + 4)       was 40f26f7: lane 3's 5481348731 items 7, 8 and 9 (3 files edited since 7c08f7e)
  004-migrate-to-triad-gate-b-status         3fd81c6  (cut from f87fd5a)  was bfc0814: findings 12 and 14 (1 new file)
  004-migrate-to-triad-candidate             e81100c  (cut from 63810dd)  was 16e6ea4: r3 at #191 8246983 (1 new file)
  004-migrate-to-triad-rollback-refresh      2e7e55e  (slice A)          unchanged, already in 7c08f7e
  004-migrate-to-triad-t018                  63bff08                      unchanged, already in 7c08f7e

Method, no rebase and no force-push: the tree is main a74c7fc's tree plus the
twelve paths of the inputs, each taken with the mode and blob it has in its
owning head, in a scratch index (read-tree, update-index --cacheinfo,
write-tree). Slice B owns the four paths it edits. One of them,
gate-ab-evidence-2026-10-09.md, is slice A's file: its blob is taken from
118a2e2 (slice A's 2e7e55e plus slice B's edits), not from 2e7e55e. The commit
keeps 118a2e2 as its first parent, so the push to
004-migrate-to-triad-evidence-3 is a fast-forward of 7c08f7e, and keeps main
and every other input head as parents. No input branch is pushed to or moved.

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>
brettheap pushed a commit that referenced this pull request Oct 13, 2026
… 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>
brettheap pushed a commit that referenced this pull request Oct 17, 2026
origin/main moved to aaa7bc8 (#191, the installer payload source, and #227,
which took the macOS and Windows jobs out of CI). The merge conflicted in two
files and both resolve in favour of main:

- .github/workflows/tests.yml: main's file in full. The comments this branch
  rewrote belonged to `tests-macos` and `tests-windows`, which are gone.
- README.md: main's CI paragraph, with its "the four bash files" reworded to
  "the shipped bash files" so the sentence no longer carries a count. The
  paragraph keeps main's length, so the README line cap is untouched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 17, 2026
…he install tests' docstrings, the pin reader, the CI runner note and the lane manual (#58)

With #191 and #227 on main the rest of #58's sites can be reached, and two
sentences found by the sweep were stale in more than their number.

- `openRepoTools` and `tests/test_openrepotools_command.py`: "ONE PATH NOTE for
  the four of them", "all four or none", "which of the four were missing",
  "each of the four files `--install` places" and "each of the ten" said a
  number the installer has not placed for a long while. They now say "everything
  it placed", "all of them" and "each of them". Comments and docstrings only.
- `.github/workflows/tests.yml`: "Nothing this repository ships is Python at
  all — three bash files —" was wrong twice. `lanes-index` and `lane-worktrees`
  are `python3` scripts, and the count was stale. The true claim, the one the
  line exists for, is that nothing shipped needs a Python package: both import
  only the standard library.
- `tests/test_upstream_pin.py`: the pin reader's docstring said the repository
  "ships three bash files and installs nothing"; it now says what is true
  without a count.
- `docs/README-lanes.md`: the `--install` comment's "thirteen commands".

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 17, 2026
…ch on the tree the PR will be judged on

origin/main at aaa7bc8 carries #191's install from the code an adopted
assembly pins (a74c7fc) and #227's Linux-only CI. A trial merge
(git merge-tree --write-tree) was clean; no file is changed on both sides:
main changed .github/workflows/tests.yml, AGENTS.md, README.md,
openRepoTools and four test modules, none of which this branch touches.
The merged tree's counts are in the commits that follow.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 17, 2026
…in's commit, find paired feature worktrees, show redirected roots under -q, run the lane manual sections, and restate the installer-source test (#195)

* Give the test suite explicit code, assembly, spec and dependency roots 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>

* Refuse composed acceptance when a leg's git status cannot be read, and 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>

* Hold the assembly's README and AGENTS.md to their own length caps, and 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>

* Assert each mixed test's code-only claims before its first document read, 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>

* Prove with a nested run that a standalone code leg still checks each 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>

* Guard the lane suite's last two manual reads, so a code leg with no spec 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>

* Name the value a refused root was named by in the composed refusal, instead 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>

* Refuse a composed run whose leg pin names another commit than the gitlink, 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>

* Find the assembly from a paired feature worktree, <assembly>/worktrees/<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>

* Say the roots a redirected run used at its foot, where -q keeps them, 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>

* Run the lane suite's manual sections with a missing, an absent and a 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>

* Read a quoted ~ in a root variable as the home directory in tests/run.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>

* Move the four-roots paragraph to the foot of AGENTS.md's testing section, 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>

* Restate the installer-source hygiene test so a fetching line must name 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>

* Trust a pin-named payload repository only when its source_repository 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 a stale marker-bearing ancestor instead of ending discovery, accept 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>

* Hold AGENTS.md's pin rules to the path that works from where the file 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>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 17, 2026
… CI patches, installer entry point, restore and rollback, #192's corrections, T018, Gate B status (#196)

* docs: preserve triad migration planning drafts

Preserve the existing OpenSpec and Speckit planning files at Brett Heap’s request during repository cleanup. No migration or adoption is executed.

* docs: refresh triad migration plan after worktree cleanup

* docs: refresh triad plan for October 4 feature updates

* docs: align triad migration with adopted amendment protocol

* docs: record triad preparation and adopter blocker

* docs: record successful triad adopter candidate rehearsal

* Record run C of the Gate B rehearsal at the post-landing main 837928a and regenerate the adoption plan from it.

Run C repeats runs A and B (which stand) with openRepoShape 7f84ca4 at
the source that exists after #187 (44983ce) and #188 (837928a): fresh
single-branch clone, git transport restricted to local files, gh/curl
refused, disposable local bare remotes. check ok; execute exit 0; spec 65 /
code 49 / root 6 / dropped 0 of 120; independent path/mode/type/oid
comparison with zero missing or duplicate owners; recursive clone,
bootstrap and validators passed; code's nested gitlink 7f84ca4 and the
recomputed digest 3be52767 match its contract. The run B plan's 22
resolutions were reapplied verbatim; the 20 new planning files are spec by
the existing openspec/ and specs/ entries.

adoption-plan.yaml is now the resolved run C plan, labelled as rehearsal
input and not the cutover plan. The arrangement is kept host-local as the
read-only input for T006-T010. Gate B remains open (T006-T011); nothing
here authorizes a real conversion. Private receipt adopter-fix-20261008-1HztL0. Part of #186.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T006's five rehearsal patches are recorded: the assembly front door with two lockstep pin bumps, and the code and spec legs' own front doors, all made against run C's rehearsed split.

Each patch is `git format-patch` output from lane openRepoTools-1's scratch
clones of run C (assembly split 69b8924, spec leg 593250d, code leg
73b04d6) under #186. Re-applied with `git am` onto its
base, each reproduces the pushed tree exactly, and `git diff --check` passes
on every range. assembly-0002 and -0003 are bump-leg.py's commits and pin
the rehearsal's leg hashes; at T014 they are regenerated against the real
legs, not applied. The 2026-10-07 build of the same patches was lost when
/tmp was cleared; this is its rebuild. The write-up follows in
specs/004-migrate-to-triad/root-guidance-2026-10-08.md.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record T010's two workflow patches for the triad: the code leg's standalone tests workflow and the assembly's exact-pin integration job in its own file.

Checkpoint of the CI evidence for #186. The legs do not exist on GitHub yet
(Gate C), so these are patches against the rehearsed run C arrangement: the
code leg's tests.yml at 73b04d6 (its six jobs and trigger policy kept, the
standalone checkout context named) and a new .github/workflows/composed.yml
for the assembly at 69b8924, its own file because validate.yml is a pinned
shape copy. Both `git apply --check` cleanly against run C. The measurements
and local proofs follow in test-roots-and-ci-2026-10-08.md.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record what the triad split breaks in the test suite, the root rules and composed contract PR #190 answers it with, and the local proofs of both T010 workflow patches.

Measured on the rehearsed run C code leg 73b04d6 in three contexts (mounted
in the assembly, standalone, standalone without the dependency): the same 17
hygiene tests fail on files the split moves out of the code leg, and nothing
else in the measured selectors. PR #190 is red at origin/main in 18 tests and
188 passed with nothing skipped on today's layout; CI green per push. In a
composed candidate pinned with bump-leg.py, the validators pass, the composed
job refuses a missing spec leg (exit 1 at its context step, exit 4 from the
suite), and the composed and standalone leg runs fail only on the code leg's
missing .gitattributes (a T006 finding) and two lane-suite stamp assertions
the long run-root path pushes past the row cut. Hosted runs wait for real
legs (Gate C). Part of #186.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T006's run C rebuild is written up: the collision inventory, 32 repaired links, the leg front doors, the 35-commit provenance correspondence and every bootstrap, validator, OpenSpec and Speckit result, with the assembly patches replaced by leaner ones that respect the root files' length caps.

specs/004-migrate-to-triad/root-guidance-2026-10-08.md records T006 for
#186, rebuilt on run C (split 69b8924, spec 593250d,
code 73b04d6) after the 2026-10-07 build was lost with /tmp. The assembly
patches are replaced: code/tests/test_repo_hygiene.py caps AGENTS.md and
README.md at today's 316 and 486 lines and admits lines only for a rule. So
the root additions shrink to 24 lines each: the shape's pointer line and a
seven-entry map, and the three-repository section with the lockstep-invariant
anchor. The leg patches are unchanged. All five patches re-apply to
identical trees, and `git diff --check` passes. Findings for lane 3, T007,
T009, T010, T014 and T017 are in its section 10. Nothing lands on main from
this task.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record T011's restore rehearsal of the source from the Gate A bundle: 90 checks pass, no captured ref moved except forward, and a dirty tree rebuilds only with the staged and unstaged diffs the October 7 capture does not take.

First commit of specs/004-migrate-to-triad/gate-ab-evidence-2026-10-09.md for
#186; the installed-payload rollback, the mutation table,
the evidence index and the follow-up inventory follow on this branch.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record T011's rollback of the installed payload, the mutation table, the Gate A/B evidence index and the follow-up patch inventory: both rollbacks return every placed file, mode and hook to main's install, the copy-back leaves the receipt stale, and nothing real was written.

Steps 2-4 of T011 in specs/004-migrate-to-triad/gate-ab-evidence-2026-10-09.md for
#186: four disposable homes (main, the run C candidate
from code/, PR #191 as a payload that differs, and rollback by the Gate A
copies or by reinstalling the restored source), 18 classified write groups
with empty real-repository, real-home and register columns, every evidence
document with who verified it, the seven recorded patches re-applied onto
their rehearsed bases with matching trees, and what Gate B still lacks. The
private receipt ID is rollback-20261009-ILvoVC; its commit follows.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name the T011 private receipt's brett-wip commit, 56f1e7c4, and say why the hygiene selector did not run on this document while the two all-files hygiene checks find nothing in it.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record T007's assembly entry point as a patch and T007/T008's installer evidence for the triad migration.

The root openRepoTools of the assembly cannot land on main before the split,
because its path is the implementation's, so it is kept here as a
format-patch of the commit made in a clone of run C's rehearsed split; git am
on a fresh clone reproduces its tree. installer-2026-10-08.md records the
arrangement relied on, the pin grammar, the one-liner contract and how it
compares with T006's README sentence, the 15-row matrix with its tests and
red-at-base results, the runs, lane 2's installer rows, and what remains for
T009, T010, T014 and T015. Nothing here authorizes a real conversion.

Part of #186.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record the composed triad candidate that carries every Gate B follow-up: its inputs, apply order, commits and trees, its validators, and its install compared with today's main.

The suite runs on the candidate are queued behind the workstation lock and
are recorded in a later commit.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Bring the T009/T010 test-roots record up to PR #190's head dbc6837, with both reviews, the .gitattributes decision and the dated follow-ups.

The record stopped at c139e88. Since then e3a76b5 refuses a leg whose git
status cannot be read and keeps .gitattributes in the fixture legs (Copilot
round 1), and dbc6837 adds T006's assembly-root caps and fails a missing lane
manual by name. Readers of the migration plan need four things here that were
not in it: the red and green runs for those commits, the CI counts at the
head, the independent review's LAND, and Copilot round 2's pin-commit gap.
That gap is reproduced here, and validate-pins.py is shown to close it in the
assembly job.

The .gitattributes decision is now stated outright. It agrees with T006's leg
patch and disagrees with plan.md's placement table, which is a follow-up for
the plan's owner. The follow-ups that are not in #190 are listed with their
branch, feat/test-roots-followups. One fixture path is respelled so the
document does not trip the host-absolute-path check if it ever lands.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Correct the test-roots record: the standalone leg's lane suite aborted on an unguarded manual read, which this record had attributed to two stamp assertions alone.

P3's log shows `ln_doc: unbound variable` at line 6595. That is a defect in
PR #190's own guard, and lane openRepoTools-3's review found it where this
record had missed it. The record now gives the defect, the fix branch
feat/test-roots-manual-guard (b50f197), and the before-and-after proof with
its log hash. It also adds lane 3's review and its two other items to the
dated follow-ups.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record the candidate's state at the coordinator's checkpoint: no suite run has finished, the composed run was stopped at about 20% and must be rerun, and the CI numbers it will be compared with.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* T018 disposable half: feature selection, OpenSpec and park/resume/status in a triad fixture (#186)

Evidence of 2026-10-09 from the composed candidate (assembly 3bd95a7, spec
9e1d006, code f0af7d9, nested openRepoShape 7f84ca4), copied into fresh bare
remotes and driven from three temporary homes under env -i, a disposable
agent root and workspace record, file-only git and a private network
namespace. Selection resolves the same root and feature directory from the
root and both legs. OpenSpec answers only from the spec leg. park commits and
pushes one WIP per leg and leaves the root and its gitlinks alone. resume
refuses a root that the bootstrap itself dirtied, and silently drops a parked
nested gitlink move. Every refusal is relayed verbatim; nothing was forced.
No task is checked complete.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Dated corrections to #192's run C and T006 evidence: #193 items 8–13

Part of #186. Text only, in three Markdown records under
specs/004-migrate-to-triad/. No patch file, plan or YAML is touched.
Every figure was measured again on 2026-10-09.

- adopter-rehearsal-2026-10-08.md
  - Item 8 (review 5464893559 finding 5): one dated block says where the
    records are now, what run C superseded, and that run B's plan stays
    at 63810dd. It records the 2026-10-08 restart that removed runs A
    and B's repositories, and the openspec 1.2.0 observation. openspec
    1.13.1 now exits 0 on 837928a and on 71b05cb.
  - Item 10 (finding 17): `check` reads the clone's local main, not
    HEAD. This plan gives plan-stale, exit 1, against any newer main.
    Reproduced both ways.
  - Item 12 (finding 6): the "no ensurepip" cause is withdrawn. The
    refusal is real, and its cause is not established.
- root-guidance-2026-10-08.md
  - Item 9 (findings 8 and 9): 18 front-door test functions (17 + 1),
    not 17. The command test stays red after code-0001. The "23 in 9"
    total disagrees with its own per-file list (26 in 11), and which is
    right is not established. Also :6, and :195's consumer-reads is on
    main.
  - Item 11 (finding 16): no receipt is cited. The record now says what
    the patches reproduce from public inputs, and which rows exist only
    in the document.
- adopter-rehearsal-2026-10-07.md
  - Item 8: one dated line. The false "On this branch" sentence is in
    this file.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record v3 of T007's entry-point patch, which checks the gitlink before it runs any pinned implementation, and the network needs T014 should document.

Lane 3's review of the v2 patch (T3) required the remote path to confirm
the gitlink at the pin's submodule_path before it fetches or runs the
implementation the pin selects. v3 (6acc147 on 69b8924) does that, and git
am on a fresh run C clone reproduces its tree 87782af. The runs table rows
for the round-2 head and CI are still to be added.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refresh the T011 record's index, inventory and Gate B list to origin at f87fd5a, and correct what lane 3's review and lane 2's receipt check found

Answers lane 3's review of fa44d75 (#186 comment 6082139424) and folds in lane
2's verification of receipt rollback-20261009-ILvoVC (#186 comment 6082370012).
Only specs/004-migrate-to-triad/gate-ab-evidence-2026-10-09.md changes. Every
refreshed passage says Refreshed or Corrected and keeps the 01:1xZ text it
qualifies.

- F11: no stage changed a hook, so the hook rollback is vacuous. The Gate A
  backup holds no settings.json, and RESTORE.md's copy-back restores only
  installed/. 849592b's subject overclaims hooks.
- F15 and F18: sections 4 to 7 re-read origin at 20:29Z-20:37Z, with main at
  f87fd5a after #192 and #190 landed. They now record T007's entry point on
  the installer branch at 51993a4, the candidate at 935f53f, every review
  since 01:13Z, and an inventory that maps each named follow-up to a patch,
  a PR, an owner or a deferral.
- F4-F9, F12-F14, F17, F19, F20, F22 and lane 2's corrections:
  - the restored bundle's identity, and the 90 checks by category against
    #189's 97;
  - the two near-tautological check families, and main's 70 files plus one
    symlink;
  - "live" meaning the shared checkout at 00:44:08Z;
  - 10 installs, not 12, and no placed file added or retired;
  - 076062af and the final snapshot;
  - T010 trees with no earlier record;
  - GIT_OPTIONAL_LOCKS, the copied capture block, and nested repositories
    rather than caches.

Part of #186.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Correct three rows of the refreshed T011 record from lane 3's review of c1283b5: the candidate branch's creation sha, the two #190 follow-up branches' creation and first move, and the owner of run C's ninth docs/ file

From lane 3's review (#186 comment 6089982753), N1 to N3, each checked at
its source:
- N1: 004-migrate-to-triad-candidate was created at b21c511 at 01:58:43Z,
  and b21c511 -> 935f53f was pushed at 02:43:24Z (GitHub activity API).
- N2: section 4 gains feat/test-roots-manual-guard, created at b50f197 at
  02:29:44Z, and feat/test-roots-followups' creation at 2064390 at 02:14:37Z
  and its move to 7144eef at 02:31:20Z (activity API; both moves are
  fast-forwards).
- N3: the plan's unnamed ninth docs/ file is tests/test_repo_hygiene.py.
  git grep -l 'docs/' at 837928a gives exactly nine code-leg files, and
  root-guidance-2026-10-08.md:191 routes that one to T009/T010.

Part of #186.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Answer lane 3's low findings N4 to N9 on the refreshed T011 record: collision rows, the dropped-check arithmetic, the copy-back's limits, deleted refs, the refresh's real time, and the verbatim run's public citation

From lane 3's review of c1283b5 (#186 comment 6089982753), each checked at
its source:
- N4: root-guidance-2026-10-08.md:69-76 holds the collision and shape/ rows
  (AGENTS.md is :69; CLAUDE.md, :70, is unchanged), so "resolved in the
  patch", not "merged".
- N5: October 7's verify.log:13 is one check of the 16 per-worktree HEAD
  refs, and main's status check moved to the singles (10-restore.log:160):
  12 - 2 + 1 + 11 = 22 and 85 - 17 = 68.
- N6: the copy-back route restores the 29 files and the receipt only, with
  no settings.json and no hook entry.
- N7: a captured ref moved forward or was deleted with its commit still on
  GitHub; re-checked against ls-remote at 22:05:16Z (15/9/9/0).
- N8: c1283b5 was committed at 20:38:20Z, so the hygiene line and the
  header's refresh window say 20:3xZ.
- N9: the public comment marks as inferred only its conclusion; the
  verbatim run's log is host-local and unpublished, so the record now says
  no public citation exists for the run itself.

Part of #186.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Gate B status walk of 2026-10-09: the 46 checklist lines (MET 30, PARTLY 15, OPEN 0, AFTER-GATE-C 1), T001-T019, the Gate C open questions with proposed wording, and a line-by-line reconciliation with lane 2's walk

specs/004-migrate-to-triad/gate-b-status-2026-10-09.md walks
gate-b-checklist.md against origin as fetched at 22:07:06Z and #186's
comments through 22:06:49Z. It approves nothing and changes no task
status.

- Section 1: each line's status, evidence, what remains and owner, and
  which lines moved since lane 1's unpublished first pass.
- Section 2: lane 2's walk, the same path on 004-migrate-to-triad-reads
  @ a7a8e00 (a cross-check that goes into no PR), line by line. It
  differs in substance on 22, 2, 12, 18, 19 and 21. It also answers
  lane 2's review of the first pass (6088829392) item by item.
- Section 3: T001-T019 against the evidence.
- Section 4: questions a-e for Brett, each with text to approve:
  - (a) openspec 1.2.0 fails migrate-to-triad and 1.6.0 passes;
  - (b) "account move";
  - (c) .gitattributes in plan.md against tests/conftest.py on main;
  - (d) hosted CI before the legs exist;
  - (e) e1-e10, including the six-commit openRepoShape drift and the
    post-cutover one-liner's API and jq dependency.
- Section 5: the blockers in dependency order.
- Section 6: the commands.

Private receipts are named by id only, and no host path appears.

Part of #186
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record the runs of #191's review rounds, the strict composed result at 6568f08, and CI on each head, in the installer evidence.

The runs table had two rows still waiting at the first checkpoint. It now
records what each review round changed and every proof, with which ran under
the lock and which ran unlocked under the coordinator's rule for scoped
proofs. It also records the strict composed run of root v3 with the head's
whole code leg, where the only failure is the code leg's missing
.gitattributes (T006), and CI per job on 6568f08.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record the r2 candidate's three suite runs and classify every failure: two candidate defects with owners (the r14 KeyError fixed in #191, #193 item 14) and four environment classes, each shown the same on main or in isolation.

r2 is code 4dbcf6c, spec 9e1d006 and assembly 5d92962, built from #190 at
b50f197 and #191 at 13cc752. Composed: 1411 passed, 5 failed, 0 skipped.
Standalone: 1389 passed, 3 failed, 24 skipped. No submodule: 1168 passed,
6 failed, 242 skipped. The install from the assembly root differs from
main's only in the implementation it hands over to. The composed rerun at
#191's final head 6568f08 with T007's v3 entry point is still owed.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Lane 3's follow-ups on 096de93: name 837928a and #190 in the T006 test note, narrow "only record", add the census row, cite 1abee1d

Part of #186. These answer the should-fix findings 1–3 and nit 4 of
lane 3's review of #193 items 8–13 (#186 comment 6092925484). Text only,
in the same three records. Every claim was checked again on 2026-10-10.

- root-guidance-2026-10-08.md, section 10 item 6: the 18 tests are now
  described as at 837928a. One dated bullet says what #190 (f87fd5a)
  changed. Through ROOTS.path_for, 17 of them read the assembly or spec
  root, and they skip in a standalone code leg. The line-ending test
  reads the code root's own .gitattributes. This was measured by direct
  calls on main's code paths.
- Runs A and B (both rehearsal records): "only record" becomes "their
  logs survive only in that receipt". Run C's legs keep 558a12b and
  48d98cd as their heads' parents. The splits, 6d52162 and the remotes
  are gone. Checked in --no-local clones of run C's legs.
- root-guidance-2026-10-08.md: section 6's 85-token census joins the
  list of rows that exist only in the document.
- adopter-rehearsal-2026-10-08.md: the plan was replaced on main in
  #192's squash 1abee1d. 1a26463 is kept and named as the branch merge.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record #191's two Copilot rounds and the final head 8246983 in the installer evidence.

Round 1's two fixes and the one finding kept, with the reason; round 2's
body correction and the test that shows the behaviour; and a strict
composed run of the module's composed and local rows with root v3 and the
final head's code leg.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Date the "4 now pass only because they read the leg's own files" bullet: since #190 three of the four skip in a standalone leg (lane 3's 5481348731, item 8 leftover)

Lane 3's review 5481348731 on #196, item 8, left one [low]: root-guidance's
bullet "4 now pass only because they read the leg's own files" sat right
under the bullet that says, since #190, the 17 document-reading tests skip in
a standalone leg. The two contradicted each other back to back.

The four are the ones that passed after code-0001 at 837928a. At main
(f87fd5a) three of them (test_agents_md_is_short_enough_to_be_read,
test_readme_is_short_enough_to_be_read, test_claude_md_points_at_agents_md)
read through ROOTS.path_for and skip, with or without code-0001's own
AGENTS.md, CLAUDE.md and README.md. The fourth,
test_the_root_carries_the_line_ending_rule, reads the code root's own
.gitattributes: FileNotFoundError before code-0001, a pass after it.

Measured on 2026-10-11 in a scratch standalone leg built from f87fd5a (spec
trees and front-door files removed): before code-0001, 3 skipped and 1
failed; after it, 3 skipped and 1 passed.

The bullet now says what held at 837928a and carries one dated correction.
Nothing else in the file moves. Part of #186.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Date the T010 record against #192's and #190's landings, correct its summary of lane 3's #190 review, and give lane 3's review of the T010 patches a disposition per finding.

Lane 3's review of #196 (5481348731) raised three points.
- Finding 4. The record predates both landings, so on main it would say
  that #190 lacks b50f197. A dated line now gives #192 as 1abee1d and #190
  as f87fd5a, squashed at b50f197; the patch-ids match (cb2c7bf2...).
- Finding 5. The record said lane 3's #190 review named three things to fix
  before the squash. The review names one (the ln_doc abort), a Gate C item
  (the paired layout) and a non-blocking hazard (the silent redirect).
- Finding 6. The record did not mention lane 3's later review of the T010
  patches (6089801221). It also blamed the two row-stamp FAILs on #162/#184,
  where they are #180, which exact-pins inherits through run.sh.

Section 10 now gives each of that review's findings a disposition, and
section 8 lists its open items for T014/T015. The two patches are
byte-identical. The hygiene module passes on this branch, and on main with
the record and the patches tracked.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Gate B walk: lines 20, 29 and 31 are PARTLY by the walk's own legend, 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>

* Point the three "no review verdict posted" rows of the T011 record at lane 3's verdict, 6089801221 (lane 3's 5481348731, item 7)

Lane 3's review 5481348731 on #196, item 7, left one [low]: gab:436, :443 and
:513 still say "no review verdict posted; lane 3's reviewer ... has resumed"
for the T010 patches and the follow-ups. Lane 3's verdict, #186 comment
6089801221, was posted at 21:47Z on 2026-10-09, after this record's origin
read of 20:29-20:30Z.

Each of the three rows now carries a dated correction that points at it and
says what it concluded for that row. The original text and the read time stay
as written.
- The `004-migrate-to-triad-ci` row and the T010 inventory row: `d28e2cc` is fit
  for evidence-3, both patches apply, and #180 reaches the composed job
  through `run.sh` (its finding 13).
- The `feat/test-roots-followups` row: `01ccc86` is fit for a PR onto main
  after one edit, the 340 to 347 assembly cap (its finding 7).

Part of #186.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name the host behind "the bench's openspec is now 1.13.1" in run C's record, and give lane 3's 1.2.0 and 1.6.0 reading (lane 3's 5481348731, item 9)

Lane 3's review 5481348731 on #196, item 9, left one [low] on
adopter-rehearsal-2026-10-08.md:53: "The bench's `openspec` is now 1.13.1."
That depends on the container. On lane 3's host at 00:1xZ on 2026-10-11,
`which -a openspec` lists a user-level npm install first, at 1.2.0, and the
system install second, at 1.6.0. `validate migrate-to-triad --strict` then
exits 1 under 1.2.0 (the SHALL or MUST error) and 0 under 1.6.0.

The sentence now names the bench, lane 2's, and the dates of the two readings
(6088829392 item 8 on 2026-10-09; 6103482530 on 2026-10-10). A dated
correction records the first wording, says the version depends on the
container, and gives lane 3's reading with its exit codes. It points at lane
1's walk, section 4 (a), which records the same split.

Locations are described, not written as paths. gate-b-status-2026-10-09.md:266
repeats the claim and is lane 1's file, so it is left for its owner.

Part of #186.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Correct root-guidance:432 after lane 2's check of 1578f99: the fourth test reads the same file at 837928a and since #190, and the note records the line's first wording (lane 3's 5481348731, item 8)

Lane 2's checker verified 1578f99. Every measurement in it holds, but its
wording on line 432 needed a fix.
- test_the_root_carries_the_line_ending_rule reads the code root's own
  .gitattributes the same way at 837928a and at f87fd5a. #190 changed only its
  docstring. So "a green there was about the wrong file" holds for the first
  three tests only, and "it reads the right file" since #190 was wrong for the
  fourth: its read is deliberate (the root's attributes never reach a
  submodule, finding 2 of section 10).
- The line did not record what it first said. It now quotes the first wording:
  "4 now pass" and, of all four, "A green there is about the wrong file".
- "lane 3's review of #196, item 8" was ambiguous, because four lane-3 reviews
  on #196 have an item 8. The note now cites lane 3's 5481348731 by id.

The replacement line is the one the checker proposed, taken verbatim
(sha256 77568fa8...64d). Nothing else in the file moves. Part of #186.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record r3 of the Gate B candidate: #191 at its final head 8246983 with T007's v3 entry point, the install modules green in composed mode, F1 gone, and the install still differing from main only in the implementation it hands over to.

Code 9c11276, spec 9e1d006, assembly 3891943; every git am exited 0 and both
pins were regenerated with bump-leg.py. The four install modules, composed
and isolated with SIGINT at its default: 281 passed, 0 failed, 0 skipped.
The intermediate r3a at 6568f08 (278 passed) is recorded beside it. The full
composed suite is redone at the frozen source for Gate C.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Close W22's findings 2-4 on 83bcec7 in the T011 record: date the section 7 leftover, restore "before the composed job is relied on", name the branch at :443 (lane 3's 5481348731, item 7)

W22's adversarial check of 83bcec7 (lane 2) found three leftovers of lane 3's
5481348731 item 7, in specs/004-migrate-to-triad/gate-ab-evidence-2026-10-09.md.

Finding 2 [low]: section 7 item 4 ("Refreshed 2026-10-09T20:30Z") still said
"The T010 patches: no verdict yet; lane 3's reviewer has resumed (#186 comment
6088703549)" with no pointer, while :436 and :513 already point at 6089801221.
It now carries the same dated correction, 2026-10-11 like its siblings: the
verdict, #186 comment 6089801221 (2026-10-09T21:47Z), came after the record's
origin read; fit for evidence-3, with #180 to settle before the composed job is
relied on (its finding 13). The original words stay as written.

Finding 3 [nit]: :436 and :513 said "One thing to settle first" and "with #180
to settle first". 6089801221 (c) says "One thing must be settled before the
composed job is relied on". Both rows now carry that condition, so it attaches
to relying on the composed job and not to "fit for evidence-3".

Finding 4 [nit]: :443 said "It answers the code-half part of #193 item 2" right
after a sentence that begins "It is #186 comment 6089801221", so "It" read as
the comment. It now says "The branch `01ccc86` answers ...".

One file, +7/-4. Part of #186.

Lane: openRepoTools-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Correct the installer record for lane 3's DO NOT LAND on #196's installer files: no host path, a T008 table of the tests at the final head, and a disposition for every open item.

Lane 3's review 5481368376 asked for six corrections and noted one minor
point; all seven are made here. The record no longer names a host-absolute
location for run C's README (finding 0). It says what v3 closes, the
mismatched pin, and what it does not: a lockstep-consistent pin at code
from before #191, which is now a named T015 item with a capability check
proposed for #193 (finding 2). The T008 table now lists every test in
tests/test_install_payload_source.py at 8246983, which is main a74c7fc
(52 tests, 109 cases). Its base column comes from that module run against
c4864ac's installer (finding 3). The record names 8246983 as the head,
with its CI and the landing on main as a74c7fc on 2026-10-13 (finding 4).
The wip_shape_ref disposition gives the F2 fallbacks (finding 5). Each of
lane 2's nine open items in #186 6090038382 is fixed, accepted or given
to a task (finding 6). The minor point: "five things" and both reviews
of 2411f40 (finding 7).

The patch is unchanged.

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Correct the installer record against lane 2's check at 8246983: the walk residue's reason, the 200-garbage refusal, row 7's cases, CI on cddd1f7 and the measured holds.

- Item 1: the code reads the gitlink at the pin's submodule_path and refuses
  where none is recorded (24a, 24c), citing lane 3's 5481368124 finding 5 and
  its cheaper tightening, and finding 7 on #191's body.
- Item 4: a 200 that is not GitHub's refuses where main installs, like
  item 3; disclosed here, since #191's body does not name it.
- Item 2: the adopted missing-ref and missing-repository rows (19c-f, 19h).
- Item 7: the patch's header and commit message overstate; it stays.
- Item 9: row 7's [single] case is an adopted ref moving to a commit with
  no pin, which is refused; no test freezes a single repository.
- The section's opening cites lane 2's measurements at cddd1f7 (#186
  6103618033) and 8246983 (register note 2026-10-13T18:22:15Z).
- The runs table gains CI on cddd1f7 (run 38096352239).

Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add dated 2026-10-13 lines to the candidate record: #191 landed as a74c7fc 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>

* Gate B walk, dated 2026-10-13: #191 landed as a74c7fc, what #196's final refold carries, and the hold for openRepoShape v1.0.0

These are dated lines only; no earlier text is rewritten. No status changes:
MET 27, PARTLY 18, OPEN 0, AFTER-GATE-C 1.

- #191 landed on main as a74c7fc, the squash of 8246983. Recorded at the
  header's #191 line and at question d's #191 CI line.
- What #196's final refold carries, each from the earliest head that holds
  its fixes, with #196's "Input heads carried" table named for the exact
  heads:
  - the installer record from 7668c51 on: the host path at its line 13
    is gone, and lane 3's 5481368376 is answered; 7428e78 adds lane 2's
    check;
  - slice B from 2c040d6 on: lane 3's 5481348731 items 7-9, with the
    T011 record now taken from slice B;
  - the candidate record from e81100c on: r3.
  Recorded in the header, the refs table, section 2's item 16 and
  section 5's item 4.
- The hold for openRepoShape v1.0.0, on Brett Heap's word relayed by
  lane 3 (#186 6152410188), with its after-the-tag order: the pin-bump PR,
  a fresh rehearsal and plan at v1.0.0, then the cutover. #196 is not
  held.

Part of #186
Lane: openRepoTools-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brett Heap <service@opensoft.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready landing gate: runs tests-macos once before the squash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants