Repository navigation
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
Conversation
… 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>
|
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 |
Reviewer's GuideThe 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
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
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>
…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
left a comment
There was a problem hiding this comment.
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.
-
CI, and the hygiene test cure.
- CI on
2411f40:guard-launch-modesuccess (-k 'guard_launch_mode or repo_hygiene'),tests-windowssuccess,parse-macossuccess, SonarCloud success.tests-macoswas skipped (noreadylabel). 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
e406e0ethe 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:tests1 failed / 1378 passed / 7 skipped,tests-no-submodule1 failed / 1164 passed / 221 skipped, plusguard-launch-modeandtests-windows. Locally that head gave 152 passed at main against 1 failed / 151 passed at its merge. - What cured it.
ecd7aa4spells 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_REPOis assigned in exactly two places:$REPO(:634), and$PIN_SOURCE_REPOSITORY(:690), which happens only after the pin has passedpin_parseand 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_REPOand 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
$REPOand the validated pin'ssource_repository, and pinPAYLOAD_REPO's two assignment sites. - Blind spot. The guard's
fetching_linesstill does not scan the twocurl … https://api.github.com/…lines (:552,:593).
- CI on
-
Byte-for-byte on today's layout: identical, offline (re-measured on
2411f40).- Setup: fresh
HOMEs,env -i,PATH=<shims>:/usr/bin:/binwith a loggingghandcurlthat both exit 1 (no real gh on that PATH), umask 022../openRepoTools --installat main63810dd, at2411f40and at the merge426fbcd. - 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, andsettings.json, which is identical, mode 600, with the same two hook entries. - The two that differ, by construction:
~/.local/bin/openRepoToolsis each tree's own source: blob9b7cbd2at main,04769c4here, 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 exceptupdatedfor 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).
- Setup: fresh
-
Behaviour change on today's layout, fetching path (Gate C item; re-measured on
2411f40).- Which runs:
payload_selectsends stdin runs (:853) and every incomplete local set (:839) throughpayload_resolve_remote. Every installed-copy reinstall is an incomplete local set. - What it asks: on today's repository,
commits/main(sha media type,:635), thengit/trees/<commit>(:643), thengit/trees/<contracts>(:650, because today's root carriescontracts/), all before any payload. The curl fallback therefore now needs api.github.com, andjqfor 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/maininstead 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 initfrom an installed copy now refuses an unresolvable source (:3108) where base fell back tomain. - 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.
- Which runs:
-
The parent-directory walk changes a complete tree's install under any assembly (
assembly_above:714-725, consulted at:825BEFORE the completeness check; re-measured on2411f40).- 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.yamlthat pinsopensoft/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.
- LF pin: this head installs and adds a 38th line,
- Reach: nothing in today's estate triggers it; no ancestor of any checkout or of
~/.local/bincarriescontracts/, 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'ssubmodule_pathandsource_repositoryto this checkout, and check completeness first.
-
The resolution core holds.
- Parser:
pin_parse(:438-514) gives the same result under real bash 3.2.57 (dockerbash:3.2) as under 5.2 on 26 cases. The standard's owntemplates/assembly-root/contracts/code-pin.yamlat7f84ca4, 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 duplicatetree_sha256;---; a quoted key. - Lenient but harmless:
verify_pin: |(only presence is read) andschema_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/REFon an unreadable pin, and every payload request after resolution names the 40-hex commit.
- A gitlink that disagrees with the pin refuses (
- 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 skippedpayload_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 takeindex.lockin the user's checkout.--no-optional-locksavoids it.- The header still calls
fetch_from_repoa "DELIBERATE COPY" of openRepoShape's shim (:250), which it no longer is. 2411f40'srecorded_gitlink(:731) reads withread … <<<. bash 3.2 has here-strings, and bash before 5.1 backs them with a file in$TMPDIR, which the install already needs formktemp -d.
- Untouched: receipts, the settings merge and retirement.
- Parser:
-
Bash 3.2: clean on
2411f40.bash -npasses under 3.2.57. The added lines (c4864ac..2411f40) contain 0 bash-4 constructs, andparse-macossucceeded. 3.2 runtime was exercised only for the parser functions.tests-macoswas skipped, so no CI job has run this head under 3.2. -
Tests.
- Collection at
e406e0e(--collect-only -k 'openrepotools_command or payload_source'): main 97/1296, merge 187/1386. The 97test_openrepotools_command.pynode ids are identical, and the +90 is exactly the new module.ecd7aa4and2411f40change no test file. - Runs: At
2411f40itself (submodule39d5c98),-k 'openrepotools_command or payload_source or repo_hygiene'gave 332 passed, 7 skipped (the composed cases), 0 failed (20m29s): 97 + 83 + 152. Earlier, ate406e0e'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_farmleaves both out),GH_TOKEN/GITHUB_TOKENare dropped, and every HOME is a temporary directory. - Fixtures are hand-written:
PIN_TEXT(:229) andPROJECT_YAML(:255) are hand-written copies, not derived fromupstream/openRepoShape/templates/assembly-root/{contracts/code-pin.yaml,project.yaml}at7f84ca4. 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_SKIPonly. - The fake root in
test_openrepotools_command.pyhas nocontracts/, so today's third call is exercised only bytest_r08_a_single_repository_without_a_code_pin_is_permitted.
- Rows 1-15 each have tests. The 7 composed cases skip in standalone code, and row 15's WSL side is
- Collection at
-
Overlap.
- #190 (
c139e88, same basec4864ac):- It merges with
e406e0eonto 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_ROOTandWINDOWS_SKIP, so this module's import holds. - This module carries its own composed-context discovery (
ASSEMBLY = REPO.parent, a regex oncode-pin.yaml,OPENREPOTOOLS_COMPOSED), parallel to #190'sROOTS/OPENREPOTOOLS_ASSEMBLY_ROOT. The two can disagree; converge onROOTSafter #190.
- It merges with
- Lane 3's
docs/triad-lane-tooling-analysis.md§4.10 (origin/feat/triad-lane-toolingc45a452):- (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.
- #190 (
-
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";
--versionunchanged;- 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-remoteis empty). - Unchanged since open: the author's body. Only the Sourcery summary was regenerated.
- These hold:
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-macoswaits on thereadylabel. - Lock deviation:
tests/run.shwaited on a workstation-widepython3 -m pytestcensus that other projects' suites kept above zero. So local runs tookflockon/tmp/openrepotools-pytest.lockand ran run.sh's in-lock invocation (python3 -m pytest tests -q -p no:cacheprovider --basetemp=<scratch run root>, withTMPDIRand pycache in the scratchpad andPATH=/usr/bin:/bin). CI is the record.tests/test_lane_helpers.shwas 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
left a comment
There was a problem hiding this comment.
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
-
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) andcollect_skills(:1959): they readPAYLOAD_DIR/PAYLOAD_MODEfrompayload_selectinstead of computingselfdir. A local source is never completed from a network.install_commands(:1693): callspayload_select(:1712) right afterplan_install_targets, and its refusals name$(payload_where).wip_shape_ref(:3093): callspayload_resolve_remote(:3108) before fetching the code'scontracts/openreposhape-pin.yaml.wip_init(:3568):ref="$(wip_shape_ref)" || exit "$?", which is equivalent to the old line underset -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-startsedreader returns all 17/3/3 names from head.
-
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=…, withghandcurlshims that log and exit non-zero. Each scenario ran once with baseopenRepoToolsand once with headopenRepoTools.- Scenarios: a fresh install from a checkout; a reinstall over it; retirement of a recorded
restart(removed) and of a foreignrestart(named and left); an unparseablesettings.json(refused, exit 2); workBenches' sentinelOPENREPOTOOLS_REPO/OPENREPOTOOLS_REFrun 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-startsentinel at:240, read at workBenchesffd5516).
- Scenarios: a fresh install from a checkout; a reinstall over it; retirement of a recorded
-
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 at7f84ca4points people to.- Request log against the PR's own fake GitHub, today's layout, no
gh:- base: 23
raw.githubusercontent.comfetches and nothing else. - head:
api.github.com/…/commits/main,…/git/trees/<root>and…/git/trees/<contracts>first. Today's root does havecontracts/. Then 23 raw fetches at the sha. Placed bytes and stdout are identical when the API answers.
- base: 23
- 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
ghinstalled but unauthenticated (401).
- Against the real GitHub, read-only, unauthenticated:
/rate_limitshowscore.limit60 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,
remaining59→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 withoutgh): both exit 2 with nothing placed. Base fetches all 23 files first and then namesjqfor the hook merge. Head refuses after one request withcurl: 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(andproject.yaml) at$REF, ghcontentsfirst 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
jqneed are the accepted price of the frozen ref on today's layout, stated in the body and in the usage/README text.
- (a) Decide the layout through the existing transport pair, without the API. Fetch
- Request log against the PR's own fake GitHub, today's layout, no
-
F2, BLOCKS: an installed
wip initon 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 whosecontracts/openreposhape-pin.yamlnames a pinned openRepoShape commit, and a decoy on openRepoShapemain.- Normal network: identical at base and head; both seeded from the pinned commit.
OPENREPOTOOLS_REF=no-such-ref: base rc 0, seeded from openRepoShapemain(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-wipexists on the fake GitHub and its clone is on disk, unseeded, and noworkspace.yamlwas written. - The refusal is
payload_resolve_remote's--installtext, raised from:3108inside$(…)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 givewip initits own refusal naming what this run already did. In either case, disclose that an unresolvable source now refuses wheremainused to be the answer.
-
Adopted path (check 3): HOLDS. I built 28 scenarios myself. The fixture is a fake assembly with
contracts/code-pin.yamlin the brief's grammar,project.yaml, acodegitlink, and a code repository holding head's payload, served by the module's fakegh/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 andsettings.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: ../xandsubmodule_path: ..;?ref=in the slug;- a duplicate
commit; leg_role: spec;- a duplicate
tree_sha256; - a backslash value.
NoPWNEDfile 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'scommits/<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.
coderecorded as a tree instead of a gitlink: refused.ghreturning garbage while curl+jqanswer: installs the pinned code.- All 28 also pass at runtime under a locally built bash 3.2.57 (finding 9).
-
Pin-grammar fidelity. Head's
pin_parse, extracted and run on every adopter-writtencontracts/code-pin.yamlon this workstation, accepts all nine (MedxGlass, InkRouter IRSS/IRRS, openXdox ×2, openDox ×2, MedxEHR, openWallet) plus the October rehearsal assembly's pin. -
Latent risk, not a block:
assembly_above(:714) walks every ancestor to/. Any ancestor carryingcontracts/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>.
- Fixture: workBenches' own layout, a complete vendored tree at
-
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_requestrun on2411f40and checks outrefs/pull/191/merge, the head merged into main63810dd.tests: 1379 passed, 7 skipped.tests-no-submodule,tests-windows,parse-macosandguard-launch-modepass.tests-macosskipped; there is noreadylabel.
- 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/mergerun 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.
- Red, head's tests against base's installer,
-
Portability (check 5).
bash -nandbash --posix -npass 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 -norprintf -v. Here-strings and${v:0:1}are 3.2-legal. - One
awk -vremains (tree_entry,:604), carrying one segment or a fixed name. - There is no
grepin the diff, so no exit-status trap. - The runtime check that counts is CI's own.
tests-macosruns the suite with "bash on PATH : /bin/bash", Apple's3.2.57(1)-release. Main run 37842584034 at837928apassed 1291 tests that way. So theready-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, staley.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 -eoncommand 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 killswip initat 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, andtests-windowsis 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.
-
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 noevalin the diff. $OPENREPOTOOLS_REPOitself is checked before any request: the sentinel on a fetching path refuses with 0 requests, where base made 2. Both exit 2.API_WHYquotes only the first stderr line ofgh/curl, and neither prints a token there.- The module's
env_forstripsGH_TOKEN/GITHUB_TOKEN, itsPATHfarm contains no realgh/curl, and every module test I ran (the red run and the direct calls) ran inside the network namespace without needing one.
- Pin values are only ever validated data:
-
Copilot (check 7). None. #191 has 0 reviews and 0 review threads (GraphQL
totalCount0).- 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.
-
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
7f84ca4is the curl pointer. recorded_gitlink's index-then-HEAD, stage-0 order matches openRepoShapescripts/repo_shape.py:804.--versionis unchanged.- The fetch refusal names the resolved commit.
- By reading: all seven composed tests call
require_composed()before anything else, soOPENREPOTOOLS_COMPOSED=1fails 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 ate406e0e; I measured2411f40.
- Reproduced:
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-macosthrough thereadylabel. 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>
…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
left a comment
There was a problem hiding this comment.
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.
-
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-macosand SonarCloud: success.tests-macos: skipped (noreadylabel).- 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
13cc752itself,-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).
-
Byte-for-byte, offline: identical. Setup: fresh
HOMEs,env -i, stubgh/curlthat exit 1, umask 022../openRepoTools --installat main63810dd, at13cc752and at the mergef6262c2:- 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
openRepoToolsis each tree's own source (blob18d4c7fhere). Ininstalled.tsv(29 rows) only that row's sha256 (a6eda1f4…) and the stamps differ.
-
My finding 3 (the API probe on today's layout) is fixed. One-liner (
curl … | bash -s -- --install), my fake serving the merge ref asmainagainst main's tree:Machine main 13cc752no 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 403exit 0 exit 0 (lane 2's (b), was exit 2) no jqexit 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.yamlandproject.yamlat$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.
-
The parent-directory walk is unchanged (my finding 4 and lane 2's (a):
assembly_above:810-821, consulted at:921before the completeness check). Re-measured on13cc752: a complete tree fromgit 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/bincarriescontracts/). It should be fixed before any consumer adopts: bound the walk to the mount parent and<root>/worktrees/*/code, or match the found pin'ssubmodule_path/source_repositoryto this checkout. Recommend tracking it on #186. -
The hygiene test still passes by spelling, unchanged.
- Honest: the new probe lines name
$REPOdirectly (:647,:657).PAYLOAD_REPOis 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.comlines.
- Honest: the new probe lines name
-
Lane 2's (d) is still open, adopted path only. The pin-fetch refusal (
:770-774) lists what was asked but not what answered, becausefetch_from_repodiscards stderr. The root entry point'sfetch_atdoes report it. Low. -
New code read.
- Probe:
payload_probe(:676-693) asks the pin first andproject.yamlonly 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$REFonce 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
$REFfile 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 sourcemainagain, as main did. An adopted source that cannot be followed refuses in step 7's words (resolve_refuse).- Bash 3.2:
bash -nunder 3.2.57 is clean, as isparse-macos.
- Probe:
-
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
$REFas given, and the layout comes from two file probes. The title's "resolve the ref once" now holds only for an adopted assembly. Commit13cc752'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 amof the recorded patch reproduces lane 1's tree79db0a4exactly.- Code
588285e= run C's73b04d6plus #191's three files at13cc752, byte for byte (73b04d6's other 49 paths equal main's blobs). - Assembly
1c72d90moves the pin and the gitlink in one commit. Its ownscripts/validate-pins.pyprintspins 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/mainonce (the only request namingmainafter the script itself), reads the pin at that commit, and fetchesopenRepoToolsfromopensoft/openRepoTools-codeat588285e. The implementation then re-resolves the immutable commit, lists the trees, checks the gitlink, and fetches all 23 payload files at588285e. - The installed tree is byte-identical to a
./openRepoTools --installfrom a13cc752checkout: 48 entries and modes, 30 files, and the receipt's name/path/sha256 columns.
T2. Local delegation is correct and has no fallback.
./openRepoTools --installat the assembly root, offline: exit 0, 0 requests, and the same bytes as T1. The only extra stdout line is the implementation'ssource: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 examplegh api repos/$REPO/contents/$PIN_SUBMODULE_PATH?ref=$sha, whoseshafield 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 to13cc752's and to2411f40's. --helpthrough the root, local and remote, is byte-identical to main's.- Temporaries:
execkeeps the exit status and signals the implementation's, and an isolatedTMPDIRis empty after a success and after three refusals. - Bash 3.2:
bash -nunder 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,
--versionprintsopenRepoTools (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 ("at1c72d90…(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'swip initfallback, and the commit's "pass at c4864ac, fail at 2411f40" test claim. I did not duplicate it. - macOS runtime:
tests-macoswaits on thereadylabel. - 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).
…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>
…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>
…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>
|
Head What changed since
The review's F2 cases are tests now
Proofs
Every locked run used 🤖 Generated with Claude Code |
…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>
There was a problem hiding this comment.
🟡 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 initthrough 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.
…: 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>
…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>
| [ -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." |
There was a problem hiding this comment.
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>
|
…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
left a comment
There was a problem hiding this comment.
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:
- Linux
testsandtests-no-submodulefinish green on8246983. They started at 00:16:22Z and were in progress at this stamp. Oncddd1f7both passed (finding 8). tests-macosruns on thereadylabel. The PR has no labels at this stamp, so no bash 3.2 runtime has run on any head.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.
-
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 andpin_mountsreading the value withpin_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 onlytests/test_openrepotools_command.py(theROOTSimport and the README read). Main'sopenRepoToolsis still blob9b7cbd2. - 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.
- The PR's own commits:
-
Byte-for-byte, offline: identical. Setup: fresh
HOMEs,env -i, stubgh/curlthat exit 1, umask 022../openRepoTools --installat mainf87fd5a, atcddd1f7and atab5e8f0:- 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.jsonand both hook entries. - The two that differ are by construction. The placed
openRepoToolsis the head's own source (sha256697fdbd4…), and ininstalled.tsv(29 rows) only that row's sha256 differs, beyond each row's install time. Stdout is identical once theHOMEpath is normalised. Re-measured after the second loss.
-
The single layout's network is today's plus one request. One-liner, my fake serving
ab5e8f0asmainagainst mainf87fd5a:Machine main cddd1f7no 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_acmeownerexit 0 exit 0 no jqexit 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.yamlat$REF, answered 404 (probe_pathopenRepoTools:652-680,payload_probe:685-695).project.yamlis no longer asked.
-
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
8246983so is a ref that moves after the probe (6b). Two residues are in finding 7. - (b)
tests-macosonready. 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 sha25667d8e7ce…), applied to a no-hardlink clone of run C's69b8924. The tree is87782af, 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 acddd1f7checkout install. - What v3 does not cover (T3's second case) belongs to the patch and is reported on #196.
- Measured with v3 (
- 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).
- (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
-
The walk residue: an unrelated pin whose
submodule_pathlines up. These are lane 2's 24a–c, inassembly_above(:843-861) withpin_mounts(:813-833), consulted at:971. Re-measured atcddd1f7; main installs in every case:outer/product: a complete tree besideouter/contracts/code-pin.yamlwithsubmodule_path: productgives 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.yamlwithsubmodule_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/bincannot 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. -
Copilot's two rounds.
- 6a. On
6568f08(5477221892).- The snapshot (Copilot's
:924on6568f08): fixed.payload_local_adopted(:881-941) copies each file into$WORKDIR/pinned-payloadwithcp -p, hashes the copy against$PIN_COMMIT, and pointsPAYLOAD_DIRat 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,codewith a trailing space, and CRLF. An unterminated"codealso 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.
- Match
- The staged-deletion fallback (Copilot's
:876;recorded_gitlink:867-879here): kept, and I agree.- The rule is openRepoShape's own
recorded_gitlink(scripts/repo_shape.py:804-835at7f84ca4). It takes a stage-0160000row 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_COMMITeither 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.
- The rule is openRepoShape's own
- The snapshot (Copilot's
- 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_probesaves 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 nocontracts/code-pin.yaml. Atcddd1f7the body said "A ref that moved between the probe and that commit is refused", which was broader than that. - Measured on
cddd1f7, before8246983existed, with my own fake:- Setup: the assembly's
mainwas commit A, which pins code X. Commit B pins X′, which is X with one line added toskills/restart/SKILL.md; B moves its pin and gitlink in one commit. - The move: the fake moved
mainfrom A to B before request 2, right after the probe. The installer ran from stdin withOPENREPOTOOLS_REF=main. - with
gh:rc=0 req=29, and X′'s marker line is in the installedSKILL.md; - without
gh:rc=0 req=29, the same; - with no move:
rc=0, and no marker.
- Setup: the assembly's
- 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.
- The code:
- 6a. On
-
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 since6568f08. That is honest. The heading should name6568f08as 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.
- "Green at the head (
- 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.
- Holds:
-
Tests.
-
CI on
cddd1f7:guard-launch-mode,tests-windows,parse-macos, SonarCloud, Sourcery and Copilot succeeded.tests-macoswas 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, andtests-macoswas skipped.testsandtests-no-submodulewere 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, withPATH=/usr/bin:/bin, gave 347 passed, 9 skipped (all nine are composed-acceptance skips), 1070 deselected, in 16m26s. At8246983,-k payload_sourcewas still running at this stamp, so CI is the record for the one new test. -
Bash 3.2:
bash -n openRepoToolsunder 3.2.57 is clean.
-
Not checked here:
- Lane 2's T008 matrix at this head. 6090038382 ran
6568f08before it was pushed.cddd1f7changes only the mounted-leg snapshot andpin_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-macosonready). - 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).
… 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>
…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>
…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>
…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>
…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>
…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>
…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>
… 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>
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>
…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>
…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>
…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>
… 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>





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 ofspecs/004-migrate-to-triad/installer-design.mdas tests. The assembly root entry point cannot exist onmainbefore the split, because its path is this file's. It is developed in a clone of the rehearsed assembly and recorded as a patch on004-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/openRepoToolsis an assembly whose root carries no payload. Every fetching install, every reinstall from an installed copy, and every installedwip initwould 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.openRepoToolsnow settles where the payload comes from once, before anything is collected or placed.payload_selectis called right afterplan_install_targets.OPENREPOTOOLS_REPO/REFrun, and a tree vendored deep inside another project that has its owncontracts/code-pin.yaml.~/.local/bin)<root>/<submodule_path>: the code leg mounted in an assembly checkouthash-object --no-filters) must name one commit; the gitlink is read from the index first, then HEAD, as openRepoShape'srecorded_gitlinkreads 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 worktreeThose two are the only places an assembly is recognised.
<root>is accepted only when itscontracts/code-pin.yamlmounts this directory as the code leg: the pin'ssubmodule_path, read with the same scalar grammarpin_parseuses (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
OPENREPOTOOLS_REFas given, file by file:gh api …/contentsfirst, then the raw URL.jqto fetch. The old fallback and the old refusal words are kept.contracts/code-pin.yamlat$REF, asked through those same two transports. GitHub answers it 404 and it prints nothing.e406e0eto2411f40resolved the ref and listed trees through the API in both layouts. The review's F1 measured what that cost, so13cc752andfdc5f18put the single layout back on today's reads.ghnames the status, and the raw URL's status is read with-w '%{http_code}'.project.yaml(as13cc752did) 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 andmake validaterefuses 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.commits/<ref>(sha media type) turnsOPENREPOTOOLS_REFinto a commit.contracts/listings (git/trees, throughgh --jqorcurl+jq), the pin and the gitlink.jqon thecurlpath. That is the new layout's need, not today's.readandcase; there is no YAML runtime.schema_version: 1,kind: pinned_contract_manifest,leg_role: code, an owner/namesource_repository, a single-segmentsubmodule_path, and a 40-hex lowercasecommit.revision_kind: commit,digest_algorithm: sha256,digest_definition: sorted-ls-tree-r-v1, a 64-hexdigests.tree_sha256,verify_pinandresync_runbook.source_repository.-code.submodule_pathmust equalcommit, and that commit must be one the pinned repository answers for.tree_sha256covers the whole tree and is the composed validator's to recompute. This PR does not claim it.wip initreads the code'scontracts/openreposhape-pin.yamlwhen no pin sits beside it.$REF's pin, read as before.contracts/.main, as before (the review's F2).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" aredaed209's. The three arrays stay one line each, because workBenches'estate-commands-startreads them withsed.Unchanged for today's single repository
./openRepoTools --installfrom 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_beforecompares a fetching install's stdout with a local one's.test_r02_…byte_for_byteruns the sentinel REPO/REF and makes no request.--helpand--version:--versionstill prints the requestedREPO @ REF.OPENREPOTOOLS_REFas given,gh api …/contentsand then the raw URL, no api.github.com and nojqfor fetching. Covered bytest_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_elseandtest_r06_a_single_repository_reads_every_ref_form_as_it_always_did[4 forms].jq: every file is fetched first, then the run refuses namingjq, in the old words (test_r01_a_single_repository_without_jq_fetches_then_names_jq_as_it_always_did).c4864acprinted, compared byte for byte bytest_r11_a_single_repository_missing_a_file_refuses_in_the_words_it_always_did[park, skills/restart/SKILL.md]andtest_r11_a_single_repository_at_a_ref_it_lacks_refuses_in_the_words_it_always_did[gh, curl].wip init: the pin at$REF;mainwhen the source does not answer or lacks the ref; and nocommits/request. Covered bytest_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_beforeand…never_needs_a_single_repositorys_commit_lookup.OPENREPOTOOLS_REPOandOPENREPOTOOLS_REF, and the sibling rule for an incomplete local set.tests/test_openrepotools_command.py: its fakeghnow answers the one layout question as a real GitHub answers it for a single repository (gh: Not Found (HTTP 404)forcontracts/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.
gh api,gh repoandcurlfrom them withgit:commits/<ref>isrev-parse,git/treesisls-tree, andcontentsand raw arecat-file.ghorcurlis onPATH, and every$HOMEis temporary.c4864ac…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_gotest_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_assemblytest_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_elsewheretest_r04_an_installed_copy_reinstalls_from_the_adopted_source_without_any_checkout;test_r04_an_installed_copy_on_a_single_repository_keeps_the_sibling_ruletest_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_nametest_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]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.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]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_answeredtest_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_fetchtest_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]test_r12_an_adopted_install_keeps_modes_receipts_targets_and_what_is_not_ourstest_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 fourtest_r13_an_installed_wip_init_…tests abovetest_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_dependencymain); skip test passestest_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_lacksWINDOWS_SKIPlike every bash-driven test here. CI'sparse-macoscovers the 3.2 parse of the changedopenRepoTools, andtests-macos(on thereadylabel) is the 3.2 runtime run.OPENREPOTOOLS_COMPOSED=1(Give the test suite explicit code, assembly, spec and dependency roots, so it runs on today's single repository and in the adopted triad, and refuses composed acceptance without all of them #190's convention) they fail instead of skipping.Evidence
6337a52, installer stillc4864ac): 75 failed, 8 passed, 6 skipped. The 8 were the single-repository preservation tests, the two static checks and the documented-skip check.c4864ac,2411f40,13cc752andfdc5f18. That is 20 tests.--basetempandPYTHONPYCACHEPREFIXall in scratch.c4864ac19 pass, 1 fail (the adopted step-7 words, new behaviour);2411f4020 fail;13cc75216 pass, 4 fail (theproject.yamlrequest, the parent walk ×2, the partial refusal);fdc5f1820 pass.6568f08, a head before the final one, merged with mainf87fd5a, run under the lock:-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.test_wip_init_and_the_lane_helpers_read_one_pointer_file_the_same_waycases in whichlanes-edit.sh whoexits 8 on this workstation's lane state.lanes-edit.shand that test are outside this PR's diff, which against main isopenRepoToolsand two test files. CI runs them green.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-macosruns on thereadylabel, in run 38155833862.6568f08, the changes are the two round-1 fixes (cddd1f7) and one test-only commit (8246983).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-macoswaits on thereadylabel.6568f08, three findings, fixed incddd1f7:submodule_pathmissed bypin_mounts; its new test fails at6568f08and passes atcddd1f7;recorded_gitlinkcontract.cddd1f7, one finding: the body overclaimed a refusal for a ref that moved. The body is corrected, and8246983adds a test of the behaviour.OPENREPOTOOLS_COMPOSED=1):4c6a0b7, codefa460f2), under the lock.-k 'payload_source or repo_hygiene or openrepotools_command or install_skill_and_hook or install_by_rename'. Result: 429 passed, 1 failed.test_the_root_carries_the_line_ending_rule, which reads.gitattributesfrom 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.004-migrate-to-triad-installer.tests/run.sh's own lock, one flock per run, withPATH=/usr/bin:$PATH, because this profile's~/bin/python3is 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
openRepoTools. It is a patch on004-migrate-to-triad-installer, and v3 checks the gitlink before it runs any implementation.jqon the curl path), which is T014's.🤖 Generated with Claude Code