Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -696,17 +696,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/<product lowercased>-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=]<path>`,
`SHAPE_PIN_SOURCE_<PRODUCT>` 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/<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. 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

Expand Down Expand Up @@ -1025,12 +1025,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 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
Expand Down Expand Up @@ -1092,9 +1092,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`

Expand Down
4 changes: 3 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,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
```

Expand Down
36 changes: 28 additions & 8 deletions docs/handbook.html
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,20 @@ <h2>The three legs, and naming</h2>
project&rsquo;s own <code>project.yaml</code>, so classification stays OFFLINE and never asks
GitHub whether <code>open&lt;Product&gt;</code> exists. The referent may also be reached
through a RECORDED chain of pins.</p>
<p><code>validate-pins.py</code> rechecks the referent too, not only the declaration. Each
<code>neutral_product_pins:</code> entry&rsquo;s
<code>contracts/&lt;product lowercased&gt;-pin.yaml</code> has its <code>commit</code>,
<code>revision_kind</code> and <code>sorted-ls-tree-r-v1</code> digest recomputed as
<code>scaffold-project.py --pin</code> computed them: OFFLINE from a local checkout when one
is found &mdash; <code>--pin-source [PRODUCT=]&lt;path&gt;</code>,
<code>SHAPE_PIN_SOURCE_&lt;PRODUCT&gt;</code> in the environment, or a checkout beside the
assembly root named for the pinned repository &mdash; else by <code>gh api</code>, with the
operator&rsquo;s <code>gh</code> credentials, when <code>gh</code> 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 <code>neutral-pin-source-unreadable</code>.
<code>make bootstrap</code>, <code>bump-leg.py</code> and <code>update-shape.py apply</code>
run this validator in the operator&rsquo;s environment, so each makes that call too; none
takes <code>--pin-source</code>.</p>
<p>A NEUTRAL product may elect the shape and be its own assembly root as well:
<code>openDox</code> mounting <code>openDox-spec</code> and <code>openDox-code</code> is a
fact about LAYOUT and not a claim about neutrality, because electing confers nothing.
Expand Down Expand Up @@ -1365,9 +1379,11 @@ <h2>Bootstrap is COPIED into the project, not fetched</h2>
<section id="current">
<p class="eyebrow">Reference</p>
<h2>Keeping a project&rsquo;s shape current</h2>
<p>The copies are the trade: a project runs its own gate offline, and an upstream FIX to a
copied file reaches it never. <code>update-shape.py</code> is the re-sync, as one
command.</p>
<p>The copies are the trade: a project runs its own gate, which never consults this standard
(its validators&rsquo; one possible network read is <code>validate-pins.py</code>&rsquo;s
<code>gh api</code> recheck of a neutral product&rsquo;s pin,
<a href="#legs">described with the legs</a>), and an upstream FIX to a copied file reaches it
never. <code>update-shape.py</code> is the re-sync, as one command.</p>
<div class="code" data-lang="sh"><pre><code>./update-shape.py check --root ../MedxEHR # writes nothing
./update-shape.py apply --root ../MedxEHR --at &lt;commit&gt; --yes \
--branch shape/update-&lt;sha&gt; # then open a pull request</code></pre></div>
Expand Down Expand Up @@ -1402,8 +1418,9 @@ <h2>Keeping a project&rsquo;s shape current</h2>
bare-clones itself; it defaults to the repository the pin names. <code>make bootstrap</code>
prints one line when the upstream has moved past the pin AND this machine can already tell
offline &mdash; <code>SHAPE_UPSTREAM_PATH</code> points at a clone, or a
<code>.shape-upstream-tip</code> 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.</p>
<code>.shape-upstream-tip</code> 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 <code>gh api</code>; see <a href="#legs">the legs</a>.)</p>
<a class="src" href="https://github.com/opensoft/openRepoShape#keeping-a-projects-shape-current">Full text &mdash; keeping the shape current</a>
</section>

Expand Down Expand Up @@ -1802,9 +1819,12 @@ <h2>For AI assistants</h2>
or the leg&rsquo;s pin that this user cannot open, by name, added (#383); what
<code>setup.sh --help</code> does from a download, which clones nothing, and which commands
list every flag, added (#341); <code>bump-leg.py --dry-run</code> running on the tracking
branch and naming the branch the real run needs, added (#477) &mdash; a page cannot name
the commit that contains it, and the blob below is the fact a test checks.
README.md blob 3a4f26ade02d2086157f1bd30c533444101710f8. Every command, flag, exit code
branch and naming the branch the real run needs, added (#477); the one network
read a project&rsquo;s own validators can make, the <code>gh api</code> recheck of a neutral
product&rsquo;s pin, said where this page and the README had said &ldquo;offline&rdquo;,
added (#411) &mdash; a page cannot name the commit that contains it, and the blob below is
the fact a test checks.
README.md blob 5e311f176503d43a188d9974c7988b396dd0e0e1. Every command, flag, exit code
and file name on this page is quoted from that README and from AGENTS.md.</p>
</footer>
</main>
Expand Down
15 changes: 11 additions & 4 deletions scripts/bump-leg.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,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_<PRODUCT>`; 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
Expand Down Expand Up @@ -1531,8 +1537,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
Expand Down
2 changes: 1 addition & 1 deletion tests/test_update_shape_pr_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 13 additions & 8 deletions update-shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,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():
Expand Down Expand Up @@ -364,9 +370,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 <path>` 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 "
"<path>` instead; nothing after it fetches the shape again.")
return Upstream(temporary / "shape.git", spec, temporary=temporary)


Expand Down Expand Up @@ -1858,9 +1864,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")

Expand Down
Loading