From 6337a52c14cc17c6826342d7d3e21644f805a11f Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Thu, 8 Oct 2026 20:51:01 +0000 Subject: [PATCH 01/10] Add the payload-source compatibility matrix tests before the resolver they describe. T008 of specs/004-migrate-to-triad/tasks.md (opensoft/openRepoTools#186): installer-design.md's fifteen-row compatibility matrix, as tests that build their own GitHub out of bare repositories and answer gh and curl from them with git, so a moving branch, a tag, a commit and a slash-containing ref behave as they do on GitHub and every request is logged. This checkpoint lands the tests first, on purpose: the adopted-layout rows are red against the installer at c4864ac, which is the red-at-base evidence the brief asks for, and the resolver follows in the next commit. Lane: openRepoTools-1 Co-Authored-By: Claude Opus 5.5 --- tests/test_install_payload_source.py | 1589 ++++++++++++++++++++++++++ 1 file changed, 1589 insertions(+) create mode 100644 tests/test_install_payload_source.py diff --git a/tests/test_install_payload_source.py b/tests/test_install_payload_source.py new file mode 100644 index 00000000..3be4f86f --- /dev/null +++ b/tests/test_install_payload_source.py @@ -0,0 +1,1589 @@ +# SPDX-License-Identifier: Apache-2.0 +"""WHERE `--install` TAKES ITS BYTES FROM: the compatibility matrix of the triad +migration (opensoft/openRepoTools#186, T007/T008 of +`specs/004-migrate-to-triad/tasks.md`; the matrix is +`specs/004-migrate-to-triad/installer-design.md`'s "Compatibility acceptance"). + +Every test here builds its own GitHub out of BARE REPOSITORIES IN A TEMPORARY +DIRECTORY: a fake `gh` and a fake `curl` (one Python program, `FAKE_GITHUB` +below) answer the calls the installer makes from those repositories with +`git` itself — `commits/` is `git rev-parse`, `git/trees/` is +`git ls-tree`, `contents/?ref=` and the raw URL are `git show` — so a +branch that MOVES, a tag, a commit and a ref with a slash in it behave as they +do on GitHub, and every request is written to a log a test can read back. No +real `gh` or `curl` is reachable: the `$PATH` a run gets is a farm of the real +one's executables with both names left out, and the fakes put back only where a +test asks for them. Every `$HOME` is a temporary directory. + +THE TWO LAYOUTS: + +* LEGACY — one repository whose root carries every payload file (today's + `opensoft/openRepoTools`). A complete checkout installs offline, byte for + byte as before; a remote install resolves the ref once and fetches every + payload file at that commit. +* ADOPTED — an ASSEMBLY root whose `contracts/code-pin.yaml` pins the CODE + leg by repository and commit, with the gitlink at the pinned path agreeing. + The payload comes from the code repository at the pinned commit and from + nowhere else. + +A test whose name carries `composed` needs THIS checkout mounted as an +assembly's code leg with the assembly's root entry point beside it; standalone +code skips it, naming why, and T015 runs it at the real assembled head. +""" + +from __future__ import annotations + +import hashlib +import json +import os +import re +import shutil +import signal +import stat +import subprocess +import sys +import textwrap +import time +from pathlib import Path + +import pytest + +from conftest import REPO, WINDOWS_SKIP + +COMMAND = REPO / "openRepoTools" + +pytestmark = [pytest.mark.skipif(shutil.which("bash") is None, + reason="openRepoTools is a bash script"), + WINDOWS_SKIP] + +#: `--install` merges two hook entries with jq and refuses without it, and the +#: fake `gh`'s `--jq` is answered by the real jq: every test that installs +#: needs one, as `tests/test_openrepotools_command.py`'s own NEEDS_JQ says. +NEEDS_JQ = pytest.mark.skipif(shutil.which("jq") is None, + reason="`--install` merges two hook entries with jq") + +#: THE PINNED STANDARD'S TEMPLATE BYTES — what `wip init` seeds a workspace +#: from. Without the submodule these tests SKIP with conftest's own words, which +#: is the documented skip row 14 of the matrix keeps. +UPSTREAM = REPO / "upstream" / "openRepoShape" +NEEDS_TEMPLATES = pytest.mark.skipif( + not (UPSTREAM / "templates" / "workspace-root").is_dir(), + reason="the pinned openRepoShape is not checked out; " + "run `git submodule update --init upstream/openRepoShape`") + +#: THE COMPOSED CONTEXT: this checkout mounted as an assembly's code leg, the +#: assembly's root entry point beside it. Read from disk, never assumed. +ASSEMBLY = REPO.parent + + +def composed_context() -> tuple[bool, str]: + pin = ASSEMBLY / "contracts" / "code-pin.yaml" + if not pin.is_file() or not (ASSEMBLY / "openRepoTools").is_file(): + return False, ("composed acceptance: needs this checkout mounted as an " + "assembly's code leg with the assembly's root " + "`openRepoTools` beside it; standalone code skips it and " + "T015 runs it at the real assembled head") + path = re.search(r"^submodule_path:\s*\"?([^\"\s]+)", pin.read_text( + encoding="utf-8"), re.MULTILINE) + if not path or path.group(1) != REPO.name: + return False, f"{ASSEMBLY} pins a code leg that is not this checkout" + return True, "" + + +COMPOSED, COMPOSED_WHY = composed_context() +NEEDS_COMPOSED = pytest.mark.skipif(not COMPOSED, reason=COMPOSED_WHY) + + +# --- the payload, read the way a consumer reads it ------------------------- + +def one_line_array(text: str, name: str) -> list[str]: + """`NAME=(a b c)` on ONE line, the way workBenches' `estate-commands-start` + `sed`-reads it out of the vendored `openRepoTools` (consumer-reads row W4): + a list that wrapped would read as nothing there, so it is asserted here.""" + lines = [line for line in text.splitlines() if line.startswith(f"{name}=(")] + assert len(lines) == 1, f"{name}=( is not one line: {lines}" + line = lines[0] + assert line.endswith(")") and line.count("(") == 1 and line.count(")") == 1, line + return line[len(name) + 2:-1].split() + + +SOURCE_TEXT = COMMAND.read_text(encoding="utf-8") +INSTALLABLES = one_line_array(SOURCE_TEXT, "INSTALLABLES") +SKILLS = one_line_array(SOURCE_TEXT, "SKILLS") +COMMANDS = one_line_array(SOURCE_TEXT, "COMMANDS") +SKILL_PATHS = [f"skills/{n}/SKILL.md" for n in SKILLS] +COMMAND_PATHS = [f"commands/{n}.md" for n in COMMANDS] +#: EVERY FILE `--install` READS, derived from the three lists and nothing else. +PAYLOAD = INSTALLABLES + SKILL_PATHS + COMMAND_PATHS + + +def destinations(home: Path, *, bin_dir: Path | None = None, + profiles: Path | None = None, + claude: Path | None = None) -> dict[str, str]: + """Every placed file's destination → the payload path it carries.""" + bin_dir = bin_dir or home / ".local" / "bin" + profiles = profiles or home / ".claude-profiles" + claude = claude or home / ".claude" + out = {str(bin_dir / n): n for n in INSTALLABLES} + for n, p in zip(SKILLS, SKILL_PATHS): + out[str(profiles / "shared" / "skills" / n / "SKILL.md")] = p + out[str(claude / "skills" / n / "SKILL.md")] = p + for n, p in zip(COMMANDS, COMMAND_PATHS): + out[str(profiles / "shared" / "commands" / f"{n}.md")] = p + out[str(claude / "commands" / f"{n}.md")] = p + return out + + +def variant(path: str, tag: str | None) -> bytes: + """This checkout's bytes for `path`, MARKED with `tag` so a test can tell + which commit an installed file came from. A comment line at the end, which + every payload file — bash, Python, Markdown and the alias table — reads + past.""" + data = (REPO / path).read_bytes() + if tag is None: + return data + return data + f"\n# payload variant {tag}\n".encode() + + +def payload_files(tag: str | None, *, without: tuple[str, ...] = ()) -> dict[str, bytes]: + return {p: variant(p, tag) for p in PAYLOAD if p not in without} + + +# --- repositories, built with git plumbing --------------------------------- + +def git(*args: str, cwd: Path | None = None, input: bytes | None = None, + env: dict | None = None) -> str: + proc = subprocess.run(["git", *args], cwd=str(cwd) if cwd else None, + input=input, capture_output=True, check=False, + env={**os.environ, **(env or {})}) + if proc.returncode != 0: + raise AssertionError(f"git {' '.join(args)} failed:\n" + f"{proc.stderr.decode(errors='replace')}") + return proc.stdout.decode() + + +IDENTITY = {"GIT_AUTHOR_NAME": "payload tests", "GIT_AUTHOR_EMAIL": "t@example.invalid", + "GIT_COMMITTER_NAME": "payload tests", + "GIT_COMMITTER_EMAIL": "t@example.invalid", + "GIT_AUTHOR_DATE": "2026-10-07T00:00:00Z", + "GIT_COMMITTER_DATE": "2026-10-07T00:00:00Z"} + + +def bare(server: Path, slug: str) -> Path: + path = server / f"{slug}.git" + if not path.exists(): + path.parent.mkdir(parents=True, exist_ok=True) + git("init", "-q", "--bare", "-b", "main", str(path)) + return path + + +def commit(repo: Path, files: dict[str, bytes], *, gitlinks: dict[str, str] | None = None, + branch: str | None = "main", message: str = "payload tests") -> str: + """One commit of exactly `files` (and `gitlinks`) in the bare `repo`. + + Executable bits follow this checkout: a payload command is 100755 because + its source is. Built from the object store up, so the tests pay for no + working tree.""" + index = repo / f"index-{os.getpid()}-{time.monotonic_ns()}" + env = {"GIT_INDEX_FILE": str(index), **IDENTITY} + lines = [] + for path, data in sorted(files.items()): + oid = git("--git-dir", str(repo), "hash-object", "-w", "--stdin", + input=data).strip() + source = REPO / path + mode = "100755" if (source.is_file() and os.access(source, os.X_OK)) else "100644" + lines.append(f"{mode} {oid}\t{path}") + for path, oid in sorted((gitlinks or {}).items()): + lines.append(f"160000 {oid}\t{path}") + git("--git-dir", str(repo), "update-index", "--index-info", + input=("\n".join(lines) + "\n").encode(), env=env) + tree = git("--git-dir", str(repo), "write-tree", env=env).strip() + index.unlink() + sha = git("--git-dir", str(repo), "commit-tree", tree, "-m", message, + env=env).strip() + if branch: + git("--git-dir", str(repo), "update-ref", f"refs/heads/{branch}", sha) + return sha + + +def tree_digest(repo: Path, sha: str) -> str: + """`sorted-ls-tree-r-v1`, as `contracts/code-pin.yaml` defines it.""" + out = subprocess.run(["git", "--git-dir", str(repo), "ls-tree", "-r", "-z", sha], + capture_output=True, check=True).stdout + records = sorted(r for r in out.split(b"\0") if r) + return hashlib.sha256(b"".join(r + b"\n" for r in records)).hexdigest() + + +#: THE PIN, in the grammar the rehearsed assembly carries (2726d3a's +#: `contracts/code-pin.yaml`), comments and quoting included. +PIN_TEXT = """\ +schema_version: 1 +kind: pinned_contract_manifest + +# =========================================================================== +# code-pin.yaml — the CODE leg, pinned by commit and digest. +# =========================================================================== + +leg_role: code +source_repository: {source} +submodule_path: {path} + +commit: "{commit}" +revision_kind: commit + +digest_algorithm: sha256 + +# WHAT IS DIGESTED, exactly. `sorted-ls-tree-r-v1`. +digest_definition: sorted-ls-tree-r-v1 +digests: + tree_sha256: "{digest}" + +verify_pin: scripts/validate-pins.py +resync_runbook: "README.md#the-lockstep-invariant" +""" + +PROJECT_YAML = """\ +schema_version: 1 +kind: project-manifest +id: openrepotools +legs: + - role: assembly + repository: opensoft/openRepoTools + path: "." + - role: code + repository: opensoft/openRepoTools-code + path: code +""" + +#: WHAT SITS AT THE ASSEMBLY ROOT IN THESE FIXTURES where the real assembly has +#: its entry point. It is never a payload: an install that placed it would put +#: a command that exits 97 on somebody's PATH, which these tests assert never +#: happens. +ROOT_PLACEHOLDER = (b"#!/usr/bin/env bash\n" + b"echo 'the assembly entry point placeholder ran' >&2\nexit 97\n") + + +def shape_pin(sha: str) -> bytes: + return (f"schema_version: 1\nkind: pinned_contract_manifest\n" + f"pin_role: upstream-standard\nsource_repository: opensoft/openRepoShape\n" + f"submodule_path: upstream/openRepoShape\n" + f'commit: "{sha}"\nrevision_kind: commit\n').encode() + + +class World: + """One fake GitHub, its repositories and its request log.""" + + def __init__(self, root: Path): + self.root = root + self.server = root / "github" + self.server.mkdir(parents=True, exist_ok=True) + self.log = root / "requests.log" + self.conf = root / "fake-github.json" + self.state = root / "fake-github.state" + self.faults: list[dict] = [] + self.moves: list[dict] = [] + self.write_conf() + + def write_conf(self) -> None: + self.conf.write_text(json.dumps({ + "server": str(self.server), "log": str(self.log), + "state": str(self.state), "faults": self.faults, + "moves": self.moves}), encoding="utf-8") + if self.state.exists(): + self.state.unlink() + + def repo(self, slug: str) -> Path: + return bare(self.server, slug) + + # -- the code leg, the assembly and the single repository -------------- + def code(self, slug: str = "opensoft/openRepoTools-code", tag: str | None = "pinned", + *, without: tuple[str, ...] = (), shape: str | None = None, + branch: str | None = "main") -> str: + files = payload_files(tag, without=without) + files["contracts/openreposhape-pin.yaml"] = ( + shape_pin(shape) if shape else + (REPO / "contracts" / "openreposhape-pin.yaml").read_bytes()) + return commit(self.repo(slug), files, branch=branch, + message=f"code {tag}") + + def assembly(self, code_commit: str, *, slug: str = "opensoft/openRepoTools", + code_slug: str = "opensoft/openRepoTools-code", + pin: str | None = None, gitlink: str | None = None, + manifest: bool = True, with_pin: bool = True, + extra: dict[str, bytes] | None = None, + root_entry: bytes = ROOT_PLACEHOLDER, + branch: str | None = "main") -> str: + code_repo = self.repo(code_slug) + if pin is None: + pin = PIN_TEXT.format(source=code_slug, path="code", commit=code_commit, + digest=tree_digest(code_repo, code_commit)) + files = {"README.md": b"# openRepoTools\n", "openRepoTools": root_entry} + if manifest: + files["project.yaml"] = PROJECT_YAML.encode() + if with_pin: + files["contracts/code-pin.yaml"] = pin.encode() + files["contracts/shape-pin.yaml"] = b"schema_version: 1\n" + files.update(extra or {}) + return commit(self.repo(slug), files, + gitlinks={"code": gitlink or code_commit}, branch=branch, + message="assembly") + + def single(self, slug: str = "opensoft/openRepoTools", tag: str | None = "legacy", + *, branch: str | None = "main") -> str: + """Today's layout: every payload file at the root, the dependency pin + in `contracts/`, and no adoption manifest.""" + files = payload_files(tag) + files["contracts/openreposhape-pin.yaml"] = ( + REPO / "contracts" / "openreposhape-pin.yaml").read_bytes() + files["README.md"] = b"# openRepoTools\n" + files["AGENTS.md"] = b"# Working in openRepoTools\n" + return commit(self.repo(slug), files, branch=branch, + message=f"single {tag}") + + def ref(self, slug: str, ref: str, sha: str) -> None: + git("--git-dir", str(self.repo(slug)), "update-ref", ref, sha) + + def tree_of(self, slug: str, sha: str, path: str) -> str: + return git("--git-dir", str(self.repo(slug)), "rev-parse", + f"{sha}:{path}").strip() + + # -- the request log --------------------------------------------------- + def requests(self) -> list[dict]: + if not self.log.exists(): + return [] + return [json.loads(line) for line in + self.log.read_text(encoding="utf-8").splitlines() if line] + + def payload_requests(self) -> list[dict]: + """Every request for a payload FILE (not a listing, not a commit).""" + out = [] + for r in self.requests(): + target = r["target"] + for path in PAYLOAD: + if (f"/contents/{path}?" in target + or target.endswith(f"/{path}") and "raw.githubusercontent" in target): + out.append({**r, "path": path}) + return out + + +#: A fake GitHub in one program: `gh api`, `gh repo …` and `curl`, answered from +#: bare repositories with git. Faults and branch moves come from its JSON +#: configuration, and every request is logged before it is answered. +FAKE_GITHUB = r''' +import json, os, re, subprocess, sys, urllib.parse + +conf_path, tool, args = sys.argv[1], sys.argv[2], sys.argv[3:] +with open(conf_path) as fh: + conf = json.load(fh) +server = conf["server"] + + +def repo_dir(slug): + return os.path.join(server, slug + ".git") + + +def git(repo, *a, data=None): + return subprocess.run(["git", "--git-dir", repo, *a], capture_output=True, + input=data) + + +def resolve(repo, ref): + p = git(repo, "rev-parse", "--verify", "--quiet", ref + "^{commit}") + return p.stdout.decode().strip() if p.returncode == 0 else "" + + +def log(entry): + with open(conf["log"], "a") as fh: + fh.write(json.dumps(entry) + "\n") + + +def tick(): + try: + with open(conf["state"]) as fh: + state = json.load(fh) + except (OSError, ValueError): + state = {"count": 0, "applied": []} + for i, move in enumerate(conf.get("moves", [])): + if i not in state["applied"] and state["count"] >= move["after"]: + git(repo_dir(move["repo"]), "update-ref", move["ref"], move["to"]) + state["applied"].append(i) + state["count"] += 1 + with open(conf["state"], "w") as fh: + json.dump(state, fh) + + +def api(path, query, accept): + m = re.match(r"repos/([^/]+)/([^/]+)/(.*)$", path) + if not m: + return 404, json.dumps({"message": "Not Found"}).encode() + owner, name, rest = m.groups() + repo = repo_dir(owner + "/" + name) + if not os.path.isdir(repo): + return 404, json.dumps({"message": "Not Found"}).encode() + if rest.startswith("commits/"): + ref = urllib.parse.unquote(rest[len("commits/"):]) + sha = resolve(repo, ref) + if not sha: + return 422, json.dumps({"message": "No commit found for SHA: " + ref, + "status": "422"}).encode() + if accept == "application/vnd.github.sha": + return 200, sha.encode() + return 200, json.dumps({"sha": sha}).encode() + if rest.startswith("git/trees/"): + ref = rest[len("git/trees/"):] + kind = git(repo, "cat-file", "-t", ref).stdout.decode().strip() + if kind == "commit": + ref = git(repo, "rev-parse", ref + "^{tree}").stdout.decode().strip() + elif kind != "tree": + return 404, json.dumps({"message": "Not Found"}).encode() + listing = git(repo, "ls-tree", "-z", ref).stdout.decode() + tree = [] + for record in filter(None, listing.split("\0")): + meta, path_ = record.split("\t", 1) + mode, typ, sha = meta.split() + tree.append({"path": path_, "mode": mode, "type": typ, "sha": sha}) + return 200, json.dumps({"sha": ref, "tree": tree, "truncated": False}).encode() + if rest.startswith("contents/"): + path_ = urllib.parse.unquote(rest[len("contents/"):]) + ref = query.get("ref", ["main"])[0] + return blob(repo, ref, path_) + return 404, json.dumps({"message": "Not Found"}).encode() + + +def blob(repo, ref, path_): + sha = resolve(repo, ref) + if not sha: + return 404, json.dumps({"message": "No commit found for the ref " + ref}).encode() + obj = sha + ":" + path_ + if git(repo, "cat-file", "-t", obj).stdout.decode().strip() != "blob": + return 404, json.dumps({"message": "Not Found"}).encode() + return 200, git(repo, "cat-file", "blob", obj).stdout + + +def raw(url_path): + parts = url_path.split("/") + if len(parts) < 4: + return 404, b"404: Not Found" + repo = repo_dir(parts[0] + "/" + parts[1]) + if not os.path.isdir(repo): + return 404, b"404: Not Found" + rest = parts[2:] + for k in range(1, len(rest)): + ref, path_ = "/".join(rest[:k]), "/".join(rest[k:]) + if resolve(repo, ref): + status, body = blob(repo, ref, path_) + return (status, body) if status == 200 else (404, b"404: Not Found") + return 404, b"404: Not Found" + + +def fault_for(target): + for f in conf.get("faults", []): + if f.get("tool", "any") in ("any", tool) and re.search(f["match"], target): + return f + return None + + +MESSAGES = {401: "Bad credentials", 403: "API rate limit exceeded for 203.0.113.7.", + 404: "Not Found", 500: "Server Error"} + + +def gh_main(): + if args[:1] == ["repo"]: + return gh_repo(args[1:]) + if args[:1] != ["api"]: + sys.stderr.write("fake gh: unexpected call: %s\n" % " ".join(args)) + return 1 + endpoint, accept, jq, method = None, "", None, "GET" + i = 1 + while i < len(args): + a = args[i] + if a in ("-H", "--header"): + h = args[i + 1] + if h.lower().startswith("accept:"): + accept = h.split(":", 1)[1].strip() + i += 2 + elif a in ("--jq", "-q"): + jq = args[i + 1] + i += 2 + elif a in ("--method", "-X"): + method = args[i + 1] + i += 2 + elif a in ("--paginate", "--silent"): + i += 1 + elif a.startswith("-"): + i += 2 + elif endpoint is None: + endpoint = a + i += 1 + else: + i += 1 + endpoint = (endpoint or "").lstrip("/") + log({"tool": "gh", "target": endpoint, "accept": accept}) + tick() + if method == "PUT": + return 0 + if endpoint == "user": + sys.stdout.write("brettheap\n") + return 0 + f = fault_for(endpoint) + if f and f["kind"] == "transport": + sys.stderr.write("error connecting to api.github.com\n" + "check your internet connection or https://githubstatus.com\n") + return 1 + if f and f["kind"] == "status": + status = f["status"] + msg = MESSAGES.get(status, "Error") + sys.stdout.write(json.dumps({"message": msg})) + sys.stderr.write("gh: %s (HTTP %d)\n" % (msg, status)) + return 1 + path, _, q = endpoint.partition("?") + status, body = api(path, urllib.parse.parse_qs(q), accept) + if f and f["kind"] == "garbage": + status, body = 200, b"a captive portal answered this" + if status >= 400: + sys.stdout.write(body.decode(errors="replace")) + msg = json.loads(body).get("message", "Error") + sys.stderr.write("gh: %s (HTTP %d)\n" % (msg, status)) + return 1 + if jq is not None: + p = subprocess.run(["jq", "-r", jq], input=body, capture_output=True) + if p.returncode != 0: + sys.stderr.write("failed to parse jq expression or JSON: " + + p.stderr.decode(errors="replace")) + return 1 + sys.stdout.buffer.write(p.stdout) + return 0 + sys.stdout.buffer.write(body) + return 0 + + +def gh_repo(rest): + log({"tool": "gh", "target": "repo " + " ".join(rest), "accept": ""}) + verb, slug = rest[0], rest[1] + repo = repo_dir(slug) + if verb == "view": + return 0 if os.path.isdir(repo) else 1 + if verb == "create": + os.makedirs(os.path.dirname(repo), exist_ok=True) + subprocess.run(["git", "init", "-q", "--bare", "-b", "main", repo], check=True) + return 0 + if verb == "clone": + p = subprocess.run(["git", "clone", "-q", repo, rest[2]]) + return p.returncode + return 1 + + +def curl_main(): + url, accept, out, fmt, fail = None, "", None, None, False + i = 0 + while i < len(args): + a = args[i] + if a in ("-H", "--header"): + h = args[i + 1] + if h.lower().startswith("accept:"): + accept = h.split(":", 1)[1].strip() + i += 2 + elif a in ("-o", "--output"): + out = args[i + 1] + i += 2 + elif a in ("-w", "--write-out"): + fmt = args[i + 1] + i += 2 + elif a == "--fail" or (a.startswith("-") and not a.startswith("--") and "f" in a): + fail = True + i += 1 + elif a.startswith("-"): + i += 1 + else: + url = a + i += 1 + log({"tool": "curl", "target": url or "", "accept": accept}) + tick() + f = fault_for(url or "") + if f and f["kind"] == "transport": + sys.stderr.write("curl: (6) Could not resolve host: %s\n" + % urllib.parse.urlsplit(url).netloc) + return 6 + parts = urllib.parse.urlsplit(url or "") + if f and f["kind"] == "status": + status, body = f["status"], json.dumps({"message": MESSAGES.get(f["status"], "Error")}).encode() + elif parts.netloc == "api.github.com": + status, body = api(urllib.parse.unquote(parts.path.lstrip("/")), + urllib.parse.parse_qs(parts.query), accept) + elif parts.netloc == "raw.githubusercontent.com": + status, body = raw(parts.path.lstrip("/")) + else: + sys.stderr.write("curl: (6) Could not resolve host: %s\n" % parts.netloc) + return 6 + if f and f["kind"] == "garbage": + status, body = 200, b"a captive portal answered this" + if status >= 400 and fail: + sys.stderr.write("curl: (22) The requested URL returned error: %d\n" % status) + return 22 + if out: + with open(out, "wb") as fh: + fh.write(body) + else: + sys.stdout.buffer.write(body) + if fmt: + sys.stdout.write(fmt.replace("%{http_code}", str(status))) + return 0 + + +sys.exit(gh_main() if tool == "gh" else curl_main()) +''' + + +def path_farm(root: Path, leave_out: tuple[str, ...]) -> Path: + """A directory of links to every executable on the real `$PATH` except + `leave_out`: a machine on which those names do not EXIST.""" + farm = root / "path-farm" + if farm.exists(): + return farm + farm.mkdir() + for directory in os.environ.get("PATH", "").split(os.pathsep): + try: + entries = sorted(os.listdir(directory)) + except OSError: + continue + for entry in entries: + source = os.path.join(directory, entry) + link = farm / entry + if entry in leave_out or link.is_symlink() or not ( + os.path.isfile(source) and os.access(source, os.X_OK)): + continue + link.symlink_to(source) + return farm + + +def network(world: World, *, gh: bool = True, curl: bool = True, + extra_shims: dict[str, str] | None = None) -> str: + """The `$PATH` a run gets: the fakes that are switched on, first, then a + farm with no real `gh` and no real `curl` in it at all.""" + fake = world.root / ("fake-" + ("g" if gh else "") + ("c" if curl else "") + + ("x" if extra_shims else "")) + fake.mkdir(exist_ok=True) + program = world.root / "fake_github.py" + program.write_text(FAKE_GITHUB, encoding="utf-8") + for name, wanted in (("gh", gh), ("curl", curl)): + if wanted: + shim = fake / name + shim.write_text(f"#!/bin/sh\nexec '{sys.executable}' -I '{program}' " + f"'{world.conf}' {name} \"$@\"\n", encoding="utf-8") + shim.chmod(0o755) + for name, body in (extra_shims or {}).items(): + (fake / name).write_text(body, encoding="utf-8") + (fake / name).chmod(0o755) + farm = path_farm(world.root, ("gh", "curl")) + return f"{fake}{os.pathsep}{farm}" + + +def env_for(home: Path, path: str, **extra: str) -> dict: + environ = dict(os.environ) + for name in list(environ): + if name.startswith("OPENREPOTOOLS_") or name in ( + "XDG_DATA_HOME", "CLAUDE_PROFILES_HOME", "CLAUDE_USER_DIR", + "AGENT_PROTOCOL_ROOT", "PROJECTS_DIR", "GH_TOKEN", "GITHUB_TOKEN"): + environ.pop(name) + environ.update({"HOME": str(home), "PATH": path, + "CLAUDE_PROFILES_HOME": str(home / ".claude-profiles"), + "CLAUDE_USER_DIR": str(home / ".claude"), + "AGENT_PROTOCOL_ROOT": str(home / ".agents"), + "XDG_CACHE_HOME": str(home / ".cache"), **IDENTITY}) + environ.update(extra) + return environ + + +def run_stdin(world: World, home: Path, path: str, *args: str, + script: Path = COMMAND, **extra: str) -> subprocess.CompletedProcess: + """The documented one-liner's shape: the implementation on stdin, from a + directory holding nothing named `bash`, so it runs as no file at all.""" + cwd = world.root / "cwd" + cwd.mkdir(exist_ok=True) + return subprocess.run(["bash", "-s", "--", *args], input=script.read_text(encoding="utf-8"), + capture_output=True, text=True, check=False, cwd=str(cwd), + env=env_for(home, path, **extra)) + + +def run_file(script: Path, home: Path, path: str, *args: str, + **extra: str) -> subprocess.CompletedProcess: + return subprocess.run(["bash", str(script), *args], input="", capture_output=True, + text=True, check=False, cwd=str(home.parent), + env=env_for(home, path, **extra)) + + +def snapshot(root: Path) -> dict[str, bytes]: + if not root.exists(): + return {} + return {str(p.relative_to(root)): (p.read_bytes() if p.is_file() else b"") + for p in sorted(root.rglob("*"))} + + +def assert_installed(home: Path, tag: str | None, result, **dirs) -> None: + for dest, path in destinations(home, **dirs).items(): + target = Path(dest) + assert target.is_file(), f"{dest} not placed\n{result.stdout}\n{result.stderr}" + assert target.read_bytes() == variant(path, tag), ( + f"{dest} does not hold the {tag!r} bytes of {path}\n{result.stdout}") + + +def assert_nothing_placed(home: Path, result) -> None: + assert result.returncode == 2, result.stdout + result.stderr + assert "REFUSED" in result.stderr, result.stderr + for dest in destinations(home): + assert not Path(dest).exists(), f"{dest} was placed by a refused run" + + +def local_tree(root: Path, tag: str | None) -> Path: + """A complete directory of payload files, the way a checkout or a vendored + tree holds them, with the executable bits the repository gives them.""" + root.mkdir(parents=True, exist_ok=True) + for path, data in payload_files(tag).items(): + target = root / path + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(data) + if os.access(REPO / path, os.X_OK): + target.chmod(0o755) + return root + + +def install_prior(world: World, home: Path, tag: str = "prior") -> None: + """An earlier, complete install, from a local tree, offline.""" + tree = local_tree(world.root / f"tree-{tag}", tag) + result = run_file(tree / "openRepoTools", home, network(world), "--install") + assert result.returncode == 0, result.stdout + result.stderr + + +# --- a local assembly checkout ---------------------------------------------- + +def local_assembly(world: World, *, tag: str = "local") -> tuple[Path, Path, str]: + """An assembly CHECKOUT with its code leg mounted at `code/`: the pin, the + manifest, and the gitlink in the assembly's index, all naming the code + checkout's own HEAD.""" + assembly = world.root / "checkout" + code = assembly / "code" + local_tree(code, tag) + (code / "contracts").mkdir(exist_ok=True) + shutil.copy2(REPO / "contracts" / "openreposhape-pin.yaml", + code / "contracts" / "openreposhape-pin.yaml") + git("init", "-q", "-b", "main", cwd=code) + git("add", "-A", cwd=code) + git("commit", "-q", "-m", "code", cwd=code, env=IDENTITY) + head = git("rev-parse", "HEAD", cwd=code).strip() + git("init", "-q", "-b", "main", cwd=assembly) + (assembly / "contracts").mkdir() + digest = tree_digest(code / ".git", head) + (assembly / "contracts" / "code-pin.yaml").write_text(PIN_TEXT.format( + source="opensoft/openRepoTools-code", path="code", commit=head, + digest=digest), encoding="utf-8") + (assembly / "project.yaml").write_text(PROJECT_YAML, encoding="utf-8") + (assembly / "openRepoTools").write_bytes(ROOT_PLACEHOLDER) + git("add", "contracts", "project.yaml", "openRepoTools", cwd=assembly) + git("update-index", "--add", "--cacheinfo", f"160000,{head},code", cwd=assembly) + git("commit", "-q", "-m", "assembly", cwd=assembly, env=IDENTITY) + return assembly, code, head + + +# ========================================================================= +# ROW 1 — the existing raw/gh one-liners +# ========================================================================= + +@NEEDS_JQ +def test_r01_the_one_liner_against_a_single_repository_installs_as_before(tmp_path): + """Today's layout through today's one-liner: every name, every destination, + and no line a person has not seen before — the output of a fetching + install is the output of a local one.""" + world = World(tmp_path) + world.single(tag=None) + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, None, result) + local_home = tmp_path / "local-home" + local = run_file(local_tree(tmp_path / "tree", None) / "openRepoTools", local_home, + network(world), "--install") + assert local.returncode == 0, local.stderr + assert (result.stdout.replace(str(home), "") + == local.stdout.replace(str(local_home), "")) + + +@NEEDS_JQ +@pytest.mark.parametrize("transport", ["gh", "curl"]) +def test_r01_the_one_liner_against_an_adopted_assembly_installs_the_pinned_code( + tmp_path, transport): + """The default `opensoft/openRepoTools` is the ASSEMBLY after the split: + the implementation and its payload are the code leg's, at the commit the + assembly pins, by `gh` or — where there is none — by `curl` and `jq`.""" + world = World(tmp_path) + code = world.code() + world.assembly(code) + home = tmp_path / "home" + result = run_stdin(world, home, network(world, gh=transport == "gh"), "--install") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "pinned", result) + assert (f"openRepoTools: source: opensoft/openRepoTools at main" + in result.stdout), result.stdout + assert f"opensoft/openRepoTools-code at {code}" in result.stdout + + +@NEEDS_COMPOSED +@NEEDS_JQ +@pytest.mark.parametrize("line", ["openRepoShape", "gh"]) +def test_r01_composed_the_documented_one_liners_resolve_through_the_root_entry_point( + tmp_path, line): + """openRepoShape's own `--install` pointer (openRepoShape:280 at 7f84ca4) + and README's `gh api` line, BYTE FOR BYTE, against an assembly whose root + carries the real entry point: the root resolves `main` ONCE, reads the code + pin there and hands every argument to the pinned implementation, which + places the pinned payload.""" + world = World(tmp_path) + code = world.code() + world.assembly(code, root_entry=(ASSEMBLY / "openRepoTools").read_bytes()) + home = tmp_path / "home" + pipeline = { + "openRepoShape": "curl -fsSL https://raw.githubusercontent.com/opensoft/" + "openRepoTools/main/openRepoTools | bash -s -- --install", + "gh": "gh api repos/opensoft/openRepoTools/contents/openRepoTools " + "-H 'Accept: application/vnd.github.raw' | bash -s -- --install", + }[line] + cwd = tmp_path / "cwd" + cwd.mkdir() + result = subprocess.run(["bash", "-c", "set -o pipefail; " + pipeline], + capture_output=True, text=True, check=False, cwd=str(cwd), + env=env_for(home, network(world))) + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "pinned", result) + resolutions = [r for r in world.requests() + if r["target"].startswith("repos/opensoft/openRepoTools/commits/") + or "/repos/opensoft/openRepoTools/commits/" in r["target"]] + assert [r["target"].rsplit("/", 1)[1] for r in resolutions].count("main") == 1, resolutions + + +@NEEDS_COMPOSED +@NEEDS_JQ +def test_r01_composed_the_root_entry_point_delegates_locally(tmp_path): + """`./openRepoTools` at an assembly checkout IS the pinned implementation + for every argument: the same help, the same refusal and exit status, the + same install.""" + world = World(tmp_path) + home = tmp_path / "home" + root = ASSEMBLY / "openRepoTools" + path = network(world) + for args in (("--help",), ("--version",), ("park",)): + mine = run_file(root, home, path, *args) + theirs = run_file(COMMAND, home, path, *args) + assert (mine.returncode, mine.stdout, mine.stderr) == \ + (theirs.returncode, theirs.stdout, theirs.stderr), args + result = run_file(root, home, path, "--install") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, None, result) + assert "nothing fetched" in result.stdout + assert world.requests() == [] + + +@NEEDS_COMPOSED +@NEEDS_JQ +def test_r01_composed_a_signal_reaches_the_implementation_and_its_temporaries_go(tmp_path): + """The root EXECs the implementation, so a TERM sent to the pid the person + started is a TERM to the installer itself, whose own EXIT trap removes its + temporary directory.""" + world = World(tmp_path) + home = tmp_path / "home" + gate = tmp_path / "gate" + real_cp = shutil.which("cp") + slow_cp = (f"#!/bin/sh\n: > '{gate}'\n" + f"while [ -e '{gate}' ]; do sleep 0.1; done\nexec '{real_cp}' \"$@\"\n") + tmp = tmp_path / "t" + tmp.mkdir() + proc = subprocess.Popen(["bash", str(ASSEMBLY / "openRepoTools"), "--install"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=env_for(home, network(world, extra_shims={"cp": slow_cp}), + TMPDIR=str(tmp))) + for _ in range(300): + if gate.exists(): + break + time.sleep(0.05) + assert gate.exists(), "the install never reached its first copy" + proc.send_signal(signal.SIGTERM) + try: + proc.wait(timeout=20) + finally: + gate.unlink() + assert proc.returncode in (-signal.SIGTERM, 128 + signal.SIGTERM), proc.returncode + assert list(tmp.iterdir()) == [], "the installer's temporary directory survived" + + +# ========================================================================= +# ROW 2 — offline local single repository / adopted checkout +# ========================================================================= + +@NEEDS_JQ +def test_r02_a_complete_single_repository_tree_installs_offline_byte_for_byte(tmp_path): + """workBenches runs `--install` from its vendored tree with a SENTINEL + `$OPENREPOTOOLS_REPO`/`$OPENREPOTOOLS_REF` so that only sibling copies can + succeed (consumer-reads W5). A complete tree is copied, nothing is asked of + any network, and the output is today's to the line.""" + world = World(tmp_path) + tree = local_tree(tmp_path / "vendored", None) + home = tmp_path / "home" + result = run_file(tree / "openRepoTools", home, network(world), "--install", + OPENREPOTOOLS_REPO="pinned-by-workBenches-no-fetch", + OPENREPOTOOLS_REF="pinned-by-workBenches-no-fetch") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, None, result) + assert world.requests() == [] + assert "source:" not in result.stdout + + +@NEEDS_JQ +def test_r02_an_adopted_checkout_installs_its_pinned_code_offline(tmp_path): + """Run from the code leg mounted in an assembly checkout: the pin and the + gitlink agree, every payload file is the pinned commit's bytes, and they + are copied with no network at all — and the run says which commit that + was.""" + world = World(tmp_path) + assembly, code, head = local_assembly(world) + home = tmp_path / "home" + result = run_file(code / "openRepoTools", home, network(world), "--install") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "local", result) + assert world.requests() == [] + assert (f"openRepoTools: source: the code mounted at {assembly}/code, " + f"opensoft/openRepoTools-code at {head}") in result.stdout, result.stdout + assert "nothing fetched" in result.stdout + + +# ========================================================================= +# ROW 3 — an explicit local code feature +# ========================================================================= + +@NEEDS_JQ +def test_r03_an_explicit_code_feature_checkout_installs_its_own_bytes_and_names_them(tmp_path): + """A paired feature worktree, `/worktrees//code`, run by + its own `openRepoTools`: ITS bytes — committed and uncommitted — are what + is placed, and the run names that checkout and its commit and says it is + not the code the assembly pins.""" + world = World(tmp_path) + assembly, code, pinned = local_assembly(world) + feature = assembly / "worktrees" / "001-x" / "code" + git("clone", "-q", str(code), str(feature)) + (feature / "park").write_bytes(variant("park", "feature-committed")) + git("commit", "-q", "-am", "feature", cwd=feature, env=IDENTITY) + head = git("rev-parse", "HEAD", cwd=feature).strip() + (feature / "status").write_bytes(variant("status", "feature-uncommitted")) + home = tmp_path / "home" + result = run_file(feature / "openRepoTools", home, network(world), "--install") + assert result.returncode == 0, result.stdout + result.stderr + bin_dir = home / ".local" / "bin" + assert (bin_dir / "park").read_bytes() == variant("park", "feature-committed") + assert (bin_dir / "status").read_bytes() == variant("status", "feature-uncommitted") + assert world.requests() == [] + assert f"the code checkout {feature} at {head} with uncommitted changes" \ + in result.stdout, result.stdout + assert f"NOT the code {assembly} pins" in result.stdout + assert pinned in result.stdout + + +@NEEDS_JQ +def test_r03_an_incomplete_code_feature_checkout_is_refused_not_completed_from_elsewhere(tmp_path): + """A feature checkout missing a payload file is not completed from any + remote: its own bytes or none.""" + world = World(tmp_path) + world.code() + world.assembly(world.code(tag="remote")) + assembly, code, _ = local_assembly(world) + feature = assembly / "worktrees" / "002-y" / "code" + git("clone", "-q", str(code), str(feature)) + (feature / "lanes-index").unlink() + home = tmp_path / "home" + result = run_file(feature / "openRepoTools", home, network(world), "--install") + assert_nothing_placed(home, result) + assert "lanes-index" in result.stderr + assert world.requests() == [] + + +# ========================================================================= +# ROW 4 — installed reinstallation and skill-only operation +# ========================================================================= + +@NEEDS_JQ +def test_r04_an_installed_copy_reinstalls_from_the_adopted_source_without_any_checkout(tmp_path): + """The INSTALLED `openRepoTools` has its seventeen siblings beside it and + no skills: that is an incomplete local set, and on an adopted source it is + never topped up with pinned skills. Every payload file comes from the code + the assembly pins NOW, and the run says the siblings were not used.""" + world = World(tmp_path) + home = tmp_path / "home" + install_prior(world, home, "prior") + code = world.code(tag="pinned-now") + world.assembly(code) + result = run_file(home / ".local" / "bin" / "openRepoTools", home, network(world), + "--install") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "pinned-now", result) + assert "are not used" in result.stdout, result.stdout + + +@NEEDS_JQ +def test_r04_an_installed_copy_on_a_single_repository_keeps_the_sibling_rule(tmp_path): + """Today's promise, unchanged on today's layout: the commands beside the + installed copy are copied, and only the skills and command files, which are + not beside it, are fetched.""" + world = World(tmp_path) + home = tmp_path / "home" + install_prior(world, home, "prior") + world.single(tag="remote") + result = run_file(home / ".local" / "bin" / "openRepoTools", home, network(world), + "--install") + assert result.returncode == 0, result.stdout + result.stderr + for dest, path in destinations(home).items(): + want = "prior" if path in INSTALLABLES else "remote" + assert Path(dest).read_bytes() == variant(path, want), dest + assert "source:" not in result.stdout + + +# ========================================================================= +# ROW 5 — single-repository override / adopted fork +# ========================================================================= + +@NEEDS_JQ +def test_r05_a_single_repository_fork_named_by_the_override_installs_as_before(tmp_path): + world = World(tmp_path) + world.single("someone/fork", tag="fork") + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install", + OPENREPOTOOLS_REPO="someone/fork") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "fork", result) + + +@NEEDS_JQ +def test_r05_an_adopted_fork_follows_its_own_pin_not_a_guessed_code_name(tmp_path): + """A fork of the assembly whose pin names `forker/tools-implementation`: + that repository is the payload, and neither `forker/openRepoTools-code` + (the guess an appended `-code` makes) nor the upstream code leg is asked + for anything.""" + world = World(tmp_path) + world.code("forker/openRepoTools-code", tag="guessed") + world.code("opensoft/openRepoTools-code", tag="upstream") + code = world.code("forker/tools-implementation", tag="fork-pinned") + world.assembly(code, slug="forker/openRepoTools", + code_slug="forker/tools-implementation") + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install", + OPENREPOTOOLS_REPO="forker/openRepoTools") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "fork-pinned", result) + assert not [r for r in world.requests() if "openRepoTools-code" in r["target"]] + + +# ========================================================================= +# ROW 6 — branch, tag, commit and slash-containing refs +# ========================================================================= + +@NEEDS_JQ +@pytest.mark.parametrize("form", ["branch", "tag", "commit", "slash"]) +@pytest.mark.parametrize("layout", ["adopted", "single"]) +def test_r06_every_ref_form_is_resolved_once_and_every_payload_names_one_commit( + tmp_path, form, layout): + """The ref is asked about ONCE, and every payload request after that names + the one immutable commit — never the ref, which can move.""" + world = World(tmp_path) + if layout == "adopted": + code = world.code(tag=form) + sha = world.assembly(code, branch=None) + payload_commit = code + else: + sha = world.single(tag=form, branch=None) + payload_commit = sha + # `main` holds something else entirely, so a run that read `main` instead + # of the ref it was given installs the decoy. + if layout == "single": + world.single(tag="decoy-main") + else: + world.assembly(world.code(tag="decoy-main", branch=None)) + ref = {"branch": "release", "tag": "v1.0", "commit": sha, + "slash": "feature/triad-x"}[form] + if form == "tag": + world.ref("opensoft/openRepoTools", f"refs/tags/{ref}", sha) + elif form != "commit": + world.ref("opensoft/openRepoTools", f"refs/heads/{ref}", sha) + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install", + OPENREPOTOOLS_REF=ref) + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, form, result) + asked = [r for r in world.requests() + if re.search(r"repos/opensoft/openRepoTools/commits/", r["target"])] + assert len(asked) == 1, asked + payload = world.payload_requests() + assert len(payload) == len(PAYLOAD), payload + for r in payload: + assert payload_commit in r["target"], r + + +# ========================================================================= +# ROW 7 — a branch that moves between downloads +# ========================================================================= + +@NEEDS_JQ +@pytest.mark.parametrize("layout", ["adopted", "single"]) +def test_r07_a_branch_that_moves_between_downloads_never_mixes_versions(tmp_path, layout): + world = World(tmp_path) + if layout == "adopted": + first = world.assembly(world.code(tag="first")) + second = world.assembly(world.code(tag="second", branch=None), branch=None) + else: + first = world.single(tag="first") + second = world.single(tag="second", branch=None) + assert first != second + world.moves = [{"after": 6, "repo": "opensoft/openRepoTools", + "ref": "refs/heads/main", "to": second}] + world.write_conf() + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "first", result) + assert git("--git-dir", str(world.repo("opensoft/openRepoTools")), "rev-parse", + "main").strip() == second, "the branch never moved: the test proved nothing" + + +# ========================================================================= +# ROW 8 — missing pin in a valid single repository / partial adopted layout +# ========================================================================= + +@NEEDS_JQ +def test_r08_a_single_repository_without_a_code_pin_is_permitted(tmp_path): + """Today's tree has `contracts/` — the dependency pin — and no code pin, + and no adoption manifest: a single repository, installed as one.""" + world = World(tmp_path) + world.single(tag="single") + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "single", result) + + +@NEEDS_JQ +def test_r08_an_adoption_manifest_without_a_code_pin_is_refused(tmp_path): + world = World(tmp_path) + world.assembly(world.code(), with_pin=False, + extra={p: variant(p, "decoy") for p in PAYLOAD}) + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install") + assert_nothing_placed(home, result) + assert "project.yaml" in result.stderr and "contracts/code-pin.yaml" in result.stderr, result.stderr + assert world.payload_requests() == [] + + +def malformed(pin: str, how: str) -> str: + sha = re.search(r'commit: "([0-9a-f]{40})"', pin).group(1) + return { + "leg-role": lambda: pin.replace("leg_role: code", "leg_role: spec"), + "commit-abbreviated": lambda: pin.replace(sha, sha[:7]), + "commit-uppercase": lambda: pin.replace(sha, sha.upper()), + "commit-a-branch": lambda: pin.replace(f'"{sha}"', "main"), + "repository-not-a-slug": lambda: pin.replace("opensoft/openRepoTools-code", + "openRepoTools-code"), + "repository-traversal": lambda: pin.replace("opensoft/openRepoTools-code", + "opensoft/.."), + "path-nested": lambda: pin.replace("submodule_path: code", "submodule_path: legs/code"), + "digest-short": lambda: re.sub(r'tree_sha256: "([0-9a-f]{63})[0-9a-f]"', + r'tree_sha256: "\1"', pin), + "digest-definition": lambda: pin.replace("digest_definition: sorted-ls-tree-r-v1", + "digest_definition: tarball-v0"), + "digest-algorithm": lambda: pin.replace("digest_algorithm: sha256", + "digest_algorithm: md5"), + "revision-kind": lambda: pin.replace("revision_kind: commit", "revision_kind: tag"), + "kind": lambda: pin.replace("kind: pinned_contract_manifest", "kind: something-else"), + "duplicate-commit": lambda: pin.replace("revision_kind: commit", + f'commit: "{"0" * 40}"\nrevision_kind: commit'), + "missing-verify-pin": lambda: pin.replace("verify_pin: scripts/validate-pins.py\n", ""), + "missing-resync-runbook": lambda: re.sub(r"resync_runbook: .*\n", "", pin), + "crlf": lambda: pin.replace("\n", "\r\n"), + "unterminated-quote": lambda: pin.replace(f'"{sha}"', f'"{sha}'), + "digests-not-a-mapping": lambda: pin.replace("digests:\n", 'digests: "x"\n'), + "empty": lambda: "", + }[how]() + + +@NEEDS_JQ +@pytest.mark.parametrize("how", [ + "leg-role", "commit-abbreviated", "commit-uppercase", "commit-a-branch", + "repository-not-a-slug", "repository-traversal", "path-nested", "digest-short", + "digest-definition", "digest-algorithm", "revision-kind", "kind", + "duplicate-commit", "missing-verify-pin", "missing-resync-runbook", "crlf", + "unterminated-quote", "digests-not-a-mapping", "empty"]) +def test_r08_a_malformed_code_pin_is_refused_never_read_as_a_single_repository(tmp_path, how): + """A pin that is PRESENT and wrong refuses, naming what is wrong — and the + assembly root's own copies of the payload (decoys here) are never what a + refusal falls back to.""" + world = World(tmp_path) + code = world.code() + good = PIN_TEXT.format(source="opensoft/openRepoTools-code", path="code", commit=code, + digest=tree_digest(world.repo("opensoft/openRepoTools-code"), code)) + world.assembly(code, pin=malformed(good, how), + extra={p: variant(p, "decoy") for p in PAYLOAD}) + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install") + assert_nothing_placed(home, result) + assert "contracts/code-pin.yaml" in result.stderr, result.stderr + assert world.payload_requests() == [] + + +@NEEDS_JQ +def test_r08_a_pin_the_gitlink_disagrees_with_is_refused(tmp_path): + world = World(tmp_path) + old = world.code(tag="old") + new = world.code(tag="new") + world.assembly(new, gitlink=old) + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install") + assert_nothing_placed(home, result) + assert old in result.stderr and new in result.stderr, result.stderr + assert world.payload_requests() == [] + + +@NEEDS_JQ +def test_r08_a_pin_naming_a_commit_its_repository_lacks_is_refused(tmp_path): + world = World(tmp_path) + elsewhere = world.code("someone/else", tag="elsewhere") + world.repo("opensoft/openRepoTools-code") + world.assembly(elsewhere, pin=PIN_TEXT.format( + source="opensoft/openRepoTools-code", path="code", commit=elsewhere, + digest=tree_digest(world.repo("someone/else"), elsewhere))) + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install") + assert_nothing_placed(home, result) + assert "opensoft/openRepoTools-code" in result.stderr, result.stderr + assert world.payload_requests() == [] + + +# ========================================================================= +# ROW 9 — missing or malformed API answers, authorization, transport +# ========================================================================= + +STEPS = { + "resolve": lambda w, a, c: r"repos/opensoft/openRepoTools/commits/", + "list-root": lambda w, a, c: rf"repos/opensoft/openRepoTools/git/trees/{a}", + "list-contracts": lambda w, a, c: (r"repos/opensoft/openRepoTools/git/trees/" + + w.tree_of("opensoft/openRepoTools", a, "contracts")), + "pin": lambda w, a, c: r"contracts/code-pin\.yaml", + "code-commit": lambda w, a, c: r"repos/opensoft/openRepoTools-code/commits/", +} +FAULTS = { + "auth-401": {"kind": "status", "status": 401}, + "rate-403": {"kind": "status", "status": 403}, + "missing-404": {"kind": "status", "status": 404}, + "transport": {"kind": "transport"}, + "garbage": {"kind": "garbage"}, +} + + +@NEEDS_JQ +@pytest.mark.parametrize("fault", list(FAULTS)) +@pytest.mark.parametrize("step", list(STEPS)) +def test_r09_an_api_failure_is_a_refusal_never_a_fallback_to_a_single_repository( + tmp_path, step, fault): + """Every round trip the resolution makes, failing every way a network + fails, on BOTH transports: a refusal that says what answered, nothing + placed, and never the assembly root's own copies (the decoys an older + installer would fetch as a single repository).""" + world = World(tmp_path) + code = world.code() + sha = world.assembly(code, extra={p: variant(p, "decoy") for p in PAYLOAD}) + world.faults = [{"tool": "any", "match": STEPS[step](world, sha, code), **FAULTS[fault]}] + world.write_conf() + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install") + assert_nothing_placed(home, result) + assert world.payload_requests() == [], world.payload_requests() + tried = {r["tool"] for r in world.requests()} + if (step, fault) != ("pin", "garbage"): + # A pin `gh` DID deliver is refused for what it says, not re-fetched. + assert tried == {"gh", "curl"}, f"both transports are tried: {tried}" + + +# ========================================================================= +# ROW 10 — changed local gitlink / changed payload / incomplete local payload +# ========================================================================= + +@NEEDS_JQ +def test_r10_a_local_gitlink_that_disagrees_with_the_pin_is_refused_before_staging(tmp_path): + world = World(tmp_path) + assembly, code, head = local_assembly(world) + (code / "README-moved").write_text("x\n", encoding="utf-8") + git("add", "README-moved", cwd=code) + git("commit", "-q", "-m", "moved on", cwd=code, env=IDENTITY) + moved = git("rev-parse", "HEAD", cwd=code).strip() + git("update-index", "--cacheinfo", f"160000,{moved},code", cwd=assembly) + home = tmp_path / "home" + result = run_file(code / "openRepoTools", home, network(world), "--install") + assert_nothing_placed(home, result) + assert moved in result.stderr and head in result.stderr, result.stderr + assert world.requests() == [] + + +@NEEDS_JQ +def test_r10_a_changed_local_payload_is_refused_before_staging(tmp_path): + world = World(tmp_path) + assembly, code, head = local_assembly(world) + (code / "park").write_bytes(variant("park", "edited-in-place")) + home = tmp_path / "home" + result = run_file(code / "openRepoTools", home, network(world), "--install") + assert_nothing_placed(home, result) + assert "park" in result.stderr and head in result.stderr, result.stderr + assert world.requests() == [] + + +@NEEDS_JQ +def test_r10_an_incomplete_local_adopted_payload_is_refused_without_a_fetch(tmp_path): + world = World(tmp_path) + world.assembly(world.code(tag="remote")) + assembly, code, head = local_assembly(world) + (code / "skills" / "restart" / "SKILL.md").unlink() + home = tmp_path / "home" + result = run_file(code / "openRepoTools", home, network(world), "--install") + assert_nothing_placed(home, result) + assert "skills/restart/SKILL.md" in result.stderr, result.stderr + assert world.requests() == [] + + +# ========================================================================= +# ROW 11 — missing command, skill or alias, or a settings merge that fails +# ========================================================================= + +@NEEDS_JQ +@pytest.mark.parametrize("withheld", ["park", "skills/restart/SKILL.md", "commands/swap.md"]) +def test_r11_a_payload_file_missing_from_the_pinned_code_changes_nothing(tmp_path, withheld): + world = World(tmp_path) + home = tmp_path / "home" + install_prior(world, home) + before = snapshot(home) + code = world.code(without=(withheld,)) + world.assembly(code) + result = run_stdin(world, home, network(world), "--install") + assert result.returncode == 2, result.stdout + result.stderr + assert snapshot(home) == before, "a refused install changed the prior one" + assert withheld in result.stderr or withheld.split("/")[1] in result.stderr, result.stderr + assert f"opensoft/openRepoTools-code at {code}" in result.stderr, result.stderr + + +@NEEDS_JQ +def test_r11_a_settings_merge_that_cannot_be_computed_changes_nothing(tmp_path): + world = World(tmp_path) + home = tmp_path / "home" + install_prior(world, home) + (home / ".claude" / "settings.json").write_text("{ not json\n", encoding="utf-8") + before = snapshot(home) + world.assembly(world.code()) + result = run_stdin(world, home, network(world), "--install") + assert result.returncode == 2, result.stdout + result.stderr + assert snapshot(home) == before + assert "settings.json" in result.stderr, result.stderr + assert world.payload_requests(), "the source was resolved before the merge was planned" + + +# ========================================================================= +# ROW 12 — modes, ownership receipts, custom targets, foreign files, hooks +# ========================================================================= + +@NEEDS_JQ +def test_r12_an_adopted_install_keeps_modes_receipts_targets_and_what_is_not_ours(tmp_path): + world = World(tmp_path) + code = world.code() + world.assembly(code) + home = tmp_path / "home" + bin_dir, profiles, claude, data = (tmp_path / "custom-bin", tmp_path / "profiles", + tmp_path / "claude", tmp_path / "data") + bin_dir.mkdir() + (bin_dir / "mytool").write_text("#!/bin/sh\necho mine\n", encoding="utf-8") + (bin_dir / "mytool").chmod(0o750) + claude.mkdir() + settings = {"theme": "dark", "hooks": { + "UserPromptSubmit": [{"hooks": [{"type": "command", "command": "usage-guard check"}]}], + "PreToolUse": [{"matcher": "Bash", "hooks": [{"type": "command", "command": "audit"}]}]}} + (claude / "settings.json").write_text(json.dumps(settings), encoding="utf-8") + result = run_stdin(world, home, network(world), "--install", + OPENREPOTOOLS_BIN_DIR=str(bin_dir), CLAUDE_PROFILES_HOME=str(profiles), + CLAUDE_USER_DIR=str(claude), OPENREPOTOOLS_DATA_DIR=str(data)) + assert result.returncode == 0, result.stdout + result.stderr + placed = destinations(home, bin_dir=bin_dir, profiles=profiles, claude=claude) + assert_installed(home, "pinned", result, bin_dir=bin_dir, profiles=profiles, claude=claude) + for dest, path in placed.items(): + want = 0o755 if path in INSTALLABLES else 0o644 + assert stat.S_IMODE(Path(dest).stat().st_mode) == want, dest + rows = [line.split("\t") for line in + (data / "installed.tsv").read_text(encoding="utf-8").splitlines() if line] + assert {row[1] for row in rows} == set(placed), rows + for row in rows: + assert row[2] == hashlib.sha256(Path(row[1]).read_bytes()).hexdigest(), row + assert len(rows) == len(INSTALLABLES) + 2 * len(SKILLS) + 2 * len(COMMANDS) + assert (bin_dir / "mytool").read_text(encoding="utf-8") == "#!/bin/sh\necho mine\n" + assert stat.S_IMODE((bin_dir / "mytool").stat().st_mode) == 0o750 + merged = json.loads((claude / "settings.json").read_text(encoding="utf-8")) + assert merged["theme"] == "dark" + assert merged["hooks"]["PreToolUse"] == settings["hooks"]["PreToolUse"] + commands = [h["command"] for e in merged["hooks"]["UserPromptSubmit"] for h in e["hooks"]] + assert "usage-guard check" in commands + assert "~/projects/xFactory/lanes-edit.sh guard" in commands + assert stat.S_IMODE((claude / "settings.json").stat().st_mode) == 0o600 + + +# ========================================================================= +# ROWS 13 AND 14 — `wip init` takes the standard from code's dependency pin +# ========================================================================= + +def shape_repository(world: World) -> tuple[str, str]: + """`opensoft/openRepoShape` with the pinned standard's real templates at + one commit and a DECOY of them on `main`, so a seed taken from the wrong + commit is visible in its bytes.""" + templates = UPSTREAM / "templates" / "workspace-root" + files = {} + for p in sorted(templates.rglob("*")): + if p.is_file(): + files[f"templates/workspace-root/{p.relative_to(templates).as_posix()}"] = p.read_bytes() + repo = world.repo("opensoft/openRepoShape") + pinned = commit(repo, files, branch=None, message="pinned standard") + decoy = {k: (v + b"\n\n" if k.endswith(".md") else v) + for k, v in files.items()} + main = commit(repo, decoy, message="moved on") + return pinned, main + + +def run_installed_wip(world: World, tmp_path: Path) -> tuple[subprocess.CompletedProcess, Path]: + home = tmp_path / "home" + bin_dir = home / "bin" + bin_dir.mkdir(parents=True) + shutil.copy2(COMMAND, bin_dir / "openRepoTools") + (home / "projects").mkdir() + result = subprocess.run( + ["bash", str(bin_dir / "openRepoTools"), "wip", "init", "--login", "brettheap", + "--team", "platform", "--org", "opensoft"], + input="", capture_output=True, text=True, check=False, cwd=str(home), + env=env_for(home, network(world), PROJECTS_DIR=str(home / "projects"), + OPENREPOTOOLS_BIN_DIR=str(bin_dir))) + return result, home / "projects" / "brettheap-wip" + + +@NEEDS_TEMPLATES +@NEEDS_JQ +def test_r13_wip_init_takes_the_standard_from_codes_dependency_pin_not_the_assemblys(tmp_path): + """An INSTALLED `openRepoTools` (no checkout beside it) seeding a workspace + against an adopted assembly: the template comes from the openRepoShape + commit the CODE leg pins in `contracts/openreposhape-pin.yaml` — not a + same-named file at the assembly root, and not `main`.""" + world = World(tmp_path) + pinned, moved = shape_repository(world) + code = world.code(shape=pinned) + world.assembly(code, extra={"contracts/openreposhape-pin.yaml": shape_pin(moved)}) + result, checkout = run_installed_wip(world, tmp_path) + assert result.returncode == 0, result.stdout + result.stderr + assert f"template files from opensoft/openRepoShape @ {pinned}" in result.stdout, result.stdout + assert "DECOY FROM MAIN" not in (checkout / "README.md").read_text(encoding="utf-8") + + +@NEEDS_TEMPLATES +@NEEDS_JQ +def test_r14_with_no_nested_standard_anywhere_the_dependency_pin_alone_selects(tmp_path): + """Standalone code with its nested dependency nowhere on disk — the + installed copy, the vendored tree: the dependency PIN is what selects the + standard, fetched from the code the assembly pins; `main` is only the + documented answer when that pin cannot be read at all.""" + world = World(tmp_path) + pinned, moved = shape_repository(world) + world.assembly(world.code(shape=pinned)) + result, checkout = run_installed_wip(world, tmp_path) + assert result.returncode == 0, result.stdout + result.stderr + assert f"@ {pinned}" in result.stdout, result.stdout + assert moved not in result.stdout + assert "DECOY FROM MAIN" not in (checkout / "README.md").read_text(encoding="utf-8") + + +def test_r14_the_documented_upstream_skip_is_unchanged(): + """Without the submodule the command tests SKIP, naming the one command + that fixes it (AGENTS.md, "Testing your changes"); conftest says so.""" + import conftest + assert "git submodule update --init upstream/openRepoShape" in \ + conftest.NEEDS_UPSTREAM.kwargs["reason"] + + +@NEEDS_COMPOSED +def test_r14_composed_acceptance_requires_the_nested_dependency(): + """Composed acceptance REFUSES an absent dependency rather than skipping + it: a mounted code leg without its pinned standard is not the assembled + state T015 accepts.""" + assert (UPSTREAM / "scripts" / "repo_shape.py").is_file(), ( + "the code leg is mounted but its nested openRepoShape is not: run " + "`git submodule update --init --recursive` (or `make bootstrap`) at the assembly") + + +# ========================================================================= +# ROW 15 — Linux/macOS bash 3.2, Windows/WSL +# ========================================================================= + +BASH4 = [(re.compile(r"\$\{[A-Za-z_][A-Za-z0-9_]*(,,|\^\^)"), "${x,,} / ${x^^}"), + (re.compile(r"\bmapfile\b|\breadarray\b"), "mapfile/readarray"), + (re.compile(r"\bdeclare\s+-[a-zA-Z]*[An]"), "declare -A / -n"), + (re.compile(r"\blocal\s+-n\b"), "local -n"), + (re.compile(r"\|&"), "|&"), + (re.compile(r"\bcoproc\b"), "coproc"), + (re.compile(r"\[\[\s+-v\b"), "[[ -v ]]")] + + +def bash4_constructs(text: str) -> list[str]: + found = [] + for number, line in enumerate(text.splitlines(), 1): + code = line.split("#", 1)[0] if not line.lstrip().startswith("#") else "" + for pattern, name in BASH4: + if pattern.search(code): + found.append(f"{number}: {name}: {line.strip()}") + return found + + +def test_r15_the_installer_uses_nothing_bash_3_2_lacks(): + """CI's `parse-macos` parses every shipped file under Apple's bash 3.2; + this names the constructs a Linux bash 5 would accept silently.""" + assert bash4_constructs(SOURCE_TEXT) == [] + assert subprocess.run(["bash", "-n", str(COMMAND)]).returncode == 0 + + +def test_r15_the_three_lists_stay_one_line_arrays_a_consumer_parses(): + """workBenches' `estate-commands-start` `sed`-reads `INSTALLABLES`, + `SKILLS` and `COMMANDS` out of the vendored `openRepoTools` (consumer-reads + row W4): one line each, or that step reads nothing.""" + for name in ("INSTALLABLES", "SKILLS", "COMMANDS"): + assert one_line_array(SOURCE_TEXT, name) + + +@NEEDS_JQ +def test_r15_the_adopted_path_holds_under_macos_one_true_awk(tmp_path): + """macOS's awk refuses a newline in an `awk -v` value (exit 2, no output); + gawk and mawk accept it, so no Linux job sees it. The adopted resolution + runs here under a shim that IS that rule.""" + real_awk = shutil.which("awk") + shim = textwrap.dedent(f"""\ + #!/bin/sh + prev="" + for a in "$@"; do + if [ "$prev" = "-v" ]; then + case "$a" in *' + '*) echo "awk: newline in string $a" >&2; exit 2 ;; esac + fi + prev="$a" + done + exec '{real_awk}' "$@" + """) + world = World(tmp_path) + world.assembly(world.code()) + home = tmp_path / "home" + result = run_stdin(world, home, network(world, extra_shims={"awk": shim}), "--install") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "pinned", result) + + +@NEEDS_COMPOSED +def test_r15_composed_the_root_entry_point_parses_and_uses_nothing_bash_3_2_lacks(): + root = ASSEMBLY / "openRepoTools" + assert bash4_constructs(root.read_text(encoding="utf-8")) == [] + assert subprocess.run(["bash", "-n", str(root)]).returncode == 0 From e406e0e28667bbba8dfcd28fee6b057c553d305c Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:56:10 +0000 Subject: [PATCH 02/10] Resolve the install source once: a single repository installs as it always did, and an adopted assembly installs the code its contracts/code-pin.yaml pins. T007 of specs/004-migrate-to-triad/tasks.md (opensoft/openRepoTools#186), following installer-design.md. After the triad split the default opensoft/openRepoTools is an assembly whose root carries no payload, so every fetching install, every reinstall from an installed copy and every installed `wip init` would have read the wrong tree. Now: - A directory beside the script that holds every payload file is copied with no network and no new output, exactly as before (workBenches' sentinel REPO/REF run included). Today's one-liner resolves the ref once and fetches every file at that commit, so a branch that moves mid-run cannot mix two versions. - Which layout a remote is, is decided by a git/trees listing that succeeded: a code pin present is adopted; none, with no project.yaml, is a single repository. Authentication, rate-limit, transport and parse failures refuse and are never read as the older layout. - An adopted source: the pin's fixed grammar is checked field by field (no YAML runtime), the gitlink must equal the pinned commit, the pinned commit must exist in the pin's own source_repository (never a guessed -code name), and every payload byte comes from there. - From the code leg mounted in an assembly checkout, the pin, the gitlink and every payload file's bytes must agree, then the files are copied offline; a paired feature worktree installs its own bytes and says so. Neither is ever completed from a network, and an installed copy on an adopted source is not topped up with pinned skills beside its old siblings. - wip init reads the code's openreposhape pin from that same source. Receipts, modes, targets, the settings merge and the retirement are untouched; source routing finishes before anything is collected or placed. tests/test_openrepotools_command.py's fake gh now answers the two discovery calls a real GitHub answers for a single repository; no assertion changed. Lane: openRepoTools-1 Co-Authored-By: Claude Opus 5.5 --- openRepoTools | 629 ++++++++++++++++++++++++++- tests/test_install_payload_source.py | 48 +- tests/test_openrepotools_command.py | 14 + 3 files changed, 666 insertions(+), 25 deletions(-) diff --git a/openRepoTools b/openRepoTools index 9b7cbd2b..e812ecdb 100755 --- a/openRepoTools +++ b/openRepoTools @@ -255,16 +255,21 @@ workdir() { # reason — each of these files is one file a person has on PATH. Two installers that # fetch the same way is a claim `tests/test_openrepotools_command.py` and # openRepoShape's own suite each assert against their own script's text. +# +# WHAT IT FETCHES FROM IS THE ONE THING THAT MOVED (#186, T007): the source +# `payload_resolve_remote` settled — the code an adopted assembly pins, or a +# single repository at the ONE commit its ref named — or the repository and +# commit a caller hands it, which is how the code pin itself is read. fetch_from_repo() { - local path="$1" dest="$2" + local path="$1" dest="$2" repo="${3:-${PAYLOAD_REPO:-$REPO}}" rev="${4:-${PAYLOAD_REV:-$REF}}" if command -v gh >/dev/null 2>&1 && - gh api "repos/$REPO/contents/$path?ref=$REF" \ + gh api "repos/$repo/contents/$path?ref=$rev" \ -H 'Accept: application/vnd.github.raw' >"$dest" 2>/dev/null && [ -s "$dest" ]; then return 0 fi if command -v curl >/dev/null 2>&1 && - curl -fsSL "https://raw.githubusercontent.com/$REPO/$REF/$path" \ + curl -fsSL "https://raw.githubusercontent.com/$repo/$rev/$path" \ >"$dest" 2>/dev/null && [ -s "$dest" ]; then return 0 fi @@ -274,6 +279,568 @@ fetch_from_repo() { return 1 } +# ------------------------------------------------ WHERE THE PAYLOAD COMES FROM +# +# opensoft/openRepoTools#186 — T007 of `specs/004-migrate-to-triad/tasks.md`, +# on `specs/004-migrate-to-triad/installer-design.md`. There are TWO LAYOUTS, +# and this asks which one it is looking at rather than guessing: +# +# * A SINGLE REPOSITORY — `opensoft/openRepoTools` today — carries every +# payload file at its root. NOTHING ABOUT IT CHANGES: a directory beside +# this file that holds every payload file (a checkout, workBenches' +# vendored tree) is copied with no network at all, byte for byte and line +# for line what it always was; one that holds only some of them copies +# those and fetches the rest; and a run from stdin fetches them all. +# * AN ADOPTED ASSEMBLY — the same slug after the triad split — carries +# `contracts/code-pin.yaml`, which names the CODE repository and commit, +# and a gitlink at the pinned path that must agree with it. The payload is +# that repository's, at that commit, and nothing else's. +# +# THE REF IS RESOLVED ONCE, in both layouts. `$OPENREPOTOOLS_REF` becomes a +# commit on the first round trip, and every request after it names that +# commit, so a branch that moves while the files come down cannot hand one run +# two versions. `$OPENREPOTOOLS_REPO` and `$OPENREPOTOOLS_REF` mean what they +# always meant: what to install from, and at which ref. +# +# WHICH LAYOUT IS DECIDED BY A LISTING THAT SUCCEEDED, NEVER BY A FAILURE. The +# root tree at that commit is listed (`git/trees`), then `contracts/`: a code +# pin that is there is the adopted layout, and one that a successful listing +# shows is NOT there, with no adoption manifest (`project.yaml`) beside it, is +# a single repository. An authentication failure, a rate limit, a transport +# failure or an answer that does not parse is a REFUSAL naming what answered — +# never evidence of the older layout, because reading it that way would +# install whatever the assembly root happened to carry. +# +# A PIN THAT IS PRESENT AND WRONG REFUSES. Its grammar is fixed, so it is read +# here with `read` and `case` rather than a YAML runtime (`pin_parse`), and +# every field is checked. The payload repository is the pin's own +# `source_repository`, never a name made by appending `-code` to a fork's: a +# fork follows the pin it actually carries. +# +# WHAT A REMOTE INSTALL DOES NOT CLAIM: the pin's `tree_sha256`. That digest is +# over the code leg's WHOLE tree and is the composed validator's to recompute +# (`scripts/validate-pins.py` in the assembly). What is verified here is the +# identity this run consumes — the commit the ref names, the gitlink at the +# pinned path, and that the pinned commit is one the pinned repository has. +# +# LOCALLY — this file run from the code leg MOUNTED in an assembly checkout, +# whose `contracts/code-pin.yaml` names this directory — the pin and the +# gitlink must agree and every payload file must be the pinned commit's bytes; +# then they are copied, offline. A code checkout under an assembly that is NOT +# its mounted leg (a paired feature worktree, `/worktrees//code`) is +# somebody installing that checkout ON PURPOSE: its bytes are copied as they +# are, and the run names that checkout and its commit and says it is not the +# pinned code. Neither is ever completed from a network — local siblings and +# remote bytes do not make one install between them. +# +# ALL OF IT HAPPENS BEFORE ANYTHING IS PLACED: `install_commands` asks +# `payload_select` straight after the targets, before the first byte is +# collected, and every refusal here places nothing. + +PAYLOAD_MODE="" # "" until chosen: local | siblings | remote +PAYLOAD_DIR="" # the directory payload files are copied from, if any +PAYLOAD_REPO="" # owner/name every fetch reads, once resolved +PAYLOAD_REV="" # the 40-hex commit every fetch reads, once resolved +SOURCE_SHA="" # $REF, resolved once +SOURCE_LAYOUT="" # single | adopted | feature, once known +API_SHA="" +API_WHY="" + +#: THE CHARACTERS GITHUB SPELLS A NAME WITH, WRITTEN OUT. A bracket RANGE such +#: as `[a-z]` follows the locale's collation in bash 3.2 and can admit capitals, +#: and `case` is how this file matches. +ORT_ALNUM='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' +ORT_TAB="$(printf '\t')" +ORT_CR="$(printf '\r')" +#: One listing row per entry: `pathtypesha`. `gh` evaluates it itself +#: (`--jq`); the curl path hands the same filter to `jq`. +ORT_TREE_JQ='.tree[] | [.path, .type, .sha] | @tsv' + +# EVERY FILE `--install` READS, derived from the three lists and from nothing +# else: the commands at the root, then each skill's and each command file's path. +payload_names() { + local name + for name in "${INSTALLABLES[@]}"; do printf '%s\n' "$name"; done + for name in "${SKILLS[@]}"; do skill_source_path "$name"; done + for name in "${COMMANDS[@]}"; do command_source_path "$name"; done +} + +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 +} + +# --- the code pin's fixed grammar --------------------------------------------- +# +# `contracts/code-pin.yaml` as openRepoShape's adopter writes it: top-level +# `key: value` lines, comments, blank lines, and one mapping, `digests:`, whose +# indented `tree_sha256:` is read. A scalar may be double- or single-quoted. +# A key this reader does not use (a `tag:` label beside the commit, say) is +# allowed once; a key it does use must appear exactly once; and every value +# it uses must have the one form the contract gives it. LF only. + +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 +} + +# --- one round trip, `gh` first and the API by `curl` second ------------------ +# +# The same order as `fetch_from_repo`, for the same reason, and for the same +# identities. Each sets API_WHY to what BOTH answered when neither did, which +# is what a refusal prints. + +api_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 +} + +#: A COMMIT, BY REF: API_SHA and 0, or API_WHY and 1. An answer that is not 40 +#: lowercase hex is no answer — `gh` prints a failed request's JSON on stdout. +api_sha() { # + local out="$WORKDIR/api-sha" why="" sha="" + API_SHA="" API_WHY="" + if command -v gh >/dev/null 2>&1; then + if gh api "repos/$1/commits/$2" -H 'Accept: application/vnd.github.sha' \ + >"$out" 2>"$out.err"; then + IFS= read -r sha <"$out" || : + if is_lower_hex 40 "$sha"; then API_SHA="$sha"; return 0; fi + why="gh: an answer that is not a commit" + else + why="$(api_said gh "$out.err")" + fi + else + why="gh: not installed" + fi + if command -v curl >/dev/null 2>&1; then + if curl -fsSL -H 'Accept: application/vnd.github.sha' \ + "https://api.github.com/repos/$1/commits/$2" >"$out" 2>"$out.err"; then + sha="" + IFS= read -r sha <"$out" || : + if is_lower_hex 40 "$sha"; then API_SHA="$sha"; return 0; fi + why="$why; curl: an answer that is not a commit" + else + why="$why; $(api_said curl "$out.err")" + fi + else + why="$why; curl: not installed" + fi + API_WHY="$why" + return 1 +} + +tree_listing_ok() { # — one or more `pathblob|tree|commit40-hex` rows + [ -s "$1" ] || return 1 + awk -F'\t' 'NF != 3 || ($2 != "blob" && $2 != "tree" && $2 != "commit") || + length($3) != 40 || $3 ~ /[^0123456789abcdef]/ { bad = 1 } + END { exit bad ? 1 : 0 }' "$1" +} + +#: A TREE'S ENTRIES, ONE LEVEL: the listing in and 0, or API_WHY and 1. +api_tree() { # + local out="$3" why="" + API_WHY="" + if command -v gh >/dev/null 2>&1; then + if gh api "repos/$1/git/trees/$2" --jq "$ORT_TREE_JQ" >"$out" 2>"$out.err"; then + tree_listing_ok "$out" && return 0 + why="gh: an answer that is not a tree listing" + else + why="$(api_said gh "$out.err")" + fi + 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/$1/git/trees/$2" >"$out.json" 2>"$out.err"; then + why="$why; $(api_said curl "$out.err")" + elif jq -r "$ORT_TREE_JQ" "$out.json" >"$out" 2>/dev/null && tree_listing_ok "$out"; then + return 0 + else + why="$why; curl: an answer that is not a tree listing" + fi + API_WHY="$why" + return 1 +} + +tree_entry() { # — " " of that entry, or 1 + awk -F'\t' -v name="$2" '$1 == name { print $2 " " $3; found = 1; exit } + END { exit found ? 0 : 1 }' "$1" +} + +url_ref() { # — with the two characters a URL path reads otherwise escaped + local r="$1" out="" + while [ -n "$r" ]; do + case "$r" in + '%'*) out="$out%25" ;; + '#'*) out="$out%23" ;; + *) out="$out${r:0:1}" ;; + esac + r="${r#?}" + done + printf '%s' "$out" +} + +# --- the remote source, resolved once --------------------------------------- + +payload_resolve_remote() { + [ -z "$SOURCE_SHA" ] || return 0 + local listing contracts pin entry link + workdir + listing="$WORKDIR/source-root.tsv" + contracts="$WORKDIR/source-contracts.tsv" + pin="$WORKDIR/code-pin.yaml" + is_slug "$REPO" || die "\$OPENREPOTOOLS_REPO is '$REPO', which is not an owner/name repository, so + there is nothing to fetch from, and nothing was fetched, placed or written." + api_sha "$REPO" "$(url_ref "$REF")" || die "could not resolve $REPO at $REF to a commit, so nothing was fetched, + placed or written. What answered: + $API_WHY + Both were asked: + gh api repos/$REPO/commits/$REF -H 'Accept: application/vnd.github.sha' + curl -fsSL -H 'Accept: application/vnd.github.sha' https://api.github.com/repos/$REPO/commits/$REF + A failed answer is a refusal, never a reading of the repository's layout." + SOURCE_SHA="$API_SHA" + api_tree "$REPO" "$SOURCE_SHA" "$listing" || die "could not list $REPO at $SOURCE_SHA ($REF), so whether it is a single + repository or an adopted assembly is not known, and nothing was fetched, + placed or written. What answered: + $API_WHY + A failed listing is a refusal, never evidence that the code pin is absent." + entry="" + if entry="$(tree_entry "$listing" contracts)" && [ "${entry%% *}" = tree ]; then + api_tree "$REPO" "${entry#* }" "$contracts" || die "could not list contracts/ in $REPO at $SOURCE_SHA ($REF), so whether it + carries a code pin is not known, and nothing was fetched, placed or written. + What answered: + $API_WHY" + entry="$(tree_entry "$contracts" code-pin.yaml)" || entry="" + else + entry="" + fi + if [ -z "$entry" ]; then + # NO CODE PIN, SHOWN BY A LISTING THAT SUCCEEDED. With no adoption + # manifest either, this is the single repository it always was. + if tree_entry "$listing" project.yaml >/dev/null; then + die "$REPO at $SOURCE_SHA ($REF) carries an adoption manifest, project.yaml, and + no contracts/code-pin.yaml: a partly adopted layout, which says neither where + its code is nor that it is a single repository. Nothing was fetched, placed or + written. Its contracts/code-pin.yaml is the file that names the code leg." + fi + SOURCE_LAYOUT=single PAYLOAD_REPO="$REPO" PAYLOAD_REV="$SOURCE_SHA" + return 0 + fi + [ "${entry%% *}" = blob ] || die "contracts/code-pin.yaml in $REPO at $SOURCE_SHA is a ${entry%% *}, not a + file, and nothing was fetched, placed or written." + fetch_from_repo contracts/code-pin.yaml "$pin" "$REPO" "$SOURCE_SHA" || + die "could not fetch contracts/code-pin.yaml from $REPO at $SOURCE_SHA ($REF), which + the listing shows is there, so nothing was fetched, placed or written. Both + were asked: + gh api repos/$REPO/contents/contracts/code-pin.yaml?ref=$SOURCE_SHA + curl -fsSL https://raw.githubusercontent.com/$REPO/$SOURCE_SHA/contracts/code-pin.yaml" + pin_parse "$pin" || die "contracts/code-pin.yaml in $REPO at $SOURCE_SHA ($REF) is not a code pin + this installer can follow: $PIN_WHY. A pin that is present and wrong is + refused rather than read around, and nothing was fetched, placed or written." + link="$(tree_entry "$listing" "$PIN_SUBMODULE_PATH")" || link="" + [ "${link%% *}" = commit ] || die "$REPO at $SOURCE_SHA ($REF) records no gitlink at $PIN_SUBMODULE_PATH, where + contracts/code-pin.yaml mounts the code leg, so nothing was fetched, placed or + written." + [ "${link#* }" = "$PIN_COMMIT" ] || die "$REPO at $SOURCE_SHA ($REF) records ${link#* } at $PIN_SUBMODULE_PATH + and its contracts/code-pin.yaml 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 was fetched, placed or written." + if ! api_sha "$PIN_SOURCE_REPOSITORY" "$PIN_COMMIT" || [ "$API_SHA" != "$PIN_COMMIT" ]; then + die "contracts/code-pin.yaml in $REPO at $SOURCE_SHA ($REF) pins $PIN_SOURCE_REPOSITORY + at $PIN_COMMIT, and that is not a commit $PIN_SOURCE_REPOSITORY answers for, so + nothing was fetched, placed or written. What answered: + ${API_WHY:-$PIN_SOURCE_REPOSITORY answered $API_SHA}" + fi + SOURCE_LAYOUT=adopted PAYLOAD_REPO="$PIN_SOURCE_REPOSITORY" PAYLOAD_REV="$PIN_COMMIT" +} + +payload_where() { # the source collected from, for a refusal to name + case "$SOURCE_LAYOUT" in + single) printf '%s at %s (%s)' "$PAYLOAD_REPO" "$REF" "$PAYLOAD_REV" ;; + adopted) printf '%s at %s (the code %s at %s pins)' "$PAYLOAD_REPO" "$PAYLOAD_REV" "$REPO" "$REF" ;; + *) printf '%s at %s' "$REPO" "$REF" ;; + esac +} + +# --- the local source --------------------------------------------------------- + +#: THE NEAREST DIRECTORY ABOVE HOLDING `contracts/code-pin.yaml`: the +#: assembly this checkout sits in, if it sits in one. Logical paths, so what is +#: printed is what the person typed; the one comparison that must see through +#: links (`payload_local_adopted`) asks `pwd -P` itself. +assembly_above() { # + local d + d="$(cd -- "$1/.." 2>/dev/null && pwd)" || return 1 + while :; do + if [ -f "$d/contracts/code-pin.yaml" ]; then + printf '%s\n' "$d" + return 0 + fi + case "$d" in / | '') return 1 ;; esac + d="$(dirname -- "$d")" + done +} + +#: THE COMMIT A SUPERPROJECT RECORDS AT : the index first and HEAD +#: second, the order openRepoShape's `recorded_gitlink` asks them in — the index +#: is what the next commit records, so a gitlink staged and not yet committed is +#: the one that counts. +recorded_gitlink() { # + local oid + oid="$(git -C "$1" ls-files -s -- "$2" 2>/dev/null | + awk -v p="$2" '$1 == "160000" && $3 == "0" && $4 == p { print $2; exit }')" || oid="" + if [ -z "$oid" ]; then + oid="$(git -C "$1" ls-tree HEAD -- "$2" 2>/dev/null | + awk -v p="$2" '$1 == "160000" && $2 == "commit" && $4 == p { print $3; exit }')" || oid="" + fi + is_lower_hex 40 "$oid" || return 1 + printf '%s\n' "$oid" +} + +payload_local_adopted() { # + local root="$1" here="$2" pin="$1/contracts/code-pin.yaml" me mount link name blob + local bad="" missing="" + pin_parse "$pin" || die "$pin is not a code pin this installer can follow: $PIN_WHY. + It is the pin of the assembly this checkout sits in, and a pin that is present + and wrong is refused rather than read around. NOTHING was installed and nothing + already installed was replaced." + me="$(cd -- "$here" && pwd -P)" + mount="$(cd -- "$root/$PIN_SUBMODULE_PATH" 2>/dev/null && pwd -P)" || mount="" + if [ "$mount" != "$me" ]; then + payload_local_feature "$root" "$here" + return 0 + fi + link="$(recorded_gitlink "$root" "$PIN_SUBMODULE_PATH")" || + die "$root records no gitlink at $PIN_SUBMODULE_PATH, where its contracts/code-pin.yaml + mounts this checkout, so which code it pins cannot be checked. NOTHING was + installed and nothing already installed was replaced." + [ "$link" = "$PIN_COMMIT" ] || die "$root records $link at $PIN_SUBMODULE_PATH and its + contracts/code-pin.yaml 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. + NOTHING was installed and nothing already installed was replaced. Make them + agree — \`make validate\` at $root names the drift — then re-run." + git -C "$here" cat-file -e "$PIN_COMMIT^{commit}" 2>/dev/null || + die "the pinned commit $PIN_COMMIT is not in $here's object store, so its + payload cannot be checked against it. NOTHING was installed and nothing already + installed was replaced. Fetch it — git -C \"$here\" fetch origin — or run + \`make bootstrap\` at $root, then re-run." + for name in $(payload_names); do + if [ -L "$here/$name" ]; then bad="${bad:+$bad }$name"; continue; fi + if [ ! -f "$here/$name" ]; then missing="${missing:+$missing }$name"; continue; fi + blob="$(git -C "$here" rev-parse --verify --quiet "$PIN_COMMIT:$name" 2>/dev/null)" || + { bad="${bad:+$bad }$name"; continue; } + [ "$(git -C "$here" hash-object --no-filters -- "$name" 2>/dev/null)" = "$blob" ] || + bad="${bad:+$bad }$name" + done + [ -z "$missing" ] || die "the code mounted at $root/$PIN_SUBMODULE_PATH is missing $missing, + so it is not the payload $root pins ($PIN_SOURCE_REPOSITORY at $PIN_COMMIT), and + an incomplete local set is never completed from a network. NOTHING was + installed and nothing already installed was replaced. Restore them — + git -C \"$here\" checkout $PIN_COMMIT -- $missing + — or run \`make bootstrap\` at $root, then re-run." + [ -z "$bad" ] || die "in the code mounted at $root/$PIN_SUBMODULE_PATH, $bad + differ from $PIN_COMMIT, the commit $root pins, so these are not the pinned + bytes. NOTHING was installed and nothing already installed was replaced. To + install the pinned code, restore them: + git -C \"$here\" checkout $PIN_COMMIT -- $bad + To install a code feature on purpose, run the openRepoTools of its own worktree + (/worktrees//code), which installs that checkout's bytes and says so." + PAYLOAD_MODE=local PAYLOAD_DIR="$here" SOURCE_LAYOUT=adopted + say "openRepoTools: source: the code mounted at $root/$PIN_SUBMODULE_PATH, $PIN_SOURCE_REPOSITORY at $PIN_COMMIT as $root pins it; every payload file was checked against that commit and nothing fetched" +} + +payload_local_feature() { # + local root="$1" here="$2" name missing="" top="" head="" dirty="" + for name in $(payload_names); do + [ -f "$here/$name" ] || missing="${missing:+$missing }$name" + done + [ -z "$missing" ] || die "$here is a code checkout under the assembly $root and not + the code it mounts, so \`--install\` from it installs THIS checkout's bytes or + none — and it is missing $missing. NOTHING was installed and nothing already + installed was replaced. Restore them in $here, or install the code $root pins + with $root/openRepoTools --install." + top="$(git -C "$here" rev-parse --show-toplevel 2>/dev/null)" || top="" + if [ -n "$top" ] && [ "$(cd -- "$top" && pwd -P)" = "$(cd -- "$here" && pwd -P)" ]; then + head="$(git -C "$here" rev-parse --verify --quiet HEAD 2>/dev/null)" || head="" + # shellcheck disable=SC2046 + [ -z "$(git -C "$here" status --porcelain -- $(payload_names) 2>/dev/null)" ] || + dirty=" with uncommitted changes to its payload" + fi + PAYLOAD_MODE=local PAYLOAD_DIR="$here" SOURCE_LAYOUT=feature + say "openRepoTools: source: the code checkout $here at ${head:-a commit this installer could not read (it is not a git checkout of its own)}$dirty, installed because its own openRepoTools was run — NOT the code $root pins, $PIN_SOURCE_REPOSITORY at $PIN_COMMIT" +} + +# --- the one decision `--install` makes --------------------------------------- + +payload_select() { + [ -z "$PAYLOAD_MODE" ] || return 0 + local selfdir="" root name missing="" + if [ -f "$SELF" ]; then + selfdir="$(cd -- "$(dirname -- "$SELF")" && pwd)" + if root="$(assembly_above "$selfdir")"; then + payload_local_adopted "$root" "$selfdir" + return 0 + fi + for name in $(payload_names); do + [ -f "$selfdir/$name" ] || missing="${missing:+$missing }$name" + done + # EVERY PAYLOAD FILE IS BESIDE THIS ONE: copied, and nothing is asked + # of any network — the run is today's to the line (workBenches runs it + # with a sentinel repository precisely so that only this can succeed). + if [ -z "$missing" ]; then + PAYLOAD_MODE=local PAYLOAD_DIR="$selfdir" + return 0 + fi + payload_resolve_remote + # A SINGLE REPOSITORY KEEPS ITS SIBLING RULE: what is beside this file + # is copied and only what is missing is fetched, at the one commit. + if [ "$SOURCE_LAYOUT" = single ]; then + PAYLOAD_MODE=siblings PAYLOAD_DIR="$selfdir" + return 0 + fi + # AN ADOPTED SOURCE IS NEVER TOPPED UP: the files beside an installed + # copy are whatever an earlier run left, and pinned skills beside them + # would be two versions in one install. + PAYLOAD_MODE=remote + say "openRepoTools: source: $REPO at $REF ($SOURCE_SHA) pins $PAYLOAD_REPO at $PAYLOAD_REV; the files beside $selfdir are not used, because an incomplete local set is never topped up with pinned files" + return 0 + fi + payload_resolve_remote + PAYLOAD_MODE=remote + [ "$SOURCE_LAYOUT" = single ] || + say "openRepoTools: source: $REPO at $REF ($SOURCE_SHA) pins $PAYLOAD_REPO at $PAYLOAD_REV" +} + # THE SEVENTEEN FILES `--install` PLACES. One list, so the installer, the fetch # fallback and the tests cannot disagree about what a complete install is. # `openRepoTools` is first because it is the one a person types to get the @@ -960,33 +1527,36 @@ retire_commands() { # artifact counts `2f44da0` corrected and as `lane-start`'s "no fifth rung". The # code below already derives every count it prints from `${#INSTALLABLES[@]}`. collect_commands() { - local name selfdir="" missing="" + local name missing="" # Run from a file, install THOSE bytes: `--install` then needs no network # and no `gh` at all — and `park`, `resume` and `status` sit BESIDE this # file, in the checkout and in the repository alike, so the siblings that # are there are copied and only a missing one is fetched. Run from stdin # (`curl … | bash -s -- --install`) there is no file to copy from at all, - # so each one is fetched at this same ref. - if [ -f "$SELF" ]; then - selfdir="$(cd -- "$(dirname -- "$SELF")" && pwd)" - fi + # so each one is fetched at this same ref. WHICH of those it is, and from + # where, is `payload_select`'s one decision (#186), made before this runs. + payload_select workdir STAGED="$WORKDIR/staged" mkdir -p "$STAGED" for name in "${INSTALLABLES[@]}"; do - if [ -n "$selfdir" ] && [ -f "$selfdir/$name" ]; then - cp -- "$selfdir/$name" "$STAGED/$name" + if [ -n "$PAYLOAD_DIR" ] && [ -f "$PAYLOAD_DIR/$name" ]; then + cp -- "$PAYLOAD_DIR/$name" "$STAGED/$name" continue fi - fetch_from_repo "$name" "$STAGED/$name" || - missing="${missing:+$missing }$name" + # A LOCAL SOURCE IS NEVER COMPLETED FROM A NETWORK: it was whole when + # it was chosen, and a file gone since is missing, not fetched. + if [ "$PAYLOAD_MODE" != local ]; then + fetch_from_repo "$name" "$STAGED/$name" && continue + fi + missing="${missing:+$missing }$name" done - [ -z "$missing" ] || die "could not fetch $missing from $REPO at $REF, so NOTHING was installed + [ -z "$missing" ] || die "could not fetch $missing from $(payload_where), so NOTHING was installed and nothing already installed was replaced. \`--install\` places all ${#INSTALLABLES[@]} files or none: a new openRepoTools beside a missing park is a half-install that reads like a whole one. Both ways were tried: - gh api repos/$REPO/contents/?ref=$REF - curl -fsSL https://raw.githubusercontent.com/$REPO/$REF/" + gh api repos/${PAYLOAD_REPO:-$REPO}/contents/?ref=${PAYLOAD_REV:-$REF} + curl -fsSL https://raw.githubusercontent.com/${PAYLOAD_REPO:-$REPO}/${PAYLOAD_REV:-$REF}/" } # WHAT MODE A PLACED ARTIFACT IS AT, asked without `stat`. GNU spells that @@ -1121,8 +1691,12 @@ install_commands() { # cannot be written THROUGH is a refusal that costs nothing at all here, # and a network round-trip if it waits (A9 Addendum 4, R-A9-12). plan_install_targets + # THE SOURCE NEXT, AND ONCE (#186): which bytes, from where, at which + # commit — every refusal in it costs nothing, because nothing is collected + # or placed until it has answered. + payload_select collect_commands - collect_skills || die "could not fetch one of the ${#SKILLS[@]} skills (${SKILLS[*]}) or the ${#COMMANDS[@]} command file (${COMMANDS[*]}) from $REPO at $REF, so + collect_skills || die "could not fetch one of the ${#SKILLS[@]} skills (${SKILLS[*]}) or the ${#COMMANDS[@]} command file (${COMMANDS[*]}) from $(payload_where), so NOTHING was installed and nothing already installed was replaced." plan_hook_merge plan_skill_targets @@ -1368,15 +1942,16 @@ BLOCK # already have — a new `/restart` beside a `handoff` that could not be fetched # is a half-install that reads like a whole one. collect_skills() { - local selfdir="" name src - [ -f "$SELF" ] && selfdir="$(cd -- "$(dirname -- "$SELF")" && pwd)" + local name src + payload_select workdir for name in "${SKILLS[@]}"; do src="$(skill_source_path "$name")" - if [ -n "$selfdir" ] && [ -f "$selfdir/$src" ]; then - cp -- "$selfdir/$src" "$WORKDIR/skill-$name.md" + if [ -n "$PAYLOAD_DIR" ] && [ -f "$PAYLOAD_DIR/$src" ]; then + cp -- "$PAYLOAD_DIR/$src" "$WORKDIR/skill-$name.md" continue fi + [ "$PAYLOAD_MODE" != local ] || return 1 fetch_from_repo "$src" "$WORKDIR/skill-$name.md" || return 1 done # THE COMMAND FILES IN THE SAME BREATH, under the same all-or-nothing rule @@ -1386,10 +1961,11 @@ collect_skills() { # nothing on screen says which. for name in "${COMMANDS[@]}"; do src="$(command_source_path "$name")" - if [ -n "$selfdir" ] && [ -f "$selfdir/$src" ]; then - cp -- "$selfdir/$src" "$WORKDIR/command-$name.md" + if [ -n "$PAYLOAD_DIR" ] && [ -f "$PAYLOAD_DIR/$src" ]; then + cp -- "$PAYLOAD_DIR/$src" "$WORKDIR/command-$name.md" continue fi + [ "$PAYLOAD_MODE" != local ] || return 1 fetch_from_repo "$src" "$WORKDIR/command-$name.md" || return 1 done return 0 @@ -2508,6 +3084,13 @@ wip_shape_ref() { [ -n "$pin" ] && { printf '%s\n' "$pin"; return 0; } fi workdir + # NO PIN BESIDE THIS FILE — the installed copy, a vendored tree: the pin is + # the one the CODE carries, read from wherever the payload comes from (#186) + # — the single repository at the commit its ref names, or the code leg an + # adopted assembly pins, never the assembly's own `contracts/`. A source + # that cannot be resolved is a refusal, as it is for `--install`; `main` + # stays the answer only for a pin that source does not carry. + payload_resolve_remote if fetch_from_repo "contracts/openreposhape-pin.yaml" "$WORKDIR/pin.yaml"; then pin="$(sed -n -e 's/^commit:[[:space:]]*//p' "$WORKDIR/pin.yaml" | head -n 1)" pin="${pin%\"}"; pin="${pin#\"}" @@ -2967,7 +3550,7 @@ $(printf '%s\n' "$ahead_log" | sed -e 's/^/ /') # place and that are already there are left exactly as they are, and the # missing ones are added — which is what makes a re-run after a # half-finished first run safe. - ref="$(wip_shape_ref)" + ref="$(wip_shape_ref)" || exit "$?" workdir for rel in "${WIP_TEMPLATE_FILES[@]}"; do dest="$WORKDIR/tpl-$(printf '%s' "$rel" | tr '/' '_')" diff --git a/tests/test_install_payload_source.py b/tests/test_install_payload_source.py index 3be4f86f..089d2d86 100644 --- a/tests/test_install_payload_source.py +++ b/tests/test_install_payload_source.py @@ -91,7 +91,17 @@ def composed_context() -> tuple[bool, str]: COMPOSED, COMPOSED_WHY = composed_context() -NEEDS_COMPOSED = pytest.mark.skipif(not COMPOSED, reason=COMPOSED_WHY) +#: `OPENREPOTOOLS_COMPOSED=1` is the assembly's exact-pin job saying the run IS +#: composed acceptance (T009's convention): there, an absent context FAILS and +#: nothing skips its way in. Anywhere else, standalone code skips these. +STRICT_COMPOSED = os.environ.get("OPENREPOTOOLS_COMPOSED") == "1" +NEEDS_COMPOSED = pytest.mark.skipif(not COMPOSED and not STRICT_COMPOSED, + reason=COMPOSED_WHY) + + +def require_composed() -> None: + if not COMPOSED: + pytest.fail(f"OPENREPOTOOLS_COMPOSED=1, and {COMPOSED_WHY}") # --- the payload, read the way a consumer reads it ------------------------- @@ -836,6 +846,7 @@ def test_r01_composed_the_documented_one_liners_resolve_through_the_root_entry_p carries the real entry point: the root resolves `main` ONCE, reads the code pin there and hands every argument to the pinned implementation, which places the pinned payload.""" + require_composed() world = World(tmp_path) code = world.code() world.assembly(code, root_entry=(ASSEMBLY / "openRepoTools").read_bytes()) @@ -865,6 +876,7 @@ def test_r01_composed_the_root_entry_point_delegates_locally(tmp_path): """`./openRepoTools` at an assembly checkout IS the pinned implementation for every argument: the same help, the same refusal and exit status, the same install.""" + require_composed() world = World(tmp_path) home = tmp_path / "home" root = ASSEMBLY / "openRepoTools" @@ -887,6 +899,7 @@ def test_r01_composed_a_signal_reaches_the_implementation_and_its_temporaries_go """The root EXECs the implementation, so a TERM sent to the pid the person started is a TERM to the installer itself, whose own EXIT trap removes its temporary directory.""" + require_composed() world = World(tmp_path) home = tmp_path / "home" gate = tmp_path / "gate" @@ -1365,7 +1378,9 @@ def test_r11_a_payload_file_missing_from_the_pinned_code_changes_nothing(tmp_pat result = run_stdin(world, home, network(world), "--install") assert result.returncode == 2, result.stdout + result.stderr assert snapshot(home) == before, "a refused install changed the prior one" - assert withheld in result.stderr or withheld.split("/")[1] in result.stderr, result.stderr + # The refusal is today's all-or-none text, naming the source it could not + # complete from: the code the assembly pins, never the assembly root. + assert "NOTHING was installed" in result.stderr, result.stderr assert f"opensoft/openRepoTools-code at {code}" in result.stderr, result.stderr @@ -1513,6 +1528,7 @@ def test_r14_composed_acceptance_requires_the_nested_dependency(): """Composed acceptance REFUSES an absent dependency rather than skipping it: a mounted code leg without its pinned standard is not the assembled state T015 accepts.""" + require_composed() assert (UPSTREAM / "scripts" / "repo_shape.py").is_file(), ( "the code leg is mounted but its nested openRepoShape is not: run " "`git submodule update --init --recursive` (or `make bootstrap`) at the assembly") @@ -1582,8 +1598,36 @@ def test_r15_the_adopted_path_holds_under_macos_one_true_awk(tmp_path): assert_installed(home, "pinned", result) +#: THE PIN READER THE ROOT ENTRY POINT CARRIES IS A DELIBERATE COPY of the +#: implementation's: the root must read the pin before there is an +#: implementation to ask. Copies that may not drift are compared, here. +PIN_READER = ("ORT_ALNUM", "ORT_TAB", "ORT_CR", "is_lower_hex", "is_slug", + "is_segment", "pin_scalar", "pin_bad", "pin_parse", "recorded_gitlink") + + +def shell_definitions(text: str) -> dict[str, str | None]: + out: dict[str, str | None] = {} + for name in PIN_READER: + if name.isupper(): + m = re.search(rf"^{name}=.*$", text, re.M) + else: + m = re.search(rf"^{name}\(\) \{{.*?^\}}\n", text, re.S | re.M) + out[name] = m.group(0) if m else None + return out + + +@NEEDS_COMPOSED +def test_r15_composed_the_root_entry_points_pin_reader_is_the_implementations(): + require_composed() + mine = shell_definitions((ASSEMBLY / "openRepoTools").read_text(encoding="utf-8")) + theirs = shell_definitions(SOURCE_TEXT) + assert all(theirs.values()), theirs + assert mine == theirs, [n for n in PIN_READER if mine[n] != theirs[n]] + + @NEEDS_COMPOSED def test_r15_composed_the_root_entry_point_parses_and_uses_nothing_bash_3_2_lacks(): + require_composed() root = ASSEMBLY / "openRepoTools" assert bash4_constructs(root.read_text(encoding="utf-8")) == [] assert subprocess.run(["bash", "-n", str(root)]).returncode == 0 diff --git a/tests/test_openrepotools_command.py b/tests/test_openrepotools_command.py index fe8f1472..7f64e2cf 100644 --- a/tests/test_openrepotools_command.py +++ b/tests/test_openrepotools_command.py @@ -2191,6 +2191,11 @@ def test_the_duplicated_fetch_logic_says_so_out_loud(): # --- the fetch path, offline, through a fake `gh` --------------------------- +#: The commit the fake resolves every ref to, and a blob id for its listing. +FAKE_COMMIT = "c0ffee" + "0" * 34 +FAKE_BLOB = "b10b" + "0" * 36 + + def fake_github(tmp_path, served_names) -> dict: """A fake `gh` serving exactly `served_names`, and a `curl` that refuses. @@ -2214,6 +2219,15 @@ def fake_github(tmp_path, served_names) -> dict: routes = "".join( f"*/contents/{name}\\?*) exec cat '{served / name}' ;;\n" for name in served_names) + # THE TWO CALLS A REAL GITHUB ANSWERS BEFORE ANY OF THOSE (#186, T007): + # the ref, resolved once to a commit, and that commit's root listed — no + # `project.yaml` and no `contracts/code-pin.yaml`, which is what a single + # repository's listing shows. Every assertion below is unchanged; the + # server just answers what the installer now asks first. + routes += ( + f"*/commits/*) printf '%s' '{FAKE_COMMIT}'; exit 0 ;;\n" + f"*/git/trees/{FAKE_COMMIT}*) printf 'openRepoTools\\tblob\\t%s\\n' " + f"'{FAKE_BLOB}'; exit 0 ;;\n") (fake / "gh").write_text( "#!/bin/sh\n" 'case "$*" in\n' From ecd7aa42d3e3c74beda6c1c6c4646b1bc2a3defc Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Fri, 9 Oct 2026 00:46:21 +0000 Subject: [PATCH 03/10] Read every remote byte through one current source that starts as $REPO, so each fetching line names it as the hygiene test requires. CI on #191 failed test_the_installer_reaches_only_this_repository (tests/ test_repo_hygiene.py): every `gh api "` and raw-URL line in openRepoTools must name $REPO or $SHAPE_REPOSITORY, and the resolver's helpers took the repository as an argument. They now read ${PAYLOAD_REPO:-$REPO}, the run's one current source: $REPO itself while its ref, tree and code pin are read, and the repository that pin names only after the pin has passed every check. fetch_from_repo is back to its original two arguments. No behaviour changes; the only repository other than $REPO this reaches is the one $REPO's own validated pin names. Lane: openRepoTools-1 Co-Authored-By: Claude Opus 5.5 --- openRepoTools | 63 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/openRepoTools b/openRepoTools index e812ecdb..e74651bc 100755 --- a/openRepoTools +++ b/openRepoTools @@ -256,20 +256,21 @@ workdir() { # fetch the same way is a claim `tests/test_openrepotools_command.py` and # openRepoShape's own suite each assert against their own script's text. # -# WHAT IT FETCHES FROM IS THE ONE THING THAT MOVED (#186, T007): the source -# `payload_resolve_remote` settled — the code an adopted assembly pins, or a -# single repository at the ONE commit its ref named — or the repository and -# commit a caller hands it, which is how the code pin itself is read. +# WHAT IT FETCHES FROM IS THE ONE THING THAT MOVED (#186, T007): the CURRENT +# SOURCE, `$PAYLOAD_REPO` at `$PAYLOAD_REV`, which is `$REPO` itself until +# `payload_resolve_remote` has read and checked `$REPO`'s own code pin, and the +# repository that pin names after that. Before any resolution it is `$REPO` at +# `$REF`, exactly as it always was. fetch_from_repo() { - local path="$1" dest="$2" repo="${3:-${PAYLOAD_REPO:-$REPO}}" rev="${4:-${PAYLOAD_REV:-$REF}}" + local path="$1" dest="$2" if command -v gh >/dev/null 2>&1 && - gh api "repos/$repo/contents/$path?ref=$rev" \ + gh api "repos/${PAYLOAD_REPO:-$REPO}/contents/$path?ref=${PAYLOAD_REV:-$REF}" \ -H 'Accept: application/vnd.github.raw' >"$dest" 2>/dev/null && [ -s "$dest" ]; then return 0 fi if command -v curl >/dev/null 2>&1 && - curl -fsSL "https://raw.githubusercontent.com/$repo/$rev/$path" \ + curl -fsSL "https://raw.githubusercontent.com/${PAYLOAD_REPO:-$REPO}/${PAYLOAD_REV:-$REF}/$path" \ >"$dest" 2>/dev/null && [ -s "$dest" ]; then return 0 fi @@ -336,6 +337,12 @@ fetch_from_repo() { # ALL OF IT HAPPENS BEFORE ANYTHING IS PLACED: `install_commands` asks # `payload_select` straight after the targets, before the first byte is # collected, and every refusal here places nothing. +# +# ONE CURRENT SOURCE, AND IT IS `$REPO`'S. Every request this file makes reads +# `${PAYLOAD_REPO:-$REPO}`: `$REPO` while its ref, its tree and its pin are +# read, and — only once that pin has passed every check below — the repository +# `$REPO`'s own pin names. No other repository is ever reached for the payload, +# and `tests/test_repo_hygiene.py` holds every fetching line to naming `$REPO`. PAYLOAD_MODE="" # "" until chosen: local | siblings | remote PAYLOAD_DIR="" # the directory payload files are copied from, if any @@ -522,13 +529,14 @@ api_said() { # — its first line, as one clause esac } -#: A COMMIT, BY REF: API_SHA and 0, or API_WHY and 1. An answer that is not 40 -#: lowercase hex is no answer — `gh` prints a failed request's JSON on stdout. -api_sha() { # +#: A COMMIT OF THE CURRENT SOURCE, BY REF: API_SHA and 0, or API_WHY and 1. An +#: answer that is not 40 lowercase hex is no answer — `gh` prints a failed +#: request's JSON on stdout. +api_sha() { # local out="$WORKDIR/api-sha" why="" sha="" API_SHA="" API_WHY="" if command -v gh >/dev/null 2>&1; then - if gh api "repos/$1/commits/$2" -H 'Accept: application/vnd.github.sha' \ + if gh api "repos/${PAYLOAD_REPO:-$REPO}/commits/$1" -H 'Accept: application/vnd.github.sha' \ >"$out" 2>"$out.err"; then IFS= read -r sha <"$out" || : if is_lower_hex 40 "$sha"; then API_SHA="$sha"; return 0; fi @@ -541,7 +549,7 @@ api_sha() { # fi if command -v curl >/dev/null 2>&1; then if curl -fsSL -H 'Accept: application/vnd.github.sha' \ - "https://api.github.com/repos/$1/commits/$2" >"$out" 2>"$out.err"; then + "https://api.github.com/repos/${PAYLOAD_REPO:-$REPO}/commits/$1" >"$out" 2>"$out.err"; then sha="" IFS= read -r sha <"$out" || : if is_lower_hex 40 "$sha"; then API_SHA="$sha"; return 0; fi @@ -563,12 +571,13 @@ tree_listing_ok() { # — one or more `pathblob|tree|commit40 END { exit bad ? 1 : 0 }' "$1" } -#: A TREE'S ENTRIES, ONE LEVEL: the listing in and 0, or API_WHY and 1. -api_tree() { # - local out="$3" why="" +#: A TREE OF THE CURRENT SOURCE, ONE LEVEL: the listing in and 0, or +#: API_WHY and 1. +api_tree() { # + local out="$2" why="" API_WHY="" if command -v gh >/dev/null 2>&1; then - if gh api "repos/$1/git/trees/$2" --jq "$ORT_TREE_JQ" >"$out" 2>"$out.err"; then + if gh api "repos/${PAYLOAD_REPO:-$REPO}/git/trees/$1" --jq "$ORT_TREE_JQ" >"$out" 2>"$out.err"; then tree_listing_ok "$out" && return 0 why="gh: an answer that is not a tree listing" else @@ -581,7 +590,7 @@ api_tree() { # 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/$1/git/trees/$2" >"$out.json" 2>"$out.err"; then + elif ! curl -fsSL "https://api.github.com/repos/${PAYLOAD_REPO:-$REPO}/git/trees/$1" >"$out.json" 2>"$out.err"; then why="$why; $(api_said curl "$out.err")" elif jq -r "$ORT_TREE_JQ" "$out.json" >"$out" 2>/dev/null && tree_listing_ok "$out"; then return 0 @@ -621,22 +630,24 @@ payload_resolve_remote() { pin="$WORKDIR/code-pin.yaml" is_slug "$REPO" || die "\$OPENREPOTOOLS_REPO is '$REPO', which is not an owner/name repository, so there is nothing to fetch from, and nothing was fetched, placed or written." - api_sha "$REPO" "$(url_ref "$REF")" || die "could not resolve $REPO at $REF to a commit, so nothing was fetched, + # THE CURRENT SOURCE IS $REPO until its own pin says otherwise. + PAYLOAD_REPO="$REPO" PAYLOAD_REV="" + api_sha "$(url_ref "$REF")" || die "could not resolve $REPO at $REF to a commit, so nothing was fetched, placed or written. What answered: $API_WHY Both were asked: gh api repos/$REPO/commits/$REF -H 'Accept: application/vnd.github.sha' curl -fsSL -H 'Accept: application/vnd.github.sha' https://api.github.com/repos/$REPO/commits/$REF A failed answer is a refusal, never a reading of the repository's layout." - SOURCE_SHA="$API_SHA" - api_tree "$REPO" "$SOURCE_SHA" "$listing" || die "could not list $REPO at $SOURCE_SHA ($REF), so whether it is a single + SOURCE_SHA="$API_SHA" PAYLOAD_REV="$API_SHA" + api_tree "$SOURCE_SHA" "$listing" || die "could not list $REPO at $SOURCE_SHA ($REF), so whether it is a single repository or an adopted assembly is not known, and nothing was fetched, placed or written. What answered: $API_WHY A failed listing is a refusal, never evidence that the code pin is absent." entry="" if entry="$(tree_entry "$listing" contracts)" && [ "${entry%% *}" = tree ]; then - api_tree "$REPO" "${entry#* }" "$contracts" || die "could not list contracts/ in $REPO at $SOURCE_SHA ($REF), so whether it + api_tree "${entry#* }" "$contracts" || die "could not list contracts/ in $REPO at $SOURCE_SHA ($REF), so whether it carries a code pin is not known, and nothing was fetched, placed or written. What answered: $API_WHY" @@ -653,12 +664,12 @@ payload_resolve_remote() { its code is nor that it is a single repository. Nothing was fetched, placed or written. Its contracts/code-pin.yaml is the file that names the code leg." fi - SOURCE_LAYOUT=single PAYLOAD_REPO="$REPO" PAYLOAD_REV="$SOURCE_SHA" + SOURCE_LAYOUT=single return 0 fi [ "${entry%% *}" = blob ] || die "contracts/code-pin.yaml in $REPO at $SOURCE_SHA is a ${entry%% *}, not a file, and nothing was fetched, placed or written." - fetch_from_repo contracts/code-pin.yaml "$pin" "$REPO" "$SOURCE_SHA" || + fetch_from_repo contracts/code-pin.yaml "$pin" || die "could not fetch contracts/code-pin.yaml from $REPO at $SOURCE_SHA ($REF), which the listing shows is there, so nothing was fetched, placed or written. Both were asked: @@ -675,13 +686,15 @@ payload_resolve_remote() { and its contracts/code-pin.yaml 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 was fetched, placed or written." - if ! api_sha "$PIN_SOURCE_REPOSITORY" "$PIN_COMMIT" || [ "$API_SHA" != "$PIN_COMMIT" ]; then + # THE PIN HAS PASSED: the source is now the repository it names. + PAYLOAD_REPO="$PIN_SOURCE_REPOSITORY" PAYLOAD_REV="$PIN_COMMIT" + if ! api_sha "$PIN_COMMIT" || [ "$API_SHA" != "$PIN_COMMIT" ]; then die "contracts/code-pin.yaml in $REPO at $SOURCE_SHA ($REF) pins $PIN_SOURCE_REPOSITORY at $PIN_COMMIT, and that is not a commit $PIN_SOURCE_REPOSITORY answers for, so nothing was fetched, placed or written. What answered: ${API_WHY:-$PIN_SOURCE_REPOSITORY answered $API_SHA}" fi - SOURCE_LAYOUT=adopted PAYLOAD_REPO="$PIN_SOURCE_REPOSITORY" PAYLOAD_REV="$PIN_COMMIT" + SOURCE_LAYOUT=adopted } payload_where() { # the source collected from, for a refusal to name From 2411f4099b0c843b57eb2288d55fb7f2a87be39e Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Fri, 9 Oct 2026 01:02:38 +0000 Subject: [PATCH 04/10] Read the recorded gitlink with read rather than awk, so the offline adopted install needs nothing a minimal PATH lacks. Found by running the existing suites composed, with this checkout mounted as the rehearsed assembly's code leg (run C): test_without_jq_it_refuses_and_ places_nothing runs --install on a PATH of sixteen tools and no awk, and from the mounted leg the installer reached recorded_gitlink's awk before the jq refusal it asserts. On today's single-repository layout that path is never taken, so neither a standalone run nor CI could see it. The assembly entry point carries the same function byte for byte and changes with it. Lane: openRepoTools-1 Co-Authored-By: Claude Opus 5.5 --- openRepoTools | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openRepoTools b/openRepoTools index e74651bc..04769c4a 100755 --- a/openRepoTools +++ b/openRepoTools @@ -729,12 +729,14 @@ assembly_above() { # #: is what the next commit records, so a gitlink staged and not yet committed is #: the one that counts. recorded_gitlink() { # - local oid - oid="$(git -C "$1" ls-files -s -- "$2" 2>/dev/null | - awk -v p="$2" '$1 == "160000" && $3 == "0" && $4 == p { print $2; exit }')" || oid="" - if [ -z "$oid" ]; then - oid="$(git -C "$1" ls-tree HEAD -- "$2" 2>/dev/null | - awk -v p="$2" '$1 == "160000" && $2 == "commit" && $4 == p { print $3; exit }')" || oid="" + # `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" From 13cc75298143302cfa0805abce6271916ed912e5 Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Fri, 9 Oct 2026 02:21:41 +0000 Subject: [PATCH 05/10] Keep a single repository's install on today's network needs and wip init's fallback to main, and ask only two files to tell the layouts apart. #191's review found two regressions on today's layout (F1, F2). F1: the fetching paths resolved the ref and listed trees through api.github.com before any file, so a curl-only machine needed api.github.com and jq where it had only ever needed raw.githubusercontent.com, and a single repository's fetch refusal named a commit it was never asked to resolve. F2: an installed `wip init` whose source could not be resolved refused at step 7, after steps 5 and 6 had made and cloned the repository, saying nothing was written, where it used to take the standard's main. The layout is now told apart by asking for contracts/code-pin.yaml and then project.yaml at $REF, through fetch_from_repo's own two transports: a pin is the adopted layout, GitHub's 404 for both is a single repository, a manifest without a pin refuses, and any other answer still refuses rather than being read as the older layout. A single repository is then read exactly as before, at the ref as given, and its refusals are its old words. Only an adopted assembly resolves the ref once through the API. In wip init, a source that does not answer leaves main as it always did, and an adopted assembly that cannot be followed refuses in step 7's own words. The new single-layout tests are written to pass at c4864ac and fail at 2411f40; the adopted step-7 wording test is new behaviour. The run that shows it is recorded on the PR. Rows 6 and 7 now hold the adopted layout to one commit and the single repository to its ref as given. Lane: openRepoTools-1 Co-Authored-By: Claude Opus 5.5 --- openRepoTools | 231 ++++++++++++++++++-------- tests/test_install_payload_source.py | 232 +++++++++++++++++++++++---- tests/test_openrepotools_command.py | 20 +-- 3 files changed, 377 insertions(+), 106 deletions(-) diff --git a/openRepoTools b/openRepoTools index 04769c4a..18d4c7f0 100755 --- a/openRepoTools +++ b/openRepoTools @@ -259,8 +259,8 @@ workdir() { # WHAT IT FETCHES FROM IS THE ONE THING THAT MOVED (#186, T007): the CURRENT # SOURCE, `$PAYLOAD_REPO` at `$PAYLOAD_REV`, which is `$REPO` itself until # `payload_resolve_remote` has read and checked `$REPO`'s own code pin, and the -# repository that pin names after that. Before any resolution it is `$REPO` at -# `$REF`, exactly as it always was. +# repository that pin names after that. For a single repository it never +# moves: `$REPO` at `$REF`, exactly as it always was. fetch_from_repo() { local path="$1" dest="$2" if command -v gh >/dev/null 2>&1 && @@ -297,20 +297,30 @@ fetch_from_repo() { # and a gitlink at the pinned path that must agree with it. The payload is # that repository's, at that commit, and nothing else's. # -# THE REF IS RESOLVED ONCE, in both layouts. `$OPENREPOTOOLS_REF` becomes a -# commit on the first round trip, and every request after it names that -# commit, so a branch that moves while the files come down cannot hand one run -# two versions. `$OPENREPOTOOLS_REPO` and `$OPENREPOTOOLS_REF` mean what they -# always meant: what to install from, and at which ref. +# A SINGLE REPOSITORY IS READ EXACTLY AS IT ALWAYS WAS: `$OPENREPOTOOLS_REF` +# as given, file by file, by `gh api …/contents` and then the raw URL — no +# other host, no `jq`, no fallback that was not there before. The one thing it +# is asked that it was not asked before is the two questions below, by those +# same two transports. Only an ADOPTED ASSEMBLY resolves the ref: +# `$OPENREPOTOOLS_REF` becomes a commit on the first API round trip and every +# request after it names that commit, so a branch that moves while the files +# come down cannot hand one run two versions. That guarantee needs +# api.github.com (and `jq` on the `curl` path), which a single repository's +# install never needed and still does not: today's layout keeps today's +# network needs, byte for byte (#191's review). `$OPENREPOTOOLS_REPO` and +# `$OPENREPOTOOLS_REF` mean what they always meant: what to install from, and +# at which ref. # -# WHICH LAYOUT IS DECIDED BY A LISTING THAT SUCCEEDED, NEVER BY A FAILURE. The -# root tree at that commit is listed (`git/trees`), then `contracts/`: a code -# pin that is there is the adopted layout, and one that a successful listing -# shows is NOT there, with no adoption manifest (`project.yaml`) beside it, is -# a single repository. An authentication failure, a rate limit, a transport -# failure or an answer that does not parse is a REFUSAL naming what answered — -# never evidence of the older layout, because reading it that way would -# install whatever the assembly root happened to carry. +# WHICH LAYOUT IS DECIDED BY AN ANSWER, NEVER BY A FAILURE. Two files are asked +# for by name at `$REF`, through `fetch_from_repo`'s own two transports +# (`probe_path`): `contracts/code-pin.yaml`, and — only where GitHub answered +# that it is not there — `project.yaml`, the adoption manifest. A pin that is +# there is the adopted layout. GitHub's 404 for both is a single repository. A +# manifest with no pin is a partly adopted layout, and refuses. An +# authentication failure, a rate limit, a transport failure or any other status +# is a REFUSAL naming what answered — never evidence of the older layout, +# because reading it that way would install whatever the assembly root happened +# to carry. # # A PIN THAT IS PRESENT AND WRONG REFUSES. Its grammar is fixed, so it is read # here with `read` and `case` rather than a YAML runtime (`pin_parse`), and @@ -352,6 +362,9 @@ SOURCE_SHA="" # $REF, resolved once SOURCE_LAYOUT="" # single | adopted | feature, once known API_SHA="" API_WHY="" +PROBE_LAYOUT="" # single | adopted | partial | unknown, once the two files are asked for +PROBE_WHY="" +RESOLVE_FOR=install # whose refusal a failed resolution is: install, or wip (`wip init` step 7) #: THE CHARACTERS GITHUB SPELLS A NAME WITH, WRITTEN OUT. A bracket RANGE such #: as `[a-z]` follows the locale's collation in bash 3.2 and can admit capitals, @@ -619,79 +632,162 @@ url_ref() { # — with the two characters a URL path reads otherwise esc printf '%s' "$out" } +# --- the two questions every remote run asks first ---------------------------- + +#: IS IN `$REPO` AT `$REF`? Asked exactly the way `fetch_from_repo` asks +#: for a payload file — `gh api …/contents` first, the raw URL second — so the +#: answer needs no host and no tool today's install did not already need. +#: 0: it is there, its bytes in . 3: it is not, which is GitHub's own 404 +#: (`gh` names the status in what it prints; the raw URL's is read with `-w`). +#: 1: no answer either way, and PROBE_WHY says what each transport said. +probe_path() { # + local path="$1" dest="$2" status="" why="" + PROBE_WHY="" + if command -v gh >/dev/null 2>&1; then + if gh api "repos/$REPO/contents/$path?ref=$REF" \ + -H 'Accept: application/vnd.github.raw' >"$dest" 2>"$dest.err"; then + return 0 + fi + why="$(api_said gh "$dest.err")" + case "$why" in *'HTTP 404'*) return 3 ;; esac + else + why="gh: not installed" + fi + if command -v curl >/dev/null 2>&1; then + if status="$(curl -sSL -o "$dest" -w '%{http_code}' "https://raw.githubusercontent.com/$REPO/$REF/$path" 2>"$dest.err")"; then + case "$status" in + 200) return 0 ;; + 404) return 3 ;; + esac + why="$why; curl: HTTP ${status:-with no status}" + else + why="$why; $(api_said curl "$dest.err")" + fi + else + why="$why; curl: not installed" + fi + PROBE_WHY="$why" + return 1 +} + +#: WHICH LAYOUT `$REPO` HAS AT `$REF`: PROBE_LAYOUT single, adopted, partial or +#: unknown, with PROBE_WHY for unknown. It asks and refuses nothing; what each +#: answer means is its callers' (`payload_resolve_remote`, `wip_shape_ref`). +payload_probe() { + [ -z "$PROBE_LAYOUT" ] || return 0 + local rc=0 + workdir + probe_path contracts/code-pin.yaml "$WORKDIR/probe-code-pin" || rc=$? + case "$rc" in + 0) PROBE_LAYOUT=adopted; return 0 ;; + 3) : ;; + *) PROBE_LAYOUT=unknown PROBE_WHY="contracts/code-pin.yaml: $PROBE_WHY"; return 0 ;; + esac + rc=0 + probe_path project.yaml "$WORKDIR/probe-project" || rc=$? + case "$rc" in + 0) PROBE_LAYOUT=partial ;; + 3) PROBE_LAYOUT=single ;; + *) PROBE_LAYOUT=unknown PROBE_WHY="project.yaml: $PROBE_WHY" ;; + esac +} + +#: A RESOLUTION THAT FAILED, refused in the words of the run it failed: an +#: `--install`, which has placed nothing yet, or `wip init`'s step 7, which +#: comes after its repository was made and cloned, so "nothing was written" +#: would not be true there. +resolve_refuse() { # + case "$RESOLVE_FOR" in + wip) die "$1 + Step 7 seeded nothing: no template file was written into the checkout and + nothing was staged. What the steps before it made is kept as it is, and a + re-run picks up from here." ;; + *) die "$1 + NOTHING was installed and nothing already installed was replaced." ;; + esac +} + # --- the remote source, resolved once --------------------------------------- payload_resolve_remote() { - [ -z "$SOURCE_SHA" ] || return 0 + [ -z "$SOURCE_LAYOUT" ] || return 0 local listing contracts pin entry link workdir + payload_probe + case "$PROBE_LAYOUT" in + single) + # TODAY'S LAYOUT, READ AS TODAY: `$REPO` at `$REF` as given, file by + # file, by the transports that always fetched it. + PAYLOAD_REPO="$REPO" PAYLOAD_REV="" SOURCE_LAYOUT=single + return 0 ;; + partial) + resolve_refuse "$REPO at $REF carries an adoption manifest, project.yaml, and no + contracts/code-pin.yaml: a partly adopted layout, which says neither where + its code is nor that it is a single repository. Its contracts/code-pin.yaml + is the file that names the code leg." ;; + unknown) + resolve_refuse "could not fetch the files that say whether $REPO at $REF is a single + repository or an adopted assembly, and a failed answer is a refusal, never a + reading of the layout. What answered: + $PROBE_WHY + Both ways were tried: + gh api repos/$REPO/contents/?ref=$REF + curl -sSL https://raw.githubusercontent.com/$REPO/$REF/" ;; + esac + # AN ADOPTED ASSEMBLY: everything again, at ONE commit. listing="$WORKDIR/source-root.tsv" contracts="$WORKDIR/source-contracts.tsv" pin="$WORKDIR/code-pin.yaml" - is_slug "$REPO" || die "\$OPENREPOTOOLS_REPO is '$REPO', which is not an owner/name repository, so - there is nothing to fetch from, and nothing was fetched, placed or written." + is_slug "$REPO" || resolve_refuse "\$OPENREPOTOOLS_REPO is '$REPO', which is not an owner/name repository, + so the code its contracts/code-pin.yaml names cannot be asked for." # THE CURRENT SOURCE IS $REPO until its own pin says otherwise. PAYLOAD_REPO="$REPO" PAYLOAD_REV="" - api_sha "$(url_ref "$REF")" || die "could not resolve $REPO at $REF to a commit, so nothing was fetched, - placed or written. What answered: + api_sha "$(url_ref "$REF")" || resolve_refuse "could not resolve $REPO at $REF to a commit. What answered: $API_WHY Both were asked: gh api repos/$REPO/commits/$REF -H 'Accept: application/vnd.github.sha' curl -fsSL -H 'Accept: application/vnd.github.sha' https://api.github.com/repos/$REPO/commits/$REF - A failed answer is a refusal, never a reading of the repository's layout." + An adopted assembly is installed from ONE commit, and a failed answer is a + refusal, never a reading of the repository's layout." SOURCE_SHA="$API_SHA" PAYLOAD_REV="$API_SHA" - api_tree "$SOURCE_SHA" "$listing" || die "could not list $REPO at $SOURCE_SHA ($REF), so whether it is a single - repository or an adopted assembly is not known, and nothing was fetched, - placed or written. What answered: - $API_WHY - A failed listing is a refusal, never evidence that the code pin is absent." + api_tree "$SOURCE_SHA" "$listing" || resolve_refuse "could not list $REPO at $SOURCE_SHA ($REF), so the gitlink its code pin + must agree with cannot be read. What answered: + $API_WHY" entry="" if entry="$(tree_entry "$listing" contracts)" && [ "${entry%% *}" = tree ]; then - api_tree "${entry#* }" "$contracts" || die "could not list contracts/ in $REPO at $SOURCE_SHA ($REF), so whether it - carries a code pin is not known, and nothing was fetched, placed or written. - What answered: + api_tree "${entry#* }" "$contracts" || resolve_refuse "could not list contracts/ in $REPO at $SOURCE_SHA ($REF). What answered: $API_WHY" entry="$(tree_entry "$contracts" code-pin.yaml)" || entry="" else entry="" fi - if [ -z "$entry" ]; then - # NO CODE PIN, SHOWN BY A LISTING THAT SUCCEEDED. With no adoption - # manifest either, this is the single repository it always was. - if tree_entry "$listing" project.yaml >/dev/null; then - die "$REPO at $SOURCE_SHA ($REF) carries an adoption manifest, project.yaml, and - no contracts/code-pin.yaml: a partly adopted layout, which says neither where - its code is nor that it is a single repository. Nothing was fetched, placed or - written. Its contracts/code-pin.yaml is the file that names the code leg." - fi - SOURCE_LAYOUT=single - return 0 - fi - [ "${entry%% *}" = blob ] || die "contracts/code-pin.yaml in $REPO at $SOURCE_SHA is a ${entry%% *}, not a - file, and nothing was fetched, placed or written." + [ -n "$entry" ] || resolve_refuse "$REPO carried contracts/code-pin.yaml at $REF when it was first asked, + and $SOURCE_SHA, the commit $REF named a moment later, does not: the ref moved + between the two reads, so this run would not be installing one answer. + Re-run once it has settled." + [ "${entry%% *}" = blob ] || resolve_refuse "contracts/code-pin.yaml in $REPO at $SOURCE_SHA is a ${entry%% *}, not a + file." fetch_from_repo contracts/code-pin.yaml "$pin" || - die "could not fetch contracts/code-pin.yaml from $REPO at $SOURCE_SHA ($REF), which - the listing shows is there, so nothing was fetched, placed or written. Both - were asked: + resolve_refuse "could not fetch contracts/code-pin.yaml from $REPO at $SOURCE_SHA ($REF), which + the listing shows is there. Both were asked: gh api repos/$REPO/contents/contracts/code-pin.yaml?ref=$SOURCE_SHA curl -fsSL https://raw.githubusercontent.com/$REPO/$SOURCE_SHA/contracts/code-pin.yaml" - pin_parse "$pin" || die "contracts/code-pin.yaml in $REPO at $SOURCE_SHA ($REF) is not a code pin + pin_parse "$pin" || resolve_refuse "contracts/code-pin.yaml in $REPO at $SOURCE_SHA ($REF) is not a code pin this installer can follow: $PIN_WHY. A pin that is present and wrong is - refused rather than read around, and nothing was fetched, placed or written." + refused rather than read around." link="$(tree_entry "$listing" "$PIN_SUBMODULE_PATH")" || link="" - [ "${link%% *}" = commit ] || die "$REPO at $SOURCE_SHA ($REF) records no gitlink at $PIN_SUBMODULE_PATH, where - contracts/code-pin.yaml mounts the code leg, so nothing was fetched, placed or - written." - [ "${link#* }" = "$PIN_COMMIT" ] || die "$REPO at $SOURCE_SHA ($REF) records ${link#* } at $PIN_SUBMODULE_PATH + [ "${link%% *}" = commit ] || resolve_refuse "$REPO at $SOURCE_SHA ($REF) records no gitlink at $PIN_SUBMODULE_PATH, where + contracts/code-pin.yaml mounts the code leg." + [ "${link#* }" = "$PIN_COMMIT" ] || resolve_refuse "$REPO at $SOURCE_SHA ($REF) records ${link#* } at $PIN_SUBMODULE_PATH and its contracts/code-pin.yaml 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 was fetched, placed or written." + means." # THE PIN HAS PASSED: the source is now the repository it names. PAYLOAD_REPO="$PIN_SOURCE_REPOSITORY" PAYLOAD_REV="$PIN_COMMIT" if ! api_sha "$PIN_COMMIT" || [ "$API_SHA" != "$PIN_COMMIT" ]; then - die "contracts/code-pin.yaml in $REPO at $SOURCE_SHA ($REF) pins $PIN_SOURCE_REPOSITORY - at $PIN_COMMIT, and that is not a commit $PIN_SOURCE_REPOSITORY answers for, so - nothing was fetched, placed or written. What answered: + resolve_refuse "contracts/code-pin.yaml in $REPO at $SOURCE_SHA ($REF) pins $PIN_SOURCE_REPOSITORY + at $PIN_COMMIT, and that is not a commit $PIN_SOURCE_REPOSITORY answers for. + What answered: ${API_WHY:-$PIN_SOURCE_REPOSITORY answered $API_SHA}" fi SOURCE_LAYOUT=adopted @@ -699,7 +795,7 @@ payload_resolve_remote() { payload_where() { # the source collected from, for a refusal to name case "$SOURCE_LAYOUT" in - single) printf '%s at %s (%s)' "$PAYLOAD_REPO" "$REF" "$PAYLOAD_REV" ;; + single) printf '%s at %s' "$REPO" "$REF" ;; adopted) printf '%s at %s (the code %s at %s pins)' "$PAYLOAD_REPO" "$PAYLOAD_REV" "$REPO" "$REF" ;; *) printf '%s at %s' "$REPO" "$REF" ;; esac @@ -838,7 +934,7 @@ payload_select() { fi payload_resolve_remote # A SINGLE REPOSITORY KEEPS ITS SIBLING RULE: what is beside this file - # is copied and only what is missing is fetched, at the one commit. + # is copied and only what is missing is fetched, at `$REF`, as it was. if [ "$SOURCE_LAYOUT" = single ]; then PAYLOAD_MODE=siblings PAYLOAD_DIR="$selfdir" return 0 @@ -3100,12 +3196,19 @@ wip_shape_ref() { fi workdir # NO PIN BESIDE THIS FILE — the installed copy, a vendored tree: the pin is - # the one the CODE carries, read from wherever the payload comes from (#186) - # — the single repository at the commit its ref names, or the code leg an - # adopted assembly pins, never the assembly's own `contracts/`. A source - # that cannot be resolved is a refusal, as it is for `--install`; `main` - # stays the answer only for a pin that source does not carry. - payload_resolve_remote + # the one the CODE carries (#186). A single repository's is read exactly as + # it always was, at `$REF`; an adopted assembly's is the code leg's, at the + # commit the assembly pins, never the assembly's own `contracts/`. A source + # that does not answer leaves `main`, as it always did, and so does a pin + # the source does not carry. An adopted assembly whose code cannot be + # resolved refuses, in step 7's words (`resolve_refuse`). + RESOLVE_FOR=wip + payload_probe + case "$PROBE_LAYOUT" in + unknown) printf 'main\n'; return 0 ;; + single) : ;; + *) payload_resolve_remote ;; + esac if fetch_from_repo "contracts/openreposhape-pin.yaml" "$WORKDIR/pin.yaml"; then pin="$(sed -n -e 's/^commit:[[:space:]]*//p' "$WORKDIR/pin.yaml" | head -n 1)" pin="${pin%\"}"; pin="${pin#\"}" diff --git a/tests/test_install_payload_source.py b/tests/test_install_payload_source.py index 089d2d86..59ec8afe 100644 --- a/tests/test_install_payload_source.py +++ b/tests/test_install_payload_source.py @@ -18,13 +18,18 @@ THE TWO LAYOUTS: * LEGACY — one repository whose root carries every payload file (today's - `opensoft/openRepoTools`). A complete checkout installs offline, byte for - byte as before; a remote install resolves the ref once and fetches every - payload file at that commit. + `opensoft/openRepoTools`). It installs EXACTLY as before: a complete checkout + offline, byte for byte; a remote install fetching every payload file at the + ref it was given, by `gh` and then the raw URL, with no other host and no + `jq`. The only new requests are the two that tell the layouts apart — + `contracts/code-pin.yaml` and `project.yaml`, each answered 404 — asked by + those same two transports. * ADOPTED — an ASSEMBLY root whose `contracts/code-pin.yaml` pins the CODE leg by repository and commit, with the gitlink at the pinned path agreeing. The payload comes from the code repository at the pinned commit and from - nowhere else. + nowhere else, and the ref is resolved ONCE, so a moving branch cannot mix + two versions. That needs api.github.com (and `jq` on the `curl` path), which + is why it is the adopted layout's and not the legacy one's. A test whose name carries `composed` needs THIS checkout mounted as an assembly's code leg with the assembly's root entry point beside it; standalone @@ -339,12 +344,14 @@ def assembly(self, code_commit: str, *, slug: str = "opensoft/openRepoTools", message="assembly") def single(self, slug: str = "opensoft/openRepoTools", tag: str | None = "legacy", - *, branch: str | None = "main") -> str: + *, branch: str | None = "main", without: tuple[str, ...] = (), + shape: str | None = None) -> str: """Today's layout: every payload file at the root, the dependency pin in `contracts/`, and no adoption manifest.""" - files = payload_files(tag) + files = payload_files(tag, without=without) files["contracts/openreposhape-pin.yaml"] = ( - REPO / "contracts" / "openreposhape-pin.yaml").read_bytes() + shape_pin(shape) if shape else + (REPO / "contracts" / "openreposhape-pin.yaml").read_bytes()) files["README.md"] = b"# openRepoTools\n" files["AGENTS.md"] = b"# Working in openRepoTools\n" return commit(self.repo(slug), files, branch=branch, @@ -836,6 +843,61 @@ def test_r01_the_one_liner_against_an_adopted_assembly_installs_the_pinned_code( assert f"opensoft/openRepoTools-code at {code}" in result.stdout +#: Every request a single repository's install may make: a file at the ref it +#: was given, by `gh api …/contents` or by the raw URL. Nothing else. +def single_layout_requests_only(world: World, ref: str = "main") -> None: + for r in world.requests(): + target = r["target"] + if r["tool"] == "gh": + assert re.fullmatch(rf"repos/opensoft/openRepoTools/contents/[^?]+\?ref={re.escape(ref)}", + target), f"a single repository's install asked {target}" + else: + assert target.startswith( + f"https://raw.githubusercontent.com/opensoft/openRepoTools/{ref}/"), ( + f"a single repository's install asked {target}") + + +@NEEDS_JQ +@pytest.mark.parametrize("api_fault", ["transport", "rate-403"]) +def test_r01_a_single_repository_by_curl_never_needs_api_github_com(tmp_path, api_fault): + """Today's layout on a machine with no `gh`: the one-liner always needed + raw.githubusercontent.com and nothing else, so an api.github.com that is + unreachable or rate-limited (60 an hour, unauthenticated, per address) + changes nothing. #191's review, F1: the single repository keeps its network + needs byte for byte.""" + world = World(tmp_path) + world.single(tag="single") + fault = ({"kind": "transport"} if api_fault == "transport" + else {"kind": "status", "status": 403}) + world.faults = [{"tool": "curl", "match": r"^https://api\.github\.com/", **fault}] + world.write_conf() + home = tmp_path / "home" + result = run_stdin(world, home, network(world, gh=False), "--install") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "single", result) + single_layout_requests_only(world) + assert len(world.payload_requests()) == len(PAYLOAD) + + +@NEEDS_JQ +def test_r01_a_single_repository_by_gh_asks_for_files_and_nothing_else(tmp_path): + """With `gh`: every request is `contents/?ref=` — no + `commits/`, no `git/trees/` — and the only files asked for that are not + payload are, at most, the two that tell the layouts apart.""" + world = World(tmp_path) + world.single(tag="single") + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "single", result) + single_layout_requests_only(world) + others = {r["target"] for r in world.requests()} - { + r["target"] for r in world.payload_requests()} + assert others <= {"repos/opensoft/openRepoTools/contents/contracts/code-pin.yaml?ref=main", + "repos/opensoft/openRepoTools/contents/project.yaml?ref=main"}, others + assert len(world.payload_requests()) == len(PAYLOAD) + + @NEEDS_COMPOSED @NEEDS_JQ @pytest.mark.parametrize("line", ["openRepoShape", "gh"]) @@ -1094,33 +1156,37 @@ def test_r05_an_adopted_fork_follows_its_own_pin_not_a_guessed_code_name(tmp_pat # ROW 6 — branch, tag, commit and slash-containing refs # ========================================================================= -@NEEDS_JQ -@pytest.mark.parametrize("form", ["branch", "tag", "commit", "slash"]) -@pytest.mark.parametrize("layout", ["adopted", "single"]) -def test_r06_every_ref_form_is_resolved_once_and_every_payload_names_one_commit( - tmp_path, form, layout): - """The ref is asked about ONCE, and every payload request after that names - the one immutable commit — never the ref, which can move.""" - world = World(tmp_path) +def ref_world(world: World, form: str, layout: str) -> tuple[str, str, str]: + """`opensoft/openRepoTools` with the `form` payload at a ref of that form, + and something else entirely on `main`, so a run that read `main` instead of + the ref it was given installs the decoy. The ref, the commit it names, and + the commit the payload is at.""" if layout == "adopted": code = world.code(tag=form) sha = world.assembly(code, branch=None) payload_commit = code + world.assembly(world.code(tag="decoy-main", branch=None)) else: sha = world.single(tag=form, branch=None) payload_commit = sha - # `main` holds something else entirely, so a run that read `main` instead - # of the ref it was given installs the decoy. - if layout == "single": world.single(tag="decoy-main") - else: - world.assembly(world.code(tag="decoy-main", branch=None)) ref = {"branch": "release", "tag": "v1.0", "commit": sha, "slash": "feature/triad-x"}[form] if form == "tag": world.ref("opensoft/openRepoTools", f"refs/tags/{ref}", sha) elif form != "commit": world.ref("opensoft/openRepoTools", f"refs/heads/{ref}", sha) + return ref, sha, payload_commit + + +@NEEDS_JQ +@pytest.mark.parametrize("form", ["branch", "tag", "commit", "slash"]) +def test_r06_every_ref_form_is_resolved_once_and_every_payload_names_one_commit( + tmp_path, form): + """ADOPTED: the ref is asked about ONCE, and every payload request after + that names the one immutable commit — never the ref, which can move.""" + world = World(tmp_path) + ref, sha, payload_commit = ref_world(world, form, "adopted") home = tmp_path / "home" result = run_stdin(world, home, network(world), "--install", OPENREPOTOOLS_REF=ref) @@ -1135,20 +1201,35 @@ def test_r06_every_ref_form_is_resolved_once_and_every_payload_names_one_commit( assert payload_commit in r["target"], r +@NEEDS_JQ +@pytest.mark.parametrize("form", ["branch", "tag", "commit", "slash"]) +def test_r06_a_single_repository_reads_every_ref_form_as_it_always_did(tmp_path, form): + """LEGACY: every ref form installs its own payload, and every request names + the ref exactly as it was given — the one-liner's behaviour before the + triad, kept byte for byte (#191's review, F1). Resolving it to a commit + first would need api.github.com, which this layout never needed.""" + world = World(tmp_path) + ref, _, _ = ref_world(world, form, "single") + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install", + OPENREPOTOOLS_REF=ref) + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, form, result) + single_layout_requests_only(world, ref) + + # ========================================================================= # ROW 7 — a branch that moves between downloads # ========================================================================= @NEEDS_JQ -@pytest.mark.parametrize("layout", ["adopted", "single"]) -def test_r07_a_branch_that_moves_between_downloads_never_mixes_versions(tmp_path, layout): +def test_r07_a_branch_that_moves_between_downloads_never_mixes_versions(tmp_path): + """ADOPTED. A single repository is read file by file at its ref, as it + always was (`test_r06_a_single_repository_reads_every_ref_form_as_it_always_did`): + freezing it would need api.github.com, which that layout never needed.""" world = World(tmp_path) - if layout == "adopted": - first = world.assembly(world.code(tag="first")) - second = world.assembly(world.code(tag="second", branch=None), branch=None) - else: - first = world.single(tag="first") - second = world.single(tag="second", branch=None) + first = world.assembly(world.code(tag="first")) + second = world.assembly(world.code(tag="second", branch=None), branch=None) assert first != second world.moves = [{"after": 6, "repo": "opensoft/openRepoTools", "ref": "refs/heads/main", "to": second}] @@ -1312,7 +1393,11 @@ def test_r09_an_api_failure_is_a_refusal_never_a_fallback_to_a_single_repository assert_nothing_placed(home, result) assert world.payload_requests() == [], world.payload_requests() tried = {r["tool"] for r in world.requests()} - if (step, fault) != ("pin", "garbage"): + if (step, fault) == ("pin", "missing-404"): + # GitHub's own 404 for the pin is an ANSWER — the path is absent — and + # the adoption manifest beside it makes that a partly adopted layout. + assert "project.yaml" in result.stderr, result.stderr + elif (step, fault) != ("pin", "garbage"): # A pin `gh` DID deliver is refused for what it says, not re-fetched. assert tried == {"gh", "curl"}, f"both transports are tried: {tried}" @@ -1384,6 +1469,35 @@ def test_r11_a_payload_file_missing_from_the_pinned_code_changes_nothing(tmp_pat assert f"opensoft/openRepoTools-code at {code}" in result.stderr, result.stderr +#: `--install`'s refusals for a single repository missing a file, in the words +#: `openRepoTools` at `c4864ac` printed them, from `$REPO` at `$REF` as given. +def single_refusal(withheld: str) -> str: + if withheld in INSTALLABLES: + return (f"could not fetch {withheld} from opensoft/openRepoTools at main, so NOTHING was installed\n" + " and nothing already installed was replaced. `--install` places all\n" + f" {len(INSTALLABLES)} files or none: a new openRepoTools beside a missing park is\n" + " a half-install that reads like a whole one. Both ways were tried:\n" + " gh api repos/opensoft/openRepoTools/contents/?ref=main\n" + " curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoTools/main/\n") + return (f"could not fetch one of the {len(SKILLS)} skills ({' '.join(SKILLS)}) or the " + f"{len(COMMANDS)} command file ({' '.join(COMMANDS)}) from opensoft/openRepoTools at main, so\n" + " NOTHING was installed and nothing already installed was replaced.\n") + + +@NEEDS_JQ +@pytest.mark.parametrize("withheld", ["park", "skills/restart/SKILL.md"]) +def test_r11_a_single_repository_missing_a_file_refuses_in_the_words_it_always_did( + tmp_path, withheld): + """#191's review, F1: the single repository's refusal names `$REPO` at + `$REF` as given, and not a commit it was never asked to resolve.""" + world = World(tmp_path) + world.single(tag="single", without=(withheld,)) + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install") + assert_nothing_placed(home, result) + assert result.stderr == "\nREFUSED: " + single_refusal(withheld), result.stderr + + @NEEDS_JQ def test_r11_a_settings_merge_that_cannot_be_computed_changes_nothing(tmp_path): world = World(tmp_path) @@ -1498,6 +1612,66 @@ def test_r13_wip_init_takes_the_standard_from_codes_dependency_pin_not_the_assem assert "DECOY FROM MAIN" not in (checkout / "README.md").read_text(encoding="utf-8") +@NEEDS_TEMPLATES +@NEEDS_JQ +def test_r13_an_installed_wip_init_whose_source_does_not_answer_seeds_from_main_as_before( + tmp_path): + """#191's review, F2: an installed `wip init` whose `$REPO` does not answer + at all (both transports fail to reach it) takes the standard's `main`, as it + always did, rather than refusing at step 7 after steps 5 and 6 made and + cloned the repository.""" + world = World(tmp_path) + shape_repository(world) + world.single(tag="single") + world.faults = [{"tool": "any", "match": r"opensoft/openRepoTools/", "kind": "transport"}] + world.write_conf() + result, checkout = run_installed_wip(world, tmp_path) + assert result.returncode == 0, result.stdout + result.stderr + assert "template files from opensoft/openRepoShape @ main" in result.stdout, result.stdout + assert (checkout / "README.md").is_file() + + +@NEEDS_TEMPLATES +@NEEDS_JQ +def test_r13_an_installed_wip_init_on_a_single_repository_reads_its_pin_as_before(tmp_path): + """Today's layout: the dependency pin at `$REPO`'s `contracts/`, at the ref + given, by `gh api …/contents` — and no `commits/` or `git/trees/` request + for `$REPO`.""" + world = World(tmp_path) + pinned, moved = shape_repository(world) + world.single(tag="single", shape=pinned) + result, checkout = run_installed_wip(world, tmp_path) + assert result.returncode == 0, result.stdout + result.stderr + assert f"template files from opensoft/openRepoShape @ {pinned}" in result.stdout, result.stdout + assert "DECOY FROM MAIN" not in (checkout / "README.md").read_text(encoding="utf-8") + asked = [r["target"] for r in world.requests() + if "opensoft/openRepoTools/" in r["target"]] + assert asked and all("/contents/" in t and t.endswith("?ref=main") for t in asked), asked + + +@NEEDS_TEMPLATES +@NEEDS_JQ +def test_r13_an_adopted_source_wip_init_cannot_follow_refuses_in_step_7s_words(tmp_path): + """An adopted assembly whose pin is wrong refuses at step 7 — after steps 5 + and 6 made and cloned the repository — so the refusal says what step 7 did + not do and keeps what came before, rather than claiming nothing was + written (#191's review, F2).""" + world = World(tmp_path) + pinned, _ = shape_repository(world) + code = world.code(shape=pinned) + good = PIN_TEXT.format(source="opensoft/openRepoTools-code", path="code", commit=code, + digest=tree_digest(world.repo("opensoft/openRepoTools-code"), code)) + world.assembly(code, pin=malformed(good, "leg-role")) + result, checkout = run_installed_wip(world, tmp_path) + assert result.returncode == 2, result.stdout + result.stderr + assert "contracts/code-pin.yaml" in result.stderr, result.stderr + assert "Step 7 seeded nothing" in result.stderr, result.stderr + assert "nothing was fetched, placed or written" not in result.stderr + assert "NOTHING was installed" not in result.stderr + assert (checkout / ".git").is_dir(), "step 6's clone is what the refusal keeps" + assert not (checkout / "README.md").exists() + + @NEEDS_TEMPLATES @NEEDS_JQ def test_r14_with_no_nested_standard_anywhere_the_dependency_pin_alone_selects(tmp_path): diff --git a/tests/test_openrepotools_command.py b/tests/test_openrepotools_command.py index 7f64e2cf..913d5b1b 100644 --- a/tests/test_openrepotools_command.py +++ b/tests/test_openrepotools_command.py @@ -2191,11 +2191,6 @@ def test_the_duplicated_fetch_logic_says_so_out_loud(): # --- the fetch path, offline, through a fake `gh` --------------------------- -#: The commit the fake resolves every ref to, and a blob id for its listing. -FAKE_COMMIT = "c0ffee" + "0" * 34 -FAKE_BLOB = "b10b" + "0" * 36 - - def fake_github(tmp_path, served_names) -> dict: """A fake `gh` serving exactly `served_names`, and a `curl` that refuses. @@ -2219,15 +2214,14 @@ def fake_github(tmp_path, served_names) -> dict: routes = "".join( f"*/contents/{name}\\?*) exec cat '{served / name}' ;;\n" for name in served_names) - # THE TWO CALLS A REAL GITHUB ANSWERS BEFORE ANY OF THOSE (#186, T007): - # the ref, resolved once to a commit, and that commit's root listed — no - # `project.yaml` and no `contracts/code-pin.yaml`, which is what a single - # repository's listing shows. Every assertion below is unchanged; the - # server just answers what the installer now asks first. + # THE TWO FILES A REAL GITHUB IS ASKED FOR BEFORE ANY OF THOSE (#186, + # T007), answered the way it answers for a single repository: a 404 for + # the code pin and a 404 for the adoption manifest, which `gh` reports on + # stderr with the status in it. Every assertion below is unchanged; the + # server just answers the two questions the installer now asks first. routes += ( - f"*/commits/*) printf '%s' '{FAKE_COMMIT}'; exit 0 ;;\n" - f"*/git/trees/{FAKE_COMMIT}*) printf 'openRepoTools\\tblob\\t%s\\n' " - f"'{FAKE_BLOB}'; exit 0 ;;\n") + "*/contents/contracts/code-pin.yaml\\?*|*/contents/project.yaml\\?*) " + "printf 'gh: Not Found (HTTP 404)\\n' >&2; exit 1 ;;\n") (fake / "gh").write_text( "#!/bin/sh\n" 'case "$*" in\n' From fdc5f1801973ad160ed5987996661e93a23cee97 Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Fri, 9 Oct 2026 13:59:48 +0000 Subject: [PATCH 06/10] Ask today's layout one question, not two, and read an assembly only from the two layouts the design names. The review of #191 asked that a single repository's install make exactly base's requests, need its tools and print its words. After 13cc752 the one remaining addition on that path was the project.yaml question asked on the code pin's 404. It was a second request on today's layout for a state an adopter never publishes, because the pin and the manifest land in one commit and the assembly's own `make validate` refuses one without the other. GitHub's 404 for contracts/code-pin.yaml is now the whole single-repository answer. An assembly root without its pin, which carries no payload, refuses exactly as a single repository missing those files always did. assembly_above used to accept any contracts/code-pin.yaml above the file's directory. It now accepts one only where the pin mounts this directory as its code leg, at / or at /worktrees//. Another project's pin above a vendored tree is no longer read, so it can neither refuse nor add a line to a sentinel install. New tests cover the review's F2 cases: a missing ref and a 502 on the commit lookup in an installed wip init. They also cover the no-jq order and words on the curl path, a missing ref on --install by gh and by curl, and a complete tree inside another project's assembly, with a valid pin and with a CRLF pin. Lane: openRepoTools-1 Co-Authored-By: Claude Opus 5.5 --- openRepoTools | 127 ++++++++++++-------- tests/test_install_payload_source.py | 166 ++++++++++++++++++++++----- tests/test_openrepotools_command.py | 10 +- 3 files changed, 221 insertions(+), 82 deletions(-) diff --git a/openRepoTools b/openRepoTools index 18d4c7f0..4279a672 100755 --- a/openRepoTools +++ b/openRepoTools @@ -299,9 +299,10 @@ fetch_from_repo() { # # A SINGLE REPOSITORY IS READ EXACTLY AS IT ALWAYS WAS: `$OPENREPOTOOLS_REF` # as given, file by file, by `gh api …/contents` and then the raw URL — no -# other host, no `jq`, no fallback that was not there before. The one thing it -# is asked that it was not asked before is the two questions below, by those -# same two transports. Only an ADOPTED ASSEMBLY resolves the ref: +# other host, no `jq`, no fallback that was not there before. The one request +# it makes that it did not make before is the question below, by those same +# two transports, and it prints nothing. Only an ADOPTED ASSEMBLY resolves the +# ref: # `$OPENREPOTOOLS_REF` becomes a commit on the first API round trip and every # request after it names that commit, so a branch that moves while the files # come down cannot hand one run two versions. That guarantee needs @@ -311,16 +312,18 @@ fetch_from_repo() { # `$OPENREPOTOOLS_REF` mean what they always meant: what to install from, and # at which ref. # -# WHICH LAYOUT IS DECIDED BY AN ANSWER, NEVER BY A FAILURE. Two files are asked +# WHICH LAYOUT IS DECIDED BY AN ANSWER, NEVER BY A FAILURE. One file is asked # for by name at `$REF`, through `fetch_from_repo`'s own two transports -# (`probe_path`): `contracts/code-pin.yaml`, and — only where GitHub answered -# that it is not there — `project.yaml`, the adoption manifest. A pin that is -# there is the adopted layout. GitHub's 404 for both is a single repository. A -# manifest with no pin is a partly adopted layout, and refuses. An +# (`probe_path`): `contracts/code-pin.yaml`. A pin that is there is the adopted +# layout. GitHub's 404 for it is a single repository, read as above. An # authentication failure, a rate limit, a transport failure or any other status -# is a REFUSAL naming what answered — never evidence of the older layout, -# because reading it that way would install whatever the assembly root happened -# to carry. +# from both transports is a REFUSAL naming what answered — never evidence of +# the older layout, because reading it that way would install whatever the +# assembly root happened to carry. NOTHING ELSE IS ASKED on that 404: a second +# question (the adoption manifest, `project.yaml`) would be a second request on +# today's layout for a state the adopter never publishes — it writes the pin +# and the manifest in one commit — and that `make validate` refuses in the +# assembly itself (#191's review). # # A PIN THAT IS PRESENT AND WRONG REFUSES. Its grammar is fixed, so it is read # here with `read` and `case` rather than a YAML runtime (`pin_parse`), and @@ -335,14 +338,18 @@ fetch_from_repo() { # pinned path, and that the pinned commit is one the pinned repository has. # # LOCALLY — this file run from the code leg MOUNTED in an assembly checkout, -# whose `contracts/code-pin.yaml` names this directory — the pin and the +# `/`, whose `contracts/code-pin.yaml` names this +# directory — the pin and the # gitlink must agree and every payload file must be the pinned commit's bytes; # then they are copied, offline. A code checkout under an assembly that is NOT # its mounted leg (a paired feature worktree, `/worktrees//code`) is # somebody installing that checkout ON PURPOSE: its bytes are copied as they # are, and the run names that checkout and its commit and says it is not the # pinned code. Neither is ever completed from a network — local siblings and -# remote bytes do not make one install between them. +# remote bytes do not make one install between them. THOSE TWO LAYOUTS AND NO +# OTHER: a `contracts/code-pin.yaml` anywhere else above this file — another +# project's assembly that happens to hold a vendored tree deep inside it — is +# not this tree's, is not read, and changes nothing (`assembly_above`). # # ALL OF IT HAPPENS BEFORE ANYTHING IS PLACED: `install_commands` asks # `payload_select` straight after the targets, before the first byte is @@ -362,7 +369,7 @@ SOURCE_SHA="" # $REF, resolved once SOURCE_LAYOUT="" # single | adopted | feature, once known API_SHA="" API_WHY="" -PROBE_LAYOUT="" # single | adopted | partial | unknown, once the two files are asked for +PROBE_LAYOUT="" # single | adopted | unknown, once contracts/code-pin.yaml is asked for PROBE_WHY="" RESOLVE_FOR=install # whose refusal a failed resolution is: install, or wip (`wip init` step 7) @@ -670,8 +677,8 @@ probe_path() { # return 1 } -#: WHICH LAYOUT `$REPO` HAS AT `$REF`: PROBE_LAYOUT single, adopted, partial or -#: unknown, with PROBE_WHY for unknown. It asks and refuses nothing; what each +#: WHICH LAYOUT `$REPO` HAS AT `$REF`: PROBE_LAYOUT single, adopted or unknown, +#: with PROBE_WHY for unknown. One request, and it refuses nothing; what each #: answer means is its callers' (`payload_resolve_remote`, `wip_shape_ref`). payload_probe() { [ -z "$PROBE_LAYOUT" ] || return 0 @@ -679,16 +686,9 @@ payload_probe() { workdir probe_path contracts/code-pin.yaml "$WORKDIR/probe-code-pin" || rc=$? case "$rc" in - 0) PROBE_LAYOUT=adopted; return 0 ;; - 3) : ;; - *) PROBE_LAYOUT=unknown PROBE_WHY="contracts/code-pin.yaml: $PROBE_WHY"; return 0 ;; - esac - rc=0 - probe_path project.yaml "$WORKDIR/probe-project" || rc=$? - case "$rc" in - 0) PROBE_LAYOUT=partial ;; + 0) PROBE_LAYOUT=adopted ;; 3) PROBE_LAYOUT=single ;; - *) PROBE_LAYOUT=unknown PROBE_WHY="project.yaml: $PROBE_WHY" ;; + *) PROBE_LAYOUT=unknown ;; esac } @@ -720,19 +720,14 @@ payload_resolve_remote() { # file, by the transports that always fetched it. PAYLOAD_REPO="$REPO" PAYLOAD_REV="" SOURCE_LAYOUT=single return 0 ;; - partial) - resolve_refuse "$REPO at $REF carries an adoption manifest, project.yaml, and no - contracts/code-pin.yaml: a partly adopted layout, which says neither where - its code is nor that it is a single repository. Its contracts/code-pin.yaml - is the file that names the code leg." ;; unknown) - resolve_refuse "could not fetch the files that say whether $REPO at $REF is a single - repository or an adopted assembly, and a failed answer is a refusal, never a - reading of the layout. What answered: + resolve_refuse "could not fetch contracts/code-pin.yaml from $REPO at $REF, the file that + says whether it is a single repository or an adopted assembly, and a failed + answer is a refusal, never a reading of the layout. What answered: $PROBE_WHY Both ways were tried: - gh api repos/$REPO/contents/?ref=$REF - curl -sSL https://raw.githubusercontent.com/$REPO/$REF/" ;; + gh api repos/$REPO/contents/contracts/code-pin.yaml?ref=$REF + curl -sSL https://raw.githubusercontent.com/$REPO/$REF/contracts/code-pin.yaml" ;; esac # AN ADOPTED ASSEMBLY: everything again, at ONE commit. listing="$WORKDIR/source-root.tsv" @@ -803,21 +798,55 @@ payload_where() { # the source collected from, for a refusal to name # --- the local source --------------------------------------------------------- -#: THE NEAREST DIRECTORY ABOVE HOLDING `contracts/code-pin.yaml`: the -#: assembly this checkout sits in, if it sits in one. Logical paths, so what is -#: printed is what the person typed; the one comparison that must see through -#: links (`payload_local_adopted`) asks `pwd -P` itself. +#: DOES MOUNT ITS CODE LEG AT ? Its `submodule_path:` line, read +#: leniently — the strict read is `pin_parse`'s, once the pin is known to be +#: this checkout's — so that another project's pin, however it is written, is +#: only ever compared with this directory's name. +pin_mounts() { # + local line value + while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + submodule_path:*) + value="${line#submodule_path:}" + value="${value%"$ORT_CR"}" + value="${value#"${value%%[! $ORT_TAB]*}"}" + value="${value%"${value##*[! $ORT_TAB]}"}" + value="${value#[\"\']}" + value="${value%[\"\']}" + [ -n "$value" ] && [ "$value" = "$2" ] + return + ;; + esac + done <"$1" + return 1 +} + +#: THE ASSEMBLY IS A CODE LEG OF, in one of the two layouts the design +#: names and no other: `/`, the mounted leg, or +#: `/worktrees//`, a paired feature worktree — each +#: with `/contracts/code-pin.yaml` naming that ``. A pin +#: anywhere else above is another project's (#191's review: workBenches +#: vendors a complete tree deep inside whatever holds it). Logical paths, so +#: what is printed is what the person typed; the one comparison that must see +#: through links (`payload_local_adopted`) asks `pwd -P` itself. assembly_above() { # - local d - d="$(cd -- "$1/.." 2>/dev/null && pwd)" || return 1 - while :; do - if [ -f "$d/contracts/code-pin.yaml" ]; then - printf '%s\n' "$d" - return 0 - fi - case "$d" in / | '') return 1 ;; esac - d="$(dirname -- "$d")" - done + local name parent up + name="${1##*/}" + parent="$(dirname -- "$1")" + if [ -f "$parent/contracts/code-pin.yaml" ] && + pin_mounts "$parent/contracts/code-pin.yaml" "$name"; then + printf '%s\n' "$parent" + return 0 + fi + up="$(dirname -- "$parent")" + [ "${up##*/}" = worktrees ] || return 1 + up="$(dirname -- "$up")" + if [ -f "$up/contracts/code-pin.yaml" ] && + pin_mounts "$up/contracts/code-pin.yaml" "$name"; then + printf '%s\n' "$up" + return 0 + fi + return 1 } #: THE COMMIT A SUPERPROJECT RECORDS AT : the index first and HEAD diff --git a/tests/test_install_payload_source.py b/tests/test_install_payload_source.py index 59ec8afe..bfc56c9a 100644 --- a/tests/test_install_payload_source.py +++ b/tests/test_install_payload_source.py @@ -21,9 +21,9 @@ `opensoft/openRepoTools`). It installs EXACTLY as before: a complete checkout offline, byte for byte; a remote install fetching every payload file at the ref it was given, by `gh` and then the raw URL, with no other host and no - `jq`. The only new requests are the two that tell the layouts apart — - `contracts/code-pin.yaml` and `project.yaml`, each answered 404 — asked by - those same two transports. + `jq`. The only new request is the one that tells the layouts apart — + `contracts/code-pin.yaml`, answered 404 — asked by those same two + transports. * ADOPTED — an ASSEMBLY root whose `contracts/code-pin.yaml` pins the CODE leg by repository and commit, with the gitlink at the pinned path agreeing. The payload comes from the code repository at the pinned commit and from @@ -879,11 +879,28 @@ def test_r01_a_single_repository_by_curl_never_needs_api_github_com(tmp_path, ap assert len(world.payload_requests()) == len(PAYLOAD) +def test_r01_a_single_repository_without_jq_fetches_then_names_jq_as_it_always_did(tmp_path): + """No `gh` and no `jq`: today's layout fetches every payload file by the raw + URL and THEN refuses naming `jq` for the hook merge, placing nothing — the + order and the words it always had (#191's review, F1).""" + world = World(tmp_path) + world.single(tag="single") + fakes = network(world, gh=False).split(os.pathsep)[0] + (world.root / "farm-without-jq").mkdir() + farm = path_farm(world.root / "farm-without-jq", ("gh", "curl", "jq")) + home = tmp_path / "home" + result = run_stdin(world, home, f"{fakes}{os.pathsep}{farm}", "--install") + assert_nothing_placed(home, result) + assert result.stderr.startswith("\nREFUSED: `--install` needs `jq` to merge its TWO hook entries"), result.stderr + single_layout_requests_only(world) + assert len(world.payload_requests()) == len(PAYLOAD) + + @NEEDS_JQ def test_r01_a_single_repository_by_gh_asks_for_files_and_nothing_else(tmp_path): """With `gh`: every request is `contents/?ref=` — no - `commits/`, no `git/trees/` — and the only files asked for that are not - payload are, at most, the two that tell the layouts apart.""" + `commits/`, no `git/trees/` — and the only file asked for that is not + payload is, at most, the one that tells the layouts apart.""" world = World(tmp_path) world.single(tag="single") home = tmp_path / "home" @@ -893,8 +910,7 @@ def test_r01_a_single_repository_by_gh_asks_for_files_and_nothing_else(tmp_path) single_layout_requests_only(world) others = {r["target"] for r in world.requests()} - { r["target"] for r in world.payload_requests()} - assert others <= {"repos/opensoft/openRepoTools/contents/contracts/code-pin.yaml?ref=main", - "repos/opensoft/openRepoTools/contents/project.yaml?ref=main"}, others + assert others <= {"repos/opensoft/openRepoTools/contents/contracts/code-pin.yaml?ref=main"}, others assert len(world.payload_requests()) == len(PAYLOAD) @@ -1010,6 +1026,38 @@ def test_r02_a_complete_single_repository_tree_installs_offline_byte_for_byte(tm assert "source:" not in result.stdout +@NEEDS_JQ +@pytest.mark.parametrize("pin", ["valid", "crlf"]) +def test_r02_a_complete_tree_inside_another_projects_assembly_installs_as_before(tmp_path, pin): + """workBenches vendors a complete tree deep inside whatever holds it. A + `contracts/code-pin.yaml` above that tree which does not mount it as a code + leg — in the two layouts the design names, `/` and + `/worktrees//` — is another project's: it is not + read, and the sentinel run is today's to the line (#191's review, note 7).""" + world = World(tmp_path) + triad = tmp_path / "someTriad" + (triad / "contracts").mkdir(parents=True) + text = PIN_TEXT.format(source="opensoft/someTriad-code", path="code", commit="a" * 40, + digest="b" * 64) + (triad / "contracts" / "code-pin.yaml").write_bytes( + (text if pin == "valid" else text.replace("\n", "\r\n")).encode()) + tree = local_tree(triad / "code" / "devBenches" / "base-image" / "files" / "openrepotools", + None) + sentinel = {"OPENREPOTOOLS_REPO": "pinned-by-workBenches-no-fetch", + "OPENREPOTOOLS_REF": "pinned-by-workBenches-no-fetch"} + home = tmp_path / "home" + result = run_file(tree / "openRepoTools", home, network(world), "--install", **sentinel) + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, None, result) + assert world.requests() == [] + plain_home = tmp_path / "plain-home" + plain = run_file(local_tree(tmp_path / "plain", None) / "openRepoTools", plain_home, + network(world), "--install", **sentinel) + assert plain.returncode == 0, plain.stderr + assert (result.stdout.replace(str(home), "") + == plain.stdout.replace(str(plain_home), "")), result.stdout + + @NEEDS_JQ def test_r02_an_adopted_checkout_installs_its_pinned_code_offline(tmp_path): """Run from the code leg mounted in an assembly checkout: the pin and the @@ -1259,15 +1307,23 @@ def test_r08_a_single_repository_without_a_code_pin_is_permitted(tmp_path): @NEEDS_JQ -def test_r08_an_adoption_manifest_without_a_code_pin_is_refused(tmp_path): +def test_r08_an_assembly_root_without_its_code_pin_refuses_as_a_single_repository_would( + tmp_path): + """GitHub's 404 for `contracts/code-pin.yaml` IS the single-repository + answer, and nothing more is asked: a second question on that path (the + manifest, `project.yaml`) would be a second request on today's layout. An + adopter writes the pin and the manifest in ONE commit and `make validate` + refuses one without the other, so a published assembly without its pin is + a broken one — and since an assembly root carries no payload, it refuses + exactly as a single repository missing those files always did.""" world = World(tmp_path) - world.assembly(world.code(), with_pin=False, - extra={p: variant(p, "decoy") for p in PAYLOAD}) + world.assembly(world.code(), with_pin=False) home = tmp_path / "home" result = run_stdin(world, home, network(world), "--install") assert_nothing_placed(home, result) - assert "project.yaml" in result.stderr and "contracts/code-pin.yaml" in result.stderr, result.stderr - assert world.payload_requests() == [] + missing = [n for n in INSTALLABLES if n != "openRepoTools"] + assert result.stderr == "\nREFUSED: " + single_refusal(missing), result.stderr + single_layout_requests_only(world) def malformed(pin: str, how: str) -> str: @@ -1375,8 +1431,12 @@ def test_r08_a_pin_naming_a_commit_its_repository_lacks_is_refused(tmp_path): @NEEDS_JQ -@pytest.mark.parametrize("fault", list(FAULTS)) -@pytest.mark.parametrize("step", list(STEPS)) +@pytest.mark.parametrize("step,fault", [ + # GitHub's own 404 for the pin is not a failure: it is the answer that + # there is no pin, which is a single repository (row 8). Every other + # answer at every step is a failure. + (step, fault) for step in STEPS for fault in FAULTS + if (step, fault) != ("pin", "missing-404")]) def test_r09_an_api_failure_is_a_refusal_never_a_fallback_to_a_single_repository( tmp_path, step, fault): """Every round trip the resolution makes, failing every way a network @@ -1393,11 +1453,7 @@ def test_r09_an_api_failure_is_a_refusal_never_a_fallback_to_a_single_repository assert_nothing_placed(home, result) assert world.payload_requests() == [], world.payload_requests() tried = {r["tool"] for r in world.requests()} - if (step, fault) == ("pin", "missing-404"): - # GitHub's own 404 for the pin is an ANSWER — the path is absent — and - # the adoption manifest beside it makes that a partly adopted layout. - assert "project.yaml" in result.stderr, result.stderr - elif (step, fault) != ("pin", "garbage"): + if (step, fault) != ("pin", "garbage"): # A pin `gh` DID deliver is refused for what it says, not re-fetched. assert tried == {"gh", "curl"}, f"both transports are tried: {tried}" @@ -1469,18 +1525,19 @@ def test_r11_a_payload_file_missing_from_the_pinned_code_changes_nothing(tmp_pat assert f"opensoft/openRepoTools-code at {code}" in result.stderr, result.stderr -#: `--install`'s refusals for a single repository missing a file, in the words +#: `--install`'s refusals for a single repository missing files, in the words #: `openRepoTools` at `c4864ac` printed them, from `$REPO` at `$REF` as given. -def single_refusal(withheld: str) -> str: - if withheld in INSTALLABLES: - return (f"could not fetch {withheld} from opensoft/openRepoTools at main, so NOTHING was installed\n" +def single_refusal(withheld: str | list[str], ref: str = "main") -> str: + names = withheld if isinstance(withheld, list) else [withheld] + if names[0] in INSTALLABLES: + return (f"could not fetch {' '.join(names)} from opensoft/openRepoTools at {ref}, so NOTHING was installed\n" " and nothing already installed was replaced. `--install` places all\n" f" {len(INSTALLABLES)} files or none: a new openRepoTools beside a missing park is\n" " a half-install that reads like a whole one. Both ways were tried:\n" - " gh api repos/opensoft/openRepoTools/contents/?ref=main\n" - " curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoTools/main/\n") + f" gh api repos/opensoft/openRepoTools/contents/?ref={ref}\n" + f" curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoTools/{ref}/\n") return (f"could not fetch one of the {len(SKILLS)} skills ({' '.join(SKILLS)}) or the " - f"{len(COMMANDS)} command file ({' '.join(COMMANDS)}) from opensoft/openRepoTools at main, so\n" + f"{len(COMMANDS)} command file ({' '.join(COMMANDS)}) from opensoft/openRepoTools at {ref}, so\n" " NOTHING was installed and nothing already installed was replaced.\n") @@ -1498,6 +1555,24 @@ def test_r11_a_single_repository_missing_a_file_refuses_in_the_words_it_always_d assert result.stderr == "\nREFUSED: " + single_refusal(withheld), result.stderr +@NEEDS_JQ +@pytest.mark.parametrize("transport", ["gh", "curl"]) +def test_r11_a_single_repository_at_a_ref_it_lacks_refuses_in_the_words_it_always_did( + tmp_path, transport): + """A ref the repository does not have: every file 404s, and the refusal + names every one of them at that ref, as it always did — not a commit + lookup that failed first (#191's review, F1).""" + world = World(tmp_path) + world.single(tag="single") + home = tmp_path / "home" + result = run_stdin(world, home, network(world, gh=transport == "gh"), "--install", + OPENREPOTOOLS_REF="no-such-ref") + assert_nothing_placed(home, result) + assert result.stderr == "\nREFUSED: " + single_refusal(list(INSTALLABLES), "no-such-ref"), \ + result.stderr + single_layout_requests_only(world, "no-such-ref") + + @NEEDS_JQ def test_r11_a_settings_merge_that_cannot_be_computed_changes_nothing(tmp_path): world = World(tmp_path) @@ -1580,7 +1655,8 @@ def shape_repository(world: World) -> tuple[str, str]: return pinned, main -def run_installed_wip(world: World, tmp_path: Path) -> tuple[subprocess.CompletedProcess, Path]: +def run_installed_wip(world: World, tmp_path: Path, + **extra: str) -> tuple[subprocess.CompletedProcess, Path]: home = tmp_path / "home" bin_dir = home / "bin" bin_dir.mkdir(parents=True) @@ -1591,7 +1667,7 @@ def run_installed_wip(world: World, tmp_path: Path) -> tuple[subprocess.Complete "--team", "platform", "--org", "opensoft"], input="", capture_output=True, text=True, check=False, cwd=str(home), env=env_for(home, network(world), PROJECTS_DIR=str(home / "projects"), - OPENREPOTOOLS_BIN_DIR=str(bin_dir))) + OPENREPOTOOLS_BIN_DIR=str(bin_dir), **extra)) return result, home / "projects" / "brettheap-wip" @@ -1631,6 +1707,40 @@ def test_r13_an_installed_wip_init_whose_source_does_not_answer_seeds_from_main_ assert (checkout / "README.md").is_file() +@NEEDS_TEMPLATES +@NEEDS_JQ +def test_r13_an_installed_wip_init_at_a_ref_the_source_lacks_seeds_from_main_as_before( + tmp_path): + """#191's review, F2: `OPENREPOTOOLS_REF` naming a ref the repository does + not have leaves the standard's `main`, as it always did — not a refusal at + step 7 after steps 5 and 6 made and cloned the repository.""" + world = World(tmp_path) + shape_repository(world) + world.single(tag="single") + result, checkout = run_installed_wip(world, tmp_path, OPENREPOTOOLS_REF="no-such-ref") + assert result.returncode == 0, result.stdout + result.stderr + assert "template files from opensoft/openRepoShape @ main" in result.stdout, result.stdout + assert (checkout / "README.md").is_file() + + +@NEEDS_TEMPLATES +@NEEDS_JQ +def test_r13_an_installed_wip_init_never_needs_a_single_repositorys_commit_lookup(tmp_path): + """#191's review, F2: a 502 on `commits/` — an endpoint a single + repository's `wip init` never asked — changes nothing: the dependency pin + is read at the ref as given and the template comes from it.""" + world = World(tmp_path) + pinned, _ = shape_repository(world) + world.single(tag="single", shape=pinned) + world.faults = [{"tool": "any", "match": r"repos/opensoft/openRepoTools/commits/", + "kind": "status", "status": 502}] + world.write_conf() + result, checkout = run_installed_wip(world, tmp_path) + assert result.returncode == 0, result.stdout + result.stderr + assert f"template files from opensoft/openRepoShape @ {pinned}" in result.stdout, result.stdout + assert "DECOY FROM MAIN" not in (checkout / "README.md").read_text(encoding="utf-8") + + @NEEDS_TEMPLATES @NEEDS_JQ def test_r13_an_installed_wip_init_on_a_single_repository_reads_its_pin_as_before(tmp_path): diff --git a/tests/test_openrepotools_command.py b/tests/test_openrepotools_command.py index 913d5b1b..be0b29a7 100644 --- a/tests/test_openrepotools_command.py +++ b/tests/test_openrepotools_command.py @@ -2214,13 +2214,13 @@ def fake_github(tmp_path, served_names) -> dict: routes = "".join( f"*/contents/{name}\\?*) exec cat '{served / name}' ;;\n" for name in served_names) - # THE TWO FILES A REAL GITHUB IS ASKED FOR BEFORE ANY OF THOSE (#186, + # THE ONE FILE A REAL GITHUB IS ASKED FOR BEFORE ANY OF THOSE (#186, # T007), answered the way it answers for a single repository: a 404 for - # the code pin and a 404 for the adoption manifest, which `gh` reports on - # stderr with the status in it. Every assertion below is unchanged; the - # server just answers the two questions the installer now asks first. + # the code pin, which `gh` reports on stderr with the status in it. Every + # assertion below is unchanged; the server just answers the one question + # the installer now asks first. routes += ( - "*/contents/contracts/code-pin.yaml\\?*|*/contents/project.yaml\\?*) " + "*/contents/contracts/code-pin.yaml\\?*) " "printf 'gh: Not Found (HTTP 404)\\n' >&2; exit 1 ;;\n") (fake / "gh").write_text( "#!/bin/sh\n" From 6fa3b4327f23ef671d4f494741b6f4391f9e5498 Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Fri, 9 Oct 2026 20:30:48 +0000 Subject: [PATCH 07/10] Make the adopted path's pin refusal say what answered, and read the documented upstream skip from conftest's text so the test holds under #190's composed mode. Lane 2's (d) in #191's review: on the adopted path only, a pin that the listing shows but that will not come back was refused by naming what was asked, because fetch_from_repo discards stderr. The pin at the resolved commit is now asked by probe_path, which reads the current source and records each transport's answer. The refusal quotes those answers. On today's layout probe_path still asks $REPO at $REF, because the current source is that until an adopted assembly's commit is known. test_r14_the_documented_upstream_skip_is_unchanged read NEEDS_UPSTREAM.kwargs["reason"], which exists only while NEEDS_UPSTREAM is a skipif. Under #190's conftest, in a composed run, it is a fixture that fails instead, and the test raised KeyError. It now checks conftest's text, which names the command in both modes. The defect was this test's assumption, not #190's. Lane: openRepoTools-1 Co-Authored-By: Claude Opus 5.5 --- openRepoTools | 32 +++++++++++++++++----------- tests/test_install_payload_source.py | 24 ++++++++++++++++++++- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/openRepoTools b/openRepoTools index 4279a672..3c06940a 100755 --- a/openRepoTools +++ b/openRepoTools @@ -641,30 +641,32 @@ url_ref() { # — with the two characters a URL path reads otherwise esc # --- the two questions every remote run asks first ---------------------------- -#: IS IN `$REPO` AT `$REF`? Asked exactly the way `fetch_from_repo` asks -#: for a payload file — `gh api …/contents` first, the raw URL second — so the -#: answer needs no host and no tool today's install did not already need. -#: 0: it is there, its bytes in . 3: it is not, which is GitHub's own 404 -#: (`gh` names the status in what it prints; the raw URL's is read with `-w`). -#: 1: no answer either way, and PROBE_WHY says what each transport said. +#: IS IN THE CURRENT SOURCE? Asked exactly the way `fetch_from_repo` +#: asks for a payload file — `gh api …/contents` first, the raw URL second — and +#: of the same source, which is `$REPO` at `$REF` until an adopted assembly's +#: own commit is known, so the question today's layout is asked needs no host +#: and no tool its install did not already need. 0: it is there, its bytes in +#: . 3: it is not, which is GitHub's own 404 (`gh` names the status in +#: what it prints; the raw URL's is read with `-w`). 1: no answer either way. +#: PROBE_WHY says what each transport said, for 3 and for 1. probe_path() { # local path="$1" dest="$2" status="" why="" PROBE_WHY="" if command -v gh >/dev/null 2>&1; then - if gh api "repos/$REPO/contents/$path?ref=$REF" \ + if gh api "repos/${PAYLOAD_REPO:-$REPO}/contents/$path?ref=${PAYLOAD_REV:-$REF}" \ -H 'Accept: application/vnd.github.raw' >"$dest" 2>"$dest.err"; then return 0 fi why="$(api_said gh "$dest.err")" - case "$why" in *'HTTP 404'*) return 3 ;; esac + case "$why" in *'HTTP 404'*) PROBE_WHY="$why"; return 3 ;; esac else why="gh: not installed" fi if command -v curl >/dev/null 2>&1; then - if status="$(curl -sSL -o "$dest" -w '%{http_code}' "https://raw.githubusercontent.com/$REPO/$REF/$path" 2>"$dest.err")"; then + if status="$(curl -sSL -o "$dest" -w '%{http_code}' "https://raw.githubusercontent.com/${PAYLOAD_REPO:-$REPO}/${PAYLOAD_REV:-$REF}/$path" 2>"$dest.err")"; then case "$status" in 200) return 0 ;; - 404) return 3 ;; + 404) PROBE_WHY="$why; curl: HTTP 404"; return 3 ;; esac why="$why; curl: HTTP ${status:-with no status}" else @@ -762,11 +764,15 @@ payload_resolve_remote() { Re-run once it has settled." [ "${entry%% *}" = blob ] || resolve_refuse "contracts/code-pin.yaml in $REPO at $SOURCE_SHA is a ${entry%% *}, not a file." - fetch_from_repo contracts/code-pin.yaml "$pin" || + # ASKED BY `probe_path`, not `fetch_from_repo`, so that a refusal can say + # what answered and not only what was asked (#191's review, lane 2's (d)). + probe_path contracts/code-pin.yaml "$pin" || resolve_refuse "could not fetch contracts/code-pin.yaml from $REPO at $SOURCE_SHA ($REF), which - the listing shows is there. Both were asked: + the listing shows is there. What answered: + $PROBE_WHY + Both were asked: gh api repos/$REPO/contents/contracts/code-pin.yaml?ref=$SOURCE_SHA - curl -fsSL https://raw.githubusercontent.com/$REPO/$SOURCE_SHA/contracts/code-pin.yaml" + curl -sSL https://raw.githubusercontent.com/$REPO/$SOURCE_SHA/contracts/code-pin.yaml" pin_parse "$pin" || resolve_refuse "contracts/code-pin.yaml in $REPO at $SOURCE_SHA ($REF) is not a code pin this installer can follow: $PIN_WHY. A pin that is present and wrong is refused rather than read around." diff --git a/tests/test_install_payload_source.py b/tests/test_install_payload_source.py index bfc56c9a..58e43dea 100644 --- a/tests/test_install_payload_source.py +++ b/tests/test_install_payload_source.py @@ -1458,6 +1458,26 @@ def test_r09_an_api_failure_is_a_refusal_never_a_fallback_to_a_single_repository assert tried == {"gh", "curl"}, f"both transports are tried: {tried}" +@NEEDS_JQ +def test_r09_a_pin_the_listing_shows_but_that_will_not_come_back_is_refused_saying_what_answered( + tmp_path): + """The adopted path's pin fetch at the resolved commit: when it fails, the + refusal says what each transport ANSWERED, not only what was asked (#191's + review, lane 2's (d)).""" + world = World(tmp_path) + code = world.code() + sha = world.assembly(code, extra={p: variant(p, "decoy") for p in PAYLOAD}) + world.faults = [{"tool": "any", "kind": "status", "status": 503, + "match": rf"code-pin\.yaml\?ref={sha}|/{sha}/contracts/code-pin\.yaml"}] + world.write_conf() + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install") + assert_nothing_placed(home, result) + assert world.payload_requests() == [] + assert "What answered:" in result.stderr, result.stderr + assert "gh: Error (HTTP 503)" in result.stderr and "curl: HTTP 503" in result.stderr, result.stderr + + # ========================================================================= # ROW 10 — changed local gitlink / changed payload / incomplete local payload # ========================================================================= @@ -1803,8 +1823,10 @@ def test_r14_the_documented_upstream_skip_is_unchanged(): """Without the submodule the command tests SKIP, naming the one command that fixes it (AGENTS.md, "Testing your changes"); conftest says so.""" import conftest + # Read from conftest's text: in a composed run `NEEDS_UPSTREAM` is a + # fixture that FAILS instead (#190), and the standalone skip is still there. assert "git submodule update --init upstream/openRepoShape" in \ - conftest.NEEDS_UPSTREAM.kwargs["reason"] + Path(conftest.__file__).read_text(encoding="utf-8") @NEEDS_COMPOSED From 6568f08129c1c561903303f14fec9302c0e6c60a Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Fri, 9 Oct 2026 20:32:50 +0000 Subject: [PATCH 08/10] Test that the assembly's root entry point refuses a pin its gitlink disagrees with before it fetches or runs any implementation. Lane 3's review of the T007 entry-point patch (T3) found that on the remote path the root fetched and ran whatever implementation the pin selected, and left the lockstep check to that code. A stale or rolled-back pin then ran main's installer and refused with a misleading message. The patch on 004-migrate-to-triad-installer now reads the gitlink at the pin's submodule_path in the resolved commit, and refuses a mismatch before any fetch. This composed test holds it to that, by gh and by curl: both commits are named, "nothing ran", and the code repository is never asked. Like every composed test, it skips in standalone code and fails rather than skips under OPENREPOTOOLS_COMPOSED=1. Lane: openRepoTools-1 Co-Authored-By: Claude Opus 5.5 --- tests/test_install_payload_source.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_install_payload_source.py b/tests/test_install_payload_source.py index 58e43dea..286c02e8 100644 --- a/tests/test_install_payload_source.py +++ b/tests/test_install_payload_source.py @@ -1326,6 +1326,32 @@ def test_r08_an_assembly_root_without_its_code_pin_refuses_as_a_single_repositor single_layout_requests_only(world) +@NEEDS_COMPOSED +@NEEDS_JQ +@pytest.mark.parametrize("transport", ["gh", "curl"]) +def test_r08_composed_the_root_entry_point_refuses_a_pin_its_gitlink_disagrees_with( + tmp_path, transport): + """The assembly's root entry point checks the lockstep rule ITSELF, before + it fetches or runs any implementation: the gitlink at the pin's + `submodule_path`, in the commit it resolved, must be the pinned commit. + A stale or rolled-back pin is refused there, naming both commits, and the + code repository is never asked for anything (lane 3's review of the T007 + patch, T3).""" + require_composed() + world = World(tmp_path) + old = world.code(tag="old") + new = world.code(tag="new") + world.assembly(new, gitlink=old, root_entry=(ASSEMBLY / "openRepoTools").read_bytes()) + home = tmp_path / "home" + result = run_stdin(world, home, network(world, gh=transport == "gh"), "--install", + script=ASSEMBLY / "openRepoTools") + assert_nothing_placed(home, result) + assert old in result.stderr and new in result.stderr, result.stderr + assert "nothing ran" in result.stderr, result.stderr + assert not [r for r in world.requests() if "openRepoTools-code" in r["target"]], \ + world.requests() + + def malformed(pin: str, how: str) -> str: sha = re.search(r'commit: "([0-9a-f]{40})"', pin).group(1) return { From cddd1f7f83c798f9da0e9e268fb6f5f6fafbb00f Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Sat, 10 Oct 2026 23:49:31 +0000 Subject: [PATCH 09/10] Install exactly the bytes that were checked from a mounted code leg, and read a pin's commented submodule_path the way pin_parse reads it. Copilot's first round on #191 raised two points that this commit fixes. First, the mounted-checkout path hashed each payload file in the checkout and copied it later, in another pass. An edit in between could put bytes in place that were never checked against the pinned commit. Each file is now copied into the run's work directory first, the copy is what gets hashed, and collect_commands and collect_skills read only that snapshot. Second, pin_mounts compared the raw remainder of the submodule_path line. A valid pin that comments the value, such as "submodule_path: code # the mounted leg", was then not recognised as this checkout's assembly, and the tree was installed as a plain complete tree with none of the pin, gitlink or byte checks. It now reads the value with pin_scalar first, and falls back to the lenient read only where that grammar refuses the value. The new test fails at 6568f08 and passes here. Lane: openRepoTools-1 Co-Authored-By: Claude Opus 5.5 --- openRepoTools | 27 +++++++++++++++++++++------ tests/test_install_payload_source.py | 22 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/openRepoTools b/openRepoTools index 3c06940a..7aa697ce 100755 --- a/openRepoTools +++ b/openRepoTools @@ -805,9 +805,11 @@ payload_where() { # the source collected from, for a refusal to name # --- the local source --------------------------------------------------------- #: DOES MOUNT ITS CODE LEG AT ? Its `submodule_path:` line, read -#: leniently — the strict read is `pin_parse`'s, once the pin is known to be -#: this checkout's — so that another project's pin, however it is written, is -#: only ever compared with this directory's name. +#: with `pin_scalar`'s grammar (quotes, a trailing `# comment`) and, where that +#: grammar refuses the value, leniently — the strict read is `pin_parse`'s, once +#: the pin is known to be this checkout's — so that another project's pin, +#: however it is written, is only ever compared with this directory's name, and +#: a valid pin of this checkout's is never missed for the way it is written. pin_mounts() { # local line value while IFS= read -r line || [ -n "$line" ]; do @@ -815,6 +817,9 @@ pin_mounts() { # submodule_path:*) value="${line#submodule_path:}" value="${value%"$ORT_CR"}" + if pin_scalar "$value" && [ -n "$PIN_SCALAR" ] && [ "$PIN_SCALAR" = "$2" ]; then + return 0 + fi value="${value#"${value%%[! $ORT_TAB]*}"}" value="${value%"${value##*[! $ORT_TAB]}"}" value="${value#[\"\']}" @@ -874,7 +879,7 @@ recorded_gitlink() { # } payload_local_adopted() { # - local root="$1" here="$2" pin="$1/contracts/code-pin.yaml" me mount link name blob + local root="$1" here="$2" pin="$1/contracts/code-pin.yaml" me mount link name blob snap local bad="" missing="" pin_parse "$pin" || die "$pin is not a code pin this installer can follow: $PIN_WHY. It is the pin of the assembly this checkout sits in, and a pin that is present @@ -900,12 +905,22 @@ payload_local_adopted() { # /dev/null)" || { bad="${bad:+$bad }$name"; continue; } - [ "$(git -C "$here" hash-object --no-filters -- "$name" 2>/dev/null)" = "$blob" ] || + mkdir -p -- "$snap/$(dirname -- "$name")" && + cp -p -- "$here/$name" "$snap/$name" || + { bad="${bad:+$bad }$name"; continue; } + [ "$(git -C "$here" hash-object --no-filters -- "$snap/$name" 2>/dev/null)" = "$blob" ] || bad="${bad:+$bad }$name" done [ -z "$missing" ] || die "the code mounted at $root/$PIN_SUBMODULE_PATH is missing $missing, @@ -921,7 +936,7 @@ payload_local_adopted() { # /worktrees//code), which installs that checkout's bytes and says so." - PAYLOAD_MODE=local PAYLOAD_DIR="$here" SOURCE_LAYOUT=adopted + PAYLOAD_MODE=local PAYLOAD_DIR="$snap" SOURCE_LAYOUT=adopted say "openRepoTools: source: the code mounted at $root/$PIN_SUBMODULE_PATH, $PIN_SOURCE_REPOSITORY at $PIN_COMMIT as $root pins it; every payload file was checked against that commit and nothing fetched" } diff --git a/tests/test_install_payload_source.py b/tests/test_install_payload_source.py index 286c02e8..b42a00be 100644 --- a/tests/test_install_payload_source.py +++ b/tests/test_install_payload_source.py @@ -1076,6 +1076,28 @@ def test_r02_an_adopted_checkout_installs_its_pinned_code_offline(tmp_path): assert "nothing fetched" in result.stdout +@NEEDS_JQ +def test_r02_an_adopted_checkout_whose_pin_comments_its_path_is_still_its_assembly(tmp_path): + """A pin `pin_parse` accepts — `submodule_path: code # the mounted leg` — + is recognised as this checkout's assembly, so the pin, the gitlink and the + bytes are all checked, rather than the tree being copied as a plain + complete tree with none of them checked (#191, Copilot's first round).""" + world = World(tmp_path) + assembly, code, head = local_assembly(world) + pin = assembly / "contracts" / "code-pin.yaml" + text = pin.read_text(encoding="utf-8") + assert "submodule_path: code\n" in text + pin.write_text(text.replace("submodule_path: code\n", + "submodule_path: code # the mounted leg\n"), encoding="utf-8") + home = tmp_path / "home" + result = run_file(code / "openRepoTools", home, network(world), "--install") + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "local", result) + assert world.requests() == [] + assert (f"openRepoTools: source: the code mounted at {assembly}/code, " + f"opensoft/openRepoTools-code at {head}") in result.stdout, result.stdout + + # ========================================================================= # ROW 3 — an explicit local code feature # ========================================================================= From 824698356c4a62cdfc00e565e1ad98c7bf6414b0 Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Sun, 11 Oct 2026 00:16:14 +0000 Subject: [PATCH 10/10] Test that a branch which moves right after the layout question is read whole at the one commit it resolves to. Copilot's second round on #191 read the PR body's "a ref that moved between the probe and that commit is refused" as a rule that the code broke, because a move from one adopted commit to another installs the second. The code is right and the body overclaimed. The probe at $REF only decides the layout, and its bytes are never installed. Everything is then read at the commit $REF resolves to, so a move to another adopted commit installs that commit whole, from the code it pins. A move to a commit with no pin is refused as a ref that moved. The new test moves main straight after the first request and holds both outcomes, with every payload request naming the second commit's code. Lane: openRepoTools-1 Co-Authored-By: Claude Opus 5.5 --- tests/test_install_payload_source.py | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/test_install_payload_source.py b/tests/test_install_payload_source.py index b42a00be..97bd7bb0 100644 --- a/tests/test_install_payload_source.py +++ b/tests/test_install_payload_source.py @@ -1312,6 +1312,42 @@ def test_r07_a_branch_that_moves_between_downloads_never_mixes_versions(tmp_path "main").strip() == second, "the branch never moved: the test proved nothing" +@NEEDS_JQ +@pytest.mark.parametrize("to", ["adopted", "single"]) +def test_r07_a_branch_that_moves_right_after_the_layout_question_is_read_at_one_commit( + tmp_path, to): + """The probe asks `contracts/code-pin.yaml` at the moving ref, and the ref + moves before it is resolved (#191, Copilot's second round). The probe only + decides the layout, and its bytes are never used. The commit the ref + resolves to is read whole: another adopted commit is installed entirely + from the code IT pins, never a mix. A commit that no longer carries a pin + is refused as a ref that moved between the two reads.""" + world = World(tmp_path) + world.assembly(world.code(tag="first")) + if to == "adopted": + second_code = world.code(tag="second", branch=None) + second = world.assembly(second_code, branch=None) + else: + second = world.single(tag="second", branch=None) + world.moves = [{"after": 1, "repo": "opensoft/openRepoTools", + "ref": "refs/heads/main", "to": second}] + world.write_conf() + home = tmp_path / "home" + result = run_stdin(world, home, network(world), "--install") + assert world.requests()[0]["target"].endswith("contents/contracts/code-pin.yaml?ref=main") + assert git("--git-dir", str(world.repo("opensoft/openRepoTools")), "rev-parse", + "main").strip() == second, "the branch never moved: the test proved nothing" + if to == "adopted": + assert result.returncode == 0, result.stdout + result.stderr + assert_installed(home, "second", result) + for r in world.payload_requests(): + assert second_code in r["target"], r + else: + assert_nothing_placed(home, result) + assert "the ref moved" in result.stderr, result.stderr + assert world.payload_requests() == [] + + # ========================================================================= # ROW 8 — missing pin in a valid single repository / partial adopted layout # =========================================================================