diff --git a/README.md b/README.md index 12c3e498..2dc61e44 100644 --- a/README.md +++ b/README.md @@ -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 /`, 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 /`, 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 @@ -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 @@ -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 @@ -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 @@ -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` diff --git a/docs/handbook.html b/docs/handbook.html index 1f6e76d1..02c5188e 100644 --- a/docs/handbook.html +++ b/docs/handbook.html @@ -472,9 +472,9 @@ - + - + @@ -610,7 +610,7 @@

The one thing to read before electing it

- +

Setup

Start here

@@ -1048,7 +1048,7 @@

What you have afterwards — the same on every platform

- +

Setup

If it fails part-way

@@ -1063,7 +1063,9 @@

If it fails part-way

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.
  • +
  • A second run is refused while they exist, at the plan, 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 with 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 @@ -1083,9 +1085,9 @@

    If it fails part-way

    - + - +

    Setup

    Reading private legs in CI

    @@ -1106,10 +1108,14 @@

    Reading private legs in CI

    SHAPE_LEGS_TOKEN PAT, contents:read on the LEGS ONLY.

    gh secret set SHAPE_LEGS_TOKEN --org <your-org> --body '<token>'

    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. Both scaffold-project.py and + delivers org secrets 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. Both + scaffold-project.py and adopt-project.py execute print a one-line reminder — App first — when they create a private or internal leg.

      @@ -1141,9 +1147,9 @@

      Reading private legs in CI

      - + - +

      Reference

      The three legs, and naming

      @@ -1221,7 +1227,7 @@

      The three legs, and naming

      - +

      Reference

      Adopting an existing repository

      @@ -1244,7 +1250,12 @@

      Adopting an existing repository

      spec | code | root | ambiguous, first match wins, each with a reason. The fourth class is not a failure: those entries get leg: null, review_required: true and the QUESTION, and execute refuses while - any remain, because an unanswered question is never an implicit root. A shape + any remain, 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, .gitignore — is written under shape/ and the collision becomes a follow-up for a human to merge, and a root @@ -1377,7 +1388,7 @@

      The InkRouter example

      - + @@ -1674,7 +1685,7 @@

      Is this repository compliant? openRepoShape --doctor

      - + diff --git a/tests/readme-cap.d/606.txt b/tests/readme-cap.d/606.txt new file mode 100644 index 00000000..40966065 --- /dev/null +++ b/tests/readme-cap.d/606.txt @@ -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.