diff --git a/specs/004-migrate-to-triad/adopter-rehearsal-2026-10-07.md b/specs/004-migrate-to-triad/adopter-rehearsal-2026-10-07.md index e6e20af..218d5fa 100644 --- a/specs/004-migrate-to-triad/adopter-rehearsal-2026-10-07.md +++ b/specs/004-migrate-to-triad/adopter-rehearsal-2026-10-07.md @@ -182,6 +182,12 @@ blob, `8043147a`. The source clone was unchanged except for the one new ref [quickstart.md](quickstart.md) and [preparation.md](preparation.md) still describe the October 4 version; the plan's owner decides when to refresh them. +- *Dated 2026-10-09 (#193 item 8):* since #192, the mapping link above opens + run C's plan ([adopter-rehearsal-2026-10-08.md](adopter-rehearsal-2026-10-08.md)), + and run B's stays in history at `63810dd`. #188 landed as `837928a`. The + bench restart of 2026-10-08 cleared both runs' disposable repositories, so + their logs survive only in this file's receipt. Run C's legs still carry + `558a12b` and `48d98cd`, as the parents of their heads. - Each run used a fresh, empty `--local-remote-dir` and `--work-dir`. Without the first, `execute` calls `gh repo create`. The plan's `execution_authorized: false` is informational and the tool does not read it. diff --git a/specs/004-migrate-to-triad/adopter-rehearsal-2026-10-08.md b/specs/004-migrate-to-triad/adopter-rehearsal-2026-10-08.md index 26f0a53..2bf0d97 100644 --- a/specs/004-migrate-to-triad/adopter-rehearsal-2026-10-08.md +++ b/specs/004-migrate-to-triad/adopter-rehearsal-2026-10-08.md @@ -21,10 +21,53 @@ kept host-local outside every repository so it survives a bench restart. Clone paths, local remote URLs, exact command lines and complete logs stay in the private receipt. +**Dated 2026-10-09, after #192 landed (#193 items 8 and 13):** + +- **Where the records are.** #192 brought this file and run C's plan to + main. The 2026-10-07 record has been on main since #189 (`63810dd`), not + on a branch as the paragraph above says. +- **What run C superseded.** No record: runs A and B stand as recorded. Its + plan did replace run B's as the + [mapping](../../openspec/changes/migrate-to-triad/adoption-plan.yaml), on + main in #192's squash `1abee1d`. (On #192's branch it was merge `1a26463`, + which main does not contain.) On main that link opens run C's plan from + both records. + So "On this branch" in the mapping section below holds for main, and the + 2026-10-07 record's "On this branch, the mapping is the regenerated run B + plan" held only on its own branch. Run B's plan stays in history at + `63810dd` (blob `f1067e3`). Neither plan is the cutover plan. +- **Runs A and B are gone from the bench.** The bench restart of + 2026-10-08, at about 09:45Z, cleared `/tmp` (#186 comment 6068861167). + Both runs were built in a session scratch directory there, as the + 2026-10-07 receipt's README records, so their disposable repositories are + gone and their logs survive only in that receipt. Two of their commits + live on in run C's legs: `558a12b`, the spec head of runs A and B, is the + parent of run C's spec head, and `48d98cd`, run A's code head, is the + parent of run C's code head. The splits `9e5f934` and `2726d3a`, run B's + code head `6d52162` and the local remotes are gone. That is why run C is + kept host-local. +- **An observation left out.** The run C comment on #186 (6068938765) + noted that `openspec validate migrate-to-triad --strict` with `openspec` + 1.2.0 fails on pristine `837928a` too (`ADDED "Installation retains the + public interface and one revision" must contain SHALL or MUST`), so run C + does not cause it. Lane 2's bench had `openspec` 1.13.1 (read on + 2026-10-09, #186 comment 6088829392 item 8, and again on 2026-10-10, + 6103482530). On 2026-10-09 the same command exited 0 on `837928a` and on + `71b05cb`, #192's head, whose tree its squash `1abee1d` carries unchanged. + *Corrected 2026-10-11 (lane 3's 5481348731 on #196, item 9): this note first + said "The bench's `openspec` is now 1.13.1", as if one version held on every + host. The version depends on the container. On lane 3's host on 2026-10-11 at + 00:1xZ, `which -a openspec` lists a user-level npm install first, at 1.2.0, + and the system install second, at 1.6.0. The validate command above exits 1 + under 1.2.0 (the SHALL or MUST error) and 0 under 1.6.0. So the result follows + which `openspec` comes first on the reader's `PATH`. Lane 1's walk records + the same split + ([gate-b-status-2026-10-09.md](gate-b-status-2026-10-09.md), section 4 (a)).* + ## Setup -- **filter-repo:** `git-filter-repo` 2.47.0 from a private venv. The rebuilt - bench has no `ensurepip`, and `python3 -m venv` refused with +- **filter-repo:** `git-filter-repo` 2.47.0 from a private venv. + `python3 -m venv` refused with `The virtual environment was not created successfully because ensurepip is not available.` So the venv was created with `--without-pip`. The wheel came from `pip download` with SHA256 @@ -32,6 +75,18 @@ the private receipt. the recorded value. That exact file was installed into the venv with `--no-index`. The venv's `git-filter-repo` came first on `PATH`, and `git filter-repo --version` printed `a40bce548d2c`. + - *Corrected 2026-10-09 (#193 item 12):* this bullet first gave the cause + as "the rebuilt bench has no `ensurepip`", and the bench contradicts it. + Its dpkg log has `python3-venv` and `python3.12-venv` installed on + 2026-09-23 at 00:18:06Z, before the 2026-10-08 boot. + `/usr/bin/python3 -m ensurepip --version` prints `pip 24.0`, and + `/usr/bin/python3 -m venv` succeeds (lane 2, #186 comments 6072333212 + and 6072724043; measured again on 2026-10-09). The refusal itself is + real: the receipt's setup log records it. Its cause is not established. + The `python3` the writer ran was the first on its `PATH`: the venv's + `pyvenv.cfg`, in run C's host-local arrangement, records a user-local + `python3` based on `/usr/bin/python3.12`. That entry is no longer on + the bench, so the refusal cannot be retried as it ran. - **Tool and source:** a fresh clone of openRepoShape at `7f84ca4`, unedited and clean afterwards, ignored files included. A fresh `--single-branch` clone of main, with one worktree and no linked worktrees. Its nested @@ -65,6 +120,20 @@ published copy. It differs from the executed plan only by its header, `local_path: .`, and the two closing status fields (`execution_authorized: false`). +*Added 2026-10-09 (#193 item 10):* `check` validates against the source +clone's local `main`, not its `HEAD`. In openRepoShape `7f84ca4`'s +`adopt-project.py`, `_default_branch` (:288) takes the branch name from +`origin/HEAD`, and `git rev-parse` of that name (:256-257) is the commit +compared with the plan's source commit (:1026-1032). This plan therefore +passes `check` only in a clone whose local `main` is `837928a`. Against any +newer `main` it exits 1 with `FINDING plan-stale: the plan was written +against 837928a1029a but main is now at …`, whatever `HEAD` is checked out. +That includes main once this plan is on it: a plan cannot name the commit +that carries it. Measured on 2026-10-09 with this plan and a fresh clone, +local `main` at `63810dd` gave that finding and exit 1, also with `HEAD` +detached at `837928a`. Local `main` moved to `837928a` gave `plan ok` and +exit 0, also with `HEAD` detached at `63810dd`. + ## Run C — main `837928a` `execute --local-remote-dir … --work-dir … --yes` exited **0** and printed diff --git a/specs/004-migrate-to-triad/candidate-2026-10-09.md b/specs/004-migrate-to-triad/candidate-2026-10-09.md new file mode 100644 index 0000000..5711220 --- /dev/null +++ b/specs/004-migrate-to-triad/candidate-2026-10-09.md @@ -0,0 +1,545 @@ +# The composed candidate carrying every follow-up — October 9, 2026 + +**For:** Gate B of [tasks.md](tasks.md), under +[#186](https://github.com/opensoft/openRepoTools/issues/186). It answers item 3 of +section 7 of `gate-ab-evidence-2026-10-09.md` (branch `004-migrate-to-triad-rollback` +at `fa44d75`): "No composed candidate carrying every follow-up has run the suite." +No task is checked complete by this record, and it approves nothing. +*2026-10-13, where that gap is recorded:* +- **The quoted sentence** is `gate-ab-evidence-2026-10-09.md:347` at `fa44d75`. + `git ls-remote` at 18:46Z shows `004-migrate-to-triad-rollback` still at `fa44d75`. +- **In #196** (`004-migrate-to-triad-evidence-3` @ `7c08f7e`) the same sentence is + `:590`. That file's dated refresh at `:602–608` (2026-10-09T20:30Z) says no suite run of + the candidate "is recorded on origin". +- **That was true then.** This record's suite results reached origin later: r2's in + `16e6ea4` (2026-10-10T23:47Z) and r3's in `e81100c` (2026-10-11T17:00Z). +**Writer:** lane openRepoTools-1, 2026-10-09: 01:47Z–02:42Z (r1), 13:29Z–18:20Z (r2), and +the classification from 21:56Z to 2026-10-10T02:4xZ. +**Evidence branch:** `004-migrate-to-triad-candidate`, from `main` `63810dd`. +**Private receipt ID:** `candidate-20261009-JR8YyA` (brett-wip `migration/openRepoTools/`). +It holds every script, log, patch and install snapshot named below. Machine paths stay +there. + +**Two builds.** **r1** was built at 01:5xZ from PR #190 `dbc6837` and PR #191 `2411f40`. +Its composed run was stopped at about 20% by the 02:42Z checkpoint for an account swap. +**r2** is the candidate of record. It was rebuilt at 13:3xZ from the moved heads, #190 +`b50f197` and #191 `13cc752`, on the coordinator's order. Everything below is r2's unless +it says r1. + +**State: final for r2 (2026-10-10T02:4xZ).** All three suite runs finished: + +| Run | Passed | Failed | Skipped | +| --- | --- | --- | --- | +| composed | 1411 | 5 | 0 | +| standalone | 1389 | 3 | 24 | +| no-submodule | 1168 | 6 | 242 | + +- **Two failures are candidate defects with owners.** F1 is fixed in #191's next head. + F2 is #193 item 14. +- **The rest are environment.** Each is shown the same on main, or in isolation + (section 5). +- **Two inputs have moved since r2:** #191 to `6568f08` (with F1's fix), and T007's + entry-point patch to v3 (`004-migrate-to-triad-installer` `4f773b8`). Neither is in + this record (section 6). + +**r3 (2026-10-11), section 9:** #191 at its final head `8246983`, with T007 v3 (patch +sha256 `67d8e7ce…`). Code `9c11276`, spec `9e1d006`, assembly `3891943`. The install +modules, composed: 281 passed, 0 failed, 0 skipped. F1 is gone. The install differs from +main `f87fd5a` only in the implementation it hands over to. + +*2026-10-13:* #191 landed on `main` as `a74c7fc`, a squash of `8246983` with parent +`f87fd5a`. Its tree, `d77e80b3`, is `8246983`'s, and `openRepoTools` is blob `7aa697c` at +`cddd1f7`, `8246983` and `a74c7fc` alike. So r3 measured the code now on `main`. + +## 1. Inputs + +Every input was read without checking it out in any existing worktree. Patches were +taken with `git show :`, and the PR commits with `git format-patch` from +the shared checkout. + +| Input | Where | Identity | +| --- | --- | --- | +| Run C's rehearsed arrangement | private prep `work-20261008/rehearsal` (read-only) | source `837928a`, split `69b8924`, spec `593250d`, code `73b04d6`, nested openRepoShape `7f84ca4` | +| T006 patches (5) | `004-migrate-to-triad-root-guidance` `041a273` | each blob equals `465fa6d`'s; sha256 `cd69c2a9…`, `574c0df1…`, `a7f8521b…`, `74b98ff9…`, `c11070f0…`, as `gate-ab-evidence` records | +| T010 patches (2) | `004-migrate-to-triad-ci` `cc7c7a1` (r1); the branch is at `d28e2cc` for r2, with both patch blobs unchanged | code `tests.yml` `7ee3c6d0…`; assembly `composed.yml` `6ee09f00…` | +| T007 entry point | `004-migrate-to-triad-installer` `51993a4` | `t007-assembly-entry-point.patch` `361c037d…`, as `installer-2026-10-08.md` records | +| PR #190 | r2: `b50f197` (on origin as `feat/test-roots-manual-guard`, a fast-forward of the PR head). r1: `dbc6837` | r2: 4 commits on `c4864ac`: `c139e88`, `e3a76b5`, `dbc6837`, `b50f197` | +| PR #191 | r2: `13cc752`, Linux CI green. r1: `2411f40` | r2: 5 commits on `c4864ac`: `6337a52`, `e406e0e`, `ecd7aa4`, `2411f40`, `13cc752` | +| The shape's `bump-leg.py` | a clone of the rehearsal's openRepoShape mirror, in scratch | `7f84ca4` | + +`assembly-0002` and `assembly-0003` were **not applied**. As `root-guidance-2026-10-08.md` +says, they pin the rehearsal's own leg hashes; the two pins were regenerated here with +`bump-leg.py` against this candidate's legs. + +#191 will move again in T007's fix cycle. This record is at `13cc752`; a later head +needs a delta rerun. + +## 2. How it was built + +All of it ran in private scratch (`work-20261008/ort-triad-candidate/`): + +- **Own copies first.** Bare copies, made with `--no-hardlinks`, of run C's two leg + remotes, of its assembly branch and of its openRepoShape mirror. Every write went to + these copies. In every clone the submodule URLs were set to them before any commit, and + a `url..insteadOf` rule sent `.gitmodules`' run C paths and the nested GitHub URL + to the copies. r2 has its own set of copies, so r1's commits stay where they were. +- **No network.** `PATH` began with refusing `gh`, `curl` and `wget` shims that log every + call; `GIT_ALLOW_PROTOCOL=file`. The shim log is empty. +- **Run C unchanged.** A listing of the rehearsal directory (type, mode, size, mtime of + every entry) was taken before the work and compared after it (section 8). + +### Apply order and results (r2) + +Every patch was applied with `git am`. **Every one exited 0; no hunk needed a resolution, +and none was dropped.** + +| # | Leg | Patch | `git am` | Commit | Tree | +| --- | --- | --- | --- | --- | --- | +| 1–4 | code, on `73b04d6` | PR #190's four commits | exit 0 | `b112e01`, `4ee37cf`, `e80350e`, `6e294fe` | `08d5b4ca` after the fourth | +| 5–9 | code | PR #191's five commits | exit 0 | `eab478f`, `85e6ffb`, `5758859`, `69b4785`, `b52df0a` | `cceeb37d` after the fifth | +| 10 | code | T006 `code-0001` | exit 0 | `085ec81` | `c3437ded` | +| 11 | code | T010 `t010-code-leg-tests-workflow.patch` | exit 0 | **`4dbcf6c`** | **`c0bedc53`** | +| 12 | spec, on `593250d` | T006 `spec-0001` (r1's commit; no spec input moved) | exit 0 | **`9e1d006`** | **`10256135`**, the tree T006 and T011 recorded | +| 13 | assembly, on `69b8924` | T006 `assembly-0001` | exit 0 | `4f1e522` | `5060f3be` | +| 14 | assembly | T010 `t010-assembly-exact-pin-job.patch` (`composed.yml`) | exit 0 | `738857c` | `7905d570` | +| 15 | assembly | T007 `t007-assembly-entry-point.patch` | exit 0 | `f4133d6` | `8b0c860b` | +| 16 | assembly | `bump-leg.py --leg spec --to 9e1d006…` | exit 0 | `f0a4feb` | `94e9d9be` | +| 17 | assembly | `bump-leg.py --leg code --to 4dbcf6c…` | exit 0 | **`5d92962`** | **`d263907a`** | + +Steps 12–16 are r1's commits, reused: none of their inputs moved. Step 17 is new. + +What was checked at each step: + +- **The PR commits are the PR heads.** Every path #190 touches has `b50f197`'s blob after + the stack, and every path #191 touches has `13cc752`'s blob. The exception is the one + file both touch. +- **The one shared file.** Both PRs touch `tests/test_openrepotools_command.py`. On top of + #190, #191 applied the same hunks as its own diff, at an offset. The hunk bodies are + identical: 14 lines at `13cc752`, and 26 at r1's `2411f40`. +- **T006's `.gitattributes` and #190's decision agree.** #190 reads `.gitattributes` from + the code root, and `code-0001` puts the monorepo's blob `3560973` there. + `git check-attr eol` on `openRepoTools`, `park` and `tests/run.sh` in the leg gives + `lf`. +- **The entry point is T007's.** `openRepoTools` at the assembly root is blob `4c2525d`, + mode 100755, sha256 `3b0d59f5…`, as `installer-2026-10-08.md` records. +- **Clean text.** `git diff --check` exits 0 over the code range (`73b04d6..4dbcf6c`), the + spec range and the assembly range. + +The legs' commits were pushed to `main` of their own bare copies, as fast-forwards from +run C's heads. The assembly was pushed as branch `candidate-r2`. `bump-leg.py` ran with +`--local-remote-dir` set to the copies, a dry run first each time, all exit 0. It added +`Lane:` and `Co-Authored-By:` trailers. Its `NEXT … push` line was not followed. + +### The candidate's identities + +| Repository | Commit | Tree | Pinned digest (`sorted-ls-tree-r-v1`) | +| --- | --- | --- | --- | +| assembly (`candidate-r2`) | `5d9296216fbf8255440c315344f29ac2aa1c19f8` | `d263907a4ab2b675c9b5272a4c60ffb2ea65ce12` | — | +| spec leg | `9e1d006e2c870615f159ad1ebab719bb7b38cc2a` | `10256135f87ae4d05166d2c5934a152c42ce19ae` | `3a4a84beb20b343390f5bae64cb166df4d0cfd693baff6309fc13c8060d1c866` (T006's spec digest) | +| code leg | `4dbcf6ce40fe897a8caf770abc5cf55535b5f0b6` | `c0bedc535b4c69afc32e6235140a874c05821c3a` | `7a5dc958db2ea74213f604b883bc2bfd374960015313bd4de24494965ccc32a6` | +| code's nested openRepoShape | `7f84ca42ca86a8902928345109d2bf6bad87bd91` | — | unchanged from run C | +| r1, superseded | assembly `3bd95a7`, code `f0af7d9` (tree `a832c8a0`, digest `6550ae2e…`), spec `9e1d006` | | | + +The commit hashes are this run's alone, because `git am` stamps a new committer and date. +The trees are what reproduce. + +## 3. Bootstrap, validators and the composed job's own steps + +| Check (in the assembly) | Result | +| --- | --- | +| `python3 scripts/bootstrap.py` | exit 0; "spec: on main at 9e1d006e2c87 (branch tip == pin)", "code: on main at 4dbcf6ce40fe (branch tip == pin)", `pins ok`, `manifest ok`, "bootstrap ok" | +| `make validate` (naming, manifest, pins) and `make pins` | exit 0 and 0. Both gitlinks equal their pins, both tree digests recompute, and all 11 copied shape files match `contracts/shape-pin.yaml` | +| `composed.yml`'s step "every context the composed run claims is here", run as written | exit 0 | +| `composed.yml`'s `validate-manifest.py`, then `validate-pins.py` | exit 0, exit 0 | +| A fresh recursive clone of the candidate from its bare copy, then `bootstrap.py` and `make validate` | exit 0 and 0. Spec `9e1d006`, code `4dbcf6c`, nested `7f84ca4`. `status --ignored` is empty | +| The assembly's `AGENTS.md` and `README.md` | 340 and 510 lines, exactly #190's assembly caps | + +`.gitmodules` still names run C's local remote paths, as the adopter wrote them in the +rehearsal. A recursive clone that does not redirect them fetches from run C, whose +remotes lack this candidate's leg commits. A real leg is an https remote and does not +have this problem. + +## 4. The install, compared with today's main + +Both installs used the same disposable home path, rebuilt from one seed, so that +absolute paths written into `settings.json` and the receipt compare byte for byte. +- **Environment:** `env -i`, with `HOME`, every `XDG_*` variable and `TMPDIR` in scratch. + `PATH` was the refusing shims, then `/usr/bin:/bin`. `GIT_ALLOW_PROTOCOL=file`. +- **The seed:** an unrelated `settings.json` (model, env, permissions, a status line, and + `SessionStart`, `UserPromptSubmit` and `Stop` hooks of its own), a foreign command in + `~/.local/bin`, and a foreign skill under each of the two skill roots. +- **The shims logged 0 calls**, in r1 and in r2. + +| Stage | Source | Result (r2, 13:34Z) | +| --- | --- | --- | +| A | today's main `63810dd` (a shallow clone of `origin/main`), `./openRepoTools --install` | exit 0; `17 of 17 placed`; 29 `installed at`; both hooks merged; 29 receipt rows | +| B | a freshly seeded home; the candidate assembly root `./openRepoTools --install` (the T007 entry point) | exit 0. First line: "source: the code mounted at `/code`, opensoft/openRepoTools-code at 4dbcf6ce40fe… as `` pins it; every payload file was checked against that commit and nothing fetched". Then `17 of 17 placed`, 29 `installed at`, both hooks merged, 29 receipt rows | +| B2 | the candidate again, over B | exit 0; 31 `unchanged` (29 files and 2 hooks) | +| C | today's main again, over B: rollback by reinstall | exit 0; `openRepoTools: updated at …`, 30 `unchanged` | +| — | the candidate's `./openRepoTools --version`; `./openRepoTools nope` | "openRepoTools (opensoft/openRepoTools @ main)", exit 0; exit 2 | + +**Every difference between A and B**, by path, type, mode and sha256 over the whole home: + +| Path | A (main) | B (candidate r2) | +| --- | --- | --- | +| `~/.local/bin/openRepoTools` | 755 `47cfe74ea96e6ce9e56b47220a71e1de8c608af2f51f33cfdffa351410a4f373` | 755 `a6eda1f4b7b72a8e832f93185dc4604de66dab5a677472f57452b3ee09342588` | +| `~/.local/share/openRepoTools/installed.tsv` | 600, its `openRepoTools` row names `47cfe74e…` | 600, its row names `a6eda1f4…`; the UTC column differs too | + +Nothing else differs: +- **The other 28 placed files** have the same path, mode and sha256. +- **`settings.json`** is byte-identical: the seed's model, env, permissions, status line + and three hooks of its own, plus the same two entries the installer merges, at mode 600. +- **The receipt's name, destination and sha256 columns** differ in the `openRepoTools` + row only. +- **The foreign command and both foreign skills** are untouched. + +The installed `openRepoTools` (`a6eda1f4…`) is the bytes of `code/openRepoTools`, PR #191's +implementation at `13cc752`. It is not the root entry point (`3b0d59f5…`), so the entry +point handed over as T007 designed. r1 gave the same result with `2411f40`'s +implementation, `118cc2cc…`; between r1's and r2's candidate installs, that file and its +receipt row are the only differences. + +**Rollback by reinstall (C) restores main.** C's whole-home listing equals A's except for +the receipt file's own digest. The receipt's name, destination and sha256 columns equal +A's; only the UTC stamps differ. This is the (c2) path of `gate-ab-evidence-2026-10-09.md` +section 2, repeated at this candidate's own commits, as its section 2 asked. + +## 5. The suite: composed, standalone, and standalone without the dependency + +**How the runs were made.** The three runs ran one after another, 13:33Z–18:20Z, from one +driver script. Each took the workstation lock on its own and released it before the next. +- **Environment:** `env -i` with only `HOME`, `PATH=/usr/bin:/bin`, `LANG=C.UTF-8`, the + user names and the CI jobs' four `GIT_AUTHOR_*`/`GIT_COMMITTER_*` variables. So no + `LANES_*`, `TMUX`, `CLAUDE_*` or workstation binding variable reached the suite. +- **Composed.** `tests/run.sh -rfEs` waited 5 minutes on the workstation census. As + briefed, this writer then stopped its own waiter and ran `run.sh`'s exact pytest line + under `flock` on the same lock, with `run.sh`'s run root. +- **Standalone and no-submodule.** `tests/run.sh -rfEs` itself started pytest in each. +- **The load average at each run's start** was 131.7 (composed), 6.9 (standalone) and + 8.6 (no-submodule). + +| Run | Where, and the variables | Result | Time | +| --- | --- | --- | --- | +| **Composed** | `/code`; `OPENREPOTOOLS_COMPOSED=1`, `OPENREPOTOOLS_ASSEMBLY_ROOT=`, `OPENREPOTOOLS_SPEC_ROOT=/spec` | **1411 passed, 5 failed, 0 skipped** (1416) | 1:48:00 | +| **Standalone** | a clone of the code leg alone, its dependency initialized; no `OPENREPOTOOLS_*` variable | **1389 passed, 3 failed, 24 skipped** (1416) | 1:00:08 | +| **No submodule** | a clone of the code leg alone, dependency not initialized ("the submodule really is not materialized", the job's own check); `tests.yml`'s workflow env, `OPENREPOTOOLS_CODE_ROOT=` and `OPENREPOTOOLS_COMPOSED=0` | **1168 passed, 6 failed, 242 skipped** (1416) | 1:31:30 | + +**The skips are the designed ones.** The composed run skipped nothing, so no check went +green by skipping. The standalone run's 24 skips are: +- 10 for `README.md`, 3 for `AGENTS.md` and 1 for `CLAUDE.md`, each naming the assembly + root it needs; +- 3 for `docs/README-lanes.md`, naming the spec root; +- 7 composed-acceptance tests of #191, which skip in standalone code. + +The no-submodule run has the same 24, plus 218 that name +`git submodule update --init upstream/openRepoShape`. Inside the lane helpers' shell suite +in both leg runs, the three manual sections printed `skip … no spec root in this run`. +There is no `unbound variable` anywhere: `b50f197`'s guard holds, and the abort that +`dbc6837` had in a code leg does not occur. + +**Against CI.** All three runs collected 1416 tests: main's 1296 plus #190's and #191's +additions. On CI the PR heads read: +- main `63810dd`: `tests` 1296 passed; `tests-no-submodule` 1084 passed, 212 skipped; +- #190 `dbc6837`: 1318 passed; 1105 passed, 213 skipped; +- #191 `2411f40`: 1379 passed, 7 skipped; 1165 passed, 221 skipped. + +None of those jobs is composed. + +### Every failure, classified + +| # | Test | Runs | First assertion line | Class | Owner | +| --- | --- | --- | --- | --- | --- | +| F1 | `test_install_payload_source.py::test_r14_the_documented_upstream_skip_is_unchanged` | composed only | `KeyError: 'reason'` at `conftest.NEEDS_UPSTREAM.kwargs["reason"]` (line 1697) | **Candidate defect, new, from combining #190 and #191.** Under #190's conftest a composed run makes `NEEDS_UPSTREAM` `pytest.mark.usefixtures("required_dependency")`, a fixture that fails, not a `skipif`, so it has no `reason`. #191's test assumed a `skipif`. It passes standalone (both leg runs) | **#191.** Fixed by the T007 writer in `6fa3b43`, inside #191's head `6568f08`: the test now reads the command from conftest's own text, which names it in both modes. Not re-run here | +| F2 | `test_repo_hygiene.py::test_agents_md_names_the_pin_rules` | composed only | `assert 'Never edit anything under \`upstream/openRepoShape\` in place' in "Read AGENTS-shape.md first …"` | **Candidate defect, known: #193 item 14**, predicted by lane 3. T006's `assembly-0001` rewrites the two `AGENTS.md` lines the test checks, and in the composed run the test reads the assembly's `AGENTS.md` | the decision is pending with lane 1's coordinator | +| F3 | `test_lane_helpers_suite.py::test_the_lane_helper_suite_passes`: shell `FAIL …with clause (c)'s directory and window in the tail` and `FAIL the row's stamp states clause (c)'s two facts for a person, in its TAIL` | all three | `expected to contain [; dir /projects/repoA; window ]`, the sandbox HOME being `/tmp/tmp.*/home`; the row ends `…; dir /projects/repoA; ... \|` | **Environment, pre-existing: #180.** The register row's stamp is cut, and under `run.sh` the directory sits under the long run root `~/.local/state/openRepoTools/tmp/-/tmp`, so the expected text falls past the cut. **On main `63810dd` the same three FAIL.** The shell suite ran directly, at a run root of the same 71 characters as the standalone run's, under one `flock`: `4595 passed, 3 failed, 0 skipped`, F3's two and F4. CI's runner has a short `TMPDIR` and passes them. | #180 | +| F4 | the same shell suite: `FAIL …having found the checkout by its real directory name, not the typed one` | standalone and no-submodule only | `expected to contain [dir /projects/repoCase;]`; the row ends `— lane-start on Eagle: window renamed, no launch; dir ... \|` | **Environment, pre-existing: #180, the same cut.** The run root here was one character longer than the composed run's (`` had 7 digits, against 6), and this third row crosses the cut at that length | #180 | +| F5 | `test_run_wrapper.py::test_a_killed_run_removes_its_run_root_and_stops_the_suite[flock-2-130]` and `[mkdir-lock-2-130]` | all three | `subprocess.TimeoutExpired: … tests/run.sh … timed out after 30 seconds` | **Environment, of this writer's harness, and not load.** The driver was launched as an asynchronous command of a non-interactive shell, so every run inherited SIGINT *ignored*, and bash cannot trap a signal ignored at entry. Only the SIGINT cases failed; the SIGTERM cases passed. Measured isolated at load 42–61: with SIGINT at its default, 4 passed in 1.9 s; launched the way the runs were, the same 2 failed with the same timeout. Main `63810dd` behaves identically (4 passed, then 2 failed) | none for the candidate. The test could start its wrapper with both signals at default, as `test_openrepotools_command.py::test_a_run_killed_mid_placement_leaves_no_temporary` already does (`preexec_fn=default_signals`, whose docstring names this very trap) (follow-up 4) | +| F6 | `test_supervised_restart_regressions.py::test_restart_observer_confirms_late_uuid_prepared_by_real_child[live]`, `::test_respawn_restores_configured_roots_in_new_pane`, `::test_completed_observer_cleanup_never_signals_saved_pid[signal]` | no-submodule only | `subprocess.TimeoutExpired: … lane-handoff --supervise …` / `Failed: observer never confirmed readiness` | **Environment: timing under load.** These files and `lane-handoff` are byte-identical to main's. All three passed in the composed and standalone runs of the same code. Rerun isolated at load 48–54: all three passed, once with SIGINT at default and once ignored. But a sibling, `test_completed_observer_cleanup_never_signals_saved_pid[exit]`, failed once in that rerun. So these tests are timing-sensitive on this bench, independent of the candidate | none for the candidate; the flake is pre-existing (follow-up 5) | + +*2026-10-13, F2's decision.* +- **Decided on 2026-10-11: the test gives.** #193 item 14 was settled by lane 1's + decision (#186 comment 6103665910, 00:16Z), as #195's writer recommended in 6103460962. + T006's `assembly-0001` is unchanged. +- **Implemented in #195 at `e378871`.** At an assembly root, + `test_agents_md_names_the_pin_rules` now expects `code/upstream/openRepoShape` and + `git -C code submodule update --init upstream/openRepoShape`, taking the prefix from + the code pin. +- **#195's status at 18:46Z:** open at `e378871`. Its CI on the merge with `a74c7fc` is + still running (run 38447402302), and lane 1 lands it when green. +- **Until #195 lands, F2 stands** against `main`'s test. + +**Lane 2's reading** (#186 comment 6087267593) **checked against the logs.** Its counts are +right for all three runs, and so is its S2 finding (no abort on r2). One refinement and +one correction: +- **The lane-suite FAILs** are all #180 row-stamp cases, as lane 2 says. They are 2 in the + composed run and 3 in each leg run; the third is F4. +- **The `test_run_wrapper` pair** is not a load timeout. It is the inherited ignored SIGINT + of F5, and it fails the same way on main. + +**One other test sends SIGINT:** `test_a_run_killed_mid_placement_leaves_no_temporary`. +It resets both signals in its child for exactly this reason, and it failed in none of the +three runs. + +### r1's stopped composed run + +It reached result 288 of 1408 before the 02:42Z checkpoint stopped it. Its one failure, at +result 211, was F1. That test alone, rerun in r1's tree, failed the same way. So it was +not the `$ln_doc` abort, which only a code leg without a spec root can reach; the composed +run has one. + +## 6. What this proves for Gate B, and what it does not + +**What it proves:** +- **Every recorded follow-up composes.** T006's three portable patches, T010's two, T007's + entry point, #190 at `b50f197` and #191 at `13cc752` stack onto run C with no conflict + and no resolution. That is the stack `gate-ab-evidence` section 5 only checked as + text. +- **The result is a valid triad.** It bootstraps and validates: legs at their pins, both + digests recomputed, all 11 copied shape files intact, and `composed.yml`'s own + context and validator steps green. +- **The whole canonical suite has run on it.** + - Composed at the exact pins, nothing skipped: **1411 of 1416 pass**. + - Of the 5 failures, 2 are candidate defects with owners. F1's fix is in #191's head + `6568f08`. F2 is #193 item 14, with a decision pending. + - The other 3 are this bench's environment. F3 is #180, the same on main (section 5). + F5 is this writer's harness, the same on main. +- **The standalone code leg behaves as T009/T010 designed.** Its document checks skip, + naming the root they need. Its dependency checks skip, naming the command. Nothing + aborts. It has no failure of its own: its 3 and 6 failures are F3–F6, all environment. +- **The install from the assembly root hands over to the code leg.** It places the same + 29 files as today's main, every one byte-identical except the implementation itself, + and merges the same hooks into a byte-identical `settings.json`. A reinstall from main + restores main. + +**What it does not prove:** +- **Hosted CI.** Not the code leg's `tests`, `tests-no-submodule`, `tests-windows`, + `parse-macos` or `tests-macos` jobs, and not `composed.yml` checking out over https. + They need the real legs, which only exist after Gate C (checklist line 35). +- **The real legs and remotes.** This used local bare copies: https `.gitmodules`, + `bump-leg.py` against the real legs, and the raw and `gh` one-liners against GitHub + are T015's. +- **The final heads.** #191 is now `6568f08`, which carries F1's fix (`6fa3b43`) and + changes the implementation `openRepoTools` (159 changed lines since `13cc752`). + T007's entry-point patch is now v3 (`4f773b8`). #190 landed as `f87fd5a`, whose + `tests/` equal `b50f197`'s, so that leg input is unchanged. + - Neither moved input ran here: not the suite, and not the install comparison. + - The T007 writer did run part of it (`installer-2026-10-08.md` at `3cca1e7`). + - Its strict composed run of the install suites, `repo_hygiene` and + `openrepotools_command` at `6568f08` with root v3: 429 passed, 1 failed. The one + is `.gitattributes`: that code leg did not carry T006's `code-0001`. + - CI on `6568f08`: `tests` 1416 passed, 9 skipped. + - Neither is this candidate, and neither is the full composed suite. + - The record owes a composed rerun at `6568f08` with T007 v3. *(2026-10-11: done in + scoped form at the final head `8246983`, in section 9. The full composed suite is + redone at the frozen source for Gate C.)* The rebuild takes about 5 + minutes: the code leg plus a regenerated code bump, and the assembly with the v3 + patch in place of v1. + - The full composed run took 1:48 at load 130 here. CI's `tests` takes 45–48 minutes. + Add the wait for the lock. + - A scoped composed rerun of the install modules (`test_install_payload_source.py`, + `test_openrepotools_command.py`, `test_install_*`), plus the install comparison, is + about 15–20 minutes. +- **A clean composed green.** Gate B's "passing compatibility test matrix" is not met + until the composed rerun at `6568f08` shows F1 gone and F2 is decided. F3 and F5 do not occur on a CI + runner (main's own `tests` is 1296 of 1296 there). +- **The bash 3.2 parse of the root entry point.** `parse-macos` parses the + implementation, not the entry point. + +## 7. Follow-ups + +1. **#191 carries F1's fix** (`6fa3b43`, in its head `6568f08`: the r14 test reads + conftest's text). The composed rerun at `6568f08` with T007 v3 is owed (section 6). + *2026-10-13, reconciled with section 9.* + - **What was asked.** Lane 3's review 5481348534, finding 6, asked for the composed + suite and the install comparison at #191's final head. + - **What r3 covered,** at `8246983` (`a74c7fc` on `main`): the install comparison in + full, and the composed suite for the four install modules only (281 passed). + - **Still owed:** the full composed suite on this candidate at the final head. + - **Why it is not run now.** openRepoShape v1.0.0 holds any new rehearsal at + `7f84ca4` (Brett Heap's word, #186 comment 6152410188). The coordinator's order of + 2026-10-11 runs it once, at the frozen source, for Gate C. + - **Lane 2's run is not this record's.** Lane 2's full composed run on its own r3 + (`f87fd5a` + #191 `8246983` + #195 `e378871`) gave 1454 passed and 1 failed: the + lane suite's 5400 s timeout (#186 comment 6152491855). +2. **F2, #193 item 14:** decide whether `test_agents_md_names_the_pin_rules` or T006's + `assembly-0001` gives, before the composed run is the record. + *2026-10-13:* decided on 2026-10-11, the test gives. It is implemented in #195 + `e378871`; see the dated note under section 5's table. +3. **#180:** under `tests/run.sh`'s run root the row-stamp cases fail locally. There are 2 + at a 70-character run root and 3 at 71, the third being F4. CI is unaffected. Today + any local full run is red on them, main included. +4. **`test_run_wrapper.py`:** start the wrapper with SIGINT and SIGTERM at their defaults, + as `test_openrepotools_command.py` does. Otherwise a suite launched from a background + shell reports two false failures (F5), on main too. +5. **`test_supervised_restart_regressions.py`'s observer tests are timing-sensitive** on a + loaded bench (F6). They are pre-existing and unchanged by the candidate. +6. **Launch any rerun of this candidate with SIGINT at default (F5).** +7. **The rehearsed split's `.gitmodules` names run C's local remote paths.** A real + adoption writes https URLs, so nothing is owed unless the rehearsal is reused for + another candidate. + +## 8. Acts outside scratch, and log SHA256s + +**Every write went to private scratch** (`work-20261008/ort-triad-candidate/`), with these +exceptions, listed so nothing is hidden: +- **The evidence worktree.** `lane-worktrees add openRepoTools-1 ort-triad-candidate` + made it (exit 0): the worktree, its new branch and one record in lane 1's host-local + tree inventory. +- **This branch.** Its commits, and fast-forward pushes of `004-migrate-to-triad-candidate`. +- **In the shared checkout, reads only.** Fetches (remote-tracking refs only), + `git show`/`format-patch` reads, and one shallow `file://` clone of `main` from its + object store. +- **`gh`, reads only.** PR views, run lists, CI job logs, and issues #180, #184 and #193. +- **`tests/run.sh`'s own conventions.** + - Run roots under `~/.local/state/openRepoTools/tmp/-/`, each removed when + its run ended. The r1 composed run's root was removed by hand after this writer + stopped that run at the 02:42Z checkpoint. + - Bytecode under `~/.cache/openRepoTools/pycache`. +- **The workstation lock.** This writer held `/tmp/openrepotools-pytest.lock`: + - r1 composed, 02:10:40–02:42:37Z (stopped); + - r2 composed, 13:59:33–15:47:49Z; + - through `tests/run.sh`, the r2 standalone and no-submodule runs, 15:4xZ–18:20Z; + - main's lane suite at the same run-root length, about 22:07Z to 23:09:57Z. + + It was released between runs. The isolated reruns of F5 and F6 (2 seconds to 2 + minutes 16 seconds each) ran without it, under the coordinator's rule for runs of about + two minutes. The r1 test collection and F1's single-test reproduction took it with + `flock -w 600`. +- **A stray file moved at once.** A temporary file was written into the private + `work-20261008/` directory by mistake, outside the rehearsal tree, and moved into scratch + within a minute. +- **The receipt and the comment.** One commit to brett-wip `main` (`git add -- `), from a single-branch clone in scratch, never the shared checkout. One comment + on #186. + +**Real surfaces, before (2026-10-09T01:59Z) and after (2026-10-10T02:44Z):** +- the real home's 29 installed files (path, mode, sha256), its receipt and its + `settings.json`: **unchanged**; +- `~/.agents/workspace.yaml` (size, inode, mtime, mode; its contents not read): + **unchanged**; +- run C's rehearsal directory, as a whole-tree listing (type, mode, size, mtime of 1830 + entries): **byte-identical** before (2026-10-09T01:47Z) and after (2026-10-10T02:44Z). + +**No network:** the refusing shims logged no call in any build, install or suite step. +Nothing was pushed to run C. No real repository was created, and the register was not +written. + +| Log (in the receipt's `logs/`) | SHA256 | +| --- | --- | +| `41-r2-code-leg.log` | `053af3830c118885238f1f50dfc2ff50bf29c2f4ac9db9cc6d539f407633d12b` | +| `42-r2-assembly.log` | `7d8f086587e63e4a78c93cc2db5d1f4996aab4a983a28e0ab389d4265c7ad272` | +| `43-r2-clones-and-steps.log` | `295db90bcfe12c626919d0a49710d8e98e080851997deab68853d80bf03bd59b` | +| `run-51-r2-composed.log` | `d57492a6be710493c51b052eef14119c248d0d5de29f8d58f2dc8a99e7b1c5c6` | +| `run-52-r2-standalone.log` | `18562f7cf544179060917128660fef4d535b95e0ac94240cd101d862fd5171da` | +| `run-53-r2-nosub.log` | `1b9cc2668593e7c44075528acef9cbfc4540a12fe2a5f2a055819131f6e7d366` | +| `44-r2-install-check.log` | `ba9e6df4f0aff84e5e362c3de35bd236d45073e5b3b1aabfbe309f9d27ae4c3d` | +| `45-r2-install-compare.log` | `eb0de425bfee2214d39490498e5e0a2ed5fc8e331258cc48c498b8033cbe20d9` | +| `60-r14-classification.log` | `920cf5db6cdbf47b03294c022d5e15087fed61e30edabde2958ab39e236af088` | +| `61-rerun-run-wrapper.log` | `5ca28af487ca34b6957b7601672005a067bcc598358bfa209ce185d0616cefb4` | +| `62-rerun-supervised-restart.log` | `e6409f8fdd7d4a96ed8c158d85ad00bde77a6e214fec43bedb89f5ff8211c927` | +| `63-run-wrapper-on-main.log` | `f5a3724535d12ff887fc44613cf011dc2adc17d6a7416f6cebed068b32b4b29f` | +| `64-main-lane-suite-at-length.log` | `d07652d526e131d271347138c7c204efb3c15c86445e6b7f8c37a36de73dcdb9` | +| `killed/run-21-composed.r1-stopped-at-checkpoint.log` | `60c90271e3fb6acaa78ebbfd56b3483598b03cd67e5cfb19711ae0036e1daf7a` | +| `killed/r1-composed-F211-repro.log` | `3df58782bf4a317516cb5f098c2b8460e63e119e8fb5a98efb7159dd6f3cd750` | +| `10-code-leg.log` | `70f1af98b15b7f52fff2c47de6a68930f156138cfcfceacaf813382fac84a378` | +| `12-assembly.log` | `256decaff94d57c4fc14ddf0a6e03dbfa39c485e3056ccec66c65a68100d119d` | +| `13-bump.log` | `5523b0e9df43ff2f11b7207e901e1bf69e084dc461752a36b17104b2b458dfed` | +| `32-install-compare.log` | `c4f48b9f4649fb775874fa1bb962773a269778dc28eb92e6c4e28420d7cc4108` | +| `rehearsal-listing-before.txt` | `1dbf1ae38a9f98fac7b9f1ded1f0b6b2d699cc8ecb7b4f914c2cd5171f412038` | +| `rehearsal-listing-after.txt` | `1dbf1ae38a9f98fac7b9f1ded1f0b6b2d699cc8ecb7b4f914c2cd5171f412038` | +| `network-shim-calls.log` | `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` | + +## 9. r3: the install path at #191's final head — October 10–11, 2026 + +**Why a scoped rerun.** After r2, #191 moved on: +- `6568f08` carried F1's fix; +- `cddd1f7` installs exactly the bytes that were checked from a mounted code leg; +- `8246983` is lane 3's LAND. + +T007's entry point also moved, to v3. On the coordinator's order this is the scoped +rerun section 6 offered: the install modules composed, plus the install comparison. The +full composed suite is redone once, at the frozen source, for Gate C. + +**Inputs.** Every input not listed here is r2's: + +| Input | Identity | +| --- | --- | +| PR #191 | `8246983`. Its 10 non-merge commits since `f87fd5a`, by `git format-patch f87fd5a..8246983`. The merge of main, `45979a4`, has no conflict hunks (`git show --cc` is empty) | +| PR #190 | as landed, `f87fd5a`. Every path under `tests/` has `b50f197`'s blob, so r2's #190 stack (`6e294fe`) is the base | +| T007 entry point v3 | `specs/004-migrate-to-triad/patches/t007-assembly-entry-point.patch` at `004-migrate-to-triad-installer` `67a978c`: blob `407741b`, unchanged since `4f773b8`; **sha256 `67d8e7ce8b0f7008d2151a44c0eabaf74cda7cd2697c6e09ee7da0c341c671b5`**, as `installer-2026-10-08.md` records. It writes `openRepoTools` as blob `ff432c6`, mode 100755, sha256 `8248224d…` | + +**Build. Every `git am` exited 0, with no resolution:** + +| Leg | Steps | Commit | Tree | +| --- | --- | --- | --- | +| code | `6e294fe` + #191's 10 commits (`e06fd90` … `830f673`) | `830f673` | `b0d15e4a` | +| code | + T006 `code-0001` (`6e4761b`) + T010 `tests.yml` | **`9c112769d4311675b4eee89852cf7e51384bfb4b`** | **`e53dfa62`**; pinned digest `268cd8ca6dcb5cd35a1c7060ee112399d060db41bba9d4b0ed4a08bcdb10dd0b` | +| spec | unchanged | **`9e1d006`** | `10256135` | +| assembly | `69b8924` + `assembly-0001` (`4f1e522`) + `composed.yml` (`738857c`) + T007 v3 (`325131e`) + `bump-leg.py` spec (`23439bb`) and code | **`3891943274f74b6c87683153e35ee9a961b5b1eb`** | **`fd997c3d`** | + +What was checked: +- **The code leg is #191's final head.** All 51 code-leg paths that `8246983` carries + have its blob. +- **The assembly is valid.** `bootstrap.py` reports both legs "branch tip == pin"; + `make validate` exits 0; `composed.yml`'s context step exits 0. +- **It reproduces.** A fresh recursive clone bootstraps and validates the same way. + +**The install modules, composed** (`test_install_payload_source.py`, +`test_openrepotools_command.py`, `test_install_skill_and_hook.py`, +`test_install_by_rename.py`): +- **How:** isolated, with `HOME`, `TMPDIR`, `--basetemp` and `PYTHONPYCACHEPREFIX` in + scratch. `OPENREPOTOOLS_COMPOSED=1`, with the assembly and spec roots named. +- **SIGINT at its default:** reset before `flock`, and printed as + `default_int_handler` by the process that then became pytest, so F5 cannot recur. +- **The lock:** one `flock` on the workstation lock, 2026-10-11T16:33:32Z–16:59:15Z, + at load about 150. + +| Run | Result | +| --- | --- | +| **r3, `8246983` + T007 v3** | **281 passed, 0 failed, 0 skipped** in 25:39 | +| r3a, `6568f08` + T007 v3 (2026-10-10T23:52Z, superseded within the hour by `cddd1f7`) | 278 passed, 0 failed, 0 skipped in 10:59; code `663d775`, assembly `e195ba9` | + +**F1 is gone.** `test_r14_the_documented_upstream_skip_is_unchanged` passes in both +module runs. Run alone in r3's composed tree it is `PASSED` (0.53 s). At `8246983` the +test reads the command from conftest's own text, so composed mode no longer depends on +`NEEDS_UPSTREAM` being a `skipif`. + +**The install, compared with main `f87fd5a`.** Same method as section 4 (one disposable +home path, the same seed, `env -i`, refusing shims: **0 calls**): + +| Stage | Result | +| --- | --- | +| A: main `f87fd5a`'s `./openRepoTools --install` | exit 0; `17 of 17 placed`; 29 `installed at`; 29 receipt rows | +| B: the r3 assembly root's `./openRepoTools --install` (T007 v3) | exit 0. First line: "source: the code mounted at `/code`, opensoft/openRepoTools-code at 9c112769d431… as `` pins it; every payload file was checked against that commit and nothing fetched". Then `17 of 17 placed`, 29 rows | +| B2: the candidate again | exit 0; 31 `unchanged` | +| C: main again, over B | exit 0; 1 `updated`, 30 `unchanged` | + +Between A and B the only differences are these two: +- `~/.local/bin/openRepoTools`: `47cfe74e…` becomes `697fdbd4977454f18b80f33d3501c8d2b7dee44335f1afafbd1e1595d76f2aed`, + which is the bytes of `8246983:openRepoTools`, not the entry point (`8248224d…`); +- that file's receipt row, and the receipt's timestamps. + +`settings.json` is byte-identical, and the foreign command and skills are untouched. C +restores A, except the receipt's timestamps. r3a gave the same result with +`6568f08`'s implementation, `b2ff0f1c…`. + +**What r3 changes, and what it does not.** +- **F1 is closed** at #191's final head. +- **The install path is green** in composed mode with T007 v3. +- **F2–F6 stand as classified in section 5.** None is in these modules. +- **This is not the full composed suite.** That run is redone once at the frozen source, + for Gate C. + +| r3 log (in the receipt's `logs/`) | SHA256 | +| --- | --- | +| `90-r3f-build.log` | `1654219464e453fdea2cc652b07decea9e6baf374055660b0ea485b9863b252a` | +| `92-r3f-install-modules-composed.log` | `3eb6c875cdd223aaaf174ea5a8cb4fd09f1d2238323406326fd4664641e572b3` | +| `93-r3f-install-check.log` | `364bd77941704c420008fa5f9ad13fc90da43e6cf6c3009acdc5e23149eb5095` | +| `94-r3f-install-compare.log` | `09369829865ad226f4c2e8a4e71852c738ccb5981a5082dc8891caebd40acee0` | +| `95-r3f-r14-composed.log` | `e66b0843a2ffa3fb74f386615ae0e6d40ad3a4f42bdbf6f3d127856e78c7f567` | +| `80-r3-build.log` (r3a) | `1663134c45fbc3c3f8588a72d461d2e20205d653eca28861b0323e50d6325754` | +| `82-r3-install-modules-composed.log` (r3a) | `bc11cf7936a75b9ba0851beca7b311dae7fd9d614103296ff36207b8cf156ddf` | +| `84-r3-install-compare.log` (r3a) | `d3d15457017dc0fe84a0a5338a005c177ea0b5c118233a12ffb595c853d42674` | + +This record authorizes no conversion, repository creation, merge, default-branch move or +lane rebinding. diff --git a/specs/004-migrate-to-triad/gate-ab-evidence-2026-10-09.md b/specs/004-migrate-to-triad/gate-ab-evidence-2026-10-09.md new file mode 100644 index 0000000..f6c2809 --- /dev/null +++ b/specs/004-migrate-to-triad/gate-ab-evidence-2026-10-09.md @@ -0,0 +1,695 @@ +# Gate A/B evidence, the restore and rollback rehearsal, and the follow-up inventory — October 9, 2026 + +**Task:** T011 of [tasks.md](tasks.md), under [#186](https://github.com/opensoft/openRepoTools/issues/186). +No task is checked complete by this record, and it approves nothing. +**Writer:** lane openRepoTools-1, 2026-10-09T00:36Z–01:1xZ. +**Refreshed:** lane openRepoTools-2, 2026-10-09T18:51Z–20:3xZ (commit `c1283b5`), on branch +`004-migrate-to-triad-rollback-refresh` (from `fa44d75`). It answers lane 3's review of +`fa44d75` (#186 comment 6082139424, whose findings are cited here as F1–F22) and folds in +lane 2's verification of the receipt (#186 comment 6082370012). Each refreshed passage +starts with "Refreshed" or "Corrected"; the 01:1xZ text it qualifies stays as written +unless a finding showed it wrong. Origin was read at 2026-10-09T20:29:34Z–20:30:29Z, with +main at `f87fd5a`, and again before the push (section 8). Lane 3's review of `c1283b5` +(#186 comment 6089982753, N1–N9) is answered by two later commits on this branch. +**Evidence branch:** `004-migrate-to-triad-rollback` (from main `837928a`). +**Private receipt ID:** `rollback-20261009-ILvoVC` (brett-wip `migration/openRepoTools/`), +holding every script, log, snapshot and digest named below. Machine paths stay there. It +has two commits (F17): +- `56f1e7c4` (00:59:32Z): 424 files, 423 of them listed in one `SHA256SUMS`; +- `076062af` (01:14:30Z): 18 more files, the final real-surface snapshot (`state/final/`, + 01:13:46Z; section 3) and the two hygiene logs (section 8), with `README.md` and + `SHA256SUMS` updated: 442 files, 441 listed. + +Refreshed: brett-wip `ea6c09d1` holds the receipt exactly as `076062af` left it, and +`sha256sum -c SHA256SUMS` gives 441 OK. + +T011 asks for three things. First, a rehearsal of restoring the local candidate and the +installed payload from the preserved artifacts. Second, proof that user work, jobs and claims +survive with no force push, no destructive reset and no workspace-pointer rewrite. Third, +publication of the Gate A/B evidence with the follow-up patch and commit inventory. +Sections 1–3 are the rehearsals, measured today. Sections 4–6 are the index and the +inventory. Section 7 is what Gate B still lacks. + +**Short answer.** +- **Source restore:** passes, 90 checks of 90. +- **Installed payload:** the 29 placed files restore to the main install exactly by either + path; only the reinstall (c2) also restores the receipt. Refreshed (F13): the target is + main's install, not the home before any install, because the first install rewrites + `settings.json` at mode 600 and both rollbacks keep that. +- **Hooks and settings: not rehearsed** (F11). No stage changed a hook entry or + `settings.json`, so section 2's hook rows test nothing about hooks, and the Gate A + backup holds no `settings.json` for `RESTORE.md`'s copy-back to restore. +- **What the rehearsals wrote:** only private scratch. Nothing in a real repository, the + real home or the register. +- **Gate B is still open.** Section 7 lists what is missing. + +## 1. Restore rehearsal of the source + +The source is the Gate A backup `prep-20261007T100559Z` (host-local, read-only). The +rehearsal followed its `RESTORE.md` commands, with only the placeholder paths replaced by +private scratch. **90 checks passed, 0 failed.** + +Refreshed (F5): how the 90 add up, from receipt `logs/10-restore.log`. +- 22 single checks: the manifest 1; `RESTORE.md`'s mirror clone, working clone, fetch and + `set-url` 4; `bundle verify` inside the clone 1; refs 3 (each restore against the + capture, and the capture against the shared checkout today); `fsck` 2; the object set 1; + `RESTORE.md`'s worktree procedure on `main` 6; the dependency bundle 4. +- 68 tree checks: 4 for each of the 17 captured trees. + +The 97 of #189 (`preservation-2026-10-07.md:89`; receipt `prep-20261007T100559Z`, +`verify.log`) were 12 single checks plus 5 for each tree (85). +- This run did not repeat 18 of them: the status-and-lists comparison for the 16 trees + other than `main` (October 7 made it for all 17, `preservation-2026-10-07.md:95–98`; + this run made it for `main` only, below), the bundle's `HEAD` against the main + checkout's (`verify.log:12`), and one check that the bundle carries the 16 per-worktree + `HEAD` refs (`verify.log:13`: one check, not 16). +- It added 11: the manifest, the mirror clone, `set-url`, `bundle verify`, the capture + against the shared checkout today, the second `fsck`, and five steps of the worktree + procedure on `main`. +- `main`'s status-and-lists comparison moved from the tree checks to the single ones + (`logs/10-restore.log:160`). So the single checks are 12 − 2 + 1 + 11 = 22, the tree + checks are 85 − 17 = 68, and the total is 97 − 18 + 11 = 90. + +- The manifest (414 entries) passes `sha256sum -c`. Refreshed (F4): it lists the two + bundles restored here, source `60db3acd…` and dependency `aa5a6eca…`, which are the + digests #189 published (`preservation-2026-10-07.md:49–50, 54`), and its own digest is + #189's `2a46dff9…` (`:59`). So the bundle restored here is the one #189 recorded. Both + bundle files still hash to those values (re-hashed 2026-10-09T20:35:05Z). +- **`RESTORE.md`'s `git bundle verify`, run from the backup directory as written, exits 1:** + `error: need a repository to verify a bundle`. Run inside the restored clone, it exits 0. + This is a defect in `RESTORE.md`'s text. It is recorded here; the backup is read-only and + was not edited. +- The `--mirror` restore and the working clone each list the same 33 refs, with the same SHA + each, as the live `for-each-ref` at bundle time. The working clone is a `clone`, then + `fetch --update-head-ok --prune '+refs/*:refs/*'`. `git fsck --full` prints nothing and + exits 0 for both. +- The 2902 objects reachable from the 50 bundled tips are identical. The sorted-list SHA256 + is `338cb7bdd397076eeced1de0852eeb1510b4f97e02fb3f62de434531c7be327d`. +- **Against the live repository today, two days on**, the 33 captured refs are as below. + Corrected: "live" here is the shared checkout's own `for-each-ref`, read at 00:44:08Z + (`10-restore.sh:58`, `logs/10-restore.log:1`), not GitHub. #189 had merged on GitHub as + `63810dd` at 00:44:03Z, and that checkout's `main` still read `837928a`. + - 26 identical; + - 7 moved forward only: `main`, `origin/main` and `origin/HEAD` to `837928a`, the three + October 7 evidence branches, and lane 3's `feat/triad-lane-tooling`; + - 0 deleted; + - **0 moved by anything other than a fast-forward**. + + 18 refs were added since the capture. + + Refreshed 2026-10-09T20:29:34Z, against GitHub (`git ls-remote`; a local branch and its + remote-tracking ref are read as the GitHub branch of that name, and `pr/N` as + `refs/pull/N/head`): 15 identical, 9 moved forward only, 9 with no GitHub ref of that + name, and **0 moved by anything other than a fast-forward**. Each of those 9 commits is + still contained in a GitHub ref; for example `e43b243` is in `refs/pull/188/head`. +- **All 17 captured trees:** + - each HEAD resolves in the restore; + - each tree ID equals the live one; + - every index object is present; + - the index a checkout of each HEAD makes equals the captured `index.ls-files-s` byte + for byte (17 of 17). + + Refreshed (F6): two of these four prove little on their own. + - "Tree ID equals the live one" compares the tree of the same commit ID, read in the + restore and in the shared checkout (`10-restore.sh:94–95`). A commit ID fixes its + tree ID, and `fsck` already covers the objects. + - All 17 captures were clean (`preservation-2026-10-07.md:69–85`, Dirty 0), so each + captured index is its HEAD's tree. The byte-equal index proves that the objects are + present, not that index state survives a restore. The dirty-tree stand-in below is + the evidence on index state. +- **One tree by `RESTORE.md`'s own procedure**: `main`, the capture with workflow files. + - The steps: `worktree add --detach`, copy the captured files back, restore the captured + exclude file. + - Its index (100 entries) equals the capture. + - All 70 copied files match `files.sha256`. Refreshed (F7): these are the 70 regular + files. With one symlink they are the 71 workflow entries #189 counts for `main` + (`preservation-2026-10-07.md:61`; receipt `prep-20261007T100559Z`, + `file-lists/main.classified.tsv`: 70 `workflow-copied-verified`, 1 + `workflow-copied-symlink-verified`), and `files.sha256` lists regular files only. 71 + ignored files are restored (`logs/10-restore.log:160`). + - Its tracked status and its untracked and ignored lists equal the capture's, less the 4 + cache entries the capture lists as not copied. Corrected: those 4 are nested + repositories (`nested-repository-not-copied` in the same file), not caches. +- Both dependency commits resolve from the dependency bundle. Each one's own `tree_digest` + gives the recorded value: `39d5c98` → `a44c0165…`, `7f84ca4` → `3be52767…`. + +### A worktree captured dirty + +The 17 Gate A captures were all clean, so the dirty case was rehearsed on a disposable +stand-in: a clone of the Gate A bundle given every kind of change: +- an unstaged edit and a staged edit; +- a staged new file and a partly staged file; +- an unstaged deletion and a staged deletion; +- a mode change; +- an untracked note; +- ignored workflow configuration and an ignored cache. + +Each capture was bundled and rebuilt into a fresh clone that held only the bundle's objects, +as a real restore would. + +| Capture | Index equal | Status equal | Every file equal (100) | +| --- | --- | --- | --- | +| A: the per-worktree block of the backup's own `capture.sh`, verbatim (corrected below: a copy) | **no**: 3 staged blobs are in no bundle | **no** | **no** | +| B: A plus `git diff --cached --binary`, `git diff --binary` and every untracked file | yes | yes | yes | + +Corrected (lane 2's verification, #186 comment 6082370012): the receipt's copy of that +block (`11-dirty.sh:42–66`) is not verbatim. Against the backup's `capture.sh:68–92` it +drops the info-exclude copy (`:75–77`) and the `skipped-secret-like.tsv` append (`:82`). +Lane 2 ran the backup's own block, verbatim, on the same stand-in: 0 of 3 again, with the +same 3 staged blobs in no bundle. Corrected: the public comment states that result and +marks as inferred only its conclusion, that the omissions change nothing. The run's own +log is host-local, in lane 2's private prep area, and is not published, so no public +citation for the run itself exists. + +**Finding for Gate A.** The October 7 capture takes three things from a tree: its index +*listing*, its ignored workflow configuration, and its HEAD. It takes none of its working +state. That was enough only because all 17 trees were clean. For any tree that is not clean, +the final capture at the owner-coordinated freeze must take B's three extra artifacts, or +must refuse to call that tree captured. B's rebuild is: +1. `worktree add --detach `; +2. `git apply --index staged.patch`; +3. `git apply unstaged.patch`; +4. unpack the untracked archive; +5. copy back the workflow files. + +## 2. Rollback rehearsal of the installed payload + +All of it ran in disposable homes under private scratch, never the real home. +- Every `--install` ran under `env -i` with: + - `HOME` and every `XDG_*` variable pointed into that home; + - `TMPDIR` in scratch; + - `PATH` = refusing `gh`/`curl`/`wget` shims, then `/usr/bin:/bin`; + - `GIT_ALLOW_PROTOCOL=file`. +- **The shims logged 0 calls.** +- Each home was seeded with things an install must leave alone: + - unrelated `settings.json` content: a model, an env, permissions, a status line, and + unrelated `SessionStart`, `UserPromptSubmit` and `Stop` hooks; + - a foreign command in `~/.local/bin`; + - a foreign skill under each of the two skill roots. + +| Stage | Source | Commit | +| --- | --- | --- | +| (a) today's main | a fresh clone of `origin/main` from GitHub, `./openRepoTools --install` | `63810dd` | +| (b) the rehearsed candidate | run C's assembly cloned recursively; installed from **`code/`**, the code leg's own `openRepoTools` | assembly `69b8924`, code `73b04d6` | +| (b″) a candidate whose payload differs | PR #191's head, a fresh clone | `ecd7aa4` | +| (c1) roll back by copy | `cp -a /installed/. "$HOME/"`, `RESTORE.md`'s command | the Gate A copies of the 2026-10-07T04:26:26Z install | +| (c2) roll back by reinstall | `./openRepoTools --install` from the source restored in section 1 | `c4864ac` | + +Notes on the sources: +- **(a):** main moved during the run. #189 landed as `63810dd` at 00:44Z. It touched + only `specs/` and `openspec/`, so its payload is byte-identical to `837928a`'s. +- **(b):** T007's assembly root entry point was **not** used. No + `004-migrate-to-triad-installer` branch is on origin, so (b) installed through + `code/openRepoTools`. Refreshed (F15): that branch has been on origin since 01:21:15Z, + at `51993a4`, 7 min 40 s after this branch's last push (section 4). +- **(b″):** it is not the T007 candidate. It is used only because a rollback is shown best + where the payload differs. + +**The four homes:** +- H1: (a), (b), (c1). +- H2: (a), (b), (c2). +- H3: (a), (b″), (c1). +- H4: (a), (b″), (c2). + +Every install exited 0, printed `17 of 17 placed` and wrote 29 receipt rows. The first in each +home merged the 2 hook entries; every later one printed `already installed … (unchanged)` for +both. + +**Refreshed (F11): so no stage changed a hook, and the hook rollback is vacuous.** +- In all four homes the two hook entries and the canonical `settings.json` are identical at + (a), at (b) or (b″), and at (c) (receipt `state/homes/H1`–`H4`, `hooks.ours` and + `settings.canon`). Every candidate here writes the same two entries main writes. +- So the row "(c) the two hook entries: same" below shows that nothing moved, not that a + moved hook comes back. Commit `849592b`'s subject, "both rollbacks return every placed + file, mode and hook to main's install", holds for hooks only in that sense. +- (c1) cannot roll back a hook or a setting at all. The Gate A backup's `installed/` holds + the 29 placed files and no `settings.json`; no file in the backup is a `settings.json`; + and `RESTORE.md:45` copies back `installed/` only. The backup does hold the 29-row + `installed.tsv` of 2026-10-07T04:26:26Z at its top level, which `RESTORE.md` does not + copy back (F14; section 7, item 6). +- No candidate measured to date changes a hook or `settings.json`: + - (b) and (b″) here; + - PR #191 at `13cc752`: `settings.json` and both hook entries are byte-identical to + main's install (lane 3's delta review 5471121210, finding 2); + - the composed candidate at `935f53f`: `settings.json` is byte-identical (lane 3's + review, #186 comment 6082068576, finding 4). + + A candidate that does change one would need this rollback rehearsed, by (c2), with a + payload whose hook entry differs. + +| Compared with (a) | H1 (b)→(c1) | H2 (b)→(c2) | H3 (b″)→(c1) | H4 (b″)→(c2) | +| --- | --- | --- | --- | --- | +| (b) placed files (path, mode, sha256) | same | same | **1 differs** | **1 differs** | +| (c) placed files (path, mode, sha256), 29 | same | same | same | same | +| (c) receipt rows (name, destination, sha256) | same | same | **1 stale row** | same | +| (c) the two hook entries (never changed; see above) | same | same | same | same | +| (c) `settings.json`, canonical; its mode | same; same | same; same | same; same | same; same | +| unrelated settings after every stage = the seed | yes | yes | yes | yes | +| foreign command and skills after every stage | untouched | untouched | untouched | untouched | +| (c) the set of paths under the home | same | same | same | same | + +**Every difference, verbatim:** +- **(b″) vs (a), in H3 and H4.** `~/.local/bin/openRepoTools 755 + 47cfe74ea96e6ce9e56b47220a71e1de8c608af2f51f33cfdffa351410a4f373` became + `71e9ba2c9a06b755c79f3d01b52516ce0c1cfcc3a3c6a92b5fd200976b0787a4`, PR #191's installer. + Both rollbacks put `47cfe74e…` back. +- **(c1) in H3: the copy-back leaves the receipt stale.** The receipt row + `openRepoTools ~/.local/bin/openRepoTools 71e9ba2c…` still names the replaced file. The + file on disk is `47cfe74e…`. `RESTORE.md`'s copy-back restores the 29 files and not + `installed.tsv`. A later `--install` that retires a file asks the receipt whether it wrote + that file, and would get the wrong answer. **(c2) rewrites the receipt** and has no such + row. +- **In every home, (c) vs (a), the whole-home comparison differs in one file only:** + `installed.tsv`. That is its UTC stamp column, plus H3's stale row above. Examples: + - H2: `145f4de8…` vs `09a03339…`; + - H4: `74cfc104…` vs `9e8b2a59…`. +- **The first install changes `settings.json`'s mode from 644 to 600.** The installer prints + that it writes the file back at mode 600. This is by design and is the same in (a) and (c). + +**What this run cannot show.** The rehearsed candidate's payload is blob-identical to +main's: filter-repo keeps every blob. So (b)→(c) in H1/H2 rolls back no bytes, and only +checks the mechanism. Receipts, hooks, unrelated settings and foreign files hold through a +candidate install and a rollback. H3/H4 show a real byte rollback with PR #191's installer. +The real T014/T015 candidate, with #191 and the T007 entry point, must repeat (c2) at its +own exact commits. + +Refreshed (F13): two more things it cannot show. +- **No stage adds or retires a placed file.** All 12 snapshots after the seed, in the four + homes, place the same 29 paths (receipt `state/homes/*/*/placed.tsv`). So two hazards are + argued here, not run: (c1) leaving behind a file a candidate added, and the stale receipt + misleading a later `--install` that retires a file (above). +- **The rollback target is main's install, not the home before it.** The first install + rewrites `settings.json` at mode 600, and neither rollback returns it to the seed's 644. + +**Run 1, kept as evidence of all-or-none.** The first seed's unrelated `SessionStart` +command contained the word `session-start`. Every install refused it as a second writer of +that hook: 10 runs across main, the candidate, #191 and the restored source, exit 2, +`NOTHING was installed`. After each refusal, every file and `settings.json` was +byte-identical to the seed. The seed was then reworded and all four homes were rebuilt. + +**The rollback targets match what Eagle runs today:** +- the real home's 29 installed files equal (a)'s placed files (path, mode, sha256), read-only; +- the Gate A backup's 29 copies carry the same 29 digests. + +## 3. User work, jobs and claims survive + +**Every command the rehearsals ran that writes anything** — 18 groups in the receipt's +`mutations.tsv` — wrote only into private scratch: +- **temp dir only: 18;** +- **real repository: 0;** +- **real home: 0;** +- **register: 0.** + +The groups are: +- restore clones, the throwaway indexes, the `RESTORE.md` worktree and its copied files; +- the dirty stand-in and its two rebuilds; +- the install sources; +- the seeds; +- 10 installs and 10 refused ones. Corrected (F12): this said 12. The receipt's + `README.md:35` and `mutations.tsv:15` say 12, but `logs/22-installs.log` has 10 + `--install` runs and the 2 copy-backs of the next line, 12 `exit=0` lines in all; +- two copy-backs, snapshots and comparisons; +- the patch-application clones. + +**No script pushes, stashes, cleans, kills a process, deletes a branch, removes a worktree, +runs `checkout -f` or `update-ref`, or writes `workspace.yaml`** (`grep` over every script +in the receipt). The one `reset --hard` is in a scratch clone of the rehearsal that section 5 +patched. The live repository was only read, with `GIT_OPTIONAL_LOCKS=0`: `for-each-ref`, +`rev-parse`, `cat-file`, `merge-base`, `ls-tree`, `show`. + +Corrected (lane 2's verification, F22): the rehearsal scripts only read the live +repository, but not all of them with `GIT_OPTIONAL_LOCKS=0`. `10-restore.sh` (`:7`) and +`snapshot-real.sh` (`:5`) set it; `snapshot-real.sh:15` also runs `worktree list`, which +the list above omits. `24-patches.sh` runs `ls-tree` and `show` there (`:14–15`) without +it (`:7`). The four fetches under "Acts outside the rehearsals" did write there: +remote-tracking refs and their objects. + +**Real surfaces, before (00:42:08Z) and after (00:53:06Z):** + +Refreshed (F17): a third snapshot, final (01:13:46Z, `state/final/`, added by `076062af`), +came after this branch's three pushes (the last at 01:13:35Z) and the first brett-wip +commit. It came before the second brett-wip commit (01:14:30Z), which adds it, and before +the comment on #186 (01:14:55Z). The rows below hold at final too, except where a +correction says otherwise. + +| Surface | Result | +| --- | --- | +| The real home's 29 installed files, its receipt, its `~/.claude/settings.json` | unchanged (sha256 and mode) | +| `~/.agents/workspace.yaml` | exists; size, inode, mtime and mode unchanged; its contents were not read | +| The Gate A backup | manifest passes both times; whole-tree listing (size, mode, mtime) unchanged | +| The run C rehearsal | whole-tree listing (size, mode, mtime) unchanged | +| Lane 1's register row and objects (`lanes --all`, `lane-objects`, both with `LANES_NO_FETCH=1`) | the row unchanged but for its age column; objects gained one line, `LANDED opensoft/openRepoTools#189` (00:49:25Z, the coordinator's). Corrected: they also gained two lines from another lane's log that the reader reports as unreadable (its count went from 3 to 5) | +| The live repository's 25 worktrees | the same 25 before and after; none locked, prunable or detached. Corrected: the same 25 paths at final, but four HEADs moved forward: the shared checkout's `main` `837928a` → `63810dd`, #191's branch `e406e0e` → `2411f40`, #190's `c139e88` → `e3a76b5`, and this writer's `837928a` → `fa44d75` | +| The live repository's refs | this writer added `refs/heads/004-migrate-to-triad-rollback` and its remote-tracking ref (fast-forward pushes). Other writers' acts moved the rest: `main` to `63810dd`, the T006 and run C branches, `004-migrate-to-triad-evidence-2`, and the #190/#191 heads. Corrected: the branch was already in the baseline at `837928a` (`state/before/live-refs.sorted:27`), made by this writer's `lane-worktrees add`; its commits moved it, and only its remote-tracking ref was new. The remote-tracking `origin/004-migrate-to-triad-evidence` (`3f7c48f`) was pruned before 00:53:06Z; #189's branch is no longer on GitHub. A remote-tracking ref here moves on a push from any worktree of this repository or on a fetch, this writer's four included, so which act moved each one is not recorded (F22) | + +Corrected (lane 2's verification): the receipt's `README.md:65–67` says these final +surfaces "all equal `state/before/`". Byte for byte, three do not, as the rows above now +say: the register row (its age column), the register objects, and the worktree list (four +HEADs). The real home's files, receipt and `settings.json`, `workspace.yaml`'s metadata, +the backup and the rehearsal do. + +The lane's register clone and brett-wip's shared checkout both moved during the window. +Both moves were other actors' commits: the coordinator's LANDING/LANDED lines for #189, and +other lanes' handoffs. This writer ran no register write. + +**What a cutover, or a rollback after one, must preserve.** + +Lane 1's live register row, read-only: +- `openRepoTools-1 LIVE Eagle team-008`; +- window `openRepoTools-1:@20`; +- transcript `df82f553-7803-49c4-8ddf-3f9acdcb8ee0`; +- the shared checkout as its directory; +- holds `CLAIMED opensoft/openRepoTools#186`. + +In-place adoption keeps that directory, so neither the row nor the claim needs rewriting. +A rebinding is T018's, at a safe breakpoint, through `lane-start`/`lanes-edit.sh`. + +`~/.agents/workspace.yaml` exists. Only `resume --workspace` and `wip init` write it, and +neither is in any rollback path; this run left its metadata unchanged. + +**The rollback path in the plan's own words:** revert or pin-bump PRs, plus the preserved +installer, through (c2). It needs: +- no force push: every captured ref has only ever moved forward, or was deleted with its + commit still on GitHub (refreshed: section 1; re-checked 2026-10-09T22:05:16Z); +- no destructive reset; +- no pointer rewrite. + +Refreshed (F8, F19): this is an argument, not a rehearsal. +- "Every captured ref has only ever moved forward" describes two days of other writers' + work. It shows that nothing has needed a force push so far. +- No revert or pin-bump PR was rehearsed, and neither was a restore of the local + candidate (run C's triad) from preserved artifacts. Section 7 lists both. +- Jobs: no before/after list of running jobs was taken, although the plan preserves + "uncertain jobs" (plan.md:245). The only evidence is that no script kills a process. + +**Acts outside the rehearsals.** These are publication and bookkeeping, listed so nothing is +hidden: +- four fetches in the live repository: three before the baseline, and one after the + rehearsals to re-read the evidence branches that had moved (remote-tracking refs only); +- `lane-worktrees add openRepoTools-1 ort-triad-rollback`: the worktree, its new branch, and + one record in lane 1's host-local tree inventory; +- the commits and fast-forward pushes of this branch; +- one commit to brett-wip `main` (`git add -- `), from a sparse clone in scratch, + never the shared checkout; +- one comment on #186; +- a stray empty file this writer made by mistake in the private `work-20261008` directory, + outside the rehearsal tree, and removed at once. + +## 4. Gate A/B evidence index + +Verification marked "T011" was done by this run: checksums and the git identities it names. + +| Branch, head | Document or receipt | What it proves | Verified by | +| --- | --- | --- | --- | +| main `63810dd` (#189) | `preservation-2026-10-07.md`, `inventory.json` | Gate A refresh (T001–T002): 17 trees, 33 refs, backup `prep-20261007T100559Z` | the writer's restore (97 PASS); lane 3's #189 review (LAND); **T011's restore today (90 PASS)** | +| main `63810dd` (#189) | `adopter-rehearsal-2026-10-07.md`, `adoption-plan.yaml` (run B's, source `e43b243`) | runs A and B at tool `7f84ca4`: 45/49/6 of 100, 0 dropped, bootstrap ok | lane 2's six-check verification (#186 comment 6036838832); #189 review | +| main `63810dd` (#189) | `consumer-reads-2026-10-07.md` | 33 outside consumers not covered, their timing | #189 review | +| main `63810dd` (#189) | `gate-b-checklist.md` | the 46-line Gate B walk of October 7 | #189 review | +| `004-migrate-to-triad-rehearsal-c` `1a26463` (#192, as `71b05cb`) | `adopter-rehearsal-2026-10-08.md`, run C's `adoption-plan.yaml` (source `837928a`) | run C: 65/49/6 of 120, 0 dropped, bootstrap ok; the arrangement T006–T011 used | **T011:** its receipt passes `sha256sum -c` (44); its three log digests are in it; the rehearsed commits resolve (`69b8924`, `593250d`, `73b04d6`, `7f84ca4`); its tree is unchanged by this run. #192 is not reviewed yet | +| `004-migrate-to-triad-root-guidance` `041a273` (#192) | `root-guidance-2026-10-08.md`, `patches/t006/` (5) | T006: 5 collisions, 32 repaired links, leg front doors, 35/35 provenance, bootstrap and validators | **T011:** the 5 patch digests match the document; every patch `git am`s onto its base; the trees equal the recorded ones (section 5). Lane 3's T006 review is not posted | +| `004-migrate-to-triad-ci` `cc7c7a1` (no PR) | `test-roots-and-ci-2026-10-08.md`, `patches/t010-*.patch` (2) | T009/T010: what the split breaks, the root rules, the composed contract, local proofs P1–P4 | **T011:** its 16 published log digests match the host-local logs (`sha256sum -c`, 16 OK); both patches apply (section 5). No review is recorded | +| PR #190 `e3a76b5` | the test-roots change | T009's explicit roots on today's layout | its own CI; lane 3's review is in progress | +| PR #191 `ecd7aa4` | the installer payload-source change | T007/T008's code side and the 15-row matrix as tests | its own CI; lane 3's review is in progress; lane 2's T008 verification is held | +| this branch | `gate-ab-evidence-2026-10-09.md` | T011 | — | +| brett-wip `f0ea2df7` | receipt `prep-20261007T100559Z` | Gate A's private record | **T011:** its manifest copy is the backup's (`2a46dff9…`), and the backup passes it | +| brett-wip `c311a78b` | receipt `adopter-fix-20261007-iM20tv` | runs A and B | **T011:** `sha256sum -c` 58 OK; the six published log digests are in it | +| brett-wip `3de0d37c` | receipt `adopter-fix-20261008-1HztL0` | run C | **T011:** `sha256sum -c` 44 OK | +| brett-wip `56f1e7c4` | receipt `rollback-20261009-ILvoVC` | T011 | its own `SHA256SUMS` (423 entries) | +| brett-wip `076062af` (refreshed, F17) | the same receipt, plus the final snapshot and two hygiene logs | T011 | its own `SHA256SUMS` (441 entries); lane 2's verification (#186 comment 6082370012) | +| host-local | backup `prep-20261007T100559Z`; rehearsal `work-20261008/rehearsal` | the preserved artifacts | **T011**, read-only, before and after | + +No private receipt exists for T006 or T009/T010. Their logs are host-local only, in the +private prep area. + +**Refreshed 2026-10-09T20:30Z (F15).** Origin moved after this index was written. Main is +`f87fd5a` (`git ls-remote`, 20:29:34Z): lane 3 landed #192 as `1abee1d` (20:24:59Z) and +then #190 as `f87fd5a` (20:26:03Z), on Brett Heap's word (#186 comment 6088703549). Each +squash equals its reviewed head: `1abee1d`'s tree is `71b05cb`'s, and `f87fd5a`'s diff is +`c4864ac..b50f197`'s. Every other move below is a fast-forward (`git merge-base +--is-ancestor`, each step). + +| Branch or PR, head now | Since 01:13Z | Reviews and verification now | +| --- | --- | --- | +| #192, landed as `1abee1d` (head `71b05cb`; `004-migrate-to-triad-evidence-2` deleted) | run C's records, its plan and T006's `root-guidance-2026-10-08.md` and five patches are on main | lane 3: **LAND** on `71b05cb` (review 5464893559), with an exact rebuild of all five T006 patch commits; lane 2: run C's receipt verified, all six checks pass (#186 comment 6072724043) | +| `004-migrate-to-triad-root-guidance` `041a273`, `004-migrate-to-triad-rehearsal-c` `1a26463` | unchanged; their content landed with #192 | as #192 | +| #190, landed as `f87fd5a` (head `b50f197`; `feat/test-roots-for-the-triad` deleted) | `e3a76b5` → `dbc6837` (01:22:43Z) → `b50f197` (13:59:10Z, the manual guard, #193 item 1) | **LAND** on `dbc6837` from lane 3 (5464931249) and lane 1's independent review (5464938427); **LAND** on the `b50f197` delta (5471080687); CI green on `b50f197`, `tests-macos` included | +| `004-migrate-to-triad-ci` `d28e2cc` | `cc7c7a1` → `b4ef6b5` → `d28e2cc` (02:30:57Z); only `test-roots-and-ci-2026-10-08.md` changed, and both T010 patch blobs are identical at both heads | no review verdict posted; lane 3's reviewer of the T010 patches has resumed (#186 comment 6088703549). *Corrected 2026-10-11 (lane 3's 5481348731 on #196, item 7): the verdict came after this record's origin read of 20:29–20:30Z. It is #186 comment 6089801221 (2026-10-09T21:47Z), on `d28e2cc` (unchanged): fit for evidence-3, carried as its three new files on main. One thing must be settled before the composed job is relied on: `composed.yml` runs the suite through `run.sh`, which brings #180 into the one job that must be green (its finding 13).* | +| #191 `13cc752` | `ecd7aa4` → `2411f40` (01:20:38Z) → `13cc752` (02:42:51Z) | **DO NOT LAND** on `2411f40` from lane 3 (5464963807) and lane 1's independent review (5465121368); **LAND** on `13cc752` from lane 3's delta review (5471121210), on condition that the PR body is corrected before the squash and `tests-macos` runs on `ready`. CI on `13cc752`: every job passed except `tests-macos`, skipped without `ready`. Lane 1's next head, `fdc5f18`, is announced and not on origin (#186 comment 6088714587, item 3) | +| `004-migrate-to-triad-installer` `51993a4` (new, 01:21:15Z; no PR) | T007's entry point, `patches/t007-assembly-entry-point.patch` (sha256 `361c037d…a277d`), and `installer-2026-10-08.md` | lane 3: **CHANGES REQUIRED** on the patch: confirm the gitlink before running the pinned implementation on the remote path (review 5471121210, T3). Lane 1 puts that change on this branch (#186 comment 6088714587, item 3) | +| `004-migrate-to-triad-candidate` `935f53f` (new: created at `b21c511` at 01:58:43Z, then `b21c511` → `935f53f` at 02:43:24Z; no PR) | `candidate-2026-10-09.md`: the composed candidate, built from #190 `dbc6837` and #191 `2411f40` (`:29–30`); receipt `candidate-20261009-JR8YyA` | lane 3: **REPRODUCES**, fit for the composed run (#186 comment 6082068576). Its section 5 records that no suite run had finished. Lane 1's r2 runs finished at 18:20:47Z (lane 2's reading, #186 comment 6087267593); no record of them is on origin. The receipt is not on brett-wip `ea6c09d1` | +| `004-migrate-to-triad-t018` `63bff08` (new, 20:28:03Z; no PR) | lane 1's T018 exercise (#186 comment 6088703549) | — | +| `feat/triad-lane-tooling` `30f9dc0` | `c45a452` → `2aa8a59` → `96da9af` → `30f9dc0` (14:18:02Z) | no PR | +| `feat/test-roots-manual-guard` `b50f197` (new: created at 02:29:44Z; no PR of its own) | the manual-guard fix, one commit on `dbc6837`, which #190 has carried since 13:59:10Z | landed with #190 (row above); the branch is still on origin | +| `feat/test-roots-followups` `01ccc86` (new: created at `2064390` at 02:14:37Z) | #190's follow-ups (#193 items 2–7); `2064390` → `7144eef` (02:31:20Z), then `7144eef` → `01ccc86` (20:31:02Z), a merge of main `f87fd5a` (re-read 20:36:57Z) | lane 3's review has resumed (#186 comment 6088703549). *Corrected 2026-10-11 (lane 3's 5481348731 on #196, item 7): the verdict came after this record's origin read (20:36:57Z for this head). It is #186 comment 6089801221 (2026-10-09T21:47Z), on `01ccc86`: fit for a PR onto main after one edit, which is to drop or re-premise the 340 → 347 assembly cap (its finding 7). The branch `01ccc86` answers the code-half part of #193 item 2.* | +| `004-migrate-to-triad-reads` `a7a8e00` | `a6830a1` → `a7a8e00` (13:50:08Z): lane 2's `gate-b-status-2026-10-09.md` | no PR; lane 1's own walk goes into its next evidence PR (#186 comment 6088714587, item 1) | +| brett-wip `99eed35` | Gate A capture tooling v2, which answers this record's three findings (#186 comment 6073036070) | lane 2's final verification: `capture.sh:170` rewrites its source's index, and `restore.sh:132` can aim at `/` (#186 comment 6087261307). Lane 1 accepts both; no capture runs on a real checkout until a fixed version is re-verified (#186 comment 6088714587, item 4) | +| issue #193 (13:42:32Z; updated 20:26:33Z) | the follow-ups deferred at the landing of #190 and #192: item 1 resolved by #190, item 15 added | — | + +## 5. Follow-up patch and commit inventory + +Every recorded patch for the rehearsed arrangement was re-applied by this run with `git am`, +in a fresh clone out of run C, onto the commit it names. Trees are compared, not commit +hashes: the committer differs, so the hashes do. + +| # | Patch (branch, head) | sha256 | Leg | Applies to | Rehearsed result | Re-applied tree (T011) | At T014 | +| --- | --- | --- | --- | --- | --- | --- | --- | +| 1 | `patches/t006/assembly-0001-…front-door…` (root-guidance `041a273`) | `cd69c2a9…c5d096` | assembly | split `69b8924` | `3361812` | — (the three stack, below) | **applies as-is** onto the real split, after the frozen source's root files are checked unchanged | +| 2 | `patches/t006/assembly-0002-Bump-spec-leg-to-39c18877e042…` | `574c0df1…3647f9` | assembly | `3361812` | `1024f18` | — | **regenerated**: `bump-leg.py` against the real spec commit | +| 3 | `patches/t006/assembly-0003-Bump-code-leg-to-6169e8e96164…` | `a7f8521b…ec14aa` | assembly | `1024f18` | `3e87e39` | 1–3: `6eb8145e…`, as recorded | **regenerated**: `bump-leg.py` against the real code commit | +| 4 | `patches/t006/code-0001-…own-front-door…` | `74b98ff9…2ae1ec1` | code | code `73b04d6` | `6169e8e` | `ca65c3b9…`, as recorded | **applies as-is** | +| 5 | `patches/t006/spec-0001-…own-front-door…` | `c11070f0…8210b3` | spec | spec `593250d` | `39c1887` | `10256135…`, as recorded | **applies as-is** | +| 6 | `patches/t010-code-leg-tests-workflow.patch` (ci `cc7c7a1`) | `7ee3c6d0…786c3b` | code | code `73b04d6` | `b95d667` | alone: exit 0; on #4: exit 0 (`007509d2…`) | **applies as-is**; its env names mean something once PR #190 is in the code | +| 7 | `patches/t010-assembly-exact-pin-job.patch` | `6ee09f00…74670b` | assembly | split `69b8924` (proof commit `386be81` sat on the rehearsal-only bump `6ccdaf8`) | `386be81` | alone: exit 0 (`c37aa4f0…`); on #1–3: exit 0 (`b351c8e9…`) | **applies as-is** (a new file); `bash code/tests/run.sh` with `OPENREPOTOOLS_COMPOSED=1` needs PR #190 in the code leg | +| — | T007's assembly root `openRepoTools` entry point | — | assembly | — | — | — | **absent**: no `004-migrate-to-triad-installer` on origin; PR #191's body says it will be recorded there | +| — | the rehearsal-only bump `6ccdaf8` (code leg to `8ebf4ec`, PR #190 on `73b04d6`) | not a patch file | assembly | — | — | — | **regenerated**, like #2–3 | + +**Counts:** +- **7 recorded patches:** T006 5, T010 2. +- **5 apply as-is:** #1, #4, #5, #6, #7. +- **2 regenerate at T014:** #2 and #3, plus the T010 proof's bump `6ccdaf8`, which has no + patch file. +- **T007's entry point: 0 recorded.** + +`git diff --check` exits 0 over the assembly stack (4) and the code stack (2). + +**They stack:** +- **assembly:** #1, #2, #3, then #7; +- **code:** #4, then #6, then PR #190's code diff (`apply --check` 0), then PR #191's on top + of that (`apply --check` 0, also with `--3way`); +- **spec:** #5 alone. + +This is a text-level check only. **No test ran on the stacked candidate.** + +Corrected (F20; lane 2's verification, #186 comment 6082370012): +- "As recorded" holds for the T006 trees (#1–#3 as a stack, #4, #5). Nothing recorded a + tree for the T010 patches before this run. `test-roots-and-ci-2026-10-08.md` names commit + `386be81` and no tree, and #6's commit `b95d667` appears only in the patch's own `From` + line. So #6's and #7's trees above are this run's first record of them, not a match. +- #7 was re-applied on the split `69b8924`, not on its rehearsed base `6ccdaf8`. +- Commit `849592b`'s body says "the seven recorded patches re-applied onto their rehearsed + bases with matching trees". The two points above are the exceptions. +- The first attempt to fetch the two PR heads ran under `GIT_ALLOW_PROTOCOL=file` and was + refused (`fatal: transport 'ssh' not allowed`, exit 128; receipt + `logs/24-patches.log:21–32`). `25-prs.sh` redid it, and the stack result above is the + redo's (`logs/25-prs.log`). + +**Refreshed 2026-10-09T20:30Z: the follow-up inventory (F15, F18).** +- T007's entry point has been recorded since 01:21:15Z, so "T007's entry point: 0 + recorded" above no longer holds. +- The stack check above used #190 `e3a76b5` and #191 `ecd7aa4`. Two later builds + supersede it. One is the composed candidate at `dbc6837` and `2411f40` + (`candidate-2026-10-09.md` section 2), which lane 3 rebuilt exactly (#186 comment + 6082068576). The other is lane 1's r2 at `b50f197` and `13cc752`, which lane 2 rebuilt + with every `git am --3way` step exiting 0 and the same trees (#186 comment 6087232548, + item 3). + +Gate B asks for "reviewed follow-up patches" (plan.md:140–141). Each named follow-up below +maps to a patch, a branch or PR, an owner, or "deferred". Line numbers in run C's plan and +in `root-guidance-2026-10-08.md` are on main since #192 (`1abee1d`). + +| Follow-up (where named) | Patch, branch or PR | Owner | State at 20:30Z | +| --- | --- | --- | --- | +| T006's five patches (the table above) | `patches/t006/`, on main since #192 | lane 1 (T006) | reviewed: LAND (5464893559). #2, #3 and the bump `6ccdaf8` regenerate at T014 | +| T010's two patches (the table above) | `004-migrate-to-triad-ci` `d28e2cc` | lane 1 (T010) | no review verdict posted; lane 3's reviewer has resumed (#186 comment 6088703549). Both are authored by the fixture identity, to carry a real author at T014 (#186 comment 6082068576, finding 9). *Corrected 2026-10-11 (lane 3's 5481348731 on #196, item 7): the verdict came after this record's origin read. It is #186 comment 6089801221 (2026-10-09T21:47Z), on `d28e2cc`: both patches apply (to run C, and the code-leg patch also to `f87fd5a`), and the code leg's `tests.yml` keeps everything the gate reads. It is fit for evidence-3, with #180 to settle before the composed job is relied on, because `composed.yml` runs the suite through `run.sh` (its finding 13). It repeats the author point: give both patches a real author at T014.* | +| T007's assembly entry point | `004-migrate-to-triad-installer` `51993a4` | lane 1 (T007) | CHANGES REQUIRED (5471121210, T3) | +| T007/T008's code side | #191 `13cc752` | lane 1 (T007) | LAND with conditions (5471121210); next head `fdc5f18` announced. Open there: the parent-directory walk (delta 4, which `fdc5f18` answers per lane 1) and the hygiene test passing by spelling (delta 5) | +| #190's manual guard (#193 item 1) | in #190 since `b50f197` | lane 1 (T009) | resolved: landed with #190 | +| #190's other follow-ups (#193 items 2–7) | `feat/test-roots-followups` `01ccc86` (main merged in at 20:31:02Z) | lane 1 (T009) | no PR yet; one against main is now possible (#186 comment 6088703549). Its `AGENTS.md` hunk does not apply to the triad's assembly `AGENTS.md` (#186 comment 6082068576, finding 8) | +| The named-root refusal wording (#193 item 15) | — | lane 1 (T009) | open (#186 comment 6088714587, item 6) | +| T006 × #190, `test_agents_md_names_the_pin_rules` (#193 item 14) | — | lane 1 decides | open; it fails in r2's composed run (#186 comment 6087267593) | +| r2's other failures: `test_r14_the_documented_upstream_skip_is_unchanged` (`KeyError: 'reason'`), the lane suite's row-stamp cases, `test_run_wrapper`'s timeouts | — | lane 1 | being classified by lane 1 (#186 comment 6088714587, item 5) | +| #192's text findings (#193 items 8–13) | lane 2's `004-migrate-to-triad-evidence-2-followups` (#186 comment 6082466833, item 3) | lane 2, for lane 1 | on origin since 20:32:51Z at `096de93`, with main `f87fd5a` merged in; no PR (re-read 20:36:57Z) | +| Gate A capture tooling v2 | brett-wip `99eed35` | lane 1 | to be fixed and re-verified before any real capture (#186 comments 6087261307, 6088714587) | +| Run C's `follow_ups`: 29 entries (`adoption-plan.yaml:392–421`) | the next five rows | | | +| … 13 entries: a shipped command or test names `docs/`, `openspec/` or `specs/` (`:393–405`) | lane 3's `feat/triad-lane-tooling` `30f9dc0`, no PR; #190 (landed) for the suite's roots | `root-guidance-2026-10-08.md:188–194` routes the files it names: `lane-start`, `lanes-edit.sh`, `test_lane_helpers.sh` and the OpenSpec keys in `lanes-edit.sh` and `lane-handoff` to lane 3; `openRepoTools` to T007; `test_repo_hygiene.py`, `test_lane_start_claude_current.py` and `tests/run.sh` to T009/T010; `claude-current` to its text owner | that routing names neither `test_install_skill_and_hook.py` (`:398`) nor `test_park_resume_commands.py` (`:405`): no owner is recorded for those two. Corrected: the plan's unnamed ninth `docs/` file is `tests/test_repo_hygiene.py` (`git grep -l 'docs/'` at `837928a`, outside `docs/`, `specs/`, `openspec/` and the root front doors, gives exactly nine files), which `root-guidance-2026-10-08.md:191` routes to T009/T010 | +| … 5 `shape/` collisions (`:406–410`) | T006's `assembly-0001` (`root-guidance-2026-10-08.md:69–76`) | lane 1 (T006) | resolved in the patch; deleting the unpinned `shape/AGENTS.md`, `shape/CLAUDE.md` and `shape/README.md` is deferred to T014's reviewer (`:76`, `:394`) | +| … open the split as a pull request (`:411`) | — | T013 (tasks.md:96–98) | deferred | +| … the entry point (`:413`) and the test roots (`:414`) | the T007 rows above; #190 and the T010 row above | lane 1 | as above | +| … 8 more plan-level items (`:412`, `:415–421`) | `:412` was rehearsed in runs B and C; `:415`, the workflow bootstrap, in T006 | by tasks.md: `:412` and `:415` T013; `:416` T016–T018; `:417` T012; `:418` and `:420` T014; `:419` T016; `:421` T015 | deferred | +| The 33 outside consumers not covered (`consumer-reads-2026-10-07.md:303`) | — | the owners listed there | deferred | + +## 6. Pull requests to main with layout-neutral changes + +| PR | Head | Files | State at 00:5xZ | +| --- | --- | --- | --- | +| #190 `feat/test-roots-for-the-triad` | `e3a76b5` | `tests/` only (8 files) | open; `guard-launch-mode`, `tests-windows`, `parse-macos` and SonarCloud green; `tests` and `tests-no-submodule` running; no `ready` label | +| #191 `feat/installer-payload-source-resolution` | `ecd7aa4` | `openRepoTools`, two test files | open; the same green set, `tests` and `tests-no-submodule` running. At `e406e0e` (00:3xZ), `guard-launch-mode` and `tests-windows` had failed; `ecd7aa4` is the push after that | + +Both are based on `c4864ac`. Both touch `tests/test_openrepotools_command.py`, and they +stack cleanly in the order #190 then #191. Neither touches a root front-door file, so T006's +`assembly-0001` is unaffected by either. + +Lane 3's `feat/triad-lane-tooling` (`c45a452`) is on origin with no PR. + +**Refreshed 2026-10-09T20:30Z (F15)** (`gh pr view`, 20:29:45Z; check runs, 20:30:23Z): + +| PR | Head | State | +| --- | --- | --- | +| #190 | `b50f197` | **landed** as `f87fd5a` at 20:26:03Z, after #192. CI on `b50f197` was green, `tests-macos` included. | +| #191 | `13cc752` | open; no `ready` label. On `13cc752` every CI job passed except `tests-macos`, which was skipped. LAND from lane 3's delta review, with conditions (section 4). Next head announced, not pushed. | +| #192 | `71b05cb` | **landed** as `1abee1d` at 20:24:59Z. CI on `71b05cb` was green, `tests-macos` included. | + +#191 is still based on `c4864ac`. It still stacks on #190 in that order: +- lane 2 applied `dbc6837` then `13cc752` cleanly onto the patched code leg (#186 comment + 6082370012, check (v)); +- lane 2's rebuild of lane 1's r2 applied `b50f197` then `13cc752`, every `git am --3way` + step exit 0 (#186 comment 6087232548, item 3). + +Lane 3's `feat/triad-lane-tooling` is at `30f9dc0`, still with no PR. + +## 7. What Gate B still lacks + +Gate B is "complete local rehearsal evidence, restore rehearsal, reviewed follow-up patches, +resolved mapping and a passing compatibility test matrix" (plan.md). Against that: + +1. **Restore rehearsal.** Done for the source and for the installed payload, with two + findings: + - for any tree that is not clean, the October 7 capture procedure is not a restore + (section 1); + - `RESTORE.md`'s copy-back leaves `installed.tsv` stale. Rollback should be (c2): a + reinstall from the preserved source, or the copy-back followed by restoring the + receipt. Refreshed: the latter for the 29 files and the receipt only; it restores no + `settings.json` and no hook entry (section 2). + + `RESTORE.md`'s `git bundle verify` line also needs a repository to run in. + + Refreshed 2026-10-09T20:30Z (F11, F13): the hook and settings rollback is not rehearsed, + and no stage added or retired a placed file (section 2). Gate A's capture tooling v2 + (brett-wip `99eed35`, #186 comment 6073036070) answers the two findings and the + `bundle verify` line. Lane 2's final verification found that its `capture.sh` rewrites + its source's index (#186 comment 6087261307), and no capture runs on a real checkout + until a fixed version is re-verified (#186 comment 6088714587, item 4). +2. **T007's root entry point is not published.** Without it the inventory has no assembly + `openRepoTools`. PR #191, the code side, is open. + + Refreshed 2026-10-09T20:30Z (F15): it has been recorded since 01:21:15Z, on + `004-migrate-to-triad-installer` @ `51993a4`. Lane 3 requires one change before it goes + into the root-split PR: confirm the gitlink before running the pinned implementation on + the remote path (review 5471121210, T3). #191 has LAND on `13cc752` with conditions, + and a next head is announced (section 4). +3. **No composed candidate carrying every follow-up has run the suite.** That candidate is + T006 + T010 + #190 + #191 + the entry point. It was only checked to apply (section 5). + The October 8 proof P1 (285 passed, 2 failed) ran on run C's code leg with #190 alone: + - one failure is the leg's missing `.gitattributes`, which T006's `code-0001` adds; + - the other is two lane-suite assertions that depend on run-root length. + + Lane 2's run C composed-suite discovery has not reported on #186. The hosted-runner part + (Windows, macOS) is blocked until real legs exist (checklist line 35). + + Refreshed 2026-10-09T20:30Z (F15): + - Lane 2's discovery reported at 01:21:53Z (#186 comment 6072319462, with its addendum + 6072333212). + - The composed candidate is recorded on `004-migrate-to-triad-candidate` @ `935f53f`, + and lane 3 rebuilt it exactly (#186 comment 6082068576). + - Lane 1's r2 runs (code with #190 at `b50f197` and #191 at `13cc752`) finished at + 18:20:47Z. In lane 2's reading of the logs (#186 comment 6087267593): composed, 5 + failed and 1411 passed; standalone, 3 failed; without the dependency, 6 failed. + Lane 1 is classifying them (#186 comment 6088714587, item 5). + - **No suite run of the composed candidate passes yet, and none is recorded on origin.** +4. **Reviews.** Lane 3's reviews of #190 and #191 are in progress. No review is recorded for + the T006 patch set (#192) or the T010 patches (checklist line 46). + + Refreshed 2026-10-09T20:30Z (F15): + - #190: LAND twice and on its delta, and landed (section 4). + - The T006 patch set: lane 3's #192 review rebuilt all five patch commits exactly (LAND, + 5464893559), and #192 landed. + - #191: LAND on `13cc752` with conditions (5471121210). + - The T007 patch: CHANGES REQUIRED (5471121210). + - The T010 patches: no verdict yet; lane 3's reviewer has resumed (#186 comment + 6088703549). *Corrected 2026-10-11 (lane 3's 5481348731 on #196, item 7): the + verdict came after this record's origin read. It is #186 comment 6089801221 + (2026-10-09T21:47Z): fit for evidence-3, with #180 to settle before the composed + job is relied on (its finding 13).* +5. **Mapping.** Main carries run B's plan, source `e43b243`, which survives only as + `refs/pull/188/head`. Run C's plan, source `837928a`, is in #192, unlanded. + + Corrected (F9): `e43b243` survives on origin only as `refs/pull/188/head` (`git + ls-remote`, 20:29:34Z). It is also in the Gate A bundle, as the captured branch + `chore/openreposhape-pin-7f84ca4` (`inventory.json` `local_branches`), and that branch + was still in the shared checkout at 01:13:46Z (receipt `state/final/live-refs.sorted`). + Refreshed 2026-10-09T20:30Z: #192 landed, so main now carries run C's plan, source + `837928a`, in place of run B's. +6. **Gate A**, for orientation: + - the final capture at an owner-coordinated freeze, taking finding 1; + - installed-file receipts for py-bench and other profiles and workstations; + - owner dispositions. + + Refreshed (F14): T002 asks for "installed receipts/config" (tasks.md:28–29; plan.md:79). + The Gate A backup holds the receipt (`installed.tsv`, 29 rows) but no `settings.json`, + and `RESTORE.md`'s copy-back restores neither (section 2). So the backup cannot restore + the hooks and settings an install writes. +7. **T011's own acceptance (refreshed, F19).** This record checks no task complete. + - Rehearsed: the source restore from the Gate A bundle, and the rollback of the + installed payload's 29 files and receipt. + - Not rehearsed: + - a restore of the local candidate (run C's triad) from preserved artifacts; + - the post-merge rollback the plan names, "reviewed revert/pin-bump PRs and the + preserved installer" (plan.md:243); + - a hook or settings rollback (section 2); + - a before/after list of running jobs (plan.md:245). + - Published: the evidence index and the follow-up inventory, refreshed above. + +**Gate C is Brett Heap's explicit approval.** It names the source and standard revisions, +the leg repositories, their visibility and the final plan. Nothing here asks for it or +infers it. + +## 8. Log SHA256s + +The logs and snapshots are in the private receipt. + +| Log | SHA256 | +| --- | --- | +| `10-restore.log` (section 1, 90 PASS) | `bb3a5829d3a1e5ea6924331456b9f6b81b4a8380d878617344adf2807e70a848` | +| `11-dirty.log` (section 1, A 0/3, B 3/3) | `e6d1448fd18ebe2889b7c564307024ef8e2e49b48e2ef642838c8e0f89d1a9da` | +| `21-clones.log` (section 2 sources) | `d4b39b1cabeae6d2395f5db3610157689863af40bb2fc1354a2116ddaf855139` | +| `22-installs.log` (section 2, 10 installs and 2 copy-backs; corrected from "12 installs") | `4a7beffda610253de630b389f1be09c07df660df4d06a8745e10ed8d1703f2a6` | +| `23-compare.log` (section 2 comparisons) | `dca56f959f8075ac0054745965896dd43e4eb9b58cc07409b89d583e0f91e72c` | +| `24-patches.log` (section 5) | `f6a385eef8a32d683643aa7637e31cc88900af69888836c7fef7923cfac6de66` | +| `25-prs.log` (section 5, the two PRs) | `4ae121cd04df1bc87b796ec2c6969ab355273582f763375b13f9f7adb5787300` | +| `network-shim-calls.log` (empty) | `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` | +| `mutations.tsv` (section 3) | `76fc8521b1738fde5471f99e1641c6d901ebf72a65bb677d612700caf7ed77b1` | +| `refs-vs-live-now.tsv` (section 1) | `d3b5de9d85fe9ae8fe8a7a965d9015cb177c1e67d77beb30639cd51a02644249` | +| `restore-trees.tsv` (section 1, 17 trees) | `ace098036ebcec7fe47dd56a0bb7f38b97b9b10bc4753c9dd2d652349a57db98` | + +**This document's own check.** `tests/run.sh -k repo_hygiene` did not run: +- the wrapper waited 5 minutes (00:57–01:02Z) on 7 other repositories' pytest processes + (corrected: the log says "7 foreign pytest processes", receipt `logs/31-hygiene-flock.log:1`); +- its exact pytest line under `flock` on the workstation lock then queued 10 minutes behind + the T007/T008 writer's full suite and was stopped before pytest started. + +Two hygiene tests read every tracked file: the host-absolute-path test and the act-0 citation +test. Their own regexes, taken from `tests/test_repo_hygiene.py`, find nothing in this +file. CI is the suite of record. + +Refreshed (2026-10-09T20:3xZ): on this refreshed file, `git diff --check` exits 0. The same two +regexes, read from `tests/test_repo_hygiene.py` at `fa44d75` (`HOST_ABSOLUTE_PATH` and +`ACT0_CITATION`), find nothing, and neither does a stricter scan for any host-absolute path +prefix. No pytest ran: another lane's openRepoTools suite was live on the workstation. CI +is still the suite of record, and this branch has no PR. Origin was re-read before the +push, at 20:36:57Z (`git ls-remote`). Main `f87fd5a`, #191 `13cc752` and +`004-migrate-to-triad-rollback` `fa44d75` were unchanged since 20:29:34Z. +`feat/test-roots-followups` and `004-migrate-to-triad-evidence-2-followups` had moved, +and sections 4 and 5 say so. + +This record authorizes no conversion, repository creation, merge, default-branch move or +lane rebinding. diff --git a/specs/004-migrate-to-triad/gate-b-status-2026-10-09.md b/specs/004-migrate-to-triad/gate-b-status-2026-10-09.md new file mode 100644 index 0000000..778278f --- /dev/null +++ b/specs/004-migrate-to-triad/gate-b-status-2026-10-09.md @@ -0,0 +1,661 @@ +# Gate B status walk and the Gate C open questions — October 9, 2026 + +**Checked:** 2026-10-09T20:30Z–20:55Z, refreshed 21:56Z–22:15Z (UTC). The git state is a +`fetch --all --prune` at 22:07:06Z. The GitHub reads were made between 20:32Z and 20:41Z and again +between 21:58Z and 22:10Z. #186's comments were read through 22:06:49Z. Anything later is not in +this walk. +**Updated 2026-10-11T16:3xZ, after lane 3's review of #196 (5481348731 findings 12 and 14):** +lines 20, 29 and 31 move from MET to PARTLY, because each carries an open defect, which this +walk's own legend makes PARTLY (the legend now also says what does not count); and the walk now +agrees with two records #196 carries beside it: the candidate record at `16e6ea4`, which +publishes r2 and classifies every failure, and lane 2's slice B at `40f26f7`, which lane 3 +reviewed as LAND. Nothing else was re-walked. These later facts are not read into the lines: +- #191 is at `8246983`, with lane 3's LAND (5481368124), `ready` since 16:31:43Z (6111192078). + *2026-10-13:* it landed on `main` as `a74c7fc`, the squash of `8246983`. +- IN is at `67a978c`. #196 carries `3cca1e7`, whose line 13 still names a host path, which + section 2 and section 5 item 4 say must go first (5481348731 finding 13). *2026-10-13:* the + host path is gone from `7668c51` on, and #196's final refold carries the installer record + from there (the 2026-10-13 entry below). +- The test-root follow-ups are #195, open at `e378871`. Lane 3's LAND (5481360555) covers + `fb77bed`; `e378871` adds #193 item 14, decided as "the test gives". +- Lane 2's verification of T18 is posted (6103482530): the record holds, with five minor + contradictions and findings a–g the record does not report. +- T007's v3 entry-point patch makes the T3 change, and #196 carries it. Its re-review is not + recorded here. +- openRepoShape `main` is `20e13f0`, 8 ahead of `7f84ca4`. `e4decfb` (#194) makes a plan that + sends `.gitmodules` and a submodule it registers to different legs a `check` finding; this + plan sends both to code (5481348731 finding 14), so line 12 stays MET. `20e13f0` refuses a + leg mount path inside a path the root holds. Both change `adopt-project.py`, so six of the + eight commits do. Still only `22c9fdd` touches a mounted file. Question e1 names the head + current at the request. + +**Updated 2026-10-13T18:4xZ, before #196's final refold.** No line's status changes; the counts +stay MET 27 · PARTLY 18 · OPEN 0 · AFTER-GATE-C 1. +- #191 landed on `main` as `a74c7fc` on 2026-10-13, the squash of `8246983`. #195 is still + open at `e378871`. +- #196's final refold carries each record from the earliest head that holds its fixes. The exact + heads are in #196's "Input heads carried" table, which moves with each fold: + - the installer record from `7668c51` on: no host path at its line 13, and lane 3's 5481368376 + answered; from `7428e78` on, also lane 2's check at `8246983`; + - slice B from `2c040d6` on, for lane 3's 5481348731 items 7–9: item 8's leftover at + `1578f99`, item 7 at `17dd54f`, item 9 (the host behind openspec 1.13.1) at `2c040d6`. The + T011 record, `gate-ab-evidence-2026-10-09.md`, is now taken from slice B, which carries RR + `2e7e55e`'s file with those corrections; + - the candidate record from `e81100c` on: r3, #191 at its final head `8246983` with T007's v3, + the four install modules green in composed mode, and F1 gone. The full composed suite is + redone at the frozen source for Gate C. + +**Hold, 2026-10-13, on Brett Heap's word relayed by lane 3 (#186 6152410188, 18:29:47Z):** the +cutover (Gate C), any new adoption rehearsal or adoption plan at `7f84ca4`, and any bump of +`upstream/openRepoShape` wait until openRepoShape v1.0.0 is tagged. After the tag, in order: the +pin-bump PR, a fresh rehearsal and adoption plan at v1.0.0, then the cutover. #196 is not held, +because it is evidence of what held at `7f84ca4`, which is what the statuses here record. The +same comment reports that five of the eight mounted files have changed upstream since +`7f84ca4`. For that later `main`, this supersedes the 2026-10-11 count of one above. Read +question e1 with this hold. + +**By:** lane openRepoTools-1's reviewer, under [#186](https://github.com/opensoft/openRepoTools/issues/186). +It ran read-only everywhere except this file and its own scratch. A first, unpublished pass was +made at 13:27Z–13:50Z; section 1 says which statuses moved since. +**Walks:** the 46 lines of [gate-b-checklist.md](gate-b-checklist.md) (main, from #189). +**Cross-check:** lane openRepoTools-2's independent walk, which has the same path as this file, +`specs/004-migrate-to-triad/gate-b-status-2026-10-09.md`, on `004-migrate-to-triad-reads` @ +`a7a8e00` (reads to 13:48:37Z). Section 2 reconciles the two line by line, and also answers lane +2's review of this walk's first pass (#186 6088829392). Lane 2's copy stays on its branch as a +cross-check and goes into no PR. + +This walk records facts. It approves nothing and changes no status in [tasks.md](tasks.md), where +all 19 tasks stay open. Gate C stays Brett Heap's explicit approval naming the actual revisions +(plan.md:160–163). Machine paths are left out (plan.md:38–39). Private receipts are named by id +only, and their contents are not quoted. Logs held only in lane 1's private scratch are cited +through the #186 comment that reported them. + +## Refs read + +Paths are under `specs/004-migrate-to-triad/` unless they start with `openspec/`. + +| Short | Ref | Head read | +| --- | --- | --- | +| M | `main` | `f87fd5a` (#190), on `1abee1d` (#192), on `63810dd` (#189) | +| CI | `004-migrate-to-triad-ci` | `d28e2cc` | +| IN | `004-migrate-to-triad-installer` | `51993a4` | +| RB | `004-migrate-to-triad-rollback` | `fa44d75` | +| RR | `004-migrate-to-triad-rollback-refresh` (lane 2's slice A) | `2e7e55e` (`c1283b5`, `612ea94`, `2e7e55e` on RB). *2026-10-13:* #196's final refold takes this file from slice B | +| EF | `004-migrate-to-triad-evidence-2-followups` (lane 2's slice B) | `40f26f7`, as #196 carries it: `096de93` plus lane 3's findings 1–3 and one nit (the branch has since moved to `1578f99`). *2026-10-13:* the final refold carries it from `2c040d6` on | +| CA | `004-migrate-to-triad-candidate` | `16e6ea4`, r2 published and classified, as #196 carries it (on 2026-10-09 the branch was `935f53f`, the r1 build). *2026-10-13:* the final refold carries r3, from `e81100c` on | +| T18 | `004-migrate-to-triad-t018` | `63bff08` | +| L2 | `004-migrate-to-triad-reads` | `a7a8e00` | +| #191 | `feat/installer-payload-source-resolution` | `13cc752` | +| FU | `feat/test-roots-followups` (no PR) | `01ccc86` | + +Main's planning files (plan, spec, tasks, the capability spec and the change's tasks) are +unchanged since `63810dd`; `git diff --stat 63810dd f87fd5a` over them is empty. Every `plan.md`, +`spec.md` and `tasks.md` line cited below holds on `f87fd5a`. + +**Live facts at the check:** +- `gh api repos/opensoft/openRepoTools-spec` and `-code`: HTTP 404 (20:32Z, 21:58Z and 22:08Z). The `gh` + account is `brettheap`, an active admin of `opensoft`. +- openRepoShape `main` is `22c9fdd`, ahead 6 and behind 0 of the pinned and rehearsed `7f84ca4`: + `b18cca9`, `21f5c33`, `f25d805`, `bc09bc5`, `4ce9cb0`, `22c9fdd` (question e1). +- `git ls-remote origin 'refs/heads/adopt/*'`: nothing. +- #191 is the only open PR, at `13cc752`. Lane 3's delta review 5471121210 is LAND, with + conditions. Lane 1's next head was announced (#186 6088714587) and was not on origin at + 22:07Z. Lane 2's T008 matrix at `13cc752` is final (#186 6088812080). +- Lane 2's slices are on origin, both meant for the evidence-3 PR. A is RR: lane 3 reviewed + `c1283b5` as LAND within evidence-3 (#186 6089982753), and `612ea94` and `2e7e55e` take in its + notes N1–N9 (6090048356). B is EF: lane 3 reviewed `096de93` as LAND within evidence-3 + (6092925484), and `40f26f7` takes in its findings 1–3 and one nit (6103529172). +- Lane 3's review of FU and the T010 patches is posted (#186 6089801221, 21:47Z). +- The r2 composed candidate's three suites finished at 18:20:47Z. Lane 2's reading of the logs is + #186 6087267593; this walk checked the summary lines against the logs. CA at `16e6ea4` + publishes them and classifies every failure, F1–F6 (CA §5). +- In progress, so not counted as evidence: the Gate A capture-tool fix (#186 6087232548, + 6087261307; nothing new in brett-wip since `99eed35`, per 6089772323 and 6089982753); lane 2's + verification of T18 (6089772323). +- Not counted, because its head is not on origin: lane 2's pre-push T008 matrix on lane 1's + unpushed #191 head and v3 entry-point patch (#186 6090038382). It reports the ancestor-walk + cases, (c) and (e) fixed, v3 refusing a gitlink that disagrees with the pin, and nine items + still open. It is a preview of the next head's check. + +## Status legend + +- **MET**: the evidence named in the checklist's "Evidence that would satisfy it" column exists + on a pushed ref and, where the line asks for a review, the review is recorded. +- **PARTLY**: evidence exists but is incomplete, unreviewed, measured on a head that has moved, + or carries an open defect or an open decision. +- **OPEN**: no finished evidence. +- **AFTER-GATE-C**: cannot be met before real legs exist. + +An open #193 item that is a defect in the line's own evidence makes the line PARTLY. A later +step listed under "What remains", such as a rerun at the frozen source or a recheck at the +request, does not (added 2026-10-11). + +Publishing the T006–T011 records on `main` is not itself a checklist line. It is listed among the +blockers in section 5. + +## 1. The 46 checklist lines, at 2026-10-09T22:1xZ + +| # | Line (short) | Status | Evidence | What remains | Owner | +| --- | --- | --- | --- | --- | --- | +| 1 | Leg names and visibility confirmed (T003, FR-012) | MET | #186 6035767825 ("legs and visibility confirmed"); M:`openspec/changes/migrate-to-triad/adoption-plan.yaml` (legs; `visibility: public`) | Recheck at the Gate C request. 404 is not proof the names are free (preparation.md:53–56) | Brett (given); lane 1 (recheck) | +| 2 | `git-filter-repo` via the approved prerequisite path | PARTLY | M:adopter-rehearsal-2026-10-08.md#Setup; M:adopter-rehearsal-2026-10-07.md#Setup; #186 6072724043 (i) | No record names who approves the private-venv path for the real `execute` (plan.md:105–106, :160–162): question e4. Neither receipt carries the October 4 receipt's "existing bench/system installations were unchanged" (preparation.md:62–63), which the evidence column asks for (lane 2, 6088829392 item 10). The "no ensurepip" cause is withdrawn on EF; the refusal's cause is not established (#193 item 12) | Brett; lane 1 | +| 3 | Plan regenerated for the rehearsal source, overrides kept | MET | M:`openspec/changes/migrate-to-triad/adoption-plan.yaml` (source `837928a`, 22 resolutions); M:adopter-rehearsal-2026-10-08.md#Mapping (T003); #192 review 5464893559; #186 6072724043 (iv) | The final plan is regenerated at the frozen source before the request (question e2). `check` reads the clone's local `main`, so the committed plan is `plan-stale` against any later `main` (#193 item 10; EF:adopter-rehearsal-2026-10-08.md:113–125) | lane 1 | +| 4 | Standard `check` passes, every path, no unapproved drop | MET | M:adopter-rehearsal-2026-10-08.md#Run C (`plan ok`; 120 = 65/49/6/0); #186 6072724043 (ii) | Rerun at the frozen source | lane 1 | +| 5 | Amendment folders to code; full proposals and specs to spec | MET | M plan: `docs/`, `ideation/`, `openspec/`, `specs/` to spec; 0 `features/` paths on `main` | Re-observe at the frozen source | lane 1 | +| 6 | No amendment paths recorded where none exist | MET | M:inventory.json `refreshed.amendment_path_observations` (every head empty) | Re-observe at the Gate A final capture | lane 1 | +| 7 | Clean isolated source clone | MET | M:adopter-rehearsal-2026-10-08.md#Setup; receipt `adopter-fix-20261008-1HztL0`, verified in #186 6072724043 | — | — | +| 8 | Rehearsal into temporary local bare remotes | MET | M run C (`execute` 0, file transport only, 0 shim calls) | — | — | +| 9 | Identity, history and path/blob accounting | MET | M:adopter-rehearsal-2026-10-08.md#Run C; #186 6072724043 (iii); #192 review finding 4 | — | — | +| 10 | Recoverability; source unchanged | MET | M run C; M:adopter-rehearsal-2026-10-07.md#Run A; RB:gate-ab-evidence-2026-10-09.md#1; no `adopt/` ref on origin | — | — | +| 11 | Standard's refusal recorded | MET | M:adopter-blocker.md | — | — | +| 12 | Upstream resolution with regressions | MET | M:adopter-rehearsal.md#Regression evidence (draft `91d5685`: each case adopter-blocker.md:61–65 names); openRepoShape #162's CI at its head `6fa4e07`: `tests`, `tests-windows` and `tests-macos` SUCCESS (2026-10-07T00:57Z–01:06Z); its merge `7f84ca4`: `tests` (`python3 -m pytest tests -q`, `tests/test_adopt_submodules.py` included) SUCCESS at 01:13:26Z | The regression "does not qualify every mixed-registration plan" (adopter-rehearsal.md:30–32); this repository's plan is not one (line 14). openRepoShape `main` has moved 6 commits past `7f84ca4`: question e1. Lane 2 holds PARTLY (section 2) | Brett; openRepoShape owner | +| 13 | Tested consumer pin bump on `main` | MET | M@`837928a` (#188); #186 6068861167 | — | — | +| 14 | Nested submodule; original `.gitmodules` | MET | M runs B and C (code keeps blob `8043147a`; the assembly gets its own) | — | — | +| 15 | Pin integrity, coupled | MET | M run C (`3be52767…` MATCH); #186 6072724043 (v) | — | — | +| 16 | Recursive bootstrap | MET | M run C (`bootstrap ok`, three validators) | — | — | +| 17 | Root collisions and leg guidance as reviewed patches | MET | M:root-guidance-2026-10-08.md#1, #4; M:patches/t006/*; #192 review 5464893559 finding 7 (exact rebuild) | #193 items 8–13, the text fixes: on EF, LAND by lane 3 (6092925484), carried by #196 at `40f26f7`; item 9's count stays open (line 20). The `shape/*.md` deletion is T014's reviewer's | lane 1 (EF by lane 2) | +| 18 | Shape-aware root bootstrap after the legs exist | PARTLY | M:root-guidance#7; T18:t018-disposable-2026-10-09.md#1 (a second run: `setup-openspeckit --shape on` exit 0, `worktree_root: worktrees`) | Neither run is reviewed, and neither has a receipt: EF now says so plainly (#193 item 11; EF:root-guidance-2026-10-08.md:31–56). Lane 2's verification of T18 has since been posted (6103482530; the dated note). T18 finds the bootstrap leaves the root "dirty" to `resume` (T18 §5 item 1): question e7 | lane 1; Brett | +| 19 | First-line guidance, relative paths, actual `.specify`, paired selection, no secrets | PARTLY | M:root-guidance#2, #3 (both verified by #192 review finding 7), #7, #8; T18 §2 (paired selection from the root, either leg and either worktree; `check-prerequisites.sh` agrees ×5) | `.specify` was read from today's checkout, not from Gate A's captured configuration (lane 2, L2 line 19). The `.specify` posture is undecided (question e7). `SPECIFY_FEATURE` alone gives a branch and directory that disagree (T18 §5 item 8). T18 is unreviewed | lane 1; Brett | +| 20 | Provenance correspondence; link repair without guessed hashes | PARTLY | M:root-guidance#6 (35/35; 47 citations resolve in the assembly), #3; #192 review finding 10 | The record's own count is open: "23 mentions in 9 files" sums to 26 in 11, and no recount settles it. EF says so and leaves it "not established" (EF:root-guidance-2026-10-08.md:210–211), and lane 3 rates that part of #193 item 9 PARTLY (6092925484). T017 recounts, with feature 004's own pre-split paths | lane 1 | +| 21 | Explicit roots, local-root precedence, supported store selection | PARTLY | M:root-guidance#7 (store precedence; strict validate in the spec leg); T18 §3 (OpenSpec from each root with 1.2.0 and 1.6.0) | Store precedence rests on the document alone (#193 item 11). T18 shows the root and the code leg pass `validate --all --strict` vacuously, so validation must run in the spec leg (T18 §5 item 7). `--store` needs openspec 1.6.0 or later (question a) | lane 1; workBenches owner | +| 22 | Protocol/command distribution per target workstation through workBenches | PARTLY (no owner) | M:root-guidance#9: Eagle's two installed protocol files carry "Triad Feature Amendments" (sha256 `f9dd6464…`, `bbbc2b2c…`) | Not via workBenches delivery; command and skill digests absent; no other workstation; no target list; no writer holds it (lane 2 read only vendoring, consumer-reads-2026-10-07.md:85–105). Section 2 records lane 2's NOBODY | Brett (names the workstations); workBenches owner; a holder to be named by lane 1 | +| 23 | Installer design | MET | M:installer-design.md | — | — | +| 24 | Installer inventory refreshed | MET | M:consumer-reads-2026-10-07.md#The installer's own assumptions; IN:installer-2026-10-08.md#Lane 2's installer rows (17/23/29/31) | installer-design.md:61–64 still carries `daed209`'s counts | lane 1 | +| 25 | Entry point and payload-source resolution as reviewed patches | PARTLY | IN:patches/t007-assembly-entry-point.patch; #191 @ `13cc752`: LAND with conditions (review 5471121210; delta 8 is the stale PR body, plus `tests-macos` under `ready`) | The next #191 head needs its own check; it was not on origin at 22:07Z. The entry-point patch is CHANGES REQUIRED (5471121210 T3: on the remote path, confirm the gitlink before running the pinned implementation). Not landed | lane 1; lane 3 | +| 26 | One-liners resolve one code pin; no developer checkout | PARTLY | 5471121210 T1–T2 (a disposable assembly at `13cc752`: one code commit, the installed tree byte-identical to a checkout install, local delegation with no fallback); #186 6088812080 (final matrix at `13cc752`) | Rerun at the next head with the T3 fix. On the single layout a ref that moves mid-install mixes versions, as on `main` (6088812080 item 7); the PR body should scope "resolve once" to the adopted layout (5471121210 delta 8). Real one-liners are T015's | lane 2; lane 1 | +| 27 | 15-row compatibility matrix measured | PARTLY | #186 6073209094 and 6073234714 (15/15 at `2411f40`); #186 6088812080 (final at `13cc752`, 337 row logs; F1, F2, (b) and (d) fixed) | Rows 18–22 bear on the next head (6088812080 items 1–6): the layout search walks every ancestor, and a HOME holding `contracts/code-pin.yaml` refuses to reinstall; a missing ref or repository is read as a single repository and never named; a 502 on a probe refuses where `main` installs; the 404 path; garbage at 200 is reported as a moved ref; (c) and (e) remain. Rerun at the next head | lane 2; lane 1 | +| 28 | Names, counts, modes, hooks, receipts, all-or-none unchanged | PARTLY | 5471121210 delta 2 (offline install byte-identical at `13cc752`); #186 6088812080 (210 of 210 refusals placed nothing; 82 of 83 installs 29/29 byte-exact, the exception row 7's deliberate mixed case; 40 of 40 receipts consistent; both hook entries present); #186 6087267593 and CA §4 (the r2 install from the assembly root: the same 29 files as main's, byte-identical except the implementation, and a byte-identical `settings.json`) | Rerun at the next head. An adopted `wip init` that refuses still leaves an empty server repository behind (#186 6087232548, its F2 row) | lane 2; lane 1 | +| 29 | Explicit roots in fixtures and hygiene checks | PARTLY | M@`f87fd5a` (#190 at `b50f197`): lane 3's LAND 5464931249 and 5464938427, and its verdict on `b50f197`, 6089801221 (a) (lane 1's own delta review is 5471080687); CI green including `tests-macos` | #193 items 2–7 and 15 are open defects of these fixtures on `main`. Item 5, the paired layout, is a Gate C item lane 3 wants settled before Gate C (section 5, item 2). The command test still reads the README before its `--help` half (6089801221 finding 9). The follow-ups were FU @ `01ccc86` at the read; see the dated note for #195 | lane 1; lane 3 | +| 30 | `tests/run.sh -k upstream_pin` inside the rehearsed code leg | MET | #186 6072319462 (all 14 `test_upstream_pin.py` pass in code leg `73b04d6`); CI:test-roots-and-ci-2026-10-08.md#1 | The runs used `tests/run.sh`'s own pytest line under the same lock, not its census (#184) | lane 1 | +| 31 | Composed checks refuse instead of skipping | PARTLY | M@`f87fd5a` (#190); #186 6087267593 (five refusal cases exit 4 on an r2-equivalent); 6089801221 finding 15 | The checklist's evidence column is met, but this refusal carries two open defects. #193 item 15: it misnames a root that was named (6089801221 finding 14). Item 4: conftest never reads a pin's `commit:`, closed in CI only by `composed.yml`'s step order (finding 13). Lane 2 confirms both fixed on #195 (6103529172), which is open. About forty other conditional skips can still pass a composed run; none fires on ubuntu-latest today (finding 13 (ii)) | lane 1 | +| 32 | Code-leg CI and assembly exact-pin job patches | PARTLY | CI:patches/t010-*.patch; CI:test-roots-and-ci#5–#6; lane 3's review 6089801221 (c): fit for evidence-3, both patches apply to run C and the code-leg patch to `f87fd5a` | `composed.yml` runs the suite through `run.sh`, whose long run root makes #180's two row-stamp assertions fail, so `exact-pins` would be red until #180 is fixed or the job uses a short `TMPDIR` (finding 13 (i), inferred). Both patches need a real author at T014 (finding 13 (iv)). `actionlint` last ran 2026-10-07. SonarCloud and required checks: question e9 | lane 1; lane 3; Brett | +| 33 | Platform policies, Bash 3.2 and serialized locking kept | MET | CI:test-roots-and-ci#5; lane 3's review 6089801221 finding 12: all six jobs keep their names and order, `tests-macos` keeps its `ready` gate verbatim, the triggers, per-job `submodules:`, `parse-macos`'s bash 3.2 file list and the Windows env are unchanged, WSL stays a documentation policy, and locking is as today | The hosted run is line 35. The assembly has no `tests-macos` check, which T015's landing tooling must allow for (finding 12) | lane 1 | +| 34 | Exact composed state passes required checks and the matrix (local) | PARTLY | CA `16e6ea4` §§2, 5: r2 = code `4dbcf6c` (#190 `b50f197`, #191 `13cc752`, T006, T010), spec `9e1d006`, assembly `5d92962`; composed 1411 passed / 5 failed / 0 skipped; standalone 1389 / 3 / 24; no submodule 1168 / 6 / 242; every failure classified; private receipt `candidate-20261009-JR8YyA`. Lane 2's counts agree (6087267593) | It does not pass yet (CA §6). Two failures are candidate defects: F1, r14's `KeyError: 'reason'`, first seen in r1, fixed in #191's `6fa3b43` and not re-run; F2, `test_agents_md_names_the_pin_rules`, #193 item 14, pending in CA and since implemented on #195 as "the test gives". F3 and F4 are #180's row stamps; F5 is the harness's inherited ignored SIGINT, not load; both fail the same on main. F6 is timing under load, pre-existing. Owed: a composed rerun at #191's final head with T007's v3, F1 gone and F2 decided | lane 1 | +| 35 | Hosted-runner part of T010 | AFTER-GATE-C | CI:test-roots-and-ci#5 ("Neither patch can run on GitHub until the legs exist"), #8 | Question d | Brett | +| 36 | Restore/rollback of the candidate and installed payload | PARTLY | RB:gate-ab-evidence-2026-10-09.md#1–#2; lane 2's independent check (#186 6082370012: restore 91 PASS, four homes reproduce); CA §4 (r2's install; a reinstall from main restores main); RR (the record now says the hook rollback is vacuous and the backup holds no `settings.json`; lane 3 LAND at `c1283b5`, 6089982753) | No hook-changing rollback, no restore of the local candidate from preserved artifacts and no post-merge revert/pin-bump rollback is rehearsed (lane 3 6082139424 findings 11 and 19; 6082370012 (iv)). The capture tooling v2 cannot stand as evidence until A1 is fixed (6087261307). RR is not on `main` | lane 1 | +| 37 | Work, jobs and claims survive; no force push, reset or pointer rewrite | PARTLY | RB:gate-ab-evidence#3; #186 6082370012 (verified; 0 non-fast-forward against GitHub); RR §3 | Jobs have no before/after record, only "no script … kills a process" (6082139424 finding 19). No force push is argued for a real rollback, not shown | lane 1 | +| 38 | Publish the Gate A evidence | MET | M:preservation-2026-10-07.md, M:inventory.json (#189); receipt `prep-20261007T100559Z` | Gate A itself stays open (section 5) | lane 1; Brett | +| 39 | Publish the Gate B rehearsal evidence (a receipt in adopter-rehearsal form) | MET | M:adopter-rehearsal-2026-10-07.md (runs A and B, #189) and M:adopter-rehearsal-2026-10-08.md (run C, #192); receipts `adopter-fix-20261007-iM20tv`, `adopter-fix-20261008-1HztL0`, verified in #186 6036838832 and 6072724043 | The other Gate B records are not on `main` (section 5, item 4) | lane 1 | +| 40 | Follow-up patch/commit inventory | PARTLY | RB:gate-ab-evidence#5 (7 patches); RR §5 (maps each follow-up to a patch, PR, owner or "deferred", including run C's 29 `follow_ups`; lane 3: finding 18 RESOLVED at `c1283b5` except N3, which `612ea94` takes in, 6089982753 and 6090048356) | Lane 3's finding 18 (6082139424) lists what the inventory lacked. Since 01:13Z: T007's entry point; #190's follow-ups (`b50f197`, the `ln_doc` abort fix, FU); #191's heads `2411f40` and `13cc752`; capture tooling v2. Known at 01:13Z: run C's 29 named `follow_ups`; the routing in root-guidance-2026-10-08.md:186–194; lane 3's `feat/triad-lane-tooling`; the 33 outside consumers not covered; the `shape/` deletion left to T014. The stack check used superseded heads. RR answers all of it, and states that T010's two patches have no recorded resulting tree (finding 20). It is not on `main`, and the heads it lists are not final: T007's entry point (T3), #191's next head and FU's edit, so it is refreshed once more at the final heads | lane 1 | +| 41 | Repeated into fresh remotes after the upstream fix | MET | M runs B and C at `7f84ca4` on openRepoShape `main` | A repin needs a new run (question e1) | — | +| 42 | Never the live consumer as `--source` | MET | M run C setup; no `adopt/` ref on origin | — | — | +| 43 | No in-place submodule edit; digests not adjusted | MET | #186 6035767825 and 6036838832 (v); M:root-guidance#1 (11/11 shape digests) | — | — | +| 44 | No real repository created for a test | MET | Both legs 404 at 20:32Z and 21:58Z; RB:gate-ab-evidence#3; T18 §1 (network namespace, 0 successful network calls) | Recheck at Gate C | lane 1 | +| 45 | Reviewed refreshed inventory (T001) with lines 2–6 | MET | M:inventory.json (stamp 2026-10-07T10:09:03Z; #189 LAND) | Refresh at the Gate A final capture | lane 1 | +| 46 | A review record for each T006–T010 patch | PARTLY | T006: 5464893559 LAND (landed). T009: #190 LAND ×2 and delta LAND (landed). T007/T008 code side: #191 `13cc752` LAND with conditions (5471121210). T010: lane 3 6089801221 (c), fit for evidence-3 | The T007 entry-point patch is CHANGES REQUIRED (5471121210 T3). #191's next head: unchecked. T010's `composed.yml` carries finding 13 (line 32) | lane 3; lane 1 | + +**Counts (46):** MET 27 · PARTLY 18 · OPEN 0 · AFTER-GATE-C 1 (on 2026-10-09: 30 · 15 · 0 · 1). +- **PARTLY:** 2, 18, 19, 20, 21, 22, 25, 26, 27, 28, 29, 31, 32, 34, 36, 37, 40, 46. +- **AFTER-GATE-C:** 35. + +**Moved since lane 1's first pass (13:27Z–13:50Z, unpublished; then MET 29 · PARTLY 15 · OPEN 1 · +AFTER-GATE-C 1):** +- **29: PARTLY → MET.** #190 landed at `b50f197`. +- **33: PARTLY → MET.** Lane 3's review of the T010 patches (21:47Z) confirms every job, gate and + policy the line names. +- **34: OPEN → PARTLY.** The r2 suites finished, with failures. +- **36 and 37: MET → PARTLY.** Lane 3's T011 review (13:43Z) shows the hook rollback, the + candidate restore, the post-merge rollback and jobs are not rehearsed. +- **39: PARTLY → MET.** Run C landed with #192. This walk now reads line 39 by the checklist's own + evidence column; the other records' publication moves to section 5. + +**Moved on 2026-10-11** (lane 3's 5481348731 finding 12, by this walk's own legend): +- **29: MET → PARTLY.** #193 items 2–7 and 15 are open defects of the fixtures it names. +- **31: MET → PARTLY.** #193 items 4 and 15 are open defects of the refusal it names. +- **20: MET → PARTLY.** The record's "23 in 9" count is open. + +## 2. Reconciliation with lane 2's walk at `a7a8e00` + +Lane 2's walk is `004-migrate-to-triad-reads` @ `a7a8e00`: +`specs/004-migrate-to-triad/gate-b-status-2026-10-09.md`, the same path as this file. Lane 2 +keeps it on that branch, unrevised, as a cross-check; it goes into no PR (lane 2, #186 +6088829392). Lane 2 read to 13:48:37Z; this walk reads to 22:10Z. + +Lane 2's legend is the checklist's own: EXISTS, IN PROGRESS, NOBODY, BLOCKED, DECIDED. It +describes who holds a line. This walk's legend describes the evidence. The two map as follows: +- DECIDED ≈ MET; +- BLOCKED ≈ AFTER-GATE-C; +- IN PROGRESS ≈ PARTLY; +- EXISTS is MET when nothing remains, and PARTLY when a review, a receipt or a decision does. + +**Lines that differ in substance, with this walk's verdict:** + +| # | Lane 2 | This walk | Verdict | +| --- | --- | --- | --- | +| 22 | NOBODY | PARTLY (no owner) | **Both are right about different things, and they agree on the gap.** Lane 2 records that no writer holds it. This walk records that a start exists: Eagle's installed protocol-file digests (root-guidance §9). But that start is not "through owning workBenches delivery" and has no command or skill digests, so even Eagle is partial. Kept as PARTLY, flagged as having **no owner**; no lane-2 writer holds it either (6088829392 item 6). It cannot finish until Brett names the target workstations (question e8) and the workBenches owner delivers. Lane 1 should name a holder. | +| 2 | EXISTS | PARTLY | Lane 2 itself notes that no source names who approved the private-venv path, and later adds that neither receipt says bench and system installs were left unchanged (6088829392 item 10). plan.md:105–106 asks for "the approved prerequisite path", and Gate C asks that "prerequisites … are current". Kept as PARTLY until Brett answers question e4. | +| 12 | EXISTS, candidate only; PARTLY in 6088829392 item 2 | MET | Lane 2 found no regression record at the merged head and asks this walk to cite #162's CI at `6fa4e07` or say there is none. This walk cites it: at #162's head `6fa4e07`, `tests`, `tests-windows` and `tests-macos` succeeded, and at its merge `7f84ca4` the `tests` job, `python3 -m pytest tests -q` with `tests/test_adopt_submodules.py` included, succeeded at 2026-10-07T01:13:26Z (Windows and macOS are skipped on that push). Each case adopter-blocker.md:61–65 names is covered by adopter-rehearsal.md:23–36, and those tests pass at the merged head. The stated limit, "does not qualify every mixed-registration plan", does not reach this repository, whose one registration moves whole to code (line 14). MET. | +| 18 | EXISTS (document only) | PARTLY | A row that exists only in the writer's document, with no receipt and no independent rerun, is evidence not yet checked (#193 item 11). T18 adds a second run by lane 1 itself, also unreviewed, and a new finding: the bootstrapped root is "dirty" to `resume`. PARTLY. | +| 19 | EXISTS | PARTLY | Lane 2's own note that `.specify` was read from today's checkout and not from Gate A's capture is a gap against the checklist's evidence column. The posture decision is open (e7), and T18 adds the `SPECIFY_FEATURE` disagreement. PARTLY. | +| 21 | EXISTS (document only) | PARTLY | As for 18. T18 also shows that validation run from the root passes vacuously. PARTLY. | +| 20, 29, 31 | EXISTS (unlanded) | PARTLY (2026-10-11) | All three landed (#192, #190), and each still carries an open defect in its own evidence: 20 the "23 in 9" count, 29 #193 items 2–7 and 15, 31 items 4 and 15. Lane 3's 5481348731 finding 12. PARTLY. | + +**Lines that agree in substance:** +- **Same status, different legend:** 1, 4–11, 13–16, 23, 24, 30, 35, 38, 41–45 (MET or + AFTER-GATE-C here; DECIDED, EXISTS or BLOCKED there). +- **Unlanded at lane 2's cutoff, landed since:** 3 and 17 (lane 2's "EXISTS (unlanded)" + are MET now). 20, 29 and 31 also landed, and are in the table above since 2026-10-11. +- **Line 33:** EXISTS there, MET here since lane 3's review of the T010 patches (6089801221 + finding 12). This walk's earlier PARTLY waited only on that review. +- **IN PROGRESS there, PARTLY here:** 25–28, 32, 34, 36, 37, 40, 46. + - The newer facts are 5471121210 (#191 LAND with conditions; T007 patch CHANGES REQUIRED), + 6088812080 (the final matrix at `13cc752`), 6087267593 (r2's three suites), 6089801221 + (lane 3's T010 review), 6089982753 (lane 3's LAND on RR), and RR and EF (lane 2's slices). + - Lane 2 also holds 36 and 37 open on its T011 verification. That verification has since + passed (6082370012), and lines 36 and 37 stay PARTLY on lane 3's review findings instead. + - Lane 2's `a7a8e00` still names holders for lines 32, 36, 37 and 40 that have since finished + (6088829392). +- **Line 39:** EXISTS there, MET here. Both read the run C receipt, which is now on `main`. + +**Lane 2's "Facts noticed" — this walk's verdicts:** +- **Item 16 (a host-absolute path in IN:installer-2026-10-08.md:13): agree.** It must be removed + before IN goes into the evidence-3 PR; plan.md:38–39. *2026-10-13:* it is removed from + `7668c51` on, and #196's final refold carries the record from there. +- **Items 1 and 8 (the candidate receipt is absent from brett-wip; the record cites #191 at + `2411f40`): agree, and both are answered.** CA at `16e6ea4` names the receipt + `candidate-20261009-JR8YyA` and records r2 against #191 at `13cc752` (line 34). +- **Item 13 (two openspec results): agree.** It is question a. T18 §3 now measures 1.2.0 and + 1.6.0 side by side. +- **Item 14 (two reviews of lane 1's PRs are headed "lane openRepoTools-1"): agree; a note, not a + status change.** The reviews of record for #190 and #191 are lane 3's; lane 1's are same-lane + second opinions. +- **Items 2–7, 9–12, 15 and 17: agree.** They are overtaken by the landings, or are text fixes + carried by #193. + +**Lane 2's review of this walk's first pass (#186 6088829392), item by item:** +1. Lines 36 and 37 to PARTLY: **adopted.** +2. Line 12 to PARTLY: **not adopted.** The merged-head CI record it asks for is cited (the table + above); MET stands. +3. Line 34 to PARTLY with the r2 numbers: **adopted**, with the note that r2 rests on `13cc752`. +4. Lines 25 and 46, 5471121210's CHANGES REQUIRED (T3) and #191's LAND with conditions: + **adopted.** +5. Question e1 is stale: **adopted.** `main` is `22c9fdd`, and its `scripts/repo_shape.py` change + is the only one of the six commits that touches a mounted file (question e1). +6. Line 22, drop "lane 2" from the owner: **adopted.** +7. Re-cites: **adopted.** Run C's records are cited on `main`; "Land #192" and "fold `b50f197`" + are gone; line 39 is MET. +8. Question a, record `command -v` and `--version` for each CLI: **adopted** (question a). +9. Questions e6, c and b: **adopted** (section 4). +10. Lines 40 and 2: **adopted** (section 1). + +Lane 2's corrections to its own records stand as it gives them: r14's `KeyError 'reason'` was +first seen in lane 1's r1, and #193 item 15 was found on lane 2's own r2-equivalent build, not in +lane 1's r2 logs (#193 now says so). + +## 3. T001–T019 against the evidence + +Every task stays open on `main`. + +| Task | What the evidence already covers | What remains before Gate C | After Gate C | +| --- | --- | --- | --- | +| T001 inventory | M:inventory.json, M:preservation-2026-10-07.md (17 trees, 33 refs, the estate-status refusal relayed, amendment observations) | Owner dispositions for every retained object, approved by Brett (question e5); a refreshed inventory at the final capture | — | +| T002 preservation | Gate A bundle; restore 97 PASS (October 7), 90 PASS (RB), 91 PASS (lane 2's independent run) | Capture tooling v3: lane 2 found that v2 rewrites the source's index (A1) and can write under `/` (A2) (#186 6087232548, 6087261307). No fix was posted by 21:44Z, and no capture runs on a real checkout until lane 2 re-verifies one. Then the owner-coordinated freeze, the final capture, and installed-file receipts for py-bench and the other workstations | — | +| T003 mapping | Names and visibility confirmed; filter-repo; run C's plan (`plan ok`, 0 drops) on `main` | Approve the prerequisite path (e4); regenerate and `check` at the frozen source; the final mapping goes into the approval (e2) | — | +| T004 rehearsal | Run C on `main` | A rerun at the frozen source if question e3 requires one | The real `execute` is T013 | +| T005 nested pin | Refusal, upstream fix and repin; the nested pin and bootstrap in runs B and C; upstream CI at `6fa4e07` and `7f84ca4` | Question e1 (stay on `7f84ca4`, or repin and rehearse again) | — | +| T006 root/leg guidance | Collisions, 32 repairs, leg front doors, provenance 35/35 (on `main`); bootstrap, paired selection and OpenSpec roots measured twice (root-guidance §7; T18 §§1–3) | #193 items 8–13 (EF: LAND, carried by #196 at `40f26f7`; item 9's count open); lane 2's verification of T18 (posted, 6103482530), then a review; a receipt, or the sentence EF adds saying there is none; questions a, c, e7, e8 | T014 applies `assembly-0001`, `code-0001` and `spec-0001`, regenerates the two bumps with `bump-leg.py`, and settles `shape/*.md`. T18's follow-ups go to workBenches and lane 3 | +| T007 entry point | The entry-point patch (IN); #191's code side, LAND with conditions at `13cc752`; T1–T2 of 5471121210 | #191's next head: proof, lane 3's check, the PR body, `tests-macos`, landing, against the matrix findings of 6088812080. The entry-point patch's T3 change, then its re-review | T014: the entry point ships in the root-split PR itself (lane 3, #186 6072087071); T015: the real one-liners | +| T008 matrix | 15 rows at `2411f40`; the final matrix at `13cc752` (337 row logs) | A rerun at #191's next head (one command, lane 2) | T015 against the real GitHub; workBenches re-vendors (W1/W3/W8) once the legs exist | +| T009 test roots | #190 landed with `b50f197`; its CI green, macOS included | Land the follow-ups (FU @ `01ccc86` at the read; #195 since): #193 items 2–7, 14 and 15, with item 5 (paired layout) before Gate C | Verify at the real head (T015) | +| T010 CI | Two workflow patches; local proofs P1–P4; lane 3's review, fit for evidence-3 | `composed.yml` against #180 (finding 13 (i)); a composed run of the final candidate that passes (line 34); question d | Hosted runs at the real legs (T015); a real author on both patches (T014); SonarCloud binding and required checks (e9) | +| T011 restore/rollback | Source restore and four rollback homes, independently verified; RR refreshes the record, LAND by lane 3 within evidence-3 | A hook-changing rollback, the candidate restore and a post-merge pin-bump rollback, or a dated statement of what cannot be rehearsed; jobs before/after | Rollback checks at the real head (T015) | +| T012 freeze and approval | #97 and #121 landed and are in the baseline | Freeze; regenerate and `check`; recheck the names; present the plan, follow-ups and dispositions; Brett's approval | Refuse source drift before `execute` | +| T013–T015 | Prepared patches re-apply (RB §5; the candidate's §2) | — | All of them; T015 carries the hosted CI that question d moves there | +| T016–T019 | T18 is the disposable half of T018 (unreviewed, on its branch; lane 2 is verifying it) | — | All of them. T016–T018 are prepared in the candidate once the legs exist; the real half of T018 and T019 follow cutover | + +## 4. Gate C open questions + +Each question ends in text for Brett Heap to approve, correct or reject. None of it is approved +by being written here. + +### (a) `openspec validate migrate-to-triad --strict` fails on `main` with the CLI first on `PATH` + +**Run** in a scratch single-branch clone of `main` at `f87fd5a` (22:00Z; the first run, at +`63810dd`, gave the same results). The change's files are identical at both heads. On lane 1's +workstation, `command -v openspec` is `~/.npm-global/bin/openspec`, and `--version` prints +`1.2.0`. The system openspec, second on `PATH`, prints `1.6.0`. + +```text +$ ~/.npm-global/bin/openspec validate migrate-to-triad --strict # 1.2.0, first on PATH +Change 'migrate-to-triad' has issues +✗ [ERROR] repository-triad-migration/spec.md: ADDED "Installation retains the public interface and one revision" must contain SHALL or MUST +exit=1 +$ validate migrate-to-triad --strict # 1.6.0 +Change 'migrate-to-triad' is valid +exit=0 +``` + +On lane 2's host the system openspec is 1.13.1, and it passes `migrate-to-triad` and 5 of 5 +changes on `main` (6088829392 item 8). EF records 1.13.1 on the bench it measured +(EF:adopter-rehearsal-2026-10-08.md:53). On lane 3's host on 2026-10-11 the order was 1.2.0, +then 1.6.0, as here (5481348731 finding 9). Which CLI runs depends on the container. + +**Cause.** 1.2.0's `extractRequirementText` (`dist/core/validation/validator.js:369-390` in its +package) takes only the first non-blank body line of a requirement. Line 45 of +`openspec/changes/migrate-to-triad/specs/repository-triad-migration/spec.md` reads `Existing +one-line installation, command names, payload destinations/modes,`, and the requirement's SHALL +is on line 46. 1.6.0 reads the whole body. Every other requirement in the file puts SHALL on its +first body line. The same 1.2.0 rule fails `interactive-lane-name-repair` (two requirements), +which is not this migration's change. T18 §3 and §5 item 7 add that from the root or the code +leg, `validate --all --strict` finds no change and passes vacuously, so validation belongs in the +spec leg. verification.md:28, :66, :96, :126 and :144 record strict validation as passed without +naming a CLI version. + +**Proposed minimal change.** Tested in scratch copies: 1.2.0 and 1.6.0 both exit 0. No +requirement is added, removed or renamed, and no scenario changes. + +```diff + ### Requirement: Installation retains the public interface and one revision + +-Existing one-line installation, command names, payload destinations/modes, +-skills/hooks/receipts and supported fork/ref/local invocation SHALL remain +-usable. An adopted assembly entry point SHALL resolve one immutable code +-revision for the complete installation. Missing/inconsistent pin evidence ++The migration SHALL keep the existing one-line installation, command names, ++payload destinations/modes, skills/hooks/receipts and supported fork/ref/local ++invocation usable. An adopted assembly entry point SHALL resolve one immutable ++code revision for the complete installation. Missing/inconsistent pin evidence + SHALL refuse before partial installation. Installed tools SHALL function + without a developer assembly checkout. +``` + +A no-wording alternative also passes both CLIs: join the first sentence onto one line. + +**Brett decides one of:** +1. "Approve replacing lines 45–48 of + `openspec/changes/migrate-to-triad/specs/repository-triad-migration/spec.md` with: *The + migration SHALL keep the existing one-line installation, command names, payload + destinations/modes, skills/hooks/receipts and supported fork/ref/local invocation usable. An + adopted assembly entry point SHALL resolve one immutable code revision for the complete + installation. Missing/inconsistent pin evidence* (the rest unchanged). It changes no + requirement's meaning." +2. "Leave the text. The validator of record is openspec 1.6.0 or later, run in the spec leg, and + the 1.2.0 copy in `~/.npm-global/bin` is removed or upgraded on every workstation that + validates." + +Optional in the same edit: line 64 ends `…its manifest/pins; product` and line 65 begins `Full +proposals`, so the sentence reads "pins; product Full proposals". The stray word does not affect +validation. + +### (b) "account move" + +**Every use.** `git grep -n -i -E 'account[ -]?move'` over `main` and every evidence branch, and +#186's body and comments, find one line: + +```text +specs/004-migrate-to-triad/adopter-rehearsal.md:103:conversion, merge, account move or lane rebinding. +``` + +It came in with `ff6f6a7` (2026-10-06, "docs: record successful triad adopter candidate +rehearsal"), squashed into `main` by #187 as `44983ce`. + +**plan.md, spec.md and tasks.md never say it**, nor do the change's proposal, design, tasks and +capability spec. Their "account" words are all about accounting for content: "account for", +"accounting", "accounted" and "misaccounts" (for example plan.md:111–112, :152, :169, :209; +spec.md:14, :40, :95, :111; tasks.md:47, :66, :89, :97, :135, :163; the capability spec's :13). +Nothing in the plan defines an account move, and no task performs one. + +**What it needs.** The phrase sits in a list of acts the October 6 receipt does not authorize. +Gate C's approval should say whether any account act belongs to the migration: +- the legs would be created in the `opensoft` organization (adoption-plan.yaml `org: opensoft`), + and `gh` here is `brettheap`, an active admin of `opensoft`; +- in-place adoption keeps `opensoft/openRepoTools` where it is; +- no repository is transferred between accounts or organizations. + +The lanes' Claude "account swaps" in #186's comments are operational and unrelated. +adopter-rehearsal.md is a dated receipt and is best left as written. + +**Text for Brett to approve or correct:** "No account move is part of this migration: +`opensoft/openRepoTools` stays in place, `opensoft/openRepoTools-spec` and +`opensoft/openRepoTools-code` are created in the `opensoft` organization by `brettheap`, and no +repository is transferred between accounts or organizations." + +### (c) `.gitattributes`: plan.md against the code on `main` + +**The plan** (M:plan.md:55–62, § Target placement): + +```text +57 | Assembly `openRepoTools` | `AGENTS.md`, `CLAUDE.md`, `README.md`, `LICENSE`, `.gitattributes`, `.gitignore`; generated manifest/Makefile/validators and assembly pins | Front door and project coordination across legs. | +59 | Code `openRepoTools-code` | All executable tools, `repos.tsv`, `commands/`, `skills/`, `tests/`, implementation CI | Shipped implementation and its installer payload. | +``` + +**The code on `main`** since #190: `f87fd5a:tests/conftest.py:62–68` says everything outside the +front-door documents and spec trees is the code leg's, "and that includes `.gitattributes`, +DELIBERATELY: the placement table puts it at the assembly, but git reads a submodule's attributes +from the submodule's own tree". So `main` now disagrees with plan.md:57 (6088829392 item 9). + +Related, and unchanged by this question: `openspec/changes/migrate-to-triad/design.md:48` ("Root owns front-door guidance, license, +project metadata and workflow bootstrap") and adoption-plan.yaml's `.gitattributes` → `root`, +which is correct for extraction. The original file and its history stay at the root. + +**Why** (#190's PR body, "Decision: `.gitattributes` stays in the code leg"; +CI:test-roots-and-ci-2026-10-08.md#2): +- Git never applies a superproject's attributes inside a submodule. Measured in the composed + candidate: `code/park` and `code/tests/run.sh` are `text: unspecified`. +- A leg split exactly by the table is red on `test_the_root_carries_the_line_ending_rule`. +- T006's `code-0001` adds the code leg's own copy, and `spec-0001` gives the spec leg one. The + candidate confirms `text: auto`, `eol: lf` in the leg. + +**Proposed amendment** (the plan is Brett's own). Row 57 is unchanged. Row 59 becomes: + +```text +| Code `openRepoTools-code` | All executable tools, `repos.tsv`, `commands/`, `skills/`, `tests/`, implementation CI; its own `.gitattributes`, added after extraction | Shipped implementation and its installer payload. Git never applies the assembly's `.gitattributes` inside a submodule, so the leg carries the LF rule its bash files and byte-for-byte installer checks need. | +``` + +Add after the table: + +```text +Extraction gives each original front-door file one owner, the assembly. T006's leg patches then +add new files to the legs: each leg's own `AGENTS.md`, `CLAUDE.md`, `README.md`, `LICENSE` and +`.gitattributes`, and the code leg's `.gitignore`. `spec-0001` also re-points three test paths in +the spec leg's `docs/README-claude-current.md`. These are reviewed changes after byte accounting, +not moved paths, so the adoption mapping does not change. +``` + +**Text for Brett:** "Approve amending plan.md § Target placement: the code leg carries its own +`.gitattributes` (and each leg its own short front door), added after extraction; the assembly +keeps the originals." The minimal form, exactly what #190 asks, is the row 59 change alone. + +### (d) Hosted CI before the real legs exist + +**The conflict.** +- T010 is in "Phase 3 — Installation and checks (Gate B completion)" and asks to "Verify the exact + rehearsed composed state passes required checks and the compatibility matrix" (tasks.md:77–81). +- "T011 closes Gate B" (tasks.md:164). +- The rehearsed commits live only in host-local bare remotes, which GitHub-hosted runners cannot + check out (checklist line 35). +- "No real repository is created for a test" (plan.md:253). #186's rules forbid a real leg + repository or a PR to one before Gate C. +- CI:test-roots-and-ci#5: "Neither patch can run on GitHub until the legs exist." +- T015 already validates "the real exact assembled head using … required platform/CI" + (tasks.md:107–108) before the root adoption merge. + +**What exists today.** The code these jobs would test already runs on hosted runners, on today's +layout: +- #190: `tests`, `tests-no-submodule`, `tests-windows`, `parse-macos`, `tests-macos`, + `guard-launch-mode` and SonarCloud, all green; +- #191: the same less `tests-macos`, which waits on `ready`. *2026-10-13:* #191 landed as + `a74c7fc`. + +What only the legs can show: +- the code leg standalone with its document checks skipping by name; +- the Windows LF check on the leg's own `.gitattributes`; +- the assembly's `composed.yml` with an https recursive checkout, which lane 3 expects to be red + until #180 is fixed (6089801221 finding 13 (i)). + +**Options for Brett:** +1. **(Recommended by this walk.)** "Gate B does not wait for hosted runners on rehearsed + commits. The hosted runs of the code leg's `tests.yml` jobs (including `tests-windows`, + `parse-macos` and `tests-macos`) and of the assembly's `composed.yml` move to T015, before the + root adoption merge. Before Gate C, the same jobs green on the code-side PRs against today's + layout and the local composed and standalone runs of the exact candidate stand in for them. No + repository is created to host them." Matching addition to tasks.md T010: "Hosted-runner checks + that need the real leg repositories run at T015 against the real exact assembled head." +2. "Approve a never-merged draft PR on `opensoft/openRepoTools` whose tree is the candidate code + leg, to run its `tests.yml` on hosted runners before Gate C." It creates no repository, but it + is a PR against the real assembly repository whose tree deletes most of `main`, and it cannot + run `composed.yml`. +3. "Approve temporary repositories for hosted CI of the rehearsed commits, + deleted after T015." This contradicts plan.md:253, which would need amending in the same word. + +Two facts T015 must carry whichever option is chosen: +- The assembly has no `tests-macos` check, so landing tooling that reads `tests-macos` by name + has nothing to read on an assembly PR (6089801221 finding 12). +- After cutover the documented curl one-liner needs api.github.com, 5 API calls per install, and + `jq` on the curl path. A gh-less, rate-limited machine cannot install (5471121210 T4; question + e10). + +### (e) Other decisions only Brett can make + +In dependency order. Each is a sentence Brett approves, corrects or rejects. + +1. **The standard revision.** openRepoShape `main` is `22c9fdd`, six commits past the pinned and + rehearsed `7f84ca4`: #170 (`b18cca9`: `execute` refuses every finding `check` reports), #182 + (`21f5c33`: shape files past a source `.gitignore` are force-staged and verified), #187 + (`f25d805`: canonical leg mount paths), #185 (`bc09bc5`: scaffold force-staging), #186 + (`4ce9cb0`: shape-doctor's submodule note) and #193 (`22c9fdd`: `load_yaml` refuses a + non-UTF-8 byte). Four of them change `adopt-project.py` itself. Only `22c9fdd` touches one of + the eight files this repository mounts (`scripts/repo_shape.py`; the eight are listed in + `837928a`'s message). A repin to `4ce9cb0` or earlier moves no mounted file. Gate C "names the + actual source/standard revisions" (plan.md:161), and the rules allow only a commit on + openRepoShape `main`. + - Either: "Execute with openRepoShape `7f84ca42ca86a8902928345109d2bf6bad87bd91`, the pinned + and rehearsed revision." + - Or: "Repin to first, through a pin PR with a recomputed digest, + and rehearse again (run D) before Gate C." +2. **The frozen source, the landing hold and where the final plan lives.** A committed plan + counts `openspec/`, which contains the plan, so no commit that carries the plan can be its + source (#192 finding 17). `check` reads the clone's local `main` (#193 item 10). + - Text: "Freeze `main` at <40-hex> once the pre-Gate-C PRs land. Hold every landing on `main` + from then until the root-split PR merges or is abandoned. The final plan for that commit is + presented in the Gate C request and kept in the private receipt, not committed to `main` + before `execute`." + - The freeze also starts Gate A's owner-coordinated final capture (plan.md:77, :154–155). +3. **Whether the rehearsal is repeated at the frozen source.** T012: "Verify current … + rehearsal evidence; refuse any source drift before execution". Gate C: "Gate A/B evidence … + current". + - Either: "A source that differs from `837928a` only under paths the plan already assigns + needs a regenerated plan and `check`, not a new rehearsal." + - Or: "Rehearse again (run D) at the frozen source before Gate C." +4. **The prerequisite path.** "`git-filter-repo` 2.47.0, installed into a private venv from the + wheel with SHA256 `2cd04929b9024e83e65db571cbe36aec65ead0cb5f9ec5abe42158654af5ad83` + (`--no-index`), is the approved prerequisite path for the real `execute`; bench and system + installs stay unchanged, and the execution receipt says so." (plan.md:105–106; checklist + line 2) +5. **Work dispositions.** T012 asks for approval of "work dispositions", and the change's + tasks.md 1.2 for "active-work dispositions". The October 7 inventory's dispositions are + proposals held in a private receipt (M:preservation-2026-10-07.md#Inventory (T001)). Text: + "Approve the dispositions in receipt as listed." +6. **#191's behaviour on today's layout.** The review rule makes any change to today's + single-repository behaviour before Gate C a Gate C item (#191 review 5464963807 finding 3). + Measured differences at `13cc752`: + - two extra probe requests per install (26 raw requests against `main`'s 24 with no gh; 25 gh + contents against 23), each answered 404 (5471121210 delta 3); + - an offline installed-copy reinstall refuses in new words that name the layout question, + where `main` names the skills (delta 3); + - the ancestor walk, unreachable in today's estate, adds an output line under an unrelated + LF pin and exits 2 under a CRLF one (delta 4); + - lane 2's matrix adds a 502 on a probe refusing where `main` installs, a missing ref read as + a single repository after 34 requests with `main`'s words, and a HOME holding + `contracts/code-pin.yaml` refusing a reinstall (6088812080 items 1–3). + + Re-check every one at #191's next head. Lane 2's pre-push matrix on that head (6090038382, + not counted here) already reports the ancestor-walk and HOME-pin cases fixed, and the missing + ref, the 502, the garbage body and the offline wording still different from `main`, plus a + residual walk match where an unrelated pin's own `submodule_path` lines up. Text, for whatever + remains: "Accept on today's layout as the price of telling the layouts + apart, disclosed in #191's body." +7. **The `.specify` posture at the assembly root.** "Keep `.specify/`, the managed blocks and the + generated agent files local and ignored per clone, as today (`.git/info/exclude`), with only + `/worktrees/` in the tracked `.gitignore`." The alternatives are to ignore them in the tracked + `.gitignore` or to commit `.specify/` (root-guidance §8). T18 §5 item 1 adds that a + bootstrapped root is "dirty" to `resume`, and measures the options: land the spec leg's three + OpenSpec scaffold files before cutover, run the bootstrap with `--no-repo-agent-pointers` or + commit its block, and carry four exact-path root excludes. +8. **Target workstations** for T006's distribution check and SC-006: "The target workstations + are ; each needs the workBenches delivery receipt before cutover." Only Eagle has + evidence (root-guidance §9). +9. **Owner acts on the new repositories** (after Gate C, but part of the plan approved there): + "Bind SonarCloud to `opensoft/openRepoTools-code`; protect each leg's `main` with its + `tests.yml` jobs required; require `composed / exact-pins` on the assembly's `main`." + CI:test-roots-and-ci#5 and #8 name the SonarCloud binding as an owner's act. +10. **The post-cutover one-liner's dependencies** (5471121210 T4). Either: "Accept that after + cutover the curl one-liner needs api.github.com (5 calls per install) and `jq`; document it + beside the one-liner." Or: "Require the one-liner to install without the API, and make that + a T007 change before Gate C." + +**A Gate C approval sentence that would cover the above** (a draft for the request, with blanks): +"Gate C for #186: I approve the in-place adoption of `opensoft/openRepoTools` as the assembly, +executed once with openRepoShape `adopt-project.py` at , from an isolated +single-branch clone of `main` at , with the plan regenerated for that source +(sha256 <…>); creating `opensoft/openRepoTools-spec` and `opensoft/openRepoTools-code`, both +public; `git-filter-repo` 2.47.0 from the private venv (wheel `2cd04929…ad83`); the follow-ups + applied after source verification through leg and root +PRs; the work dispositions in ; and a landing hold on `main` from until the +root-split PR merges or is abandoned. It does not approve lane rebinding, closing old PRs or +retiring worktrees." + +## 5. Blockers to requesting Gate C, in dependency order + +1. **#191 to a landable head.** Push the next head; prove its red/green claims; lane 2's matrix + rerun and lane 3's check at that head, against 6088812080's items 1–7 and the open items of + lane 2's pre-push run (6090038382); correct the PR body + (5471121210 delta 8); `tests-macos` under `ready`; land. Then the T007 entry-point patch's T3 + change and its re-review. Or Brett's ruling on any kept difference (question e6). +2. **The test-root follow-ups and the T010 patches.** The follow-ups, now #195 (#193 items + 2–7, 14 and 15, item 5's paired-layout discovery among them), reviewed at their final head + and landed before Gate C; `composed.yml` against #180 (finding 13 (i)). +3. **The composed candidate at the final heads.** r2 is published with its receipt, and every + failure is classified (CA `16e6ea4`). Owed: a composed rerun at #191's final head with + T007's v3, with F1 gone and F2 (#193 item 14) decided, then the install and rollback recheck + at those heads (lines 34, 36, 40). +4. **The evidence-3 PR, #196.** Fold CI (`d28e2cc`'s three new files), IN (with the host-absolute path + at installer-2026-10-08.md:13 removed), RR at `2e7e55e` (the branch tip's file: a cherry-pick of + `c1283b5` alone conflicts, 6089982753), EF `096de93`, the candidate record, T18 + and this walk onto `main`; do not reuse `849592b`'s subject or body (6088884829); review; + land. *2026-10-13:* the final refold carries the installer record from `7668c51` on (host + path removed), slice B from `2c040d6` on (with the T011 record now taken from it), and the + candidate record from `e81100c` on (r3). The exact heads are in #196's "Input heads carried" + table. +5. **Gate A.** Capture tooling v3 (A1, A2) and lane 2's re-verification; the owner-coordinated + freeze; the final capture; owner dispositions (question e5); installed-file receipts for + py-bench and the other workstations. +6. **Brett's decisions:** questions a–e, e10 included. +7. **The request.** Regenerate the plan at the frozen source and `check` it, rehearsing again if + question e3 says so; recheck the leg names; present the plan, follow-ups, dispositions and the + approval sentence above. + +## 6. How this walk was read + +Read-only except this file and scratch. `` is lane 1's checkout of this repository and +`` is lane 1's private scratch. + +```sh +git -C fetch --all --prune # 20:30:48Z, 21:58:55Z, 22:07:06Z +git -C rev-parse --short origin/ +git -C show : # every document cited +git -C diff --stat 63810dd f87fd5a -- # empty +git -C ls-remote origin 'refs/heads/adopt/*' # empty +gh pr list --repo opensoft/openRepoTools --state open # #191 at 13cc752 only +gh issue view 186 --repo opensoft/openRepoTools --json body,comments # through 22:06:49Z +gh issue view 193 --repo opensoft/openRepoTools --json body +gh pr view {190,191,192} --repo opensoft/openRepoTools --json body,reviews,comments +gh api repos/opensoft/openRepoTools-{spec,code} # 404, 404 +gh api user; gh api user/memberships/orgs/opensoft # brettheap; admin, active +gh api repos/opensoft/openRepoShape/compare/7f84ca4...main # ahead 6, behind 0 +gh api repos/opensoft/openRepoShape/commits/ # files changed +gh api repos/opensoft/openRepoShape/pulls/162 # head 6fa4e07, merge 7f84ca4 +gh api repos/opensoft/openRepoShape/commits/{6fa4e07,7f84ca4}/check-runs +gh api repos/opensoft/openRepoTools/pulls/196/reviews # 5481348731 (2026-10-11) +git -C show 16e6ea4:specs/004-migrate-to-triad/candidate-2026-10-09.md # CA +git -C show 40f26f7: # EF +git clone -q --no-local --single-branch --branch main /main-clone +command -v openspec; which -a openspec; --version # 1.2.0 first, 1.6.0 second +(cd /main-clone && ~/.npm-global/bin/openspec validate migrate-to-triad --strict) # exit 1 +(cd /main-clone && validate migrate-to-triad --strict) # exit 0 +# the proposed text and the one-line join, each in a scratch copy: both CLIs exit 0 +git grep -n -i -E 'account[ -]?move' -- . +git grep -n -i account origin/main -- specs/004-migrate-to-triad openspec/changes/migrate-to-triad +``` diff --git a/specs/004-migrate-to-triad/installer-2026-10-08.md b/specs/004-migrate-to-triad/installer-2026-10-08.md new file mode 100644 index 0000000..ea63cf2 --- /dev/null +++ b/specs/004-migrate-to-triad/installer-2026-10-08.md @@ -0,0 +1,243 @@ +# Installer payload source and the assembly entry point: October 8–9, 2026 + +**Task:** T007 and T008 of [tasks.md](tasks.md), under [#186](https://github.com/opensoft/openRepoTools/issues/186). Neither task is checked complete by this record. + +**Code:** [#191](https://github.com/opensoft/openRepoTools/pull/191), branch `feat/installer-payload-source-resolution` (head `8246983`, based on `c4864ac` with main `f87fd5a` merged in as `45979a4`). + +**Landed:** #191 landed on main as `a74c7fc` on 2026-10-13, the squash of `8246983`. + +**Assembly entry point:** [patches/t007-assembly-entry-point.patch](patches/t007-assembly-entry-point.patch). It cannot land on `main` before the split, because its path is the implementation's. + +**Writer:** lane openRepoTools-1. + +## The rehearsed arrangement relied on + +Run C (its `rehearsal/README.txt` in the private prep area's `work-20261008/`), cloned out of it read-only: + +| Repository | Commit | +| --- | --- | +| Source (`main` when cloned) | `837928a1029ac53b050a9bdcbc0ef4055b10719b` | +| Assembly split (`adopt/three-repo-shape`) | `69b89245029d0339afd3317df7c9b1b3bbaf7fc1` | +| Spec leg | `593250d600ca291f610671d993c2a6fc36cd690e` | +| Code leg | `73b04d637634f897ad24e22ee4c12bed287172d6` | +| Code's nested openRepoShape | `7f84ca42ca86a8902928345109d2bf6bad87bd91` | + +Clone commands: + +```sh +git -c protocol.file.allow=always clone -b adopt/three-repo-shape /source +git -C -c protocol.file.allow=always \ + -c url./mirror/openRepoShape.git.insteadOf=https://github.com/opensoft/openRepoShape.git \ + submodule update --init --recursive +``` + +The brief named run B (split `2726d3a`, code `6d52162`, spec `558a12b`). The 2026-10-08 bench restart wiped it from `/tmp`. Before run C existed, this writer rebuilt run B in its own scratch to see whether it reproduces. The rebuild used the same tool (`adopt-project.py` at `7f84ca4`), the same published plan (`004-migrate-to-triad-rehearsal` at `36c0324`), the same source (`e43b243`), local bare remotes, and refusing `gh`/`curl` shims (zero calls). + +- `execute` exited 0 with the same 45/49/6/0 accounting and 18 added paths. +- Spec reproduced as `558a12b` and code as `6d52162`, byte for byte. +- The split (`fea6ad8`) differed from `2726d3a` only in its commit time and in three files that embed local paths: `.gitmodules`, `shape/README.md` and `shape/AGENTS.md`. + +Run C, which the coordinator made the arrangement of record, superseded it. + +### The pin grammar validated + +This is `contracts/code-pin.yaml` as run C's adopter writes it. It is read with `read` and `case`; there is no YAML runtime. + +| Field | Required value | +| --- | --- | +| `schema_version` | `1` | +| `kind` | `pinned_contract_manifest` | +| `leg_role` | `code` | +| `source_repository` | an owner/name slug; never inferred by appending `-code` | +| `submodule_path` | one path segment | +| `commit` | 40 lowercase hex | +| `revision_kind` | `commit` | +| `digest_algorithm` | `sha256` | +| `digest_definition` | `sorted-ls-tree-r-v1` | +| `digests.tree_sha256` | 64 lowercase hex | +| `verify_pin`, `resync_runbook` | present | + +Each key that is read must appear once. The file must be LF-only, quotes must close, and backslashes are refused. + +## T007: the assembly entry point + +- Commit `6acc147f751e5a2ca57b4814395769b7b75b339e` sits on `69b8924` in the writer's clone, on branch `t007-assembly-entry-point-v3`. + - Tree: `87782af2ff3075284682f02fa41763d5df7112ff`. + - `openRepoTools`: blob `ff432c6dc189ce272e778a89e375076094737b48`, mode 100755, sha256 `8248224d3bb425f69a944a41cdfe0a68f0b449d13978f67c76b6bc5a0a73a773`. + - The patch file's sha256 is `67d8e7ce8b0f7008d2151a44c0eabaf74cda7cd2697c6e09ee7da0c341c671b5`. + - `git am` of the patch on a fresh clone of run C's split reproduces tree `87782af` exactly. + - History: + - v1, `a02a45c` (tree `a9fa46e`): the first version. + - v2, `8ed83b7` (tree `79db0a4`): `recorded_gitlink` reads the gitlink with `read` instead of `awk` (see the runs below). + - v3, this commit: on the remote path, the gitlink is checked against the pin BEFORE any implementation is fetched or run. That is the change T3 of lane 3's review of v2 required (#191 review 5471121210): a pin its gitlink disagrees with now refuses, naming both commits, with no request to the code repository (measured by lane 3 in 5481368376, finding 1). + - **What v3 does not close** is T3's second case, a pin and gitlink that agree on code from before #191 (a rollback, or run C's own `73b04d6`). It passes the check, and the root then runs that older installer, which reads the assembly root as a single repository. On a real root that is a refusal in that installer's words, with nothing placed and the cause unnamed. With decoy files at the root (synthetic) it installs them (lane 2's item 7; lane 3's 5481368376, finding 2). This is recorded as a T015 item below, and a capability check is proposed for #193. +- **In a checkout,** it requires the pin (strict grammar), the gitlink (index first, then HEAD) and `code/openRepoTools`'s raw bytes (`hash-object --no-filters`) to name one commit. Then it `exec`s `code/openRepoTools` with every argument. That is the same process, so the exit status, signals and the implementation's own temporary-file cleanup are the implementation's. There are no temporaries of its own. +- **Anywhere else** (stdin, or a copy of the file on its own), it does five things: + - it resolves `OPENREPOTOOLS_REF` once, `gh` first and `curl` second; + - it reads `contracts/code-pin.yaml` at that commit; + - it reads the gitlink at the pin's `submodule_path` in that commit (one level of the tree, by `gh api --jq`, or by `curl` and `jq`). It refuses, naming both commits and with "nothing ran", unless the gitlink is the pinned commit; + - it fetches `openRepoTools` from the pin's `source_repository` at the pinned commit (the file must begin `#!`); + - it runs it from stdin, as the one-liners always ran it, with `OPENREPOTOOLS_REF` set to the resolved commit, so the implementation reads the same pin and checks it again. +- **It has no installer mechanics.** It does no placement, receipt, merge or retirement. +- **It assumes nothing the legs may not carry.** That covers `.gitattributes`, README, AGENTS and LICENSE (T006 §10.2). +- **It is bash 3.2-clean.** Arguments are forwarded as `${1+"$@"}`, because a bare `"$@"` under `set -u` is unbound in 3.2. +- **Its pin reader is a deliberate copy** of the implementation's (10 definitions: three character constants, the six pin-reader functions and `recorded_gitlink`). `test_r15_composed_the_root_entry_points_pin_reader_is_the_implementations` fails when the two differ. + +**Rehearsal-only pin moves (not part of the patch).** + +- Openrepotools' PR #191 was applied to code `73b04d6` in a clone. It was pushed only to the writer's own bare copy of the code remote, never to run C. +- The standard's `scripts/bump-leg.py` (tool `7f84ca4`, with `--local-remote-dir` pointing at that copy) made the lockstep commits. Its validators printed `pins ok` and `manifest ok` and recomputed both digests: + - on `t007-assembly-entry-point`, `9321107` moved the code pin to `ecb4ef3` (PR at `e406e0e`), and `58e3ac5` moved it to `d12345b` (PR at `ecd7aa4`); + - on `t007-assembly-entry-point-v2`, `7bbd60b` moved it to `ea17f30` (PR at `2411f40`), then `e5a901e` and `316aed6` to the round-1 heads; + - on `t007-assembly-entry-point-v3`, `4c6a0b7` moved it to `fa460f2`. That commit is the whole code leg as the PR head `6568f08` has it, merged with main `f87fd5a`. It includes #190's test roots, and every one of the leg's paths is the PR head's blob. `4b8b05a` then moved the pin to `3fc1e95`, the code leg at the final head `8246983`. +- Each bump printed bump-leg's `NEXT … push -u origin …` line. It was deliberately not followed. +- The clone's push URL is disabled. + +### The one-liner contract + +| Command line | What it resolves | +| --- | --- | +| `curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoTools/main/openRepoTools \| bash -s -- --install` (README:228; openRepoShape's `--install` pointer, openRepoShape:280 at `7f84ca4`, byte for byte) | 1. The root entry point at `main`.
2. `main` resolved once to assembly commit *A*.
3. `contracts/code-pin.yaml` at *A*.
4. The gitlink at `submodule_path` in *A* must be `commit`, or nothing runs.
5. `openRepoTools` from `source_repository` at `commit`.
6. That implementation, with `OPENREPOTOOLS_REF=A`, resolves *A* (a commit, which cannot move), lists *A*'s tree, re-reads and re-checks the pin, checks the gitlink, confirms the commit exists in `source_repository`, fetches the 23 payload files from there, and places 29 files and 2 hook entries. | +| `gh api repos/opensoft/openRepoTools/contents/openRepoTools -H 'Accept: application/vnd.github.raw' \| bash -s -- --install` (README:236–237) | The same, through `gh`. | +| `./openRepoTools --install` in an assembly checkout | Checks that pin, gitlink and implementation bytes agree, then `exec`s `code/openRepoTools`. The implementation checks every payload file against the pinned commit and copies offline. | +| `openRepoTools --install`, installed in `~/.local/bin` | The implementation alone. Its default `REPO` is the assembly, which it resolves itself. It never needs the root file or a checkout again. | + +With run C's identities, *A* = `69b8924` and the pin names `opensoft/openRepoTools-code` at `73b04d6`. `73b04d6` predates T007, so the real cutover must pin a code commit that carries this change. That is T015's to verify at the assembled head. + +Transcript of row 3 in the writer's clone (assembly `7bbd60b`, code pin `ea17f30`), into a throwaway `HOME` with `gh`/`curl` shims that log and refuse: + +- exit 0, and **0 network calls**; +- first line: `openRepoTools: source: the code mounted at /code, opensoft/openRepoTools-code at ea17f302310fa75fdf61ad7199912a74971513bd as pins it; every payload file was checked against that commit and nothing fetched`; +- 29 `installed at` lines and 29 receipt rows; +- the installed `openRepoTools` is byte-identical to `code/openRepoTools`, the implementation and not the root file; +- `--version` prints `openRepoTools (opensoft/openRepoTools @ main)`; +- `./openRepoTools nope` exits 2. + +**Against T006's README sentence** (root-guidance-2026-10-08.md §10.9: "the `openRepoTools` they fetch is this root's entry point, which hands over to the installer `code/openRepoTools` at the commit this root pins"), the contract matches, with two precisions: + +1. Remotely, the implementation is fetched from the pin's `source_repository` at the pinned commit, which is the repository `code/` mounts. +2. In a one-liner run, `--version` prints the resolved commit rather than `main`, because the handover freezes `OPENREPOTOOLS_REF`. + +## T008: the compatibility matrix + +The tests are `tests/test_install_payload_source.py` at #191's final head `8246983`, which is main `a74c7fc`: 52 tests, 109 cases. Every `def test_` line in that file is in the table below, and no name outside it is. The fixtures build their own GitHub out of bare repositories, answered by one fake for `gh api`, `gh repo` and `curl` that runs `git` and logs every request. There is no real `gh` or `curl` on `PATH`, and every `$HOME` is temporary. + +`tests/test_openrepotools_command.py` gains no test. Its fake `gh` answers the one layout question (`contracts/code-pin.yaml`) with GitHub's 404, and 4 of its 63 tests run the single-repository fetch path through that fake: `test_install_places_all_nine_or_none`, `test_a_withheld_command_file_places_nothing_either`, `test_install_from_stdin_fetches_itself_into_a_live_workdir` and `test_a_fetching_install_follows_the_ref_it_was_given`. No assertion of theirs changed. + +**Base column:** `8246983`'s module was run against `c4864ac`'s installer, under the lock, on 2026-10-11: **27 passed, 73 failed, 9 skipped** (the composed cases). Every pass is a case that must behave exactly as base does (today's single repository, or a tree that is not this assembly's), or a static check. **The `8246983` column:** CI run 38097860219 (`tests`: 1419 passed, 9 skipped, the 9 being the composed cases). The composed tests passed in the strict composed runs recorded below. + +| # | Row | Tests at `8246983` (cases) | Base `c4864ac`, per test | `8246983` | +| --- | --- | --- | --- | --- | +| 1 | Raw/gh one-liners | `test_r01_the_one_liner_against_a_single_repository_installs_as_before`; `test_r01_the_one_liner_against_an_adopted_assembly_installs_the_pinned_code` (2); `test_r01_a_single_repository_by_curl_never_needs_api_github_com` (2); `test_r01_a_single_repository_without_jq_fetches_then_names_jq_as_it_always_did`; `test_r01_a_single_repository_by_gh_asks_for_files_and_nothing_else`; `test_r01_composed_the_documented_one_liners_resolve_through_the_root_entry_point` (2); `test_r01_composed_the_root_entry_point_delegates_locally`; `test_r01_composed_a_signal_reaches_the_implementation_and_its_temporaries_go` | pass; 2 red; 2 pass; pass; pass; skip (composed); skip (composed); skip (composed) | pass; composed tests pass composed | +| 2 | Offline local single / adopted checkout | `test_r02_a_complete_single_repository_tree_installs_offline_byte_for_byte`; `test_r02_a_complete_tree_inside_another_projects_assembly_installs_as_before` (2); `test_r02_an_adopted_checkout_installs_its_pinned_code_offline`; `test_r02_an_adopted_checkout_whose_pin_comments_its_path_is_still_its_assembly` | pass; 2 pass; red; red | pass | +| 3 | Explicit local code feature | `test_r03_an_explicit_code_feature_checkout_installs_its_own_bytes_and_names_them`; `test_r03_an_incomplete_code_feature_checkout_is_refused_not_completed_from_elsewhere` | red; red | pass | +| 4 | Installed reinstall / skill-only | `test_r04_an_installed_copy_reinstalls_from_the_adopted_source_without_any_checkout`; `test_r04_an_installed_copy_on_a_single_repository_keeps_the_sibling_rule` | red; pass | pass | +| 5 | Override / adopted fork | `test_r05_a_single_repository_fork_named_by_the_override_installs_as_before`; `test_r05_an_adopted_fork_follows_its_own_pin_not_a_guessed_code_name` | pass; red | pass | +| 6 | Branch, tag, commit, slash refs | `test_r06_every_ref_form_is_resolved_once_and_every_payload_names_one_commit` (4); `test_r06_a_single_repository_reads_every_ref_form_as_it_always_did` (4) | 4 red; 4 pass | pass | +| 7 | Branch moves between downloads | `test_r07_a_branch_that_moves_between_downloads_never_mixes_versions`; `test_r07_a_branch_that_moves_right_after_the_layout_question_is_read_at_one_commit` (2) | red; 2 red | pass | +| 8 | Missing pin (single) / partial or wrong pin | `test_r08_a_single_repository_without_a_code_pin_is_permitted`; `test_r08_an_assembly_root_without_its_code_pin_refuses_as_a_single_repository_would`; `test_r08_composed_the_root_entry_point_refuses_a_pin_its_gitlink_disagrees_with` (2); `test_r08_a_malformed_code_pin_is_refused_never_read_as_a_single_repository` (19); `test_r08_a_pin_the_gitlink_disagrees_with_is_refused`; `test_r08_a_pin_naming_a_commit_its_repository_lacks_is_refused` | pass; pass; skip (composed); 19 red; red; red | pass; composed tests pass composed | +| 9 | API answer missing/malformed, auth, transport | `test_r09_an_api_failure_is_a_refusal_never_a_fallback_to_a_single_repository` (24); `test_r09_a_pin_the_listing_shows_but_that_will_not_come_back_is_refused_saying_what_answered` | 24 red; red | pass | +| 10 | Changed gitlink / payload, incomplete local set | `test_r10_a_local_gitlink_that_disagrees_with_the_pin_is_refused_before_staging`; `test_r10_a_changed_local_payload_is_refused_before_staging`; `test_r10_an_incomplete_local_adopted_payload_is_refused_without_a_fetch` | red; red; red | pass | +| 11 | Missing command/skill/alias, failed merge | `test_r11_a_payload_file_missing_from_the_pinned_code_changes_nothing` (3); `test_r11_a_single_repository_missing_a_file_refuses_in_the_words_it_always_did` (2); `test_r11_a_single_repository_at_a_ref_it_lacks_refuses_in_the_words_it_always_did` (2); `test_r11_a_settings_merge_that_cannot_be_computed_changes_nothing` | 3 red; 2 pass; 2 pass; red | pass | +| 12 | Modes, receipts, targets, foreign files, hooks | `test_r12_an_adopted_install_keeps_modes_receipts_targets_and_what_is_not_ours` | red | pass | +| 13 | WIP init / template lookup | `test_r13_wip_init_takes_the_standard_from_codes_dependency_pin_not_the_assemblys`; `test_r13_an_installed_wip_init_whose_source_does_not_answer_seeds_from_main_as_before`; `test_r13_an_installed_wip_init_at_a_ref_the_source_lacks_seeds_from_main_as_before`; `test_r13_an_installed_wip_init_never_needs_a_single_repositorys_commit_lookup`; `test_r13_an_installed_wip_init_on_a_single_repository_reads_its_pin_as_before`; `test_r13_an_adopted_source_wip_init_cannot_follow_refuses_in_step_7s_words` | red; pass; pass; pass; pass; red | pass | +| 14 | Missing nested dependency | `test_r14_with_no_nested_standard_anywhere_the_dependency_pin_alone_selects`; `test_r14_the_documented_upstream_skip_is_unchanged`; `test_r14_composed_acceptance_requires_the_nested_dependency` | red; pass; skip (composed) | pass; composed tests pass composed | +| 15 | bash 3.2, Windows/WSL | `test_r15_the_installer_uses_nothing_bash_3_2_lacks`; `test_r15_the_three_lists_stay_one_line_arrays_a_consumer_parses`; `test_r15_the_adopted_path_holds_under_macos_one_true_awk`; `test_r15_composed_the_root_entry_points_pin_reader_is_the_implementations`; `test_r15_composed_the_root_entry_point_parses_and_uses_nothing_bash_3_2_lacks` | pass; pass; red; skip (composed); skip (composed) | pass; composed tests pass composed | + +The module is `WINDOWS_SKIP`, like every bash-driven test here. The composed tests skip in standalone code, and under `OPENREPOTOOLS_COMPOSED=1` (T009's convention) they fail instead. + +### Runs + +Locked runs used `tests/run.sh`'s own invocation under its lock. That is the coordinator's deviation for a census that never reaches zero. They ran with `PATH=/usr/bin:$PATH`, because this profile's `~/bin/python3` is a venv without pytest. Runs marked "unlocked" are scoped proofs of about two minutes, run under the coordinator's rule of 2026-10-09, with HOME, TMPDIR, `--basetemp` and `PYTHONPYCACHEPREFIX` all in the writer's scratch. + +| Run | Code | Selection | Result | +| --- | --- | --- | --- | +| Red at base, 2026-10-08T21:04–21:10Z | `6337a52` (installer `c4864ac`) | the module | **75 failed, 8 passed, 6 skipped**. The 8 are the single-repository preservation tests, two static checks and the documented-skip check; every red was checked against its first failing assertion. | +| Green | `e406e0e` | module + `openrepotools_command`, `install_skill_and_hook`, `install_by_rename`, `wip_init` | **318 passed, 7 skipped** (the composed cases) | +| CI on `e406e0e` | | | `test_the_installer_reaches_only_this_repository` failed. Every fetching line must name `$REPO`; the helpers took the repository as an argument. Fixed in `ecd7aa4` (one current source, `${PAYLOAD_REPO:-$REPO}`), with no behaviour change. | +| Green with hygiene | `ecd7aa4` | the above + `repo_hygiene`, `upstream_pin` | **479 passed, 5 failed, 7 skipped**. All 5 are `test_wip_init_and_the_lane_helpers_read_one_pointer_file_the_same_way` cases whose failing assertion is the exit status of `lanes-edit.sh`, which is unchanged from base, as are its test and conftest. They passed in the green run above. This session inherits `LANES_WORKSPACE_ROOT` and other `LANES_*` binding variables, and with those unset 3 still fail on workstation state. CI's clean runner is the record. | +| Composed, strict | entry point `a02a45c` + code `ecb4ef3` | the module | **90 passed, 0 skipped** | +| Composed, strict, 2026-10-09T00:47–01:01Z | entry point `a02a45c` + code `d12345b` (= `ecd7aa4`) | module + install suites + `repo_hygiene` | **395 passed, 19 failed**. 17 `repo_hygiene` checks and `test_the_help_and_the_readme_say_the_mode_the_receipt_is_born_at` are T006 §10.6's front-door findings (T009's): they read root documents the code leg does not carry. The 19th was this change's. `test_without_jq_it_refuses_and_places_nothing` saw exit 2 but `line 734: awk: command not found` instead of "needs `jq`": the mounted-leg path read the gitlink with `awk`, which the test's minimal `PATH` lacks. Fixed in `2411f40` and in the entry point (`8ed83b7`) by reading it with `read`. | +| Green with the `read` fix, 2026-10-09T01:05–01:20Z | `2411f40` | as "Green with hygiene" | **479 passed, 5 failed, 7 skipped**: the same five `lanes-edit.sh` cases, nothing else. | +| Composed, strict | entry point `8ed83b7` + code `ea17f30` (= `2411f40`) | module + install suites + `repo_hygiene` | Not completed. The 2026-10-09T01:3xZ account swap stopped it, and `6568f08`'s run below superseded it. | +| CI on `ecd7aa4` | | | All jobs green (run 37866464029). | +| CI on `2411f40` | | | All jobs green (run 37869321635). `tests-macos` skipped (no `ready`). | +| Review of #191 at `2411f40` | | | DO NOT LAND (lane 1's reviewer, 5465121368; lane 3's 5464963807 reached the same verdict on F1). F1: on today's layout, the curl path needed api.github.com and `jq`. F2: an installed `wip init` refused at step 7 where base fell back to `main`. | +| F1/F2 fix | `13cc752` | module | 90 passed, 7 skipped. CI on `13cc752`: green. Lane 3's delta review: LAND (5471121210), with the stale body (delta 8) and the `ready` macOS run as conditions. | +| Round 2 | `fdc5f18`, `6fa3b43`, `6568f08` | | Dropped the `project.yaml` probe; bounded the assembly search (delta 4); the adopted pin refusal now says what answered (delta 6); r14 reads conftest's text (the `KeyError 'reason'` under #190's composed mode); a composed test for the root's gitlink check; merged main `f87fd5a`. | +| Module, under the lock | `fdc5f18` content | `-k payload_source` | **96 passed, 7 skipped** | +| New and changed tests × four installers, unlocked | `c4864ac` / `2411f40` / `13cc752` / `fdc5f18` | the 20 new and changed tests | **19 pass + 1 fail** (the adopted step-7 words, which are new behaviour) / **20 fail** / **16 pass + 4 fail** (the `project.yaml` request, the parent walk ×2, the partial refusal) / **20 pass** | +| Delta 6 and r14, unlocked | `6fa3b43` | what-answered, documented skip, malformed pins, gitlink pins | **23 passed** | +| Composed root gitlink test, strict, unlocked | root v2 `8ed83b7` / v3 `6acc147`, with code `fa460f2` | the module's composed tests | v2: the new test fails ×2. v3: **9 passed**. | +| Broader suites, under the lock | `6568f08` | `payload_source`, `openrepotools_command`, `wip_init`, `install_skill_and_hook`, `install_by_rename`, `repo_hygiene`, `upstream_pin`, `test_roots` | **516 passed, 9 skipped, 5 failed**. The 5 are the `lanes-edit.sh who` exit-8 cases on this workstation's lane state. That file and its test are outside the diff, and CI runs them green. | +| Composed, strict, under the lock, 2026-10-09T22:08–23:18Z | assembly `4c6a0b7` = root v3 `6acc147` + code `fa460f2` (this head's whole code leg) | module + install suites + `repo_hygiene` + `openrepotools_command` | **429 passed, 1 failed**. The failure is `test_the_root_carries_the_line_ending_rule`, which needs `.gitattributes` in the code leg; run C's legs carry none (T006 §10.2). After #190's roots, the 17 earlier front-door failures read the assembly root and pass. | +| CI on `6568f08` | | | Run 37997688693: `tests` 1416 passed, 9 skipped. `tests-no-submodule`: 1196 passed, 229 skipped. `tests-windows`: 177 passed. `guard-launch-mode`: 165 passed. `parse-macos`, SonarCloud and Sourcery: pass. `tests-macos` waits on `ready`. | +| Copilot round 1, on `6568f08` | `cddd1f7` | | Three findings. Fixed: a copy-time race on the mounted-leg path (the checked snapshot is what is installed), and a commented `submodule_path` that `pin_mounts` missed (its new test fails at `6568f08` and passes at `cddd1f7`, scoped and unlocked). Kept: the index-then-HEAD gitlink fallback, which is openRepoShape's `recorded_gitlink` contract. Local rows r02/r03/r10/r15, scoped and unlocked: **13 passed, 2 skipped**. | +| CI on `cddd1f7` | | | Run 38096352239: `tests` 1417 passed, 9 skipped. `tests-no-submodule`: 1197 passed, 229 skipped. `tests-windows`, `guard-launch-mode` and `parse-macos`: pass. `tests-macos` skipped (no `ready`). | +| Copilot round 2, on `cddd1f7` | `8246983` | | One finding: the PR body overclaimed a refusal for a ref that moves between the probe and the resolution. The body is corrected, and a test (moved right after the probe: adopted → installed whole at the second commit; single → refused) passes, together with row 7, scoped and unlocked: **3 passed**. The cap is reached. | +| Composed, strict, unlocked | assembly `4b8b05a` = root v3 + code `3fc1e95` (= `8246983`) | the module's composed tests + r02, r03, r07, r10 | **22 passed** | +| CI on `8246983` | | | Run 38097860219: `tests` 1419 passed, 9 skipped. `tests-no-submodule`: 1199 passed, 229 skipped. `tests-windows`: 177 passed. `guard-launch-mode`: 165 passed. `parse-macos`, SonarCloud and Sourcery: pass. `tests-macos` ran on the `ready` label (run 38155833862). Lane 3's delta check: LAND (5481368124). | +| Base column, under the lock, 2026-10-11 | module at `8246983`, installer `c4864ac` | `-k payload_source` | **27 passed, 73 failed, 9 skipped** (the composed cases). This is the T008 table's base column. | +| Landed | `a74c7fc` | | #191 landed on main as `a74c7fc` on 2026-10-13, the squash of `8246983`. | + +## Lane 2's installer rows + +These are the 15 rows of "The installer's own assumptions" in `consumer-reads-2026-10-07.md`. + +| Row (`openRepoTools` at `c4864ac`) | Disposition | +| --- | --- | +| `:6-11`, README one-liners | Covered: the entry point patch and the composed one-liner tests. | +| `:70-72` `REPO`/`REF`/`SELF` | Covered: the meanings are unchanged; the adopted source is resolved through the pin. | +| `:258-275` `fetch_from_repo` | Covered: it reads the resolved source, still `gh` first. | +| `:282-285, 338, 962-990` `INSTALLABLES`/`collect_commands` | Covered: the names sit at the code root, and the three arrays stay one line (`test_r15_…one_line_arrays…`, the W4 constraint). | +| `:1290-1398` `collect_skills` from `~/.local/bin` | Covered: rows 4 and 11. | +| `:2502-2517` `wip_shape_ref` | Covered: rows 13 and 14. For a single repository the pin is read at `$REF` as before. For an adopted assembly it is the pinned code's pin. `main` is the fallback, as on main, for a source that carries no pin, a source that does not answer, and a ref the source lacks (#191's F2 fix: `test_r13_an_installed_wip_init_whose_source_does_not_answer_…`, `…_at_a_ref_the_source_lacks_…`, `…_never_needs_a_single_repositorys_commit_lookup`). An adopted source that cannot be followed refuses at step 7, in step 7's words (`test_r13_an_adopted_source_wip_init_cannot_follow_refuses_in_step_7s_words`). | +| `:2519-2523` `wip_fetch_template_file` | Unchanged. Row 13 shows the template comes from the standard the code pins. | +| `:1218-1222` `venv_note` names `docs/README-lanes.md` | **Not changed.** It is a comment this PR does not touch, and it names a spec-leg path after the split. Listed for T014's reconciliation (also T006 §10.9). | +| `:28-56, 393-396` counts vs design `:61-64` | Recorded: 17 commands, 23 payload files, 29 placed files, 31 artifacts on this layout, derived from the lists. The design's 15/27/29 are `daed209`'s. | +| Paths unmapped against `daed209` | Not this task's (plan regeneration, T003/T012). Run C's accounting covers all 120 entries. | +| `repos.tsv` leg aliases | Not this task's (lane 3; waits for real legs). | +| `:1320, 1352, 2211` hooks, `workspace.yaml` | Unchanged. | +| `lane-worktrees`/`lanes-edit.sh` lane roots | Not this task's (lane 3, I4). | +| `:291-292, 298-300, 322` consumer comments | Unchanged. | +| `.gitmodules`, `upstream/`, `contracts/openreposhape-pin.yaml` | Covered: the dependency unit is code's, and `wip init` reads code's pin. | + +workBenches-side: the sentinel REPO/REF partial row is covered (`test_r02_…byte_for_byte`), and so is W4 (one-line arrays). W1, W3 and W8 (re-vendoring from `openRepoTools-code`) are workBenches follow-ups that wait for real legs; nothing here changes workBenches. + +## Lane 2's open items on #191 (#186 comment 6090038382) + +Lane 2's matrix ran on `6568f08` with v3, and lane 2 re-ran it on each later head: + +- at `cddd1f7`, of 341 rows only the new row 25 changed, and it is fixed (#186 comment 6103618033); +- at `8246983`, 0 of 341 verdicts differ from `cddd1f7` (lane 2's register note of 2026-10-13T18:22:15Z, `lanes/log/openRepoTools-2.md` in brett-wip). The installer is the same blob at both heads, `7aa697c`, and at `a74c7fc`. + +So each item below holds at the final head as measured. Each is fixed, accepted (with the reason), or given to another task: + +| Item | Disposition | +| --- | --- | +| 1. The walk residue: an unrelated pin whose `submodule_path` names this directory (lane 2's rows 24a–c, re-measured by lane 3 at `cddd1f7` in #191 review 5481368124, finding 5). Main installs in all three. | **Accepted residual (low), proposed for #193.** `fdc5f18` limited the walk to the two layouts the design names, and the pin must name this directory, but the walk takes that name match as identity. The code then reads the gitlink at that name, and where none is recorded it refuses instead of taking the plain-tree path main takes (`openRepoTools:894-897` at `8246983`). 24a, a complete tree at `outer/product` under `outer/contracts/code-pin.yaml` with `submodule_path: product`, exits 2 with "…/outer records no gitlink at product". 24c, an installed copy in `$H/bin` under a `$H` pin with `submodule_path: bin`, refuses to reinstall with "… records no gitlink at bin" after 0 requests. 24b, at `outer/worktrees/001-x/product`, installs with one extra line, "NOT the code … pins". Lane 3's cheaper tightening uses that same gitlink read: take the parent as the mount only when it records a gitlink at that name, and otherwise fall through to the plain-tree path (5481368124, finding 5). That would close 24a and 24c. #193 item 21 proposes the stricter identity match (the checkout's `origin` is the pin's `source_repository`, or its HEAD is the pinned commit). Unreachable today: no ancestor of a checkout or of `~/.local/bin` carries `contracts/` (lane 3, #191 review 5471121210, delta 4, and 5481368124, finding 5). #191's body sentence "A pin anywhere else above the file is another project's and is not read" is true except for this name match (5481368124, finding 7). | +| 2. A missing ref or repository is read as a single repository and refused in main's words, without naming the ref. | **Accepted, by the byte-for-byte rule.** These are main's words for the same request (`test_r11_a_single_repository_at_a_ref_it_lacks_refuses_in_the_words_it_always_did[gh, curl]`). That test is single-layout only. In lane 2's rows, an adopted assembly at a missing ref (19c, 19d, 19h) and a missing repository (19e, 19f) end the same way, in main's words, with one request more than main (35 against 34; 18 against 17 with curl only). | +| 3. A 502 on `contracts/code-pin.yaml` refuses where main installs. | **Accepted and disclosed.** It is the one extra request on today's layout. An unanswered layout question is a refusal, never a reading of the older layout (the design's "no silent legacy fallback"). #191's body says so. | +| 4. Garbage or an empty body at 200 on `code-pin.yaml` refuses where main installs, saying "the ref moved between the two reads" (lane 2's rows: 8 of 8 exit 2, with nothing placed and 0 payload requests). | **A refusal where main installs, like item 3: accepted, and disclosed here; clearer words proposed for #193 (item 20).** On today's layout, `probe_path` takes any `gh` success or curl 200 as a pin (`openRepoTools:656-658`, `:668` at `8246983`). The listing at the resolved commit then shows no pin, and the run refuses at `:761-764`. Main never asks for that file, so it installs. As for item 3, an answer that is not GitHub's 404 is never read as the older layout. #191's body does not disclose this case. It says that "anything but a 404 (offline, 5xx, 401/403, a portal)" refuses "naming what answered", and that the one new refusal on this layout is a layout question with no answer at all. A 200 that is not GitHub's, a portal's page among them, is an answer: it refuses, names nothing that answered, and blames a moved ref. The proposed words name both readings (the ref moved, or the first answer was not GitHub's). They keep "the ref moved", which `test_r07_a_branch_that_moves_right_after_the_layout_question_is_read_at_one_commit[single]` asserts. | +| 5. The offline reinstall from an installed copy refuses in different words from main's. | **Accepted and disclosed** in #191's body, under its one new refusal on today's layout, a layout question with no answer at all (lane 3's delta 3). Base refused there too. | +| 6. A missing pin on the adopted path is read as a single repository, so decoys at the root (synthetic) install. | **Accepted, by structure.** Asking `project.yaml` on the 404 would add a second request to today's layout. An adopter writes the pin and the manifest in one commit, and `make validate` refuses one without the other. A real assembly root carries no payload, so it refuses in main's words (`test_r08_an_assembly_root_without_its_code_pin_refuses_as_a_single_repository_would`). For T015: confirm that the real assembled root carries no payload file. | +| 7. v3 with pin = gitlink at code before #191. | **T015 item** (see T007 above). The cutover must pin a code commit that carries #191, and T015 checks it at the assembled head. Proposed for #193: the root refuses an implementation that lacks the adopted resolver (a declared marker, or `payload_resolve_remote()`), naming the pin as older than T007. The patch's header comment ("a stale or rolled-back pin is named as what it is") and its commit message ("not left to whichever code a stale pin selects") overstate: a rolled-back pin that agrees with its gitlink is not named. The patch stays as it is, because this residual is a named T015 item (lane 3, 5481368376). | +| 8. The adopted one-liner needs `jq` at the entry point on the curl path. | **T014** (the network-needs sentence below). | +| 9. Row 7, single: a branch that moves mid-install still mixes versions, as on main (lane 2's row 7 single: a failure, with 5 + 24 files placed). | **Accepted, by the byte-for-byte rule.** Freezing the ref needs api.github.com, which today's layout never needed. Row 7's tests are about an adopted ref. `test_r07_a_branch_that_moves_between_downloads_never_mixes_versions` is adopted only. In `test_r07_a_branch_that_moves_right_after_the_layout_question_is_read_at_one_commit`, the `[adopted]` case moves to another adopted commit and installs it whole. The `[single]` case moves an adopted ref to a commit with no pin, which is refused as "the ref moved". Neither is a single repository moving, and no test freezes one. | + +## For other tasks + +- **T009:** `test_the_installer_reaches_only_this_repository` holds because every fetching line names `$REPO`. Its docstring should say that the one other repository reached is the one `$REPO`'s own validated pin names. The composed hygiene results above are T006 §10.6's front-door findings. +- **T010:** a composed run whose code leg is checked out at a commit other than its pin will see `--install` from the mounted leg refuse (changed payload). Run install tests from the pinned state or from a standalone copy. +- **T014:** + - `venv_note`'s `docs/README-lanes.md` comment. + - The README's one-liners need a sentence on network needs after the cutover, next to the one-liner (lane 3's T4 on the v2 patch). Today's single repository needs raw.githubusercontent.com only, or `gh`. After the cutover, the curl one-liner needs api.github.com and `jq`. By reading v3 and the PR head, that is 6 api.github.com requests per install: the root makes 2 (`commits`, `git/trees`), and the implementation makes 4 (`commits`, two `git/trees`, the pinned commit). Lane 3 measured 5 on v2, which had no root tree read. So a gh-less machine that is rate-limited (60 an hour per address, unauthenticated) cannot install. +- **T015:** + - Validate at the real assembled head, with a code pin that carries this change, against the real GitHub. That includes the one-liners, `bump-leg.py`'s lockstep, and the 3.2 parse of the root entry point (`parse-macos` covers the implementation only). + - **Named item, the pre-T007 pin:** confirm that the pinned code carries #191 before the cutover. v3 does not refuse a lockstep-consistent pin at older code (lane 2's item 7). + - **Named item, a root with no payload:** confirm that the assembled root carries no payload file (lane 2's item 6). +- **#193 (follow-ups, proposed):** a capability check in the root entry point (item 7); a 200-garbage probe, which refuses where main installs, and clearer words for it (item 4); an identity match for the walk (item 1). + +## What this does not do + +It creates no repository, pushes nothing to run C, moves no default branch, rebinds no lane and authorizes no real conversion. Gate B remains open; Gate C is Brett Heap's word. diff --git a/specs/004-migrate-to-triad/patches/t007-assembly-entry-point.patch b/specs/004-migrate-to-triad/patches/t007-assembly-entry-point.patch new file mode 100644 index 0000000..407741b --- /dev/null +++ b/specs/004-migrate-to-triad/patches/t007-assembly-entry-point.patch @@ -0,0 +1,441 @@ +From 6acc147f751e5a2ca57b4814395769b7b75b339e Mon Sep 17 00:00:00 2001 +From: Brett Heap <1513478+brettheap@users.noreply.github.com> +Date: Fri, 9 Oct 2026 20:33:03 +0000 +Subject: [PATCH] Add the assembly's openRepoTools front door, which runs the + implementation the code pin names and nothing else. + +T007 of specs/004-migrate-to-triad/tasks.md (opensoft/openRepoTools#186). The +documented one-liners fetch openRepoTools from this repository's root, and +after the split the installer is the code leg's. This file keeps that line +working and adds no installer mechanics. + +In a checkout it requires the pin, the gitlink and the implementation's +bytes to name one commit, and then execs code/openRepoTools with every +argument. That is the same process, so the exit status and signals are the +implementation's. + +Anywhere else it resolves the ref once and reads contracts/code-pin.yaml at +that commit. It checks that the gitlink at the pin's submodule_path in that +commit is the pinned commit, so the lockstep rule is enforced here and not +left to whichever code a stale pin selects. Only then does it fetch +openRepoTools from the pinned repository at the pinned commit, and run it +from stdin with OPENREPOTOOLS_REF set to that commit, so the implementation +reads the same pin. + +The pin reader is a deliberate copy of the implementation's, guarded by the +code leg's tests/test_install_payload_source.py. + +Lane: openRepoTools-1 +Co-Authored-By: Claude Opus 5.5 +--- + openRepoTools | 398 ++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 398 insertions(+) + create mode 100755 openRepoTools + +diff --git a/openRepoTools b/openRepoTools +new file mode 100755 +index 0000000..ff432c6 +--- /dev/null ++++ b/openRepoTools +@@ -0,0 +1,398 @@ ++#!/usr/bin/env bash ++# SPDX-License-Identifier: Apache-2.0 ++# ++# openRepoTools — THE ASSEMBLY'S FRONT DOOR, and nothing else. ++# ++# The installer is the CODE leg's `openRepoTools`. This file is the one the ++# documented one-liners still fetch from this repository's root: ++# ++# curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoTools/main/openRepoTools | bash -s -- --install ++# gh api repos/opensoft/openRepoTools/contents/openRepoTools \ ++# -H 'Accept: application/vnd.github.raw' | bash -s -- --install ++# ++# and the one `./openRepoTools` runs in a checkout of this repository. It ++# SELECTS the implementation `contracts/code-pin.yaml` pins and hands it every ++# argument. It places nothing, merges nothing, writes no receipt and retires ++# nothing — every one of those is the implementation's, which is also what ++# `--install` puts on a PATH, so an installed `openRepoTools` never needs this ++# file or a checkout of this repository again (opensoft/openRepoTools#186, ++# T007; `specs/004-migrate-to-triad/installer-design.md`). ++# ++# * IN A CHECKOUT (this file beside `contracts/code-pin.yaml`): the pin, the ++# gitlink at its `submodule_path` and the implementation's bytes must name ++# ONE commit, and then this EXECs `/openRepoTools` with ++# every argument — the same process, so its exit status and every signal ++# are the implementation's own. That implementation then checks the rest ++# of the payload against the same pin before it copies a byte. ++# * ANYWHERE ELSE (stdin, or a copy of this file on its own): `$OPENREPOTOOLS_REF` ++# is resolved ONCE to a commit of `$OPENREPOTOOLS_REPO`, the code pin is ++# read AT that commit, and the gitlink at its `submodule_path` in that ++# commit must name the pinned commit too — the lockstep rule, checked HERE, ++# before any implementation is fetched or run, so a stale or rolled-back pin ++# is named as what it is rather than left to whichever code it selects. ++# Then the implementation is fetched from the pinned repository AT the ++# pinned commit and run from stdin — the way the one-liners always ran it — ++# with `$OPENREPOTOOLS_REF` set to that commit, so the implementation reads ++# the same pin and no branch that moves in between can give it another. ++# Reading the gitlink is one level of the assembly's tree: `gh api --jq`, ++# or `curl` with `jq` (an adopted assembly's one-liner needs api.github.com). ++# ++# NOTHING HERE ASSUMES A FILE THE LEGS MAY NOT CARRY: no `.gitattributes`, no ++# README, no AGENTS.md. Bytes are compared raw (`hash-object --no-filters`). ++# ++# bash 3.2 (macOS's /bin/bash): no `${x,,}`, `mapfile`, `declare -A` or ++# `local -n`, as everywhere in this project. ++ ++set -euo pipefail ++ ++REPO="${OPENREPOTOOLS_REPO:-opensoft/openRepoTools}" ++REF="${OPENREPOTOOLS_REF:-main}" ++SELF="${BASH_SOURCE[0]:-$0}" ++PIN_FILE="contracts/code-pin.yaml" ++ ++die() { printf '\nREFUSED: %s\n' "$1" >&2; exit "${2:-2}"; } ++ ++# --------------------------------------------------------------------------- ++# THE CODE PIN'S GRAMMAR — A DELIBERATE COPY of the implementation's own ++# reader (`pin_parse` and the five helpers above it in the code leg's ++# `openRepoTools`), byte for byte, because this file has to read the pin ++# BEFORE there is an implementation to ask. The code leg's ++# `tests/test_install_payload_source.py` fails when the two copies differ. ++# --------------------------------------------------------------------------- ++ ++ORT_ALNUM='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' ++ORT_TAB="$(printf '\t')" ++ORT_CR="$(printf '\r')" ++ ++is_lower_hex() { # ++ case "$2" in '' | *[!0123456789abcdef]*) return 1 ;; esac ++ [ "${#2}" -eq "$1" ] ++} ++ ++is_slug() { # — a repository as GitHub spells one, and nothing else ++ local owner="${1%%/*}" name="${1#*/}" ++ [ "$owner" != "$1" ] || return 1 ++ case "$owner" in '' | -* | *- | *[!${ORT_ALNUM}-]*) return 1 ;; esac ++ case "$name" in '' | . | .. | */* | *[!${ORT_ALNUM}._-]*) return 1 ;; esac ++ return 0 ++} ++ ++is_segment() { # — exactly one path segment ++ case "$1" in '' | . | .. | *[!${ORT_ALNUM}._-]*) return 1 ;; esac ++ return 0 ++} ++ ++PIN_SCALAR="" ++pin_scalar() { # — PIN_SCALAR, or 1 for a quote that does not close ++ local v="$1" q rest ++ while :; do case "$v" in ' '* | "$ORT_TAB"*) v="${v#?}" ;; *) break ;; esac; done ++ case "$v" in ++ '"'* | "'"*) ++ q="${v:0:1}" ++ rest="${v#?}" ++ case "$rest" in *"$q"*) ;; *) return 1 ;; esac ++ PIN_SCALAR="${rest%%"$q"*}" ++ rest="${rest#*"$q"}" ++ while :; do case "$rest" in ' '* | "$ORT_TAB"*) rest="${rest#?}" ;; *) break ;; esac; done ++ case "$rest" in '' | '#'*) ;; *) return 1 ;; esac ++ # No escapes in this grammar: a backslash is a value nobody wrote. ++ case "$PIN_SCALAR" in *\\*) return 1 ;; esac ++ ;; ++ '#'*) PIN_SCALAR="" ;; ++ *) ++ PIN_SCALAR="${v%% #*}" ++ while :; do ++ case "$PIN_SCALAR" in *' ' | *"$ORT_TAB") PIN_SCALAR="${PIN_SCALAR%?}" ;; *) break ;; esac ++ done ++ ;; ++ esac ++ return 0 ++} ++ ++pin_bad() { # — PIN_WHY, and 1 ++ if [ -z "$2" ]; then ++ PIN_WHY="\`$1\` is missing" ++ else ++ PIN_WHY="\`$1\` is '$2', $3" ++ fi ++ return 1 ++} ++ ++pin_parse() { # — the PIN_* fields and 0, or PIN_WHY and 1 ++ local line body key n=0 seen=" " block="" tree_seen=0 ++ PIN_SCHEMA="" PIN_KIND="" PIN_LEG_ROLE="" PIN_SOURCE_REPOSITORY="" ++ PIN_SUBMODULE_PATH="" PIN_COMMIT="" PIN_REVISION_KIND="" ++ PIN_DIGEST_ALGORITHM="" PIN_DIGEST_DEFINITION="" PIN_TREE_SHA256="" ++ PIN_VERIFY="" PIN_RUNBOOK="" PIN_WHY="" ++ [ -s "$1" ] || { PIN_WHY="it is empty"; return 1; } ++ while IFS= read -r line || [ -n "$line" ]; do ++ n=$((n + 1)) ++ case "$line" in ++ *"$ORT_CR"*) PIN_WHY="line $n ends in a carriage return, and the grammar is LF-only"; return 1 ;; ++ '' | '#'*) continue ;; ++ ' '* | "$ORT_TAB"*) ++ body="$line" ++ while :; do case "$body" in ' '* | "$ORT_TAB"*) body="${body#?}" ;; *) break ;; esac; done ++ case "$body" in '' | '#'*) continue ;; esac ++ # AN INDENTED LINE BELONGS TO THE KEY ABOVE IT, and the only one ++ # this reads into is `digests:`. ++ [ "$block" = digests ] || continue ++ key="${body%%:*}" ++ [ "$key" != "$body" ] || { PIN_WHY="line $n is not \`key: value\`"; return 1; } ++ pin_scalar "${body#*:}" || { PIN_WHY="line $n has a quoted value that does not close cleanly"; return 1; } ++ if [ "$key" = tree_sha256 ]; then ++ [ "$tree_seen" -eq 0 ] || { PIN_WHY="\`digests.tree_sha256\` appears twice"; return 1; } ++ tree_seen=1 ++ PIN_TREE_SHA256="$PIN_SCALAR" ++ fi ++ continue ;; ++ esac ++ block="" ++ key="${line%%:*}" ++ [ "$key" != "$line" ] || { PIN_WHY="line $n is not \`key: value\`"; return 1; } ++ case "$key" in ++ '' | *[!abcdefghijklmnopqrstuvwxyz0123456789_]*) ++ PIN_WHY="line $n's key '$key' is not a key this grammar has"; return 1 ;; ++ esac ++ case "$seen" in *" $key "*) PIN_WHY="\`$key\` appears twice"; return 1 ;; esac ++ seen="$seen$key " ++ pin_scalar "${line#*:}" || { PIN_WHY="line $n has a quoted value that does not close cleanly"; return 1; } ++ case "$key" in ++ schema_version) PIN_SCHEMA="$PIN_SCALAR" ;; ++ kind) PIN_KIND="$PIN_SCALAR" ;; ++ leg_role) PIN_LEG_ROLE="$PIN_SCALAR" ;; ++ source_repository) PIN_SOURCE_REPOSITORY="$PIN_SCALAR" ;; ++ submodule_path) PIN_SUBMODULE_PATH="$PIN_SCALAR" ;; ++ commit) PIN_COMMIT="$PIN_SCALAR" ;; ++ revision_kind) PIN_REVISION_KIND="$PIN_SCALAR" ;; ++ digest_algorithm) PIN_DIGEST_ALGORITHM="$PIN_SCALAR" ;; ++ digest_definition) PIN_DIGEST_DEFINITION="$PIN_SCALAR" ;; ++ verify_pin) PIN_VERIFY="$PIN_SCALAR" ;; ++ resync_runbook) PIN_RUNBOOK="$PIN_SCALAR" ;; ++ digests) ++ [ -z "$PIN_SCALAR" ] || { PIN_WHY="\`digests\` is '$PIN_SCALAR', not a mapping"; return 1; } ++ block=digests ;; ++ esac ++ done <"$1" ++ [ "$PIN_SCHEMA" = 1 ] || { pin_bad schema_version "$PIN_SCHEMA" "not 1"; return 1; } ++ [ "$PIN_KIND" = pinned_contract_manifest ] || ++ { pin_bad kind "$PIN_KIND" "not pinned_contract_manifest"; return 1; } ++ [ "$PIN_LEG_ROLE" = code ] || { pin_bad leg_role "$PIN_LEG_ROLE" "not code"; return 1; } ++ is_slug "$PIN_SOURCE_REPOSITORY" || ++ { pin_bad source_repository "$PIN_SOURCE_REPOSITORY" "not an owner/name repository"; return 1; } ++ is_segment "$PIN_SUBMODULE_PATH" || ++ { pin_bad submodule_path "$PIN_SUBMODULE_PATH" "not one path segment"; return 1; } ++ is_lower_hex 40 "$PIN_COMMIT" || ++ { pin_bad commit "$PIN_COMMIT" "not 40 lowercase hex — a branch, a tag or an abbreviated commit is a moving reference"; return 1; } ++ [ "$PIN_REVISION_KIND" = commit ] || { pin_bad revision_kind "$PIN_REVISION_KIND" "not commit"; return 1; } ++ [ "$PIN_DIGEST_ALGORITHM" = sha256 ] || ++ { pin_bad digest_algorithm "$PIN_DIGEST_ALGORITHM" "not sha256"; return 1; } ++ [ "$PIN_DIGEST_DEFINITION" = sorted-ls-tree-r-v1 ] || ++ { pin_bad digest_definition "$PIN_DIGEST_DEFINITION" "not sorted-ls-tree-r-v1"; return 1; } ++ is_lower_hex 64 "$PIN_TREE_SHA256" || ++ { pin_bad digests.tree_sha256 "$PIN_TREE_SHA256" "not 64 lowercase hex"; return 1; } ++ [ -n "$PIN_VERIFY" ] || { pin_bad verify_pin "" ""; return 1; } ++ [ -n "$PIN_RUNBOOK" ] || { pin_bad resync_runbook "" ""; return 1; } ++ return 0 ++} ++ ++# --------------------------------------------------------------------------- ++# IN A CHECKOUT: three facts, one commit, then the implementation itself. ++# --------------------------------------------------------------------------- ++ ++#: THE COMMIT A SUPERPROJECT RECORDS AT : the index first and HEAD ++#: second, the order openRepoShape's `recorded_gitlink` asks them in. ++recorded_gitlink() { # ++ # `read`, not `awk`: the offline path needs nothing a minimal PATH lacks. ++ local listed mode oid stage kind path ++ listed="$(git -C "$1" ls-files -s -- "$2" 2>/dev/null)" || listed="" ++ read -r mode oid stage path <<<"$listed" || : ++ if [ "$mode" != 160000 ] || [ "$stage" != 0 ] || [ "$path" != "$2" ]; then ++ listed="$(git -C "$1" ls-tree HEAD -- "$2" 2>/dev/null)" || listed="" ++ read -r mode kind oid path <<<"$listed" || : ++ [ "$mode" = 160000 ] && [ "$kind" = commit ] && [ "$path" = "$2" ] || return 1 ++ fi ++ is_lower_hex 40 "$oid" || return 1 ++ printf '%s\n' "$oid" ++} ++ ++delegate_local() { # ++ local root="$1" link code blob ++ shift ++ pin_parse "$root/$PIN_FILE" || die "$root/$PIN_FILE is not a code pin this can follow: $PIN_WHY. ++ A pin that is present and wrong is refused rather than read around; nothing ran." ++ link="$(recorded_gitlink "$root" "$PIN_SUBMODULE_PATH")" || ++ die "$root records no gitlink at $PIN_SUBMODULE_PATH, where $PIN_FILE mounts the code ++ leg, so which implementation it pins cannot be checked; nothing ran." ++ [ "$link" = "$PIN_COMMIT" ] || die "$root records $link at $PIN_SUBMODULE_PATH and $PIN_FILE says ++ $PIN_COMMIT. The gitlink and the pin move in ONE commit (the lockstep rule), so ++ this checkout does not say which implementation it means; nothing ran. Make ++ them agree — \`make validate\` names the drift — then re-run." ++ code="$root/$PIN_SUBMODULE_PATH" ++ [ -f "$code/openRepoTools" ] || die "the code leg is not checked out at $code, so there is no ++ implementation to run; nothing ran. Mount it, then re-run: ++ git -C \"$root\" submodule update --init --recursive (or: make bootstrap)" ++ git -C "$code" cat-file -e "$PIN_COMMIT^{commit}" 2>/dev/null || ++ die "the pinned commit $PIN_COMMIT is not in $code's object store, so the ++ implementation there cannot be checked against it; nothing ran. Run ++ \`make bootstrap\` at $root, then re-run." ++ blob="$(git -C "$code" rev-parse --verify --quiet "$PIN_COMMIT:openRepoTools" 2>/dev/null)" || ++ die "the pinned commit $PIN_COMMIT has no openRepoTools at its root; nothing ran." ++ [ "$(git -C "$code" hash-object --no-filters -- openRepoTools 2>/dev/null)" = "$blob" ] || ++ die "$code/openRepoTools is not the implementation $root pins ($PIN_SOURCE_REPOSITORY ++ at $PIN_COMMIT), so this front door does not run it; nothing ran. Put the code ++ leg back at its pin (git -C \"$code\" checkout $PIN_COMMIT, or make bootstrap), ++ or run a code checkout's own openRepoTools ON PURPOSE, which says what it installs." ++ exec "${BASH:-bash}" "$code/openRepoTools" ${1+"$@"} ++} ++ ++# --------------------------------------------------------------------------- ++# ANYWHERE ELSE: resolve once, read the pin there, run the pinned implementation. ++# --------------------------------------------------------------------------- ++ ++TMP="" ++ ++said() { # — its first line, as one clause ++ local first="" ++ IFS= read -r first <"$2" 2>/dev/null || : ++ case "$first" in ++ '') printf '%s failed and said nothing\n' "$1" ;; ++ "$1:"*) printf '%s\n' "$first" ;; ++ *) printf '%s: %s\n' "$1" "$first" ;; ++ esac ++} ++ ++#: `gh api` first and the raw URL second, the implementation's own order, into ++#: a file that is checked before it is used. WHY holds what both said. ++fetch_at() { # ++ WHY="" ++ if command -v gh >/dev/null 2>&1; then ++ if gh api "repos/$1/contents/$3?ref=$2" -H 'Accept: application/vnd.github.raw' \ ++ >"$4" 2>"$TMP/err" && [ -s "$4" ]; then ++ return 0 ++ fi ++ WHY="$(said gh "$TMP/err")" ++ else ++ WHY="gh: not installed" ++ fi ++ if command -v curl >/dev/null 2>&1; then ++ if curl -fsSL "https://raw.githubusercontent.com/$1/$2/$3" >"$4" 2>"$TMP/err" && ++ [ -s "$4" ]; then ++ return 0 ++ fi ++ WHY="$WHY; $(said curl "$TMP/err")" ++ else ++ WHY="$WHY; curl: not installed" ++ fi ++ return 1 ++} ++ ++#: THE GITLINK `$REPO` RECORDS AT IN , from one level of its tree: ++#: `gh --jq` first, `curl` + `jq` second. 0 with GITLINK — empty when the tree ++#: holds no gitlink there, which is an answer — or 1 with WHY when neither ++#: transport answered. is a pin's `submodule_path`, which `pin_parse` ++#: has already held to one segment of letters, digits, `.`, `_` and `-`. ++gitlink_at() { # ++ local filter=".tree[] | select(.type == \"commit\" and .path == \"$2\") | .sha" ++ GITLINK="" WHY="" ++ if command -v gh >/dev/null 2>&1; then ++ if gh api "repos/$REPO/git/trees/$1" --jq "$filter" >"$TMP/link" 2>"$TMP/err"; then ++ IFS= read -r GITLINK <"$TMP/link" || : ++ return 0 ++ fi ++ WHY="$(said gh "$TMP/err")" ++ else ++ WHY="gh: not installed" ++ fi ++ if ! command -v curl >/dev/null 2>&1; then ++ WHY="$WHY; curl: not installed" ++ elif ! command -v jq >/dev/null 2>&1; then ++ WHY="$WHY; curl: not asked, because reading api.github.com's JSON needs \`jq\` and there is none" ++ elif ! curl -fsSL "https://api.github.com/repos/$REPO/git/trees/$1" >"$TMP/tree.json" 2>"$TMP/err"; then ++ WHY="$WHY; $(said curl "$TMP/err")" ++ elif jq -er '.tree | arrays | length >= 0' "$TMP/tree.json" >/dev/null 2>&1 && ++ jq -r "$filter" "$TMP/tree.json" >"$TMP/link" 2>/dev/null; then ++ IFS= read -r GITLINK <"$TMP/link" || : ++ return 0 ++ else ++ WHY="$WHY; curl: an answer that is not a tree listing" ++ fi ++ return 1 ++} ++ ++delegate_remote() { # ++ local sha="" ref="" impl ++ is_slug "$REPO" || die "\$OPENREPOTOOLS_REPO is '$REPO', which is not an owner/name repository; nothing ran." ++ TMP="$(mktemp -d)" ++ trap 'rm -rf -- "$TMP"' EXIT ++ # `%` and `#` mean something else in a URL path; nothing else a ref may hold does. ++ ref="$(printf '%s' "$REF" | sed -e 's/%/%25/g' -e 's/#/%23/g')" ++ WHY="" ++ if command -v gh >/dev/null 2>&1; then ++ if gh api "repos/$REPO/commits/$ref" -H 'Accept: application/vnd.github.sha' \ ++ >"$TMP/sha" 2>"$TMP/err"; then ++ IFS= read -r sha <"$TMP/sha" || : ++ is_lower_hex 40 "$sha" || { sha=""; WHY="gh: an answer that is not a commit"; } ++ else ++ WHY="$(said gh "$TMP/err")" ++ fi ++ else ++ WHY="gh: not installed" ++ fi ++ if [ -z "$sha" ]; then ++ if command -v curl >/dev/null 2>&1 && ++ curl -fsSL -H 'Accept: application/vnd.github.sha' \ ++ "https://api.github.com/repos/$REPO/commits/$ref" >"$TMP/sha" 2>"$TMP/err"; then ++ IFS= read -r sha <"$TMP/sha" || : ++ is_lower_hex 40 "$sha" || { sha=""; WHY="$WHY; curl: an answer that is not a commit"; } ++ elif command -v curl >/dev/null 2>&1; then ++ WHY="$WHY; $(said curl "$TMP/err")" ++ else ++ WHY="$WHY; curl: not installed" ++ fi ++ fi ++ [ -n "$sha" ] || die "could not resolve $REPO at $REF to a commit, so no implementation was ++ selected and nothing ran. What answered: ++ $WHY" ++ fetch_at "$REPO" "$sha" "$PIN_FILE" "$TMP/pin" || die "could not read $PIN_FILE from $REPO at ++ $sha ($REF), so which implementation it pins is not known and nothing ran. ++ What answered: ++ $WHY ++ This is the assembly's front door. A single repository is installed by its own ++ root openRepoTools: gh api repos/$REPO/contents/openRepoTools?ref=$REF ..." ++ pin_parse "$TMP/pin" || die "$PIN_FILE in $REPO at $sha ($REF) is not a code pin this can ++ follow: $PIN_WHY. A pin that is present and wrong is refused rather than read ++ around; nothing ran." ++ # THE LOCKSTEP RULE, BEFORE ANYTHING IS FETCHED TO RUN: the gitlink at the ++ # pinned path in this same commit names the pinned commit, or nothing runs. ++ gitlink_at "$sha" "$PIN_SUBMODULE_PATH" || die "could not list $REPO at $sha ($REF), so the gitlink its $PIN_FILE ++ must agree with cannot be read, and nothing ran. What answered: ++ $WHY" ++ [ "$GITLINK" = "$PIN_COMMIT" ] || die "$REPO at $sha ($REF) records ${GITLINK:-no gitlink} at $PIN_SUBMODULE_PATH and ++ its $PIN_FILE says $PIN_COMMIT. The gitlink and the pin move in ONE commit ++ (the lockstep rule), so this assembly does not say which code it means, and ++ nothing ran." ++ fetch_at "$PIN_SOURCE_REPOSITORY" "$PIN_COMMIT" openRepoTools "$TMP/impl" || ++ die "could not fetch openRepoTools from $PIN_SOURCE_REPOSITORY at $PIN_COMMIT, the ++ implementation $REPO at $sha ($REF) pins, so nothing ran. What answered: ++ $WHY" ++ case "$(head -n 1 "$TMP/impl")" in ++ '#!'*) ;; ++ *) die "what came back for openRepoTools from $PIN_SOURCE_REPOSITORY at $PIN_COMMIT is not a ++ script, so nothing ran." ;; ++ esac ++ impl="$(cat "$TMP/impl")" ++ rm -rf -- "$TMP" ++ trap - EXIT ++ # THE REF THE IMPLEMENTATION RESOLVES IS THE COMMIT THIS ONE READ, so the ++ # pin it reads is this pin; it runs from stdin, as the one-liners run it. ++ OPENREPOTOOLS_REPO="$REPO" ++ OPENREPOTOOLS_REF="$sha" ++ export OPENREPOTOOLS_REPO OPENREPOTOOLS_REF ++ exec bash -s -- ${1+"$@"} <<<"$impl" ++} ++ ++if [ -f "$SELF" ] && [ -f "$(cd -- "$(dirname -- "$SELF")" && pwd)/$PIN_FILE" ]; then ++ delegate_local "$(cd -- "$(dirname -- "$SELF")" && pwd)" ${1+"$@"} ++fi ++delegate_remote ${1+"$@"} +-- +2.43.0 + diff --git a/specs/004-migrate-to-triad/patches/t010-assembly-exact-pin-job.patch b/specs/004-migrate-to-triad/patches/t010-assembly-exact-pin-job.patch new file mode 100644 index 0000000..2b51c62 --- /dev/null +++ b/specs/004-migrate-to-triad/patches/t010-assembly-exact-pin-job.patch @@ -0,0 +1,128 @@ +From 386be81959ea32836be0777c1af8547cac044e2d Mon Sep 17 00:00:00 2001 +From: openRepoTools CI +Date: Thu, 8 Oct 2026 21:52:59 +0000 +Subject: [PATCH] Add the assembly's exact-pin integration job as its own + workflow, which runs the code leg's suite composed and refuses any absent + context. + +validate.yml is a pinned shape copy, so the job lives in composed.yml. +T010 of specs/004-migrate-to-triad/tasks.md (#186). + +Lane: openRepoTools-1 +--- + .github/workflows/composed.yml | 103 +++++++++++++++++++++++++++++++++ + 1 file changed, 103 insertions(+) + create mode 100644 .github/workflows/composed.yml + +diff --git a/.github/workflows/composed.yml b/.github/workflows/composed.yml +new file mode 100644 +index 0000000..ef28e08 +--- /dev/null ++++ b/.github/workflows/composed.yml +@@ -0,0 +1,103 @@ ++name: composed ++ ++# THE EXACT-PIN INTEGRATION GATE (opensoft/openRepoTools#186, T010 of ++# `specs/004-migrate-to-triad/tasks.md`; FR-008). `validate` beside this file ++# says the three records of each leg agree — gitlink, pin, digest. This says ++# the code those records name PASSES ITS OWN SUITE, composed with the spec leg ++# and the dependency it was pinned beside: the whole arrangement at the exact ++# commits this root records, and nothing allowed to skip its way to green. ++# ++# WHY A SIBLING AND NOT A JOB IN `validate.yml`: that file is a COPY of ++# openRepoShape's, digest-pinned in `contracts/shape-pin.yaml`. An edit to it ++# is drift `scripts/validate-pins.py` and `status` both report. This file is ++# this project's own, and it references no leg by `/@`, so the ++# lockstep rule has nothing in it to move. ++# ++# LINUX ONLY, on purpose. The platform matrix — macOS's bash 3.2 parse and its ++# `ready`-label suite, Windows and the no-submodule skip path — is the code ++# leg's own `tests` workflow, and it stays there. What only this root can run ++# is the COMPOSED suite: `OPENREPOTOOLS_COMPOSED=1` turns every absent root ++# into a refusal and every check that would have skipped for want of one into ++# a failure, so a green run here is a claim about all three legs at once. ++# ++# IT REFUSES ABSENT CONTEXT TWICE OVER. The step below fails on any missing ++# leg, pin or dependency file before a test runs; and the suite's own ++# `tests/conftest.py` refuses a composed session (pytest's exit 4) when the ++# assembly or spec root is absent, the dependency is not initialized, or the ++# code or spec leg is not at the commit this root records for it. A leg this ++# job cannot fetch — a private leg with no credential — fails the checkout, ++# which is the same refusal: composed acceptance with a leg missing is the one ++# thing this job must never report. The legs are fetched over https, as ++# `.gitmodules` names them; nothing here allows a `file://` transport. ++ ++on: ++ pull_request: ++ push: ++ branches: [main] ++ workflow_dispatch: ++ ++jobs: ++ exact-pins: ++ runs-on: ubuntu-latest ++ steps: ++ # PINNED BY COMMIT, the same two actions the code leg pins. RECURSIVE: ++ # the legs, and the dependency nested in the code leg. ++ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 ++ with: ++ submodules: recursive ++ fetch-depth: 1 ++ persist-credentials: false ++ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 ++ with: ++ python-version: '3.x' ++ ++ - name: every context the composed run claims is here ++ run: | ++ set -euo pipefail ++ missing=0 ++ for need in project.yaml contracts/code-pin.yaml contracts/spec-pin.yaml \ ++ code/openRepoTools code/tests/run.sh code/tests/conftest.py \ ++ code/contracts/openreposhape-pin.yaml \ ++ code/upstream/openRepoShape/scripts/repo_shape.py \ ++ code/upstream/openRepoShape/templates/workspace-root/README.md; do ++ if [ ! -f "$need" ]; then ++ echo "::error::the composed run needs $need, and this checkout has none" ++ missing=1 ++ fi ++ done ++ if [ ! -d spec/openspec ] && [ ! -d spec/specs ] && [ ! -d spec/docs ]; then ++ echo "::error::the spec leg is not checked out at spec/" ++ missing=1 ++ fi ++ git submodule status --recursive ++ exit "$missing" ++ ++ # The same two the `validate` workflow runs, here as the gate's FIRST ++ # half: a suite that passes at a gitlink the pin does not name passes ++ # at the wrong commit. ++ - name: the manifest and the lockstep pins ++ run: | ++ python3 scripts/validate-manifest.py ++ python3 scripts/validate-pins.py ++ ++ - name: install the test runner ++ run: python3 -m pip install --disable-pip-version-check pytest==8.3.4 ++ ++ - name: git identity for the fixture commits (env, not global config) ++ run: | ++ { ++ echo "GIT_AUTHOR_NAME=openRepoTools CI" ++ echo "GIT_AUTHOR_EMAIL=ci@openrepotools.invalid" ++ echo "GIT_COMMITTER_NAME=openRepoTools CI" ++ echo "GIT_COMMITTER_EMAIL=ci@openrepotools.invalid" ++ } >> "$GITHUB_ENV" ++ ++ # THE CODE LEG'S OWN ENTRY POINT, serialized as it is everywhere: the ++ # roots are named, not inferred, and `-rs` prints any skip that remains ++ # (a platform one; none for want of a root can reach this far). ++ - name: the code leg's suite, composed, at the exact pins ++ env: ++ OPENREPOTOOLS_COMPOSED: '1' ++ OPENREPOTOOLS_ASSEMBLY_ROOT: ${{ github.workspace }} ++ OPENREPOTOOLS_SPEC_ROOT: ${{ github.workspace }}/spec ++ run: bash code/tests/run.sh -rs +-- +2.43.0 + diff --git a/specs/004-migrate-to-triad/patches/t010-code-leg-tests-workflow.patch b/specs/004-migrate-to-triad/patches/t010-code-leg-tests-workflow.patch new file mode 100644 index 0000000..199bcd6 --- /dev/null +++ b/specs/004-migrate-to-triad/patches/t010-code-leg-tests-workflow.patch @@ -0,0 +1,88 @@ +From b95d667339a5cef54605a949cdcae2d264dd3b6a Mon Sep 17 00:00:00 2001 +From: openRepoTools CI +Date: Thu, 8 Oct 2026 21:53:33 +0000 +Subject: [PATCH] Name the code leg's standalone checkout context in its tests + workflow (T010, #186). + +--- + .github/workflows/tests.yml | 36 ++++++++++++++++++++++++++++++++++-- + 1 file changed, 34 insertions(+), 2 deletions(-) + +diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml +index e27bc64..f078694 100644 +--- a/.github/workflows/tests.yml ++++ b/.github/workflows/tests.yml +@@ -21,6 +21,25 @@ name: tests + # job. A suite nothing runs is not a suite (act 3, obligation 6). It is bash + # and it skips on Windows, like every other bash claim here; the macOS + # `/bin/bash -n` step below parses it under bash 3.2 whatever it does. ++# ++# THIS IS THE CODE LEG'S WORKFLOW, AND EVERY JOB IN IT IS STANDALONE ++# (opensoft/openRepoTools#186, T010 of `specs/004-migrate-to-triad/tasks.md`). ++# The leg is cloned at its own root: `actions/checkout` puts the code root at ++# `$GITHUB_WORKSPACE`, named below as `OPENREPOTOOLS_CODE_ROOT`, and ++# `submodules: true` initializes the one submodule the leg has, the dependency ++# `upstream/openRepoShape`. There is no assembly above it and no spec leg ++# beside it, so `tests/conftest.py` resolves neither: the checks of the ++# documents those two own (`README.md`, `AGENTS.md`, `CLAUDE.md`, ++# `docs/README-lanes.md`) SKIP here naming the root they need, and `-rs` ++# prints every one. `OPENREPOTOOLS_COMPOSED: '0'` says in the file what the ++# jobs are: nothing here is composed acceptance. That claim is the assembly ++# root's `composed` workflow, which checks out all three at their exact pins ++# and runs this same suite with nothing allowed to skip. ++# ++# The platform policy is unchanged by the move: `tests`, `tests-no-submodule`, ++# `tests-windows`, `parse-macos` and `guard-launch-mode` on every ++# `pull_request` push, `tests-macos` on the `ready` label, on main, nightly ++# and on dispatch — Brett Heap's RULING of 2026-09-16 travels with the code. + + # `pull_request` names `labeled` beside GitHub's own three defaults + # (`opened`, `synchronize`, `reopened`): the coordinator applying `ready` to +@@ -45,6 +64,13 @@ on: + - cron: '17 4 * * *' # 04:17 UTC, nightly + workflow_dispatch: + ++# THE CHECKOUT CONTEXT, NAMED (#186, T010): the code root is the checkout's ++# own root, and the run is standalone. `github.workspace` is the same root on ++# every runner this file uses, Windows' `D:\a\...` included. ++env: ++ OPENREPOTOOLS_CODE_ROOT: ${{ github.workspace }} ++ OPENREPOTOOLS_COMPOSED: '0' ++ + jobs: + # Keep the process launch contract independently visible while the full + # lane-helper suite runs. Each GitHub job has its own workstation runner; +@@ -110,15 +136,19 @@ jobs: + echo "GIT_COMMITTER_EMAIL=ci@openrepotools.invalid" + } >> "$GITHUB_ENV" + ++ # `-rs`: in the leg the document checks SKIP for want of the assembly and ++ # spec roots, and a skip a person cannot read is a skip nobody audits. + - name: tests +- run: python3 -m pytest tests -q ++ run: python3 -m pytest tests -q -rs + + # THE SKIP PATH, PROVED. A fork's first command is `git clone` without + # `--recurse-submodules` and then `pytest`, and if that goes RED the fork is + # one nobody finishes. So one job checks out with NO submodules and asserts + # the suite still exits 0: the estate tests skip, naming + # `git submodule update --init upstream/openRepoShape`, and the hygiene and +- # installer tests — which need nothing but this repository — still run. ++ # installer tests — which need nothing but this repository — still run. In ++ # the leg the document checks skip too, naming the assembly or spec root, ++ # exactly as they do in `tests` above: no submodule changes nothing there. + # + # `pytest -q` exits 0 on skips and 1 on failures, so the job's own status IS + # the assertion; `-rs` prints the skip reasons, which is what a person +@@ -151,6 +181,8 @@ jobs: + # THE SAME SUITE ON WINDOWS, because a person on Windows still CLONES this + # repository and reads it. What this job proves is the checkout — that the + # `.gitattributes` rule holds and the three bash files are LF in the index — ++ # (in the leg that is the LEG'S OWN `.gitattributes`: git never reads the ++ # assembly's into a submodule, so the leg must carry the rule itself) — + # and the hygiene tests, which need no shell. The estate and installer tests + # carry `WINDOWS_SKIP` in `tests/conftest.py`: these files are bash, there is + # no PowerShell twin to run instead, and on Windows the way in is WSL2. +-- +2.43.0 + diff --git a/specs/004-migrate-to-triad/root-guidance-2026-10-08.md b/specs/004-migrate-to-triad/root-guidance-2026-10-08.md index ed61085..c70514e 100644 --- a/specs/004-migrate-to-triad/root-guidance-2026-10-08.md +++ b/specs/004-migrate-to-triad/root-guidance-2026-10-08.md @@ -3,7 +3,7 @@ **Task**: T006 of [tasks.md](tasks.md), for opensoft/openRepoTools#186. **Writer**: lane openRepoTools-1, on 2026-10-08 and 2026-10-09. **Evidence branch**: `004-migrate-to-triad-root-guidance`. -**State**: rehearsal evidence only. Nothing here lands on `main`, and today's layout is untouched. +**State**: rehearsal evidence only. No patch here is applied to today's layout, which is untouched. (Corrected 2026-10-09: this line first said "Nothing here lands on `main`", but #192 brought this document and the patch files to main as evidence.) The 2026-10-07 build of this task ran against run B. It was lost when the bench restart cleared `/tmp`: clones, patches and logs alike. Nothing from it was ever @@ -28,6 +28,36 @@ three run C remotes and of the openRepoShape mirror. Nothing was written into the rehearsal directory, and nothing reached a real remote except this evidence branch. +**No receipt (added 2026-10-09, #193 item 11).** T006's evidence cites no +private receipt. None is named here, in the five patches, in #192's +description or in T006's comment on #186 (6071824778). brett-wip's +`migration/openRepoTools/`, which holds the migration's receipts, has none +for T006 either (read 2026-10-09). Run C has one, +`adopter-fix-20261008-1HztL0`. + +What the committed patches let anyone reproduce from public inputs alone +(main `837928a` and openRepoShape `7f84ca4`), checked on 2026-10-09: +- the patch SHA256s below; +- the leg trees: `code-0001` and `spec-0001`, applied with `git am` to the + paths the plan gives each leg at `837928a` (trees `3915e78` and + `ad6cdcb`), give exactly `ca65c3b9…` and `10256135…`; +- `assembly-0001`, applied with `git am` to `837928a`'s six root files, + gives section 2's 340-line `AGENTS.md` and 510-line `README.md`. + +The exact commit IDs, the assembly tree `6eb8145e…`, bootstrap and +`make validate` need run C's host-local arrangement. These rows exist only +in this document: +- section 4's doctor and `shape_advisory` rows; +- section 7's `setup-openspeckit`, paired-selection, `check-prerequisites`, + worktree-override and store-precedence rows, and its `openspec` 1.6.0 + exits; +- section 6's 85-token census of cited hashes, whose tokens were resolved + against the original checkout's branches as well as `main`; +- section 9's protocol hashes. + +T006's #186 comment repeats some of those exit codes from the same work, +which is not separate evidence. + The patches are in [patches/t006/](patches/t006/), as `git format-patch` output: @@ -178,6 +208,7 @@ doctor's row for it is a `note`, which the shape's own agent rules say is - Pre-split code paths appear in 17 files under `openspec/` and `specs/`. - In completed features (002, 003 and the four other changes) they record acts done against the monorepo at a commit. Example: specs/002 tasks.md:9, "`bash tests/run.sh -k …` passed 165 checks … on implementation commit `64f4e93`". Rewriting such a line would falsify the record. - **Feature 004's own working files** carry 23 mentions in 9 files: plan.md 4, installer-design.md 4, proposal.md 4, design.md 3, adoption-plan.yaml 3, tasks.md 2, adopter-rehearsal.md 2, and spec.md, quickstart.md, preparation.md and adopter-blocker.md with 1 each. These are T017's to translate with the feature. + - *Noted 2026-10-09 (#193 item 9):* that list adds up to 26 mentions in 11 files, not 23 in 9. Section 10 and T006's comment on #186 (6071824778) repeat 23. The counting rule is not recorded, and three plausible recounts here reproduced neither the list nor the total, so which figure is right is not established. - 005's `analysis.md`:13 is likewise T016/T017's. - **`docs/README-lanes.md` (lane 3: the manual's content).** 21 lines name code-leg files: - lines 72, 572, 3601, 4163: `tests/test_lane_helpers.sh`; @@ -192,7 +223,7 @@ doctor's row for it is a `note`, which the shape's own agent rules say is - `claude-current` 2: line 89 is a comment, and line 123 is `--help` output, so changing it changes the shipped command. Its owner is whoever owns `claude-current`'s text; it is not changed here. - A comment-only repair ahead of the functional change beside it would leave the comment disagreeing with the code. - **Code-leg OpenSpec object keys (lane 3).** `lanes-edit.sh` (12 lines, e.g. 280, 2770, 2814–2827, 2895–2897) and `lane-handoff`:2439 key a claim as `owner/repo:openspec/changes/`. After the split a change lives in `opensoft/openRepoTools-spec`, not the assembly. -- **External referrers (lane 2's reads; follow-ups, not edited here).** From lane 2's `consumer-reads-2026-10-07.md` on `004-migrate-to-triad-reads` (76dcb93, which contains ae70dc5); line numbers are as lane 2 recorded them. +- **External referrers (lane 2's reads; follow-ups, not edited here).** From lane 2's `consumer-reads-2026-10-07.md` on `004-migrate-to-triad-reads` (76dcb93, which contains ae70dc5); line numbers are as lane 2 recorded them. That file has been on main since #189 (`63810dd`), in a later revision than `76dcb93`'s; what is cited here is as read at `76dcb93` (noted 2026-10-09). - **brett-wip:** - `lanes/LANES.md`:36 and :1305; - 12 `docs/README-lanes` lines in 9 files (first: `handoffs/openRepoTools/brief-lane-openRepoTools-1-2026-09-16.md`:9, `session-handoff-2026-09-16-lane-openRepoTools-1.md`:33, `lanes/log/openRepoTools-1.md`:22); @@ -394,9 +425,11 @@ Lane 2 is reading that side. 4. **`shape/` copies.** Keep `shape/.gitignore` and `shape/.gitattributes` (pinned). `shape/AGENTS.md`, `shape/CLAUDE.md` and `shape/README.md` are unpinned, and the shape's own follow-up says to delete the first two. Their deletion is left to T014's reviewer. 5. **Doctor placement.** `MISPLACED (3 paths)` (`code/commands/`, `code/contracts/`, `code/skills/`) is the adoption plan's reasoned overrides, identical before and after. Relay it; do not move files. 6. **T009: the hygiene tests read the wrong front door after the split.** - - 17 test functions read `README.md`, `AGENTS.md`, `CLAUDE.md`, `.gitattributes` or `docs/README-lanes.md` at the **code leg's** root: 16 in `test_repo_hygiene.py`, 1 in `test_openrepotools_command.py`. - - Measured on the code leg alone: before `code-0001`, 17 failed (all `FileNotFoundError`). After it, 13 fail (4 `FileNotFoundError` on `docs/README-lanes.md`, 9 assertions on the short leg README and AGENTS). - - **4 now pass only because they read the leg's own files:** `test_agents_md_is_short_enough_to_be_read`, `test_readme_is_short_enough_to_be_read`, `test_claude_md_points_at_agents_md` and `test_the_root_carries_the_line_ending_rule`. A green there is about the wrong file. + - At `837928a`, 18 test functions read `README.md`, `AGENTS.md`, `CLAUDE.md`, `.gitattributes` or `docs/README-lanes.md` at the **code leg's** root: 17 in `test_repo_hygiene.py`, 1 in `test_openrepotools_command.py` (`test_the_help_and_the_readme_say_the_mode_the_receipt_is_born_at`). *Corrected 2026-10-09 (#193 item 9): this line first said 17, as 16 + 1.* + - Measured on run C's code leg (`837928a`'s code paths) alone with `-k repo_hygiene`, which selects only `test_repo_hygiene.py`: before `code-0001`, 17 failed (all `FileNotFoundError`). After it, 13 fail (4 `FileNotFoundError` on `docs/README-lanes.md`, 9 assertions on the short leg README and AGENTS). + - The command test is outside that subset. Called directly on `837928a`'s code paths on 2026-10-09, it fails before `code-0001` (`FileNotFoundError` on `README.md`) and still fails after it (`AssertionError`: the leg's README has no install-receipt paragraph). + - *Since #190 (`f87fd5a`; noted 2026-10-10):* 17 of the 18 read their file from the root that owns it, through `ROOTS.path_for` in `tests/conftest.py`. `README.md`, `AGENTS.md` and `CLAUDE.md` belong to the assembly root and `docs/README-lanes.md` to the spec root, so in a standalone code leg these 17 skip, and a composed run reads them from those roots. The 18th, `test_the_root_carries_the_line_ending_rule`, deliberately reads the code root's own `.gitattributes`. Called directly on main's code paths (`f87fd5a`), the 17 skip, including the command test. The 18th raises `FileNotFoundError` before `code-0001` and passes after it. + - **At `837928a`, 4 passed only because they read the leg's own files:** `test_agents_md_is_short_enough_to_be_read`, `test_readme_is_short_enough_to_be_read`, `test_claude_md_points_at_agents_md` and `test_the_root_carries_the_line_ending_rule`. For the first three, a green there was about the wrong file. *Corrected 2026-10-11 (lane 3's 5481348731 on #196, item 8): this line first said "4 now pass" and, of all four, "A green there is about the wrong file". Since #190 (`f87fd5a`), the first three read through `ROOTS.path_for`, so in a standalone leg they skip, with or without the leg's own `AGENTS.md`, `CLAUDE.md` and `README.md` from `code-0001`. The fourth still reads the code root's own `.gitattributes`, as it did at `837928a`. #190 changed only its docstring, which makes that read deliberate: the root's attributes never reach a submodule (finding 2). So its green is about the right file. Run directly on main's code paths (`f87fd5a`, spec trees and front-door files removed): before `code-0001`, 3 skip and 1 fails (`FileNotFoundError` on `.gitattributes`); after it, 3 skip and 1 passes.* - Pointed back at the assembly root, the two length caps (`<= 316`, `<= 486`, today's counts) must rise to 340 and 510, each in a dated entry naming the rule its lines buy: - AGENTS.md: the shape's first-line pointer and the triad map the bootstrap protocol requires. - README.md: the lockstep-invariant anchor the leg pins name, and the three-repository front door. @@ -408,7 +441,7 @@ Lane 2 is reading that side. - `repos.tsv` has no `openRepoTools-spec` or `openRepoTools-code` aliases. - The installed handoff skill's writer-poll globs miss `/worktrees/`. - The checkout's `/handoffs` symlink and `.git/info/exclude` persist through the in-place adoption. -8. **T017.** Feature 004's own working files in the spec leg carry 23 pre-split path mentions. Some cited 004 commits (`dda91f1`, `52ce64e`) resolve only while the 004 branch or #187's PR ref survives. +8. **T017.** Feature 004's own working files in the spec leg carry the pre-split path mentions listed in section 5: 23 by its stated total, 26 in 11 files by its per-file list, which is not settled (see there). Some cited 004 commits (`dda91f1`, `52ce64e`) resolve only while the 004 branch or #187's PR ref survives. 9. **T007.** `assembly-0001`'s README sentence assumes T007's design: the root `openRepoTools` hands over to `code/openRepoTools` at the pinned commit. `code/openRepoTools`:1218 (`venv_note`) still cites `docs/README-lanes.md`. ## 11. What this task did not do diff --git a/specs/004-migrate-to-triad/t018-disposable-2026-10-09.md b/specs/004-migrate-to-triad/t018-disposable-2026-10-09.md new file mode 100644 index 0000000..9208f40 --- /dev/null +++ b/specs/004-migrate-to-triad/t018-disposable-2026-10-09.md @@ -0,0 +1,253 @@ +# T018, disposable half: feature selection, OpenSpec and the estate verbs in a triad fixture — October 9, 2026 + +**Task:** [T018](tasks.md)'s disposable workflow checks under +[#186](https://github.com/opensoft/openRepoTools/issues/186). The tasks.md +dependency section allows this as provisional rehearsal before Gate C. No task +is checked complete by it. Real leg repositories, the real cutover, lane +rebinding and WIP transitions in real estates come after Gate C. None of them +was attempted. +**Authority:** Brett Heap's words of 2026-10-07, "start the triad migration" +(#186), and of 2026-10-09, "usage reset, resume all. read handoff and resume +and fan out wide and do as much as possible in parallel". +**Fixture:** the composed candidate of 2026-10-09: assembly `3bd95a7` +(run C's split `69b8924` plus the T006, T007 and T010 follow-ups and two leg +bumps), spec leg `9e1d006`, code leg `f0af7d9`, and the code leg's nested +`upstream/openRepoShape` at `7f84ca4`. Every repository was copied fresh into +this run's own bare remotes. The candidate and run C were only read. +**Logs:** host-local, under the run directory (`/logs/00…93`), and not +committed. Below, `` is that directory and `~` is the temporary HOME of +the workstation named. + +## 1. The sandbox + +| Concern | How it was held | +| --- | --- | +| HOME | Three temporary homes, `/home1`, `home2` and `home3`, one per simulated workstation. Every command ran under `env -i` with only the variables below. | +| Agent root | `AGENT_PROTOCOL_ROOT=~/.agents`: a dereferenced copy (`cp -rL`) of the real agent root, with its own two-line `workspace.yaml` (`repository: t018-sandbox/t018-wip`, `path: ~/wip/t018-wip`). The real `workspace.yaml` was never written; its sha256 and mtime are unchanged. | +| Lane register | No `LANES_*` variable reached any command. No lane command ran in the fixture. | +| Git transport | `GIT_ALLOW_PROTOCOL=file`; `protocol.file.allow=always`. Each home's `~/.gitconfig` points `https://github.com/opensoft/`, `git@github.com:opensoft/` and run C's bare-remote prefix (the URLs the candidate's `.gitmodules` carries) at `/remotes/`, which holds this run's own bare copies plus a seeded disposable WIP repository. | +| Network | Every command ran inside `unshare --map-current-user --net`, a namespace in which a TCP connect fails with "Network is unreachable". Refusing `gh`/`curl`/`wget` shims logged two calls, both `gh api` from `status --fetch`, and both were refused. | +| Commands under test | Installed into each temporary HOME by the candidate assembly's own front door, `./openRepoTools --install`. That printed "every payload file was checked against that commit and nothing fetched", "17 of 17 placed" and "29 placed files recorded". The installed `park`, `resume`, `status` and `openRepoTools` are byte-identical to the code leg's blobs (`132ce6d…`, `92fd430…`, `bb5c255…`, `04769c4…`). | +| Speckit overlay | `setup-openspeckit` and its overlay templates were copied byte for byte from workBenches `ffd5516` (44 files, each equal to its `HEAD` blob) and run with `WORKBENCHES_ROOT` set to that copy. | +| No writes outside the run | Afterwards, the ref digests of the eight source repositories (the candidate's four bare copies, run C's two leg remotes, its source clone and its openRepoShape mirror) were unchanged, and no file under run C's remotes, its source's refs or the candidate's remotes was newer than the run's start. The real `~/.agents/workspace.yaml` and the real installed `park`, `resume`, `status`, `openRepoTools` and `setup-openspeckit` were unchanged too. | + +Tool versions: git 2.43.0, GNU make 4.3, bash 5.2.21, Python 3.12.3, `specify` +0.12.12.dev0, and OpenSpec 1.2.0 and 1.6.0. 1.2.0 is the `openspec` first on +the bench's `PATH`; 1.6.0 is `/usr/bin/openspec`. + +The estate on workstation 1 was a recursive clone of the assembly, then +`make bootstrap`. That exited 0: legs on `main` at their pins, "pins ok", and +`11 copied shape file(s) match their digests`. Then came the documented +bootstrap, `setup-openspeckit --shape on --integration codex --base-branch main +--no-global-agent-pointers`. It exited 0 with `git-config.yml` reading +`checkout_mode: worktree`, `base_branch: main` and `worktree_root: worktrees`. +It also left the tree dirty, which section 4 turns on: + +- at the root, `AGENTS.md` and `CLAUDE.md` each gained the 22-line managed + block, and `.specify/`, `.agents/`, `.claude/`, `.codex/` and 17 other agent + files or directories became untracked; +- in the spec leg, `openspec/README.md`, `openspec/changes/archive/.gitkeep` + and `openspec/specs/.gitkeep` became untracked, which the root reports as + ` M spec`. + +## 2. Speckit feature selection and branch naming + +| Command (workstation 1, from the assembly root unless named) | Exit | Result | +| --- | --- | --- | +| `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --dry-run --short-name t018-probe "T018 disposable probe"` | 0 | `BRANCH_NAME 006-t018-probe`, `REPO_SHAPE three-leg`, `SPEC_WORKTREE_PATH …/worktrees/006-t018-probe/spec`, `CODE_WORKTREE_PATH …/worktrees/006-t018-probe/code`, `FEATURE_DIR …/worktrees/006-t018-probe/spec/specs/006-t018-probe` | +| The same, invoked as `../.specify/…` from `code/` and from `spec/` | 0, 0 | Identical JSON: `PROJECT_ROOT` is the assembly root, found from the script's own location, not from the current directory | +| A local branch `009-code-only-probe` in the code leg only, then the same dry run | 0 | `010-t018-probe`: the code leg's branches count | +| The real creation | 0 | Both legs got branch `006-t018-probe` from `origin/main`, as worktrees at `worktrees/006-t018-probe/{spec,code}`. The root stayed on `main` at `3bd95a7`, registered no worktree and showed only `!! worktrees/`. `.specify/feature.json` = `{"feature_directory":"worktrees/006-t018-probe/spec/specs/006-t018-probe"}` | +| `.specify/scripts/bash/check-prerequisites.sh --json --paths-only`, run from the root, `code/`, `spec/`, the spec worktree and the code worktree (each by its relative path to the root's script) | 0 ×5 | Identical: `REPO_ROOT` the assembly, `BRANCH 006-t018-probe`, `FEATURE_DIR` in the spec worktree | +| `.specify/` from inside a leg | — | None exists in either leg or either leg worktree. A cwd-relative `.specify/scripts/…` works only from the root | +| `SPECIFY_FEATURE_DIRECTORY=worktrees/006-t018-probe/spec/specs/004-migrate-to-triad` | 0 | Overrides `feature.json`: `BRANCH 004-migrate-to-triad`, `FEATURE_DIR` that directory | +| `SPECIFY_FEATURE=004-migrate-to-triad` | 0 | `BRANCH 004-migrate-to-triad`, but `FEATURE_DIR` stays 006's from `feature.json`. **The pair disagrees** | +| The `speckit-git-validate` skill's three-leg rule, applied by hand | — | The root's `main` is not validated. Spec and code are both on `006-t018-probe`, which matches `^[0-9]{3,}-.+`; the legs agree; the spec directory was found. The extension's own `speckit.git.validate.md` has no three-leg block; the installed skill does | +| Upstream of the new branch in each leg | — | `branch.006-t018-probe.merge=refs/heads/main`: it tracks `origin/main` | +| The code worktree's nested dependency | — | `-7f84ca4… upstream/openRepoShape`, an empty directory: no nested submodule is initialised | + +**How the number is chosen.** In a three-leg root the feature number is one more +than the highest of four sources: `specs/` in the spec leg's `origin/main`; +`specs/` in its working tree; and the `NNN-` branch names, local and +remote-tracking, of both legs. The root's own branches are not read. The branch +name is the same in both legs, and the root never gets one. On workstation 2, +the next feature came out as `007-nested-pin-probe`. + +## 3. OpenSpec from each root (workstation 1, feature 006 open) + +| Directory | `openspec list` 1.2.0 / 1.6.0 | `validate --all --strict` 1.2.0 / 1.6.0 | `validate migrate-to-triad --strict` 1.2.0 / 1.6.0 | +| --- | --- | --- | --- | +| assembly root | exit 1 "No OpenSpec changes directory found" / **exit 0 "No active changes found."** | **exit 0 "No items found to validate." / exit 0, the same** | exit 1 "Unknown item" / exit 1 | +| `code/` and the code worktree | as the root | as the root | as the root | +| `spec/` and the spec worktree | exit 0, five changes / exit 0, five changes | **exit 1, 3 passed and 2 failed** / exit 0, 5 passed | **exit 1** / exit 0, "is valid" | + +`openspec list --specs` printed "No specs found." everywhere: no canonical spec +exists yet under `openspec/specs/`. Under 1.6.0, `openspec context` from the +root or the code leg exits 1 with "No OpenSpec root found from the current +directory". From the spec leg or the spec worktree it names that directory as +the root. The 1.2.0 failures are +`ADDED "Installation retains the public interface and one revision" must contain SHALL or MUST` +in `migrate-to-triad`, and two of the same kind in +`interactive-lane-name-repair`. **They are not caused by the split:** 1.2.0 +gives the same 3 passed, 2 failed on today's single-repository `main` +`63810dd`, where 1.6.0 passes all five. + +## 4. The estate verbs `status`, `park` and `resume` + +Workstation 1 created and parked the feature. Workstation 2 started from +nothing and resumed it. Workstation 3 tested one resume flag. Feature 006 +carried four kinds of change: a new spec file and a staged edit in the spec +leg, and a tracked edit and an untracked file in the code leg. + +| Log | WS | Command | Exit | What it did | +| --- | --- | --- | --- | --- | +| 10 | 1 | `status openRepoTools`, before any clone | 2 | Refused: no estate (verbatim below) | +| 14 | 1 | `status openRepoTools`, cloned and bootstrapped | 0 | `0 finding(s) in 3 repositories` | +| 15 | 1 | `park openRepoTools --dry-run`, no overlay yet | 2 | `make park` refused (verbatim below) | +| 18 | 1 | `status`, after `setup-openspeckit` | 1 | Two findings: root `dirty: 23 path(s)`, spec leg `dirty: 3 path(s)` | +| 50 | 1 | `status`, feature open and edited in both legs | 1 | The same two findings. With no parked record, the open feature worktrees are not listed | +| 51, 52 | 1 | `park openRepoTools --dry-run --lane t018-sandbox`, then without `--dry-run` | 0, 0 | `PARKED: 006-t018-probe`, spec `4ea12d2` and code `139031a`, each "pushed origin/006-t018-probe". The record `workspaces/opensoft/openrepotools.yaml` was committed as `8f5cb0d` and pushed. Reported, not touched: "24 path(s) changed at the root … the root carries no feature branch at all" | +| 53 | — | Verification | — | One WIP commit per leg, each with a single parent at the leg's base, subject `wip: park 2026-10-09T13:47:46Z — lane t018-sandbox`. Spec: `M quickstart.md`, `A specs/006-t018-probe/spec.md`. Code: `M README.md`, `A t018-probe.txt`. The code WIP's `upstream/openRepoShape` gitlink was unchanged at `7f84ca4`. **The root was untouched**: still `3bd95a7`, gitlinks `9e1d006` and `f0af7d9`, assembly remote unmoved. Both worktrees were left clean at their WIP commits. The record lists both legs with `role`, `parked_commit`, `wip_depth: 1` and `pushed: true` | +| 54, 55 | 1 | `status`, then `status --fetch` | 1, 1 | Four findings. Two are the dirt above. The other two say, for each leg, "branch 006-t018-probe: 1 commit(s) unpushed to origin/main", although `origin/006-t018-probe` equals `HEAD` (log 62: the branch tracks `origin/main`). Under `--fetch`, both `gh api` checks were refused by the shim and reported on the row | +| 58, 59 | 1 | `park --dry-run` from inside the code worktree, and `status` from inside `spec/`, with no name | 0, 1 | Both walk up to the assembly root | +| 60, 61 | 1 | `park.sh --dry-run`, run directly from `code/` or from the code worktree | 1, 1 | `Error: not inside a Git repository or Speckit checkout.` | +| 56, 88 | 1 | `resume openRepoTools --dry-run`, then the real run | 1, 1 | **Root refused, because of the bootstrap's own outputs.** `make resume` never ran. The real run pulled the workspace record first | +| 57, 82 | 1 | `make resume ARGS=--dry-run`, then `make resume` at the root: the extension, as AGENTS-shape.md names it | 0, 0 | Same-workstation resume worked. "worktree already registered … left as it is", "un-committed 1 WIP commit" in each leg, and all four files' sha256 equal their values before the park | +| 71 | 2 | `resume openRepoTools` on a fresh home | 2 | It pulled the record, cloned the root with both legs and the nested dependency, and passed `make bootstrap`. Then `make resume` refused: overlay missing | +| 72–74 | 2 | `setup-openspeckit … --no-repo-agent-pointers`, plus the four per-clone excludes today's checkout carries (`/.agents/`, `/.claude/`, `/.codex/`, `/.specify/`), then `resume --dry-run` | 1 | **Still refused**: the three untracked spec-leg files make the root ` M spec` | +| 75, 76 | 2 | Then three exact-path excludes in the spec leg's common `info/exclude`, then `resume` | 0 | `RESUMED: 006-t018-probe`, "un-committed 1 WIP commit" in both legs. `FEATURE_JSON` and `STATE_FILE` were restored; the root stayed at `3bd95a7`, clean, with legs on `main` | +| 77 | — | Verification | — | All four sha256 values were OK. The staged edit came back unstaged, by design. The untracked file came back untracked. The branch now tracks `origin/006-t018-probe` | +| 78 | 2 | `status openRepoTools` | 0 | `0 finding(s)`, although the next `park` here refuses (log 79) | +| 79, 80 | 2 | A further edit, then `park --dry-run` and `park` | 2, 2 | Refused: behind by one parked WIP commit (verbatim below). No leg ref and no worktree moved. **But the real run still committed and pushed the record as `c1288a1`** ("0 feature(s)"), rewriting `parked_at` and `parked_by_lane` while keeping 006's old entries | +| 84–86 | 2 | New feature 007: the code worktree's nested dependency initialised, moved to `39d5c98`, plus a new file; then `park` | 2 (`make: *** … Error 3`) | 007 parked. Spec: `wip 0`, branch pushed at its base. Code: WIP `032c85b`, carrying the gitlink move `7f84ca4 → 39d5c98` while `contracts/openreposhape-pin.yaml` still says `7f84ca4`. 006 was refused again. Record `653f67d` | +| 89, 90 | 1 | `make resume` | 0 | 007 recreated with "un-committed 1 WIP commit". The worktree shows only `?? t018-nested.txt`. **The nested gitlink move is gone from both the index and the working tree**, because the nested submodule is not initialised there, and nothing warned. It survives only in `032c85b` on `origin/007-nested-pin-probe` | +| 92, 93 | 3 | `make resume "ARGS=--keep-staged --feature 007-nested-pin-probe"` | 0 | The move stays staged (`M upstream/openRepoShape` at `39d5c98`). `git submodule update --init upstream/openRepoShape` then checks out `39d5c98` | + +### Refusals, verbatim + +`~` stands for the workstation's temporary HOME. + +```text +REFUSED: no estate named 'openRepoTools' under your projects directory. + An estate is a FAMILY folder (openRepoTools/openRepoTools/family.yaml) or a standalone + project root (openRepoTools/project.yaml). Bring it back with `resume openRepoTools`, or + read one of these: + + (none) +``` + +```text +make park needs the Speckit worktree overlay; install it with: setup-openspeckit +make: *** [Makefile:55: park] Error 2 +``` + +```text +make resume needs the Speckit worktree overlay; install it with: setup-openspeckit +make: *** [Makefile:59: resume] Error 2 +``` + +```text + REFUSED root openRepoTools: uncommitted changes in ~/projects/openRepoTools. + Nothing was fast-forwarded and nothing was reset. + look: git -C ~/projects/openRepoTools status --short + then: commit or stash them yourself, and re-run `resume openRepoTools`. + [root openRepoTools] bootstrap skipped: refused above. Nothing here was fetched, moved or reset. +``` + +```text +Error: 006-t018-probe (spec leg) is behind origin by 1 parked WIP commit(s); that feature was NOT parked. +The parked commit 4ea12d231fdaa50dc2afe0377e5ced2c83575f6b is still the remote tip and this workspace recorded it, so retiring it +cannot destroy anyone else's work: + git -C worktrees/006-t018-probe/spec push --force-with-lease=006-t018-probe:4ea12d231fdaa50dc2afe0377e5ced2c83575f6b origin 006-t018-probe +park does not force-push on your behalf. Re-run `make park` afterwards, or pass +--retire-parked-wip to let park do exactly that one push. +``` + +```text +Error: not inside a Git repository or Speckit checkout. +``` + +No refusal was forced. The leased push and `--retire-parked-wip` are the +person's to choose, and neither was run. The excludes in rows 72–76 are a +measured posture, not a repair: they show which local state `resume` reads as +dirty. + +## 5. Follow-ups before cutover + +1. **A bootstrapped triad root is "dirty" to `resume`.** `setup-openspeckit` + writes the managed block into the tracked `AGENTS.md` and `CLAUDE.md` + (unless `--no-repo-agent-pointers` is passed), and leaves `.specify/` and + the agent directories untracked. It also writes three untracked files into + the spec leg, which the root reports as ` M spec`. Any of these makes + `resume ` refuse the root and skip `make resume`. Today's per-clone + excludes do not cover the spec-leg files. + - This is Brett Heap's posture decision (root-guidance §8). The options + measured here: land `openspec/README.md`, `openspec/changes/archive/.gitkeep` + and `openspec/specs/.gitkeep` in the spec leg before cutover, through a + spec PR and a root pin bump; run the bootstrap with + `--no-repo-agent-pointers`, or commit its block; and carry the four root + excludes. + - A leg's `info/exclude` lives in its common git dir, so it applies to + **every feature worktree** of that leg. A directory pattern there would + hide feature work from park's `git add -A`. Use exact paths only. + - Owners: Brett (posture); workBenches (what the bootstrap writes). +2. **The overlay does not travel with a clone.** On a fresh workstation, + `resume ` clones and bootstraps, then refuses until the person runs + `setup-openspeckit` and runs `resume` again. Put that order in the T018 + real-half and T019 runbook. Under a temporary HOME, the shim also needs + `WORKBENCHES_ROOT`. +3. **Feature worktrees never initialise the code leg's nested dependency.** + Neither `create-new-feature.sh` nor `resume.sh` runs a nested + `submodule update`. Two consequences: + - `upstream/openRepoShape` is empty in every code feature worktree, so the + command tests skip there and composed checks lack the dependency. + - A parked gitlink move is **silently dropped** by a default resume on + another workstation. The workaround measured here is + `git submodule update --init upstream/openRepoShape` in the code worktree, + and `resume -- --keep-staged` when the parked code WIP moves a + gitlink. + - Owner: workBenches (initialise nested submodules in leg worktrees, or warn + when an un-committed WIP carries a `160000` change). +4. **New feature branches track `origin/main`,** so `status` calls a pushed + park "unpushed to origin/main". After a resume elsewhere the branch tracks + `origin/`, so the same feature reads differently on two machines. + Owners: workBenches (`--no-track`, or `push -u` at park) or this repository + (`status` reading `origin/`). +5. **A park that refuses every feature still rewrites the record's header.** + `parked_at` and `parked_by_lane` change, with the subject "0 feature(s)". + A third machine would then misread whose park the record holds. Owner: + workBenches `park.sh`. +6. **Partial parks reach the person as exit 2.** `park.sh` exits 3, make prints + `Error 3`, and `park` says "`make park` exited 2". The Makefile header says + so, and the make line is the one to read. This repository's `park` could + relay the code. Optional. +7. **Run OpenSpec only from the spec leg or a spec worktree.** + - From the root or the code leg, 1.6.0 reports "No active changes found." + and `validate --all --strict` passes vacuously with exit 0. Any CI or + agent validation step must change into the spec leg, or use a store that + resolves it. + - The bench's `PATH`-first 1.2.0 fails two of five changes that 1.6.0 + passes. This predates the split, but the bench should resolve 1.6.0. + Owner: workBenches. +8. **Select a feature with `.specify/feature.json` or `SPECIFY_FEATURE_DIRECTORY`, + never `SPECIFY_FEATURE` alone.** The latter yields a `BRANCH` and a + `FEATURE_DIR` that disagree. +9. **`speckit.git.validate.md` has no three-leg rule;** the installed skill + does. Owner: workBenches, in the command doc. +10. **`status` does not report open feature worktrees,** either before any + record exists or after a resume that `park` will next refuse (rows 50 + and 78). Consider a line for each. Owner: this repository. +11. **Wording:** `resume --dry-run` reports the estate "absent … the workspace + was not cloned" when the workspace clone exists but has not been pulled. + +## 6. What this did not do + +- It touched no real leg repository, real estate or lane register, and did no + lane rebinding or real WIP transition. Those are T018's post-Gate-C half. +- It ran no `--retire-parked-wip`, no force-push and no reset. +- It wrote nothing to the candidate's or run C's repositories. +- It ran no suite in the fixture. Only this branch's own hygiene check ran. +- It did not exercise macOS, or the `gh`-backed fork and shape-currency checks. +- Every workstation reports the same `parked_on: docker-desktop`, because all + three homes are on one host. Distinguishing workstations by host was not + tested. diff --git a/specs/004-migrate-to-triad/test-roots-and-ci-2026-10-08.md b/specs/004-migrate-to-triad/test-roots-and-ci-2026-10-08.md new file mode 100644 index 0000000..cbc8960 --- /dev/null +++ b/specs/004-migrate-to-triad/test-roots-and-ci-2026-10-08.md @@ -0,0 +1,494 @@ +# Explicit test roots and the triad's CI — October 8, 2026 + +**Updated:** October 9, 2026, for `e3a76b5`, `dbc6837`, both reviews and the +follow-ups. +**Landed (added October 11, 2026):** #192 landed as `1abee1d` +(2026-10-09T20:24:58Z), then #190 as `f87fd5a` (20:26:03Z), squashed at +`b50f197`. `git diff 1abee1d f87fd5a` and `git diff c4864ac b50f197` have the +same patch-id (`cb2c7bf2…`). So `b50f197`, which is listed below as a +follow-up outside #190, is in #190 as it landed. #192 landed before #190, as +section 2 asks. The rest of section 9's follow-ups are #195 and #193. +**Tasks:** T009 and T010 of [tasks.md](tasks.md), under +[#186](https://github.com/opensoft/openRepoTools/issues/186). No task is +checked complete by this record; Gate B stays open. +**Code change:** [PR #190](https://github.com/opensoft/openRepoTools/pull/190) +(`feat/test-roots-for-the-triad`, head `dbc6837`: `c139e88`, `e3a76b5` and +`dbc6837` on `main` `c4864ac`; `ready` applied 2026-10-09T02:11Z). +**Follow-ups (not in #190):** branch `feat/test-roots-followups`, `6e63083` +and `2064390` on `dbc6837`; branch `feat/test-roots-manual-guard`, `b50f197` +on `dbc6837` (sections 6 and 9). +**CI patches:** [patches/t010-code-leg-tests-workflow.patch](patches/t010-code-leg-tests-workflow.patch) +and [patches/t010-assembly-exact-pin-job.patch](patches/t010-assembly-exact-pin-job.patch). +**Rehearsed arrangement:** run C of 2026-10-08 (private prep +`work-20261008/rehearsal`): source `main` `837928a`, assembly split +`69b8924`, spec leg `593250d`, code leg `73b04d6`, code's nested +openRepoShape `7f84ca4`. Every clone here was taken *out* of it, with +`-c protocol.file.allow=always` (git 2.43 refuses `file://` submodule +transport by default) and an offline `insteadOf` for the nested +openRepoShape URL; nothing was written in it. + +The first scratch of this work, and the October 7 run B arrangement it was +measured against, were lost when the bench restart cleared `/tmp`. An interim +reproduction of run B into disposable local remotes (adopter `check` and +`execute` exit 0, `gh`/`curl` shims never called) gave the same leg commits as +the October 7 receipt — spec `558a12b`, code `6d52162` — and is superseded by +run C for everything below. + +## 1. What the split breaks — measured before the change + +The suite at run C's code leg `73b04d6` (that is, `main` without PR #190), run +with `tests/run.sh -k 'repo_hygiene or upstream_pin or wip_init or park_resume' -rfEs` +in three contexts: + +| Context | Result | Log SHA256 | +| --- | --- | --- | +| M1 — the code leg mounted in the recursive assembly clone (`assembly/code`) | **17 failed**, 350 passed | `e1658c99…eed0e` | +| M2 — the code leg cloned on its own, dependency initialized | **17 failed**, 350 passed | `442588dd…c8dd5` | +| M3 — the code leg cloned on its own, dependency **not** initialized | **17 failed**, 141 passed, 209 skipped | `59027b3c…08238` | + +The 17 are the same tests, failing the same way, in all three: every one is +`FileNotFoundError` on a file the split moved out of the code leg. M1 equals +M2 because the suite never looked above its own root. + +| Test (`tests/test_repo_hygiene.py`) | Missing file | +| --- | --- | +| `test_the_readme_prints_the_install_line_the_pointer_prints`, `…_also_carries_the_gh_api_form`, `…_carries_the_two_line_onboarding_chain`, `test_no_document_offers_a_windows_powershell_twin`, `test_the_documents_say_what_bare_park_does_now`, `…_what_status_is_and_is_not`, `…_the_sweep_skips_a_root_without_the_overlay`, `…_what_a_bare_lanes_lists`, `test_readme_is_short_enough_to_be_read` | `README.md` | +| `test_claude_md_points_at_agents_md` | `CLAUDE.md` | +| `test_agents_md_names_the_pin_rules`, `test_agents_md_is_short_enough_to_be_read`, `test_the_suite_wrapper_takes_one_lock_and_names_it_where_agents_read_it` | `AGENTS.md` | +| `test_the_directory_precedence_states_every_rung_it_implements`, `test_the_exit_3_documentation_agrees_with_the_die_message_it_describes`, `test_the_manual_offers_the_fork_act_and_not_a_kill` | `docs/README-lanes.md` | +| `test_the_root_carries_the_line_ending_rule` | `.gitattributes` | + +What passed in all three is as informative: `tests/test_upstream_pin.py` +already read the code leg's own gitlink and pin, so it needed no rewiring to +stay correct in a leg — only names and a guard (below). The dependency-reading +estate, `wip init` and pin checks skipped in M3 with today's reason, as they +do today. + +Two more readers are outside those selectors and were found by reading: +`tests/test_openrepotools_command.py`'s receipt-mode check reads `README.md`, +and two sections of `tests/test_lane_helpers.sh` (14 assertions) read +`docs/README-lanes.md` with `cat … 2>/dev/null || :`, so in a leg they would +have run against an empty string rather than fail on a missing file. PR #190 +covers all of them: the 16 hygiene document tests, the installer's README +check, the two lane-suite sections, and the `.gitattributes` rule (kept on the +code root, section 2). The T006 writer's independent count of code-leg tests +reading those documents was 17 functions, two of them in the lane suite. + +## 2. The root rules (PR #190) + +| Root | Named by | Otherwise | +| --- | --- | --- | +| code | `OPENREPOTOOLS_CODE_ROOT` | the tree the suite was loaded from | +| assembly | `OPENREPOTOOLS_ASSEMBLY_ROOT` | the nearest directory above the code root that carries `contracts/code-pin.yaml` and `project.yaml`, is a repository's top level, and records the code root as the gitlink its code pin's `submodule_path` names; none on today's layout | +| spec | `OPENREPOTOOLS_SPEC_ROOT` | the assembly's spec leg (`contracts/spec-pin.yaml`'s `submodule_path`) when it is checked out; none otherwise | +| dependency | — | `/upstream/openRepoShape`, initialized or not | + +A file is read from the root that owns it: `README.md`, `AGENTS.md` and +`CLAUDE.md` from the assembly; anything under `docs/`, `openspec/`, `specs/` +or `ideation/` from the spec root; everything else from the code root. +Today's single repository carries `openspec/` and `specs/` beside the code, so +it answers for all three roles, and every reader reads exactly the file it +read before. The layout is decided by those trees and never by the documents, +so a README deleted from today's tree is still a failure, not a skip. + +**Decision: `.gitattributes` stays the code root's** in every layout, +against the placement table, because git never applies a superproject's +attributes inside a submodule. Measured in the composed candidate +(`proof-check-attr-both.log`): +- `README.md` and `AGENTS.md` at the assembly have `text: auto`, `eol: lf`. +- `park` and `tests/run.sh` in `code/` have `text: unspecified`, + `eol: unspecified`. + +Reading the assembly's copy would pass while the leg's bash files went +unprotected. + +This agrees with T006: its leg patch `code-0001` gives the code leg its own +`.gitattributes`, and the assembly keeps its copy for its own files. + +It disagrees with the placement table in `plan.md` § Target placement on +`main`, which puts `.gitattributes` at the assembly only. Amending that table +is a follow-up for the plan's owner (section 9). A leg split exactly as the +table says is red on `test_the_root_carries_the_line_ending_rule`: see P1, P3 +and P4 below, and the independent review's C5 and C6. + +**Length caps** (`dbc6837`, T006): +- On today's layout, `AGENTS.md` stays at 316 and `README.md` at 486. +- When an assembly root is named, the caps are 340 and 510. The difference is + the 24 lines each that T006's `assembly-0001` adds. + +The dated entries cite `root-guidance-2026-10-08.md`, which is on #192's +branch: #192 should land with or before #190, or that citation points +nowhere until it does. + +## 3. The composed-versus-standalone contract + +* **Standalone** (the default, and the only mode today's layout has): a check + whose root is absent skips, naming the root — the courtesy the missing + submodule already gets. The no-submodule skip is today's, same condition and + reason. On today's layout nothing new skips. +* **Composed** (`OPENREPOTOOLS_COMPOSED=1`): `tests/conftest.py` refuses the + session (`pytest.UsageError`, exit 4, `ERROR: …`) when the assembly or spec + root is absent, the dependency lacks `scripts/repo_shape.py` or + `templates/workspace-root/README.md`, or the code or spec leg is not at the + commit the assembly records, has changes to tracked files, or answers no + `git status` (`e3a76b5`, Copilot round 1). The refusal names each problem. + `NEEDS_UPSTREAM` becomes a fixture that fails, and a document test that + reaches an absent root fails. A variable naming the wrong kind of tree, or a + mode other than `0`/`1`, is refused in either mode. + +The conftest does not check the pin files' own `commit:` and `tree_sha256`. +That is the job of the standard's `scripts/validate-pins.py`, which the +assembly job runs in the step before the suite. Copilot's round-2 finding on +#190 is exactly this gap. + +I reproduced it in a copy of the composed candidate (`proof-stale-pin.log`). +I moved `contracts/code-pin.yaml`'s `commit:` to `73b04d6` and left the +gitlink and HEAD at `8ebf4ec`. On that tree: +- `conftest.ROOTS.refusal()` is `None`. +- `validate-pins.py` exits 1 with `pin-gitlink-mismatch` and + `pin-digest-mismatch`. + +In CI, the job stops before the suite runs. A composed run made by hand +without the validator would accept that leg. The follow-up is in section 9. + +## 4. Red at the base, green on the branch + +* **Red, round 1.** I copied `tests/test_test_roots.py` as of `c139e88` + (SHA256 `be4edff7…6cff`) unchanged into a clone of `origin/main` `c4864ac` + and ran `tests/run.sh -k roots`. Result: **18 failed**, every test of the + module. The 3 passes are tests in other modules whose names contain + `roots`. + - The seven behavioural tests failed on what they assert. The nested suite + read this checkout's README, manual, gitlink and dependency, whatever the + variables named. A composed triad with its spec leg moved off its pin ran + instead of exiting 4. `tests/run.sh` passed a relative root through + unchanged. + - The eleven resolver tests failed because `conftest.resolve_roots` and + `ROOTS` do not exist at the base. + + Log `eb7d2bc4…525c`. +* **Red, round 2.** I copied `dbc6837`'s module (SHA256 `aeb4b742…ea0c`) the + same way and ran `-k 'length_caps or missing_manual or status_cannot_be_read'`. + Result: **3 failed**, the three tests added since round 1: + - a leg whose `git status` cannot be read; + - the assembly-root caps ("passed on an assembly file one line over its + cap"); + - the lane suite's missing-manual failure. + + Log `bb87a06e…ad16`. The independent review copied the same module whole + into `c4864ac` and got **21 failed**: all 21 of the module's test cases. +* **Green, today's layout**, with `tests/run.sh -k 'repo_hygiene or upstream_pin or roots or composed'`: + + | Tree | Result | Log | + | --- | --- | --- | + | `c139e88` | **188 passed** | `b0289afa…cf4` | + | the working tree that became `e3a76b5` | **189 passed** | `d6f84d7f…65e3` (its header names the HEAD at its start, `c139e88`) | + | `dbc6837`, and its merge onto `main` `63810dd` | **191 passed, 0 skipped** | the independent review's run | + + My local run on `dbc6837` itself was queued for the lock at the + account-swap checkpoint and was stopped there. CI is the record. +* **CI on #190, head `dbc6837`** (run 37869489919, merge `5ad6205` onto + `63810dd`): + + | Job | `dbc6837` | `main` `63810dd` | + | --- | --- | --- | + | `tests` | 1318 passed | 1296 passed | + | `tests-no-submodule` | 1105 passed, 213 skipped | 1084 passed, 212 skipped | + | `tests-windows` | 176 passed, 1142 skipped | — | + | `guard-launch-mode` | 165 passed | — | + | `parse-macos`, SonarCloud | green | — | + + The added `tests-no-submodule` skip is the composed positive control, and + it skips with today's reason. `tests-macos` runs under `ready` (run + 37873415160). Earlier heads: `c139e88` had `tests` at 1315 passed, and + `e3a76b5` was green. + +## 5. The two CI patches (T010) + +**Code leg, `.github/workflows/tests.yml`** (against run C's code leg +`73b04d6`, whose copy is byte-identical to `main`'s). The six jobs and their +trigger policy are kept exactly — `tests`, `tests-no-submodule`, +`tests-windows`, `parse-macos` and `guard-launch-mode` on every pull-request +push, `tests-macos` on the `ready` label, on `main`, nightly and on dispatch. +The leg is cloned at its own root, so its checkout and submodule contexts were +already right; the patch names them: workflow `env` +`OPENREPOTOOLS_CODE_ROOT: ${{ github.workspace }}` and +`OPENREPOTOOLS_COMPOSED: '0'`, `-rs` on `tests` so the leg's document skips +are printed, and comments saying that nothing in the leg is composed +acceptance and that the leg must carry its own `.gitattributes`. +SonarCloud Code Analysis is a GitHub App check, not a job in this file; the +code leg needs its own SonarCloud project binding, which is an owner's act. + +**Assembly, new `.github/workflows/composed.yml`** (against run C's split +`69b8924`). Its own file because the root `validate.yml` is a pinned shape +copy: `contracts/shape-pin.yaml` records it at +`fb8c0360863ad953af7825f3be01ed27f8cf1269e5cab19dc0d6c5423161b487`, which +the rehearsed file matches, so a job added there is drift that +`validate-pins.py` refuses. One Linux job, `exact-pins`: checkout with +`submodules: recursive` over https (no `file://` allowance anywhere in it), +a step that fails on any missing leg, pin or dependency file, then +`scripts/validate-manifest.py` and `scripts/validate-pins.py`, then +`bash code/tests/run.sh -rs` with `OPENREPOTOOLS_COMPOSED=1`, +`OPENREPOTOOLS_ASSEMBLY_ROOT=$GITHUB_WORKSPACE` and +`OPENREPOTOOLS_SPEC_ROOT=$GITHUB_WORKSPACE/spec`. The macOS, Windows and +no-submodule policies stay in the code leg's workflow. + +**Neither patch can run on GitHub until the legs exist** (Gate C). Both +`git apply --check` cleanly against run C, and both were proven locally as +below. + +## 6. Local proofs + +The composed candidate is a clone of run C's assembly in which PR #190's +commit was applied onto the code leg (`8ebf4ec`, on `73b04d6`), pushed to a +private copy of the code leg's bare remote, and pinned with the standard's own +`bump-leg.py --leg code --to 8ebf4ec… --local-remote-dir …` (exit 0; commit +`6ccdaf8`, `contracts/code-pin.yaml` digest recomputed as `577cdcf1…d850`); +the composed workflow is commit `386be81` on top. + +These proofs ran on `c139e88`'s content (`8ebf4ec`). They were not repeated +for `e3a76b5` or `dbc6837`. The independent review's fake triad exercised +`dbc6837` both composed and standalone (its C1–C6). + +| Check (in the candidate, as the job runs it) | Exit | Log SHA256 | +| --- | ---: | --- | +| `python3 scripts/validate-manifest.py` | 0 | `0caaed6c…83a4` | +| `python3 scripts/validate-pins.py` (both legs at their gitlinks, both digests recompute, 11 shape copies match) | 0 | `fb085bee…cf4b` | +| `make validate` / `make pins` | 0 / 0 | `85c8fd9f…bbf0` / `440c5bf4…74e4` | +| the job's context step, all three legs present | 0 | `c4e4cdf2…ac22` | +| the job's context step, spec leg not checked out | **1** (`::error::the spec leg is not checked out at spec/`) | `fcf47871…444a` | +| P2: `code/tests/run.sh -k upstream_pin`, composed, spec leg not checked out | **4** — `ERROR: openRepoTools composed run (OPENREPOTOOLS_COMPOSED=1) refused … OPENREPOTOOLS_SPEC_ROOT=… is not a spec root` | `38889cab…1059` | +| P1: `code/tests/run.sh -k 'repo_hygiene or upstream_pin or roots or composed or openrepotools_command or lane_helpers_suite'`, composed, as the job runs it | 1: **285 passed, 2 failed, 0 skipped** | `f611c198…fe34` | +| P3: the code leg standalone with the patched workflow's context (`OPENREPOTOOLS_CODE_ROOT`=the leg, `COMPOSED=0`), same selector | 1: **268 passed, 2 failed, 17 skipped** | `0f5e8c21…6144` | +| P4: the code leg standalone without its dependency, `-k 'repo_hygiene or upstream_pin or roots'` | 1: **159 passed, 1 failed, 28 skipped** | `6cc26bb1…55d3` | + +The failures, recorded rather than deleted: + +* **`test_the_root_carries_the_line_ending_rule`, in P1, P3 and P4** — the + code leg has no `.gitattributes`. A finding for T006: the leg must carry + `* text=auto eol=lf` itself (section 2). Until it does, the leg's CI is red + on this one test, which is the point of keeping it. +* **Two assertions of `tests/test_lane_helpers.sh`, in P1 and P3** — "…with + clause (c)'s directory and window in the tail" and "the row's stamp states + clause (c)'s two facts for a person, in its TAIL". Both expect the lane's + directory in a register row that is cut at 240 characters, and under + `tests/run.sh` that directory sits beneath the run root + (`~/.local/state/openRepoTools/tmp/-/tmp/tmp.*/`, then the + fixture home's `projects/…`), + long enough to fall past the cut. CI runs the same suite with a short + `TMPDIR` and passed it (1315 passed at `c139e88`, 1318 at `dbc6837`). These sections are untouched + by PR #190. + - The length interaction is **#180**. That is inferred from the expected and + actual rows, not bisected. + - This record first sent it to the run-root work of #162/#184. It was + corrected on 2026-10-11, after lane 3's 6089801221, finding 13 (i). + - `composed.yml` runs the suite through `code/tests/run.sh` (assembly patch, + line 125), so `exact-pins` inherits #180. It would be red on these two + assertions until #180 is fixed (section 8). +* **The lane suite ABORTED in P3**, after those two FAILs, at + `test_lane_helpers.sh: line 6595: ln_doc: unbound variable`. This is a + defect in PR #190's own guard. `ln_doc` is bound only inside the + `if [ -n "$LANE_MANUAL" ]` section, the `--retire` section uses it 350 + lines later outside any guard, and the suite runs under `set -u`. + - This record first attributed P3's lane-suite failure to the stamp + assertions alone. Lane openRepoTools-3's review of #190 found the abort, + and it is in P3's own log. + - At `dbc6837` the lines are 6601–6602. + - The fix is `feat/test-roots-manual-guard` @ `b50f197`, one commit on + `dbc6837`. It guards the two lines with the same named skip, and adds a + text test that every use of the manual's text sits inside such a guard. + - With the section run alone under `set -u` and an empty manual + (`proof-retire-block.log`): `dbc6837` aborts, exit 1; `b50f197` gives 4 + passed and 1 skipped, exit 0. + - Today's layout always takes the guard. + +Every skip in P3 and P4 names its root. P3's 17 are `README.md` ten times +(nine hygiene tests and the installer's check), `AGENTS.md` three times, +`CLAUDE.md` once and `docs/README-lanes.md` three times; inside the lane suite +its two manual sections printed `skip … no spec root in this run` as well. +P4's 28 are the hygiene module's 16 document skips plus today's 12 dependency +skips (`the pinned openRepoShape is not checked out; run git submodule update --init upstream/openRepoShape`). +P1 skipped nothing. + +Both workflow files parse and hold their structure (`check_workflows.py`: +the leg's six jobs, triggers, `ready` gate, per-job submodule contexts and +`env`; the composed job's one Linux job, recursive checkout, step order and +`env`). `actionlint` passed both on 2026-10-07, on the same content less one +comment sentence in `composed.yml`; it was not installed after the bench +restart, so it was not rerun. + +## 7. How the local runs were made + +The workstation-wide `pgrep` census in `tests/run.sh` did not reach zero for +some runs (the xFactory worktrees keep 3–20 `python3 -m pytest` alive), so a +run that had not started within 5 minutes was replaced, by the coordinator's +interim rule, with run.sh's own pytest line under `flock` on the same lock +file (M1 was; each log says which). That census is now a comment on +[#184](https://github.com/opensoft/openRepoTools/issues/184#issuecomment-6069746447). +On this profile `~/bin/python3` resolves to a virtual environment without +pytest, so every local run had `/usr/bin` first on `PATH`. CI is the record. + +## 8. What remains for T015 + +* Run both workflows on GitHub at the real exact pins once the legs exist, + including `tests-macos` and `tests-windows` in the real code leg. +* T006: the code leg's own `.gitattributes`, before the leg's `tests` can be + green. +* A SonarCloud binding for the code leg, and a leg credential for the + composed job if the legs are ever private (`project.yaml` says public). +* Re-measure against the frozen source chosen at Gate C; the run C commits + here are rehearsal identities, not the cutover's. +* The follow-ups of section 9. Items 1, 4, 5 and 6 should land before the + real split. Their state on 2026-10-11: + - item 6 (1), `b50f197`, is in #190; + - items 1–4, 6 (2) and (3), and 7 are #195; + - item 5 is the plan owner's. +* From lane 3's 6089801221 (section 10). The patches here stay byte-identical + as rehearsed; each item below changes them at T014/T015, not in this + record. + - **#180 before `exact-pins` is relied on.** Fix #180, or give the job + `run.sh`'s pytest line with a short `TMPDIR` (finding 13 (i)). + - **Make "never skips" a guarantee.** About forty other conditional skips + would still pass with exit 0, so fail `exact-pins` on any skip, for + example on the junit skipped count (finding 13 (ii)). + - **The landing tooling reads `tests-macos` by name, and the assembly has + no such check** (finding 12). + - **Regenerate both patches with a real author.** They carry the fixture + identity `openRepoTools CI ` (finding 13 (iv)). + +## 9. Reviews and dated follow-ups + +* **Copilot round 1** (on `c139e88`) raised two findings. Both are fixed in + `e3a76b5`, and both threads are resolved: + - a `git status` that fails is a refusal; + - the fixture code legs keep `.gitattributes`. +* **Independent adversarial review** (lane 1's reviewer, read-only): **LAND** + on `dbc6837` + ([review](https://github.com/opensoft/openRepoTools/pull/190#pullrequestreview-5464938427)). + Its own fake triad agreed with this record: + - a split leg gives 17 = 16 named skips plus `.gitattributes`; + - every subprocess of a composed run stayed inside its fixture; + - the caps reproduced against T006's `assembly-0001`. +* **Lane openRepoTools-3, reviewer of record:** **LAND** on `dbc6837` + (review 5464931249). It names one defect that is best fixed before the + squash, and two later items: + - (1) the `ln_doc` abort of section 6 (its finding 10), with the fix ready; + - (2) a **Gate C item** (its finding 11): the paired layout + `/worktrees//code` finds no assembly (measured on run + C), so in a feature worktree every document test would skip; + - (3) a **non-blocking hazard**: `OPENREPOTOOLS_CODE_ROOT` and + `OPENREPOTOOLS_ASSEMBLY_ROOT` redirect the suite silently, because the + report header is hidden by `-q`. + + Corrected 2026-10-11 (lane 3's 5481348731, finding 5). This record first + said all three were to be fixed before the squash, following lane 3's + summary on #186 (6072817992). The review itself dates them as above. +* **Copilot round 2** (on `dbc6837`) raised two findings: + - (High) the pin `commit:` gap of section 3; + - (Medium, "previously missed") `_carries` accepts a directory where a + marker file belongs. + + I answered on the thread. Nothing was pushed, because the macOS gate runs + on `dbc6837`. + +These follow-ups are dated 2026-10-09 and recorded on #186. None of them was +in #190 at `dbc6837`. Item 6 (1) landed in it (see **Landed** at the top). + +1. **Review finding 11.** Three code-only assertions came after the first + document read (`test_repo_hygiene.py:994`, `:1052` and `:1264` at + `dbc6837`), so a standalone code leg skipped them along with the + documents. + - `feat/test-roots-followups` `6e63083` asserts every mixed test's + command half first. + - `2064390` proves it with a nested run against a fixture leg whose + commands lost those lines. +2. **Review finding 13.** The lane suite's missing-manual failure is checked + only by its text. Its `bad` branch did run locally + (`proof-manual-block.log`): the block, extracted unchanged with stub + helpers, printed `FAIL the lane manual is where the run's roots put it` + for `/nonexistent/README-lanes.md` and skipped on an empty value. A + behaviour test that runs the block itself is still to do. +3. **Review finding 14.** The four variables are not in `AGENTS.md`. + `6e63083` adds seven lines to "Testing your changes" and moves the cap + from 316 to 323 (340 to 347 at an assembly root), in a dated entry. +4. **Copilot round 2.** + - The conftest should refuse a pin whose `commit:` differs from the + gitlink, before it compares HEAD, with its own red-at-base test. + - Root markers should be regular files. +5. **Placement table.** The plan's owner amends `plan.md`'s placement table + to say the code leg keeps its own `.gitattributes` (section 2). +6. **Lane 3's review.** + - (1) `feat/test-roots-manual-guard` `b50f197`, unless the coordinator + takes it into #190 before the squash. He did: it was fast-forwarded at + 2026-10-09T13:59Z and landed in `f87fd5a`. + - (2) Assembly discovery for the paired worktree layout, before Gate C. + - (3) Say which roots a run used where `-q` cannot hide it. +7. **Review nits.** + - `Roots.owner_of` gives `LICENSE` and `.gitignore` to the code root. + This is latent. + - `tests/run.sh`'s `abs_root` turns a quoted `~/x` into `$PWD/~/x`. + - The `GIT_ENV` comment in `tests/test_test_roots.py` says "file + transport allowed". + +## 10. Lane 3's review of the T010 patches (6089801221), dated 2026-10-11 + +Lane 3's review of #190's follow-ups and of these patches came after this +record (2026-10-09T21:47Z, on `d28e2cc`). Its verdict on this branch was +"fit for evidence-3", with one thing to settle before the composed job is +relied on (finding 13 (i)). Each finding's disposition is below. "#195" +means that PR as it stands on 2026-10-11: open at `e378871`, with lane 3's +LAND on `fb77bed` (5481360555), and not landed. + +| # | Finding | Disposition | +| --- | --- | --- | +| 1–3 | `b50f197` covers every manual read, changes no outcome on today's layout, and does what it says | Confirmed. It landed in #190 (see **Landed** at the top). | +| 4 | The text test finds the guard by exact line match: a false red, never a false green | Fixed in #195 (`43ee05b`): the test now follows the `if`/`fi` nesting. | +| 5, 6 | How the follow-ups sit on main; no outcome changes | Confirmed. The follow-ups are #195. | +| 7 (i) | The AGENTS.md paragraph describes rather than gives a rule | Open. #195 does not change it, and it is not yet filed. | +| 7 (ii) | The 340 → 347 assembly cap rested on a false premise | Fixed in #195 (`c7b27bc`): the paragraph moved below `assembly-0001`'s hunks, so the patch applies and gives 347. | +| 7 (iii) | The paragraph names only `docs/` for the spec leg | Open: #193 item 20. | +| 8 | The nested proof covers 3 of 5 reordered tests, and asserts only the outcome | Open. The missing two are #193 item 20. That the proof checks only the outcome is not yet filed. | +| 9 | The `--help` half of the receipt test; the refusal wording; items 3–7 and 15 | The `--help` half is #193 item 20. The rest is #195. | +| 10 | Run C, standalone and composed, with the follow-ups' tests | Recorded. The `.gitattributes` failure is T006's `code-0001` (section 2). | +| 11 | The patches apply; the branch as pushed conflicts with main | Confirmed. Evidence-3 (#196) carries this branch's three files onto main. | +| 12 | `tests.yml` keeps everything the gate reads; the assembly has no `tests-macos` check | Confirmed. The missing check is in section 8. | +| 13 | Copilot round 2's High is closed in CI only, by step order | Closed in the suite too by #195 (`e79bd82`): a pin whose `commit:` differs from the gitlink is refused. | +| 13 (i) | #180 lands in `exact-pins` through `run.sh` | Open. Section 6 is corrected and section 8 lists it. The patch is unchanged here. | +| 13 (ii) | "Fails, never skips" holds only for roots and the dependency | Open. Section 8: fail the job on any skip. | +| 13 (iii), 14 | The refusal misnames a named root | Fixed in #195 (`0c89b9e`, #193 item 15). | +| 13 (iv) | The patches carry the fixture author | Open. Section 8: regenerate at T014. | +| 15 | The composed refusal contract holds | Confirmed. | +| 16 | Eight local shell FAILs attributed to the environment | Recorded. CI is the record. | + +## Log SHA256s + +The logs stay in the private prep area (`work-20261008/ort-test-roots/logs/`). + +| Log | SHA256 | +| --- | --- | +| M1 `m1-assembly-code.log` | `e1658c99b088bb2b571369431942a0170a4c78b14da9f5bd88c959ddc89eed0e` | +| M2 `m2-code-standalone.log` | `442588dd56997ec00f5a2773dfe3e6102bf7b2cfd137b755057d62f2991c8dd5` | +| M3 `m3-code-nosub.log` | `59027b3ceed4f52e1e6b2d0be926b0b7f3a2c7abfda2846b4fc9656b35208238` | +| red at base `red-at-base.log` | `eb7d2bc460f5b4d474a71ccfb2a19ecca0798e4fc778685e620311d37384525c` | +| green on branch `green-branch.log` | `b0289afab41217eb3c1e8ece140233b8a458492b37042727c668066a0f5a8cf4` | +| red at base, round 2 `red-at-base-r2.log` | `bb87a06e0e09785aae078c0cea5a89be40bef0dc2c90328b3a2e6025a489ad16` | +| green, 189 `green-branch-r1.log` | `d6f84d7f05d13c1d42d702c7c5c7f846c39b876392e1ee9d94ab6658b25665e3` | +| green, 191 on the follow-ups tree `6e63083` `green-branch-r3.log` | `78deeb7e2a36a1f10322a15b5bad6def7d2cc5242e503211bd468bc46ce8fb01` | +| `proof-check-attr-both.log` | `fb0561553b4d3b38554507f48267985eb128b0ff9f39d3665171b90ce9bdc6c3` | +| `proof-stale-pin.log` | `6b2223d39d58372f73b42f77f7a0d314c431807783f8cc68f4b8e1d8835da68c` | +| `proof-retire-block.log` | `c593520e6659febe5a7952df174feb4c03a8939a593369d010e3f9d2aa4e0393` | +| `proof-manual-block.log` | `b8b8217c07ecc82bf95295200f5b2c1a1202667ef7ca66a64feec7206c8f21d9` | +| P1 `p1-composed.log` | `f611c1981520c4d479556d4b5ec9094845166a69772779dee7539113f473fe34` | +| P2 `p2-composed-refuses-without-spec.log` | `38889cab1e4a3c842bfddc9687e30b21df9c183b238df0676088dd1e8e981059` | +| P3 `p3-leg-standalone.log` | `0f5e8c2163fbe1457bb692eba3bdbc4b67e03020cf7ec1ba0ee0fc7e38806144` | +| P4 `p4-leg-no-submodule.log` | `6cc26bb153da368df80752f0b935ea660c965bb06b135c090f0835d0dbb055d3` | +| `proof-validate-manifest.log` | `0caaed6c40864ebce25366b7599b3f56149f8698d02d2ba7a42dafb53bc283a4` | +| `proof-validate-pins.log` | `fb085bee84b9f76fc814a78ca8e5ed17d1d9fd5c0030726633fb92fd7b8fcf4b` | +| `proof-make-validate.log` | `85c8fd9f834556cc7f8c42ac9625108b4cd33c90cd7db2bf6fbe49bbb39bbbf0` | +| `proof-make-pins.log` | `440c5bf4b7250418076bce4fb0be5a21c21def76c5fe9990808fa6b1cbc874e4` | +| `proof-context-step.log` | `c4e4cdf2ed3d39dee6775d9a01a27ab76869e76feefc561399aa686227ffac22` | +| `proof-context-step-nospec.log` | `fcf47871f23e005da6f06ace2953acdb81a2117b4bc2d0009d80986ca4e7444a` | +| `bump.log` | `aee4d464a7ef847c92d0acc00c28d539ae24e9ea54426a1747c0ffec8220fc80` | + +This record authorizes no conversion, repository creation, merge or +default-branch move.