Skip to content
Merged
32 changes: 21 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,10 @@ manifest, sets the topic `xf-project-atlas` on all three, clones the root as
> created. A failed push says so in as many words — *NOTHING has been rolled
> back. What already exists is listed above; delete it by hand if you want a
> clean re-run* — and it is no less true of a failure anywhere else. A second
> run is refused while they exist, and there is no `--force`: delete what was
> created — `gh repo delete <org>/<name>`, which needs the `delete_repo` scope
> run is refused while they exist, at the plan in (3), before the `yes`: the
> plan is the scaffold's `--dry-run`, which asks whether each of the three
> already exists. There is no `--force`: delete what was created —
> `gh repo delete <org>/<name>`, which needs the `delete_repo` scope
> (`gh auth refresh -s delete_repo`) — or scaffold under a different
> `--project`, or re-run with `--reuse-empty-repo`. That reuses each of the
> three that has ZERO commits, a reserved name rather than a project, and a
Expand Down Expand Up @@ -510,9 +512,9 @@ commits, not on a detached HEAD.
**If the legs are private**, CI needs a credential to fetch them: a GitHub
App with Contents read on the legs, registered as `SHAPE_LEGS_APP_ID` and
`SHAPE_LEGS_APP_PRIVATE_KEY`, or the `SHAPE_LEGS_TOKEN` fallback — as
REPOSITORY secrets on the GitHub Free plan, where organisation secrets do not
reach private repositories (see "Reading private legs in CI" below). Without
either, `validate` degrades with a warning instead of failing.
REPOSITORY secrets on the assembly root when it is private or internal and the
organisation is on the GitHub Free plan (see "Reading private legs in CI"
below). Without either, `validate` degrades with a warning instead of failing.

**Daily work.** Dana works in `spec/` and `code/` as ordinary repositories:
branches, commits, pull requests in `Atlas-spec` and `Atlas-code`. Committing
Expand Down Expand Up @@ -626,9 +628,12 @@ Both `scaffold-project.py` and `adopt-project.py execute` print a one-line
reminder — App first — when they create a private or internal leg.

**On the Free plan, use `--repo`, not `--org`.** GitHub delivers org secrets
only to PUBLIC repositories, so on a private one `secrets.SHAPE_LEGS_APP_ID`
is silently empty and `validate` goes GREEN with the pin check skipped. Set
both per repository, or upgrade to Team; the creating tools check and say so.
only to PUBLIC repositories, and `validate` runs in the root, so on a private
or internal root `secrets.SHAPE_LEGS_APP_ID` is silently empty and `validate`
goes GREEN with the pin check skipped. Set both on the root, or upgrade to
Team; a public root takes org secrets registered with `--visibility selected`
and the root in `--repos`, or `all`, which shows them to every repository (the
commands above default to `private`). The creating tools check and say so.

