From 9605d40ca624fc3cb20ce1053c12b2cbd61233d3 Mon Sep 17 00:00:00 2001
From: Brett Heap
Date: Mon, 12 Oct 2026 19:43:17 +0000
Subject: [PATCH 1/2] README.md, docs/handbook.html and the comments in
bump-leg.py and update-shape.py say when a project's own gate reaches a
network: `validate-pins.py` asks `gh api` for a neutral product's pin with
the operator's `gh` credentials, and `make bootstrap`, `bump-leg.py` and
`update-shape.py apply` run it in the operator's environment, so none of them
says any more that a project runs its own gate offline or that `apply` is
otherwise offline (#411)
`templates/assembly-root/scripts/validate-pins.py` looks for a declared neutral product's tree in `--pin-source`, then `SHAPE_PIN_SOURCE_`, then a checkout beside the assembly root, and only then runs `gh api repos///git/trees/?recursive=1`; with no `gh`, no login or no network that is a `skip` line and exit 0. `make bootstrap`, `bump-leg.py` and `update-shape.py apply` start it with their own environment and none takes `--pin-source`, so for a project declaring `neutral_product_pins:` with no local checkout of the product each makes that call with whatever `gh` is logged in as. Re-measured on dd8d0ea with a `gh` stub first on PATH, GH_TOKEN=bogus and an empty GH_CONFIG_DIR: the same call, with GH_TOKEN, from all three; no call with a sibling checkout, with `SHAPE_PIN_SOURCE_OPENGLASS` exported, or for a project declaring no neutral product. Nothing in the call is new. Only the words claimed otherwise.
- README.md, in the paragraph that documents `validate-pins.py`'s referent recheck: the call is `gh api` with the operator's `gh` credentials, a skip is exit 0, the three tools run the gate in the operator's environment and so make it too, and none takes `--pin-source`.
- README.md, "Keeping a project's shape current": "a project runs its own gate offline" is "a project runs its own gate, which never consults this standard", naming the one possible network read; the paragraph's 2026-09-03 story loses its list of manual steps, which the `apply` paragraph below already gives.
- README.md, the `make bootstrap` paragraph: "It adds no network call" is "That line adds no network call", said of the upstream-moved notice, with a clause that the pins gate bootstrap runs may ask `gh api`.
- docs/handbook.html: the "offline" sentence in "Keeping a project's shape current" and the bootstrap sentence get the same words; the legs section gains the referent-recheck paragraph the page lacked; the footer's blob is the new README.md blob.
- scripts/bump-leg.py, `VALIDATORS`' comment: drops "offline" and says the child gets this process's environment and what that costs.
- update-shape.py: `open_upstream`'s docstring says the bare clone is the one place the tool fetches the shape, not the only place `apply` touches a network, and names the other two; its refusal's remediation says "the one step that fetches the shape from a network" and "nothing after it fetches the shape again" where it said "needs a network" and "everything after it is offline"; `tracking_branch_of` no longer says `apply` is otherwise offline.
- tests/test_update_shape_pr_base.py: its module docstring's "`apply` is otherwise offline" is "the base comes from the manifest".
Unchanged: every tool's behaviour, exit codes and printed lines, except the one remediation sentence above; `--help` text (docs/cli.md is not regenerated); README.md's line count (1419, the cap); AGENTS.md; every file under `templates/`, so no digest a project records moves. `bootstrap.py`'s comment that "NO NETWORK CALL IS ADDED HERE" is about its upstream notice, which is true, so the pinned copy is left alone.
Left out: a test that pins the callers' `gh api` call (it cannot fail on main); starting the three children offline as #357 did the doctor's; passing a known source to them.
Lane: openreposhape-3 (openRepoShape-3)
Co-Authored-By: Claude Sonnet 5.5
---
README.md | 40 +++++++++++++++---------------
docs/handbook.html | 34 +++++++++++++++++++------
scripts/bump-leg.py | 10 ++++++--
tests/test_update_shape_pr_base.py | 2 +-
update-shape.py | 21 ++++++++++------
5 files changed, 68 insertions(+), 39 deletions(-)
diff --git a/README.md b/README.md
index e8da9381..d25081fd 100644
--- a/README.md
+++ b/README.md
@@ -693,17 +693,17 @@ link that broke. `LedgerxWallet` -> `openXwallet` -> `openWallet` is the same
shape, and `openWallet` need not exist anywhere for that to be the answer.
**`validate-pins.py` rechecks the referent too, not only the declaration.**
-Every `neutral_product_pins:` entry's `contracts/-pin.yaml`
-has its `commit`, `revision_kind` and `sorted-ls-tree-r-v1` digest recomputed
-exactly the way `scaffold-project.py --pin` computed them: OFFLINE from a
-local checkout when one can be found — `--pin-source [PRODUCT=]`,
-`SHAPE_PIN_SOURCE_` in the environment, or a checkout sitting beside
-the assembly root and named for the pinned repository — else from `gh api`
-when `gh` can read it. Neither answering is a named SKIP, never a finding and
-never a failure: a project with no local checkout of its referent and no
-network is not thereby lying about its descent. A digest that recomputes
-differently, or a pin file the declaration names but that is not there, is
-reported as drift like every other pin in this file.
+Each `neutral_product_pins:` entry's `contracts/-pin.yaml`
+has its `commit`, `revision_kind` and `sorted-ls-tree-r-v1` digest recomputed as
+`scaffold-project.py --pin` computed them: OFFLINE from a local checkout when
+one is found — `--pin-source [PRODUCT=]`, `SHAPE_PIN_SOURCE_` in
+the environment, or a checkout beside the assembly root named for the pinned
+repository — else by `gh api`, with the operator's `gh` credentials, when `gh`
+can read it. Neither answering is a named SKIP, exit 0, never a finding or a
+failure: no checkout and no network does not make a descent a lie. `make
+bootstrap`, `bump-leg.py` and `update-shape.py apply` run this gate in the
+operator's environment, so each makes that call too; none takes `--pin-source`.
+A differing digest or missing pin file is drift, as everywhere.
### A neutral product may elect the shape and be its own assembly root
@@ -1022,12 +1022,12 @@ the rest is the project's own, which an upstream fix must never overwrite.
## Keeping a project's shape current
-The copies are the trade: a project runs its own gate offline, and an upstream
-FIX to a copied file reaches it never. On 2026-09-03 that bill came due — one
-change to the assembly-root `validate.yml`, and both projects carrying the
-shape were updated BY HAND: re-copy, recompute the file's `sha256` row, move
-`commit` and `digests.tree_sha256`, mirror both into `project.yaml`, re-run the
-validators. `update-shape.py` is that, as one command:
+The copies are the trade: a project runs its own gate, which never consults this
+standard (its one possible network read is `validate-pins.py`'s `gh api` recheck
+of a neutral product's pin, above), and an upstream FIX to a copied file reaches
+it never. On 2026-09-03 that bill came due — one change to the assembly-root
+`validate.yml`, and both projects with the shape were updated BY HAND: re-copy,
+re-pin, re-validate. `update-shape.py` is that, as one command:
```sh
./update-shape.py check --root ../MedxEHR # writes nothing
@@ -1089,9 +1089,9 @@ by every `check` until it is taken or a file arrives at that path.
`owner/repo` it bare-clones itself; it defaults to the repository the pin
names. `make bootstrap` prints one line when the upstream has moved past the
pin AND this machine can already tell offline — `SHAPE_UPSTREAM_PATH` points at
-a clone, or a `.shape-upstream-tip` file carries a commit. It adds no network
-call: a bootstrap that paused to ask GitHub a question would have made the
-shape a dependency again.
+a clone, or a `.shape-upstream-tip` file carries a commit. That line adds no
+network call: a bootstrap that paused to ask GitHub a question would have
+made the shape a dependency again. (Its pins gate may ask `gh api`; see above.)
### Is this repository compliant? `openRepoShape --doctor`
diff --git a/docs/handbook.html b/docs/handbook.html
index 5eddf825..722654a9 100644
--- a/docs/handbook.html
+++ b/docs/handbook.html
@@ -1107,6 +1107,19 @@ The three legs, and naming
project’s own project.yaml, so classification stays OFFLINE and never asks
GitHub whether open<Product> exists. The referent may also be reached
through a RECORDED chain of pins.
+ validate-pins.py rechecks the referent too, not only the declaration. Each
+ neutral_product_pins: entry’s
+ contracts/<product lowercased>-pin.yaml has its commit,
+ revision_kind and sorted-ls-tree-r-v1 digest recomputed as
+ scaffold-project.py --pin computed them: OFFLINE from a local checkout when one
+ is found — --pin-source [PRODUCT=]<path>,
+ SHAPE_PIN_SOURCE_<PRODUCT> in the environment, or a checkout beside the
+ assembly root named for the pinned repository — else by gh api, with the
+ operator’s gh credentials, when gh can read it. Neither
+ answering is a named SKIP, exit 0, never a finding or a failure: no checkout and no network
+ does not make a descent a lie. make bootstrap, bump-leg.py and
+ update-shape.py apply run this gate in the operator’s environment, so each
+ makes that call too; none takes --pin-source.
A NEUTRAL product may elect the shape and be its own assembly root as well:
openDox mounting openDox-spec and openDox-code is a
fact about LAYOUT and not a claim about neutrality, because electing confers nothing.
@@ -1357,9 +1370,11 @@
Bootstrap is COPIED into the project, not fetched
Reference
Keeping a project’s shape current
- The copies are the trade: a project runs its own gate offline, and an upstream FIX to a
- copied file reaches it never. update-shape.py is the re-sync, as one
- command.
+ The copies are the trade: a project runs its own gate, which never consults this standard
+ (its one possible network read is validate-pins.py’s gh api
+ recheck of a neutral product’s pin, described with the legs), and an
+ upstream FIX to a copied file reaches it never. update-shape.py is the re-sync,
+ as one command.
./update-shape.py check --root ../MedxEHR # writes nothing
./update-shape.py apply --root ../MedxEHR --at <commit> --yes \
--branch shape/update-<sha> # then open a pull request
@@ -1394,8 +1409,9 @@ Keeping a project’s shape current
bare-clones itself; it defaults to the repository the pin names. make bootstrap
prints one line when the upstream has moved past the pin AND this machine can already tell
offline — SHAPE_UPSTREAM_PATH points at a clone, or a
- .shape-upstream-tip file carries a commit. It adds no network call: a bootstrap
- that paused to ask GitHub a question would have made the shape a dependency again.
+ .shape-upstream-tip file carries a commit. That line adds no network call: a
+ bootstrap that paused to ask GitHub a question would have made the shape a dependency again.
+ (Its pins gate may ask gh api; see the legs.)
Full text — keeping the shape current
@@ -1790,9 +1806,11 @@ For AI assistants
refusing an untracked or ignored one it would rewrite, added (#207); the assistant rules
saying that some scaffold refusals name a value to change, added (#288);
bump-leg.py refusing a workflow file or the leg’s pin that is not UTF-8, by
- name, added (#289) — a page cannot name the commit that contains it, and the blob below
- is the fact a test checks.
- README.md blob e8da93817b0c1a195300800373a2895c0e0280e5. Every command, flag, exit code
+ name, added (#289); the one network read a project’s own gate can make, the
+ gh api recheck of a neutral product’s pin, said where this page and the
+ README had said “offline”, added (#411) — a page cannot name the commit
+ that contains it, and the blob below is the fact a test checks.
+ README.md blob d25081fd5675d608aa9f362cb901b8db819481ef. Every command, flag, exit code
and file name on this page is quoted from that README and from AGENTS.md.
diff --git a/scripts/bump-leg.py b/scripts/bump-leg.py
index d00af077..6ef97022 100755
--- a/scripts/bump-leg.py
+++ b/scripts/bump-leg.py
@@ -106,8 +106,14 @@
MANIFEST = "project.yaml"
#: The root's OWN validators, run after the rewrite and before the commit.
-#: Copies, not this checkout's: a project runs its own gate offline, and the
-#: only opinion that matters here is the one its pull request will report.
+#: Copies, not this checkout's: a project runs its own gate, and the only
+#: opinion that matters here is the one its pull request will report.
+#: NOT NETWORK-FREE, and the child gets this process's environment (#411): for
+#: a project declaring `neutral_product_pins:` whose product has no local
+#: checkout (a sibling of the root or `SHAPE_PIN_SOURCE_`; this tool
+#: takes no `--pin-source`), `validate-pins.py` asks `gh api` for the pinned
+#: tree with the operator's `gh` credentials. Without `gh`, a login or a
+#: network that is its `skip` line, exit 0, and the bump goes on.
VALIDATORS = ("scripts/validate-pins.py", "scripts/validate-manifest.py")
#: A local-path remote is a `file://` fetch, which git has refused for
diff --git a/tests/test_update_shape_pr_base.py b/tests/test_update_shape_pr_base.py
index 90d309d2..c895b4b1 100644
--- a/tests/test_update_shape_pr_base.py
+++ b/tests/test_update_shape_pr_base.py
@@ -16,7 +16,7 @@
manifest records NO tracking branch at all — which `validate-manifest.py`
accepts and which every other tool (`bootstrap.py`, `bump-leg.py`,
`family.py`, `commit_on_branch`'s own refusal) reads as `main`. The remote's
-HEAD is never consulted for that last case: `apply` is otherwise offline.
+HEAD is never consulted for that last case: the base comes from the manifest.
THE UPSTREAM IS A CLONE OF THIS REPOSITORY, the project is scaffolded out of
it into bare repositories in a temporary directory, and `gh` is stubbed at the
diff --git a/update-shape.py b/update-shape.py
index 3dae6df0..e1ffdfd0 100755
--- a/update-shape.py
+++ b/update-shape.py
@@ -323,8 +323,14 @@ def open_upstream(spec: str) -> Upstream:
A PATH IS USED IN PLACE AND NOTHING IS FETCHED. That is deliberate: it is
what lets the tests run with no network, and it is also the honest answer
for an organisation whose fork is the upstream. `owner/repo` is bare-cloned
- into a temporary directory, which is the one place this tool touches a
- network, and it refuses loudly rather than degrading to a guess.
+ into a temporary directory, which is the one place this tool fetches the
+ shape, and it refuses loudly rather than degrading to a guess. It is not
+ the only place `apply` can touch a network (#411): the project's own
+ `validate-pins.py` runs in this process's environment, so for a project
+ declaring `neutral_product_pins:` whose product has no local checkout it
+ asks `gh api` for the pinned tree with the operator's `gh` credentials
+ (`skip` line and exit 0 without `gh`, a login or a network), and `--push`
+ and `--pr` push the branch and open its pull request.
"""
candidate = Path(spec).expanduser()
if candidate.exists():
@@ -356,9 +362,9 @@ def open_upstream(spec: str) -> Upstream:
"update-upstream-unresolvable",
f"could not clone {url} (git exit {proc.returncode}): "
f"{(proc.stderr or '').strip()}",
- "Remediation: this is the one step that needs a network. Clone "
- "the shape once by hand and pass `--upstream ` instead; "
- "everything after it is offline.")
+ "Remediation: this is the one step that fetches the shape from a "
+ "network. Clone the shape once by hand and pass `--upstream "
+ "` instead; nothing after it fetches the shape again.")
return Upstream(temporary / "shape.git", spec, temporary=temporary)
@@ -1731,9 +1737,8 @@ def tracking_branch_of(manifest: dict | None) -> str:
`main` where the manifest records none: the default `bootstrap.py`,
`bump-leg.py` and `family.py` read the field with. The remote's own HEAD
- is never asked: `apply` is otherwise offline, and a base taken from the
- network could disagree with the one the `--branch` refusal and `--pr`
- read from the manifest.
+ is never asked: a base taken from the network could disagree with the
+ one the `--branch` refusal and `--pr` read from the manifest.
"""
return str((manifest or {}).get("tracking_branch") or "main")
From 892ec7d2450ca06454d0f106b74f1730bd21f727 Mon Sep 17 00:00:00 2001
From: Brett Heap
Date: Mon, 12 Oct 2026 20:04:02 +0000
Subject: [PATCH 2/2] README.md, docs/handbook.html and `bump-leg.py
--local-remote-dir`'s help say that the validators, not the whole gate, make
the one possible network read, that a checkout found beside the root that
cannot answer is the finding `neutral-pin-source-unreadable` and not a skip,
and that the test path's leg needs no network while `validate-pins.py` may
still ask `gh api`, so none of them claims more than the code does (#411)
The review of the first commit found three words that said more than they could.
- "its one possible network read" was said of the project's gate. The same paragraph names `validate.yml`, which is part of the gate and reads the network in the root checkout, the App-token mint and `git submodule update` of the legs, none of it through `gh api`. It is now "its validators' one possible network read" in README.md and in the handbook's paragraph, and the handbook's footer clause says "validators" too. The footer's README blob is the new `git hash-object README.md`.
- "Neither answering is a named SKIP, exit 0, never a finding or a failure" held only when no source is found. `../` is taken whenever it has a `.git`, and one that lacks the pinned commit is the finding `neutral-pin-source-unreadable`, exit 1 (`bootstrap.py` prints `bootstrap FAILED: pins (exit 1)`; `bump-leg.py` and `apply` refuse), with no `gh` call. README.md and the handbook now say "Finding neither is a named SKIP, exit 0", and name that finding. To keep README.md at 1419 lines, the paragraph drops the sentence that a differing digest or a missing pin file is drift, which every validator finding already is.
- `bump-leg.py --local-remote-dir` said "(the TEST path; no network)", yet the project's own `validate-pins.py` runs in the same process's environment and may still call `gh api`. The help now says "the leg needs no network, but the project's own validate-pins.py may still ask `gh api`", and docs/cli.md is regenerated with `scripts/render-cli-reference.py`.
Unchanged: every tool's behaviour, exit codes and printed lines; README.md's line count (1419, the cap); AGENTS.md (at its cap; the nit that it never says the call exists stays with the issue author); every file under `templates/`.
Lane: openreposhape-3 (openRepoShape-3)
Co-Authored-By: Claude Sonnet 5.5
---
README.md | 20 ++++++++++----------
docs/cli.md | 4 +++-
docs/handbook.html | 23 ++++++++++++-----------
scripts/bump-leg.py | 5 +++--
4 files changed, 28 insertions(+), 24 deletions(-)
diff --git a/README.md b/README.md
index d25081fd..4ce951da 100644
--- a/README.md
+++ b/README.md
@@ -699,11 +699,11 @@ has its `commit`, `revision_kind` and `sorted-ls-tree-r-v1` digest recomputed as
one is found — `--pin-source [PRODUCT=]`, `SHAPE_PIN_SOURCE_` in
the environment, or a checkout beside the assembly root named for the pinned
repository — else by `gh api`, with the operator's `gh` credentials, when `gh`
-can read it. Neither answering is a named SKIP, exit 0, never a finding or a
-failure: no checkout and no network does not make a descent a lie. `make
-bootstrap`, `bump-leg.py` and `update-shape.py apply` run this gate in the
-operator's environment, so each makes that call too; none takes `--pin-source`.
-A differing digest or missing pin file is drift, as everywhere.
+can read it. Finding neither is a named SKIP, exit 0: no checkout and no network
+does not make a descent a lie. A checkout that cannot answer is the finding
+`neutral-pin-source-unreadable`. `make bootstrap`, `bump-leg.py` and
+`update-shape.py apply` run this validator in the operator's environment, so
+each makes that call too; none takes `--pin-source`.
### A neutral product may elect the shape and be its own assembly root
@@ -1023,11 +1023,11 @@ the rest is the project's own, which an upstream fix must never overwrite.
## Keeping a project's shape current
The copies are the trade: a project runs its own gate, which never consults this
-standard (its one possible network read is `validate-pins.py`'s `gh api` recheck
-of a neutral product's pin, above), and an upstream FIX to a copied file reaches
-it never. On 2026-09-03 that bill came due — one change to the assembly-root
-`validate.yml`, and both projects with the shape were updated BY HAND: re-copy,
-re-pin, re-validate. `update-shape.py` is that, as one command:
+standard (its validators' one possible network read is `validate-pins.py`'s `gh
+api` recheck of a neutral product's pin, above), and an upstream FIX to a copied
+file reaches it never. On 2026-09-03 that bill came due — one change to the
+assembly-root `validate.yml`, and both projects with the shape were updated BY
+HAND: re-copy, re-pin, re-validate. `update-shape.py` is that, as one command:
```sh
./update-shape.py check --root ../MedxEHR # writes nothing
diff --git a/docs/cli.md b/docs/cli.md
index 94b40038..a6a26ba1 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -579,7 +579,9 @@ options:
writes; repeatable, kept in the order given
--local-remote-dir LOCAL_REMOTE_DIR
resolve the leg's remote to a bare repository here
- instead of its origin (the TEST path; no network)
+ instead of its origin (the TEST path; the leg needs no
+ network, but the project's own validate-pins.py may
+ still ask `gh api`)
--dry-run print what would move and change nothing
```
diff --git a/docs/handbook.html b/docs/handbook.html
index 722654a9..ec426119 100644
--- a/docs/handbook.html
+++ b/docs/handbook.html
@@ -1115,11 +1115,12 @@ The three legs, and naming
is found — --pin-source [PRODUCT=]<path>,
SHAPE_PIN_SOURCE_<PRODUCT> in the environment, or a checkout beside the
assembly root named for the pinned repository — else by gh api, with the
- operator’s gh credentials, when gh can read it. Neither
- answering is a named SKIP, exit 0, never a finding or a failure: no checkout and no network
- does not make a descent a lie. make bootstrap, bump-leg.py and
- update-shape.py apply run this gate in the operator’s environment, so each
- makes that call too; none takes --pin-source.
+ operator’s gh credentials, when gh can read it. Finding
+ neither is a named SKIP, exit 0: no checkout and no network does not make a descent a lie. A
+ checkout that cannot answer is the finding neutral-pin-source-unreadable.
+ make bootstrap, bump-leg.py and update-shape.py apply
+ run this validator in the operator’s environment, so each makes that call too; none
+ takes --pin-source.
A NEUTRAL product may elect the shape and be its own assembly root as well:
openDox mounting openDox-spec and openDox-code is a
fact about LAYOUT and not a claim about neutrality, because electing confers nothing.
@@ -1371,10 +1372,10 @@
Bootstrap is COPIED into the project, not fetched
Reference
Keeping a project’s shape current
The copies are the trade: a project runs its own gate, which never consults this standard
- (its one possible network read is validate-pins.py’s gh api
- recheck of a neutral product’s pin, described with the legs), and an
- upstream FIX to a copied file reaches it never. update-shape.py is the re-sync,
- as one command.
+ (its validators’ one possible network read is validate-pins.py’s
+ gh api recheck of a neutral product’s pin,
+ described with the legs), and an upstream FIX to a copied file reaches it
+ never. update-shape.py is the re-sync, as one command.
./update-shape.py check --root ../MedxEHR # writes nothing
./update-shape.py apply --root ../MedxEHR --at <commit> --yes \
--branch shape/update-<sha> # then open a pull request
@@ -1806,11 +1807,11 @@ For AI assistants
refusing an untracked or ignored one it would rewrite, added (#207); the assistant rules
saying that some scaffold refusals name a value to change, added (#288);
bump-leg.py refusing a workflow file or the leg’s pin that is not UTF-8, by
- name, added (#289); the one network read a project’s own gate can make, the
+ name, added (#289); the one network read a project’s own validators can make, the
gh api recheck of a neutral product’s pin, said where this page and the
README had said “offline”, added (#411) — a page cannot name the commit
that contains it, and the blob below is the fact a test checks.
- README.md blob d25081fd5675d608aa9f362cb901b8db819481ef. Every command, flag, exit code
+ README.md blob 4ce951da2a58bd100b4323281549d25314c61ea4. Every command, flag, exit code
and file name on this page is quoted from that README and from AGENTS.md.
diff --git a/scripts/bump-leg.py b/scripts/bump-leg.py
index 6ef97022..73ff2d20 100755
--- a/scripts/bump-leg.py
+++ b/scripts/bump-leg.py
@@ -1314,8 +1314,9 @@ def build_parser() -> argparse.ArgumentParser:
"order given")
parser.add_argument("--local-remote-dir", type=Path, default=None,
help="resolve the leg's remote to a bare repository "
- "here instead of its origin (the TEST path; no "
- "network)")
+ "here instead of its origin (the TEST path; the "
+ "leg needs no network, but the project's own "
+ "validate-pins.py may still ask `gh api`)")
parser.add_argument("--dry-run", action="store_true",
help="print what would move and change nothing")
return parser