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
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -686,15 +686,15 @@ is an addition, and `MedxChart` answers exactly as it did on 2026-09-02.
Each link past the first is a declaration in ANOTHER tree — `openXdox` declaring
`neutral_product_pins: [openDox]` — and it is read where that tree happens to be
on the disk: `--link-source PRODUCT=<path>`, `SHAPE_PIN_SOURCE_<PRODUCT>`, a
checkout beside the root, then a bare `--link-source <path>`: the same lookup
order `validate-pins.py` uses below for the same checkout. A link no tree
answers for is reported `declared-unverified` — a warning, never a finding, and
the name still classifies — because the recorded declaration is the offline fact
and the tree is the stronger check, available or not. A link that IS readable
and declares something else BREAKS the chain: the residual assembly-root reading
wins, the overlap is recorded, and the message names the link that broke.
`LedgerxWallet` -> `openXwallet` -> `openWallet` is the same shape, and
`openWallet` need not exist anywhere for that to be the answer.
checkout beside the root, then a bare `--link-source <path>`, for the first
link's tree only: the order `validate-pins.py` uses below for the same checkout.
A link no tree answers for is reported `declared-unverified` — a warning, never
a finding, and the name still classifies — because the recorded declaration is
the offline fact and the tree is the stronger check, available or not. A link
that IS readable and declares something else BREAKS the chain: the residual
assembly-root reading wins, the overlap is recorded, and the message names the
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.**
Each `neutral_product_pins:` entry's `contracts/<product lowercased>-pin.yaml`
Expand Down
16 changes: 12 additions & 4 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -904,11 +904,19 @@ options:
or of a leg's naming.referent_chain: under --project),
and then that reading wins even over a directory
spelled like the whole value (./PATH reads that
directory); any other value is a bare PATH, which
applies to whichever link has no more specific answer.
Repeatable; a value no link used is noted.
directory); any other value is a bare PATH: the tree
of the chain's FIRST link (the pin the project holds),
read when that link has no more specific answer, and
never a later link's, which needs its own
PRODUCT=PATH, its SHAPE_PIN_SOURCE_<PRODUCT> or a
checkout beside the project. When the chains read here
begin at two different links, a bare PATH is read for
neither. A value that names no path (PRODUCT=, or
empty) is refused. Repeatable; a value no link used is
noted. Every link is tried against PRODUCT=PATH,
SHAPE_PIN_SOURCE_<PRODUCT> and a checkout beside the
project are tried too. A link no source answers for is
project, in that order, and a bare PATH last, for the
first link alone. A link no source answers for is
declared-unverified, and a source that cannot be read
is named with the system's reason: both are warnings
and not findings, an explicit source included.
Expand Down
6 changes: 3 additions & 3 deletions docs/handbook.html
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@

<!-- reads README.md § Starting a project in a new organisation: blob 95f06438cf8088e29bba6f56991898f304405f3b -->

<!-- reads README.md § The three legs: blob 6ebed796b7b135151ff3ca2c5280fb501e001acc -->
<!-- reads README.md § The three legs: blob 39ec2c2372920177b6f7dcfb821b917eadc34314 -->

<!-- reads README.md § The double pin, and the lockstep invariant: blob 57fffa08ffad5476f8eae73dd3e8494a02e050c9 -->

Expand Down Expand Up @@ -1064,7 +1064,7 @@ <h2>If it fails part-way</h2>

<!-- reads README.md § Starting a project in a new organisation: blob 95f06438cf8088e29bba6f56991898f304405f3b -->

<!-- reads README.md § The three legs: blob 6ebed796b7b135151ff3ca2c5280fb501e001acc -->
<!-- reads README.md § The three legs: blob 39ec2c2372920177b6f7dcfb821b917eadc34314 -->

<p class="eyebrow">Setup</p>
<h2>Reading private legs in CI</h2>
Expand Down Expand Up @@ -1122,7 +1122,7 @@ <h2>Reading private legs in CI</h2>

<!-- reads README.md § Starting a project in a new organisation: blob 95f06438cf8088e29bba6f56991898f304405f3b -->

<!-- reads README.md § The three legs: blob 6ebed796b7b135151ff3ca2c5280fb501e001acc -->
<!-- reads README.md § The three legs: blob 39ec2c2372920177b6f7dcfb821b917eadc34314 -->

<p class="eyebrow">Reference</p>
<h2>The three legs, and naming</h2>
Expand Down
8 changes: 5 additions & 3 deletions scaffold-project.py
Original file line number Diff line number Diff line change
Expand Up @@ -885,9 +885,11 @@ def _declaration(args) -> tuple[dict, set, tuple, dict]:
`codexDox` pins `openXdox` and reaches `openDox` through it; the chain is a
DECLARATION, so it is checked by `_check_declared_names` below and written
into the manifest rather than re-derived by every reader. A link is
verified against its own tree when `--pin-source` (or
`SHAPE_PIN_SOURCE_<PRODUCT>`) points at one, and is declared-unverified
otherwise — a warning, never a refusal, because the check is offline.
verified against its own tree when `SHAPE_PIN_SOURCE_<PRODUCT>` points
at one, or `--pin-source` for the FIRST link alone (#441: it is the
pinned product's checkout, never a later link's tree), and is
declared-unverified otherwise — a warning, never a refusal, because the
check is offline.

A tree `--pin-source` reached whose declaration could not be read (a
`neutral_product_pins:` that is not a list of names) is declared-unverified
Expand Down
19 changes: 16 additions & 3 deletions scripts/repo_shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -2722,7 +2722,9 @@ def resolve_link_source(product: str, root: Path | None = None,
1. `--link-source <product>=<path>` (case-insensitive on the name)
2. `SHAPE_PIN_SOURCE_<PRODUCT>` in the environment
3. a checkout sitting BESIDE this project (`../openXdox`)
4. a bare `--link-source <path>` with no `product=` prefix
4. a bare `--link-source <path>` with no `product=` prefix, which is
asked only when `default_source` is passed: `link_pins_from_trees`
passes it for a chain's FIRST link and for no other (#441)

The first that holds a manifest answers, and one that does not is passed
over whatever the reason: one that cannot be entered no longer raises
Expand Down Expand Up @@ -2810,16 +2812,27 @@ def link_pins_from_trees(links, root: Path | None = None,
declared-unverified. Only a hand-written chain can hold two such links:
`scaffold-project.py` takes every link but the first from the naming
policy's `open<Product>` form, which has no `-`, `.` or `_` to fold.

`default_source` IS THE FIRST LINK'S TREE AND NO OTHER'S (#441). It is
one tree — the naming validator's bare `--link-source`, the scaffold's
`--pin-source` — and the first link is the pin this project holds, the
one tree a person who typed one path has to hand. It used to be offered
to every link, so in a chain of three or more links a later link with
no other source was read from the same tree: `openXdox`'s manifest,
declaring `openYdox`, read again as `openYdox`'s, which "declares
openYdox, not openInk", and a chain that holds was broken. A later link
is read through rules 1 to 3 of `resolve_link_source`, or is
declared-unverified.
"""
found: dict[str, set] = {}
unreadable: dict[str, str] = {}
links = list(links)
shared = pin_source_env_shared(repo_basename(str(link)) for link in links)
for link in links:
for index, link in enumerate(links):
name = repo_basename(str(link))
asked: list = []
path, _how = resolve_link_source(name, root, keyed_sources,
default_source,
None if index else default_source,
pin_product_key(name) in shared,
tried=asked)
if tried is not None:
Expand Down
Loading
Loading