The root repository is always readable by the workflow's own default token, so
`actions/checkout` never carries a `token:` override. Putting a legs credential
Expand Down Expand Up @@ -784,8 +789,9 @@ The contracts decision costs something, and the plan says so instead of letting
it be discovered later: `scripts/validate.py` reads `contracts/*.yaml` from
beside it and must now read ACROSS the root. `plan` finds every such file with
`git grep` and lists up to eight per directory in `follow_ups:`, counting the
rest; the root Makefile it materializes exports `CONTRACTS_DIR ?=
$(CURDIR)/spec/contracts`. The tool does not make the edit — naming it is the
rest; the root Makefile it materializes exports `CONTRACTS_DIR`, pointing at the
`contracts/` of the spec leg where the plan mounts it (`$(CURDIR)/spec/contracts`
by default). The tool does not make the edit — naming it is the
honest half, making it silently would be the dangerous one. And `.specify/` and
the ten identical assistant stubs stay at the root because an assistant reading
`CLAUDE.md` from inside the code leg would be told about specifications it
Expand All @@ -798,7 +804,11 @@ ambiguous`, first match wins, each with a reason. The fourth class is not a
failure: a classifier that guessed at `examples/golden-run/` would be wrong
about half the time and silent about it. Those entries get `leg: null`,
`review_required: true` and the QUESTION; `execute` refuses while any remain,
because an unanswered question is never an implicit `root`.
because an unanswered question is never an implicit `root`. One question comes
answered: a source's own `single-repository.yaml` record is written `leg: drop`,
its rule's default, still `review_required: true` with its question beside it,
and `execute` takes it as written, so it drops the record unless a person
changes `leg:` (the question offers `root`).

A shape file whose name the source already holds — `README.md`, `Makefile` — is
written under `shape/` as a follow-up to merge, and a kept root `.gitattributes`
Expand Down
45 changes: 28 additions & 17 deletions docs/handbook.html
Original file line number Diff line number Diff line change
Expand Up @@ -472,9 +472,9 @@

<!-- reads README.md § preamble: blob ddadd23a162129f7e64fefce899105661440c05c -->

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

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

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

Expand Down Expand Up @@ -610,7 +610,7 @@ <h4>The one thing to read before electing it</h4>

<section id="start">

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

<p class="eyebrow">Setup</p>
<h2>Start here</h2>
Expand Down Expand Up @@ -1048,7 +1048,7 @@ <h4>What you have afterwards &mdash; the same on every platform</h4>

<section id="partway">

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

<p class="eyebrow">Setup</p>
<h2>If it fails part-way</h2>
Expand All @@ -1063,7 +1063,9 @@ <h2>If it fails part-way</h2>
<div class="out"><pre><code>NOTHING has been rolled back. What already exists is listed above; delete it by hand if you want a clean re-run</code></pre></div>
<p>&mdash; and it is no less true of a failure anywhere else.</p>
<ul class="plain">
<li>A second run is refused while they exist, and there is no <code>--force</code>.</li>
<li>A second run is refused while they exist, at the plan, before the <code>yes</code>: the
plan is the scaffold&rsquo;s <code>--dry-run</code>, which asks whether each of the three
already exists. There is no <code>--force</code>.</li>
<li>Delete what was created with <code>gh repo delete &lt;org&gt;/&lt;name&gt;</code>, which
needs the <code>delete_repo</code> scope (<code>gh auth refresh -s delete_repo</code>). Or
scaffold under a different <code>--project</code>, or re-run with
Expand All @@ -1083,9 +1085,9 @@ <h2>If it fails part-way</h2>

<section id="ci">

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

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

<p class="eyebrow">Setup</p>
<h2>Reading private legs in CI</h2>
Expand All @@ -1106,10 +1108,14 @@ <h2>Reading private legs in CI</h2>
<code>SHAPE_LEGS_TOKEN</code> PAT, <code>contents:read</code> on the LEGS ONLY.</p>
<div class="code" data-lang="sh"><pre><code>gh secret set SHAPE_LEGS_TOKEN --org &lt;your-org&gt; --body '&lt;token&gt;'</code></pre></div>
<p><strong>On the Free plan, use <code>--repo</code>, not <code>--org</code>.</strong> GitHub
delivers org secrets only to PUBLIC repositories, so on a private one
<code>secrets.SHAPE_LEGS_APP_ID</code> is silently empty and <code>validate</code> goes GREEN
with the pin check skipped. Set both per repository, or upgrade to Team; the creating tools
check and say so. Both <code>scaffold-project.py</code> and
delivers org secrets only to PUBLIC repositories, and <code>validate</code> runs in the root,
so on a private or internal root <code>secrets.SHAPE_LEGS_APP_ID</code> is silently empty and
<code>validate</code> goes GREEN with the pin check skipped. Set both on the root, or
upgrade to Team; a public root takes org secrets registered with
<code>--visibility selected</code> and the root in <code>--repos</code>, or
<code>all</code>, which shows them to every repository (the commands above default to
<code>private</code>). The creating tools check and say so. Both
<code>scaffold-project.py</code> and
<code>adopt-project.py execute</code> print a one-line reminder &mdash; App first &mdash; when
they create a private or internal leg.</p>
<ul class="plain">
Expand Down Expand Up @@ -1141,9 +1147,9 @@ <h2>Reading private legs in CI</h2>

<section id="legs">

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

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

<p class="eyebrow">Reference</p>
<h2>The three legs, and naming</h2>
Expand Down Expand Up @@ -1221,7 +1227,7 @@ <h2>The three legs, and naming</h2>

<section id="adopt">

<!-- reads README.md § Adopting an existing repository: blob a768fa1b4fe592c0a9dde7c6abb415e8af37d99c -->
<!-- reads README.md § Adopting an existing repository: blob ca4b6731cf6de1caeac426eeff5eb7184c53f562 -->

<p class="eyebrow">Reference</p>
<h2>Adopting an existing repository</h2>
Expand All @@ -1244,7 +1250,12 @@ <h2>Adopting an existing repository</h2>
<code>spec | code | root | ambiguous</code>, first match wins, each with a reason. The fourth
class is not a failure: those entries get <code>leg: null</code>,
<code>review_required: true</code> and the QUESTION, and <code>execute</code> refuses while
any remain, because an unanswered question is never an implicit <code>root</code>. A shape
any remain, because an unanswered question is never an implicit <code>root</code>. One
question comes answered: a source&rsquo;s own <code>single-repository.yaml</code> record is
written <code>leg: drop</code>, its rule&rsquo;s default, still
<code>review_required: true</code> with its question beside it, and <code>execute</code>
takes it as written, so it drops the record unless a person changes <code>leg:</code> (the
question offers <code>root</code>). A shape
file whose name the source already holds &mdash; <code>README.md</code>,
<code>Makefile</code>, <code>.gitignore</code> &mdash; is written under <code>shape/</code> and
the collision becomes a follow-up for a human to merge, and a root
Expand Down Expand Up @@ -1377,7 +1388,7 @@ <h3>The InkRouter example</h3>

<section id="doublepin">

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

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

Expand Down Expand Up @@ -1674,7 +1685,7 @@ <h2>Is this repository compliant? <code>openRepoShape --doctor</code></h2>

<section id="park">

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

<!-- reads README.md § Carrying in-flight work to another workstation: blob b84512ad39c09085dfe6bf7ea694017f5e28c1dd -->

Expand Down
43 changes: 43 additions & 0 deletions tests/readme-cap.d/606.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
+10
2026-10-15
Ten lines for four sentences: three that #566, #572 and #573 left false or
incomplete in the README once those pull requests landed, and one that #621
(#602) will leave false once it lands (#606). Each is the words that pull
request's own body held, as far as the code bore them out, on main or, for
#621, on its branch.
One: "Ambiguous is an answer" said every entry the policy cannot call gets
`leg: null` and that `execute` refuses while any remain. #566 gives one rule,
a source's own `single-repository.yaml` record, a default: `plan` writes
`leg: drop`, still `review_required: true` with its question beside it, and
`execute` takes it as written. A reader who answers every `leg: null` and
meets that entry would not know it is already answered, and that leaving it
drops a file. It cannot be shorter: what is written, why it still asks, and
what `execute` does with it are three things and the sentence says each once
(+4, five lines where there was one).
Two: the worked example said the Makefile exports `$(CURDIR)/spec/contracts`,
which is true of the default mount only; after #572 it is the `contracts/` of
the spec leg where the plan mounts it, and a reader who mounts it elsewhere
would look for a directory that is not there. The clause costs one line (+1).
Three: the "If it fails part-way" box said a second run is refused while the
repositories exist, with no word on when. After #573 it is the plan, before
the `yes`, because the plan is the scaffold's `--dry-run` and asks whether
each of the three exists; a reader who expects the refusal after typing `yes`
would not look at the plan. The sentence has to say where, and why, and that
takes two lines (+2).
Four, written 2026-10-16: once #621 (#602) lands, the Free-plan hint goes by the
assembly root, where `validate` reads the secrets, and only when the root is
private or internal; a public root over private legs gets the per-leg note and
no hint, because on Free the organisation's secrets reach public repositories.
The README said "REPOSITORY secrets on the GitHub Free plan" of any project
whose legs are private, which a public root makes false, and "on a private one"
without saying which repository. The sentence under "Starting a project in a new
organisation" is reworded in the six lines it had. The paragraph under "Reading
private legs in CI" has to say that it is the root, that a public root is the
exception, and on what condition: the `gh secret set --org` commands above it
default to `--visibility private`, which on Free reaches no repository, so a
public root told only that it "takes org secrets" would keep the silent green
with the pin check skipped. The sentence names `selected` with the root in
`--repos` first, the least-privilege form, and `all` after it with its cost,
that every repository of the organisation sees the App key (rv627n N1). Naming
the root and the exception takes one line more than the paragraph had (+1), the
condition two (+2); that is +3 of the ten. #627 lands after #621.
Loading