diff --git a/templates/assembly-root/project.yaml b/templates/assembly-root/project.yaml index c4a4b0e0..285125f5 100644 --- a/templates/assembly-root/project.yaml +++ b/templates/assembly-root/project.yaml @@ -54,8 +54,14 @@ topic: {{TOPIC}} # `private`, `public` or `internal` (an enterprise org-internal repository). # NOT ENFORCED HERE — GitHub is the source of truth for what a repository's # visibility actually is — but recording it is what lets a reader tell, -# without three `gh repo view` calls, why the legs are internal. For a -# scaffolded project this is all three repositories' visibility; for an +# without three `gh repo view` calls, why the legs it created are internal. +# It is the visibility the run that created this file ASKED FOR, which is not +# always what a repository is: for a scaffold, `--visibility` where one was +# given; where none was, the visibility GitHub gave, before the plan, of an +# existing assembly root the run reuses; and otherwise `private`, a root +# whose read failed included. A repository the run REUSES keeps the +# visibility it already has — the run changes no repository's visibility — +# so this can differ from what GitHub says of one of the three. For an # in-place adoption it is the value the two NEW legs were created with — the # assembly root IS the adopted source and keeps whatever visibility it # already had on GitHub, unchanged by this tool. diff --git a/templates/family-root/family.yaml b/templates/family-root/family.yaml index 27d05c8b..8c73c8cf 100644 --- a/templates/family-root/family.yaml +++ b/templates/family-root/family.yaml @@ -42,7 +42,13 @@ created_on: "{{CREATED_ON}}" # `private`, `public` or `internal`. NOT ENFORCED HERE — GitHub is the source # of truth for what a repository's visibility actually is — but recording it -# is what lets a reader tell why the members need a credential in CI. +# is what lets a reader tell why the members need a credential in CI. It is +# the visibility the `family.py init` run that wrote this file ASKED FOR, not +# a reading of any repository: `--visibility`, which is `private` unless the +# run was given another. It is the HOLDER's alone, since each member has its +# own, and a holder the run REUSES keeps the visibility it already has — the +# run changes no repository's visibility — so this can differ from what +# GitHub says of the holder. visibility: "{{VISIBILITY}}" # The HOLDER's own branch: the one a new holder is created on, and the one diff --git a/tests/test_visibility_field_words.py b/tests/test_visibility_field_words.py new file mode 100644 index 00000000..53dcf738 --- /dev/null +++ b/tests/test_visibility_field_words.py @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: Apache-2.0 +"""What the comment above `visibility:` says the field records (#913, U039). + +`templates/assembly-root/project.yaml` said "For a scaffolded project this is +all three repositories' visibility". It is not wherever a repository was +reused: the run records the visibility IT asked for, which is not always what +a repository is, in an order `_leg_visibility` follows (`--visibility` where +one was given; where none was, the reused root's own, as GitHub gave it before +the plan; otherwise `private`), and a repository it reuses keeps the +visibility it has, because no run changes one. `family.yaml` is the same for +the holder, with no root to inherit from: a reused PUBLIC holder under the +default `--visibility private` records `private`, as +`test_free_plan_hint_reads.py` holds. + +Brett Heap ruled on 2026-10-21T10:39:44Z that the WORDS change and no code +does ("U039 and U037: take your recommendations"): `visibility:` is "the +visibility this run asked for". The behaviour the words describe is pinned +where it is run, by `test_reuse_visibility.py` (the reused root's own is +inherited, and an explicit `--visibility` wins) and by +`test_free_plan_hint_reads.py` (a family keeps the run's); this file pins the +words, which no test did. The comments are copied into every project and +family this tool makes, so a word that goes back says again what a reused +repository does not do. + +NO NETWORK, NO GITHUB, NO PROCESS: the two templates, read as text. +""" + +from __future__ import annotations + +import pytest + +from conftest import REPO + +ASSEMBLY = REPO / "templates" / "assembly-root" / "project.yaml" +FAMILY = REPO / "templates" / "family-root" / "family.yaml" +BOTH = pytest.mark.parametrize("path", [ASSEMBLY, FAMILY], + ids=["project.yaml", "family.yaml"]) + +#: The line the comment sits on. +FIELD = 'visibility: "{{VISIBILITY}}"' + + +def comment_above_visibility(path) -> str: + """The comment block directly above `visibility:`, its `#` and its line + breaks gone, so a phrase is found whichever line it was wrapped on.""" + lines = path.read_text(encoding="utf-8").splitlines() + at = lines.index(FIELD) + block: list[str] = [] + for line in reversed(lines[:at]): + if not line.startswith("#"): + break + block.append(line.lstrip("#").strip()) + return " ".join(reversed(block)) + + +@BOTH +def test_visibility_is_what_the_run_asked_for(path): + """The key claim of #913's ruling, in both manifests: what is recorded + is what the run was asked for, and a reused repository keeps its own, + which no run changes.""" + comment = comment_above_visibility(path) + for phrase in ("ASKED FOR", "`--visibility`", "`private`", + "REUSES keeps the visibility it already has", + "changes no repository's visibility"): + assert phrase in comment, (phrase, comment) + + +@BOTH +def test_visibility_is_not_called_the_repositories_visibility(path): + """The sentence the ruling replaced, in the words it used.""" + comment = comment_above_visibility(path) + assert "this is all three repositories' visibility" not in comment + assert "all three repositories" not in comment, comment + + +@BOTH +def test_github_is_still_the_source_of_truth(path): + comment = comment_above_visibility(path) + assert "NOT ENFORCED HERE" in comment, comment + assert "GitHub is the source of truth" in comment, comment + + +def test_the_scaffold_comment_says_what_is_recorded_in_the_order_it_is(): + """`_leg_visibility`'s order, in the words of the comment: `--visibility` + where one was given, which wins over a reused root; where none was, the + visibility GitHub gave, before the plan, of an existing assembly root the + run reuses; and otherwise `private`. Where the root is the answer the + field IS a reading of a repository, so the comment must not say it is + never one.""" + comment = comment_above_visibility(ASSEMBLY) + order = ("for a scaffold, `--visibility` where one was given; where none " + "was, the visibility GitHub gave, before the plan, of an " + "existing assembly root the run reuses; and otherwise " + "`private`") + assert order in comment, comment + assert "not a reading of any repository" not in comment, comment + assert "which is not always what a repository is" in comment, comment + assert "why the legs it created are internal" in comment, comment + + +def test_the_in_place_adoption_sentence_holds(): + """Adoption creates the two legs at `plan --visibility` and keeps the + source's root as it is, which is what the sentence says and the ruling + left standing.""" + comment = comment_above_visibility(ASSEMBLY) + for phrase in ("in-place adoption it is the value the two NEW legs were " + "created with", + "keeps whatever visibility it already had on GitHub, " + "unchanged by this tool"): + assert phrase in comment, (phrase, comment) + + +def test_the_family_comment_says_it_is_the_holders_alone(): + """`family.yaml` records the HOLDER's, as `family.py init` fills it from + `--visibility`; a member is a project, with a `visibility:` of its own.""" + comment = comment_above_visibility(FAMILY) + assert "not a reading of any repository" in comment, comment + assert "It is the HOLDER's alone" in comment, comment + assert "a holder the run REUSES keeps" in comment, comment