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
6 changes: 3 additions & 3 deletions AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,15 @@ Run [`WORKFLOW.md`][workflow]'s methodology against the repo's **own** Actions:

- **General settings** - diff the live repository settings against [`repo-config/settings.json`][repo-config-settings], and confirm the two state-dependent settings: `has_discussions` follows visibility (public on / private off) and `default_branch` is `main`.

```sh
```bash
live=$(gh api "repos/<owner>/<repo>" --jq '{has_wiki,has_projects,allow_merge_commit,allow_squash_merge,allow_rebase_merge,allow_auto_merge,allow_update_branch,delete_branch_on_merge}')
diff <(jq -S . repo-config/settings.json) <(jq -S . <<<"$live") \
&& echo "settings: in sync" || echo "settings: DRIFT"
```

- **Rulesets** - diff each live ruleset against the committed expected payload with a normalized comparison (sort the order-insensitive `rules[]` on each rule's whole content before diffing, so a reordered but equivalent ruleset does not read as drift). The compared subset is `name`, `target`, `enforcement`, `conditions` and `rules`, and `bypass_actors` sits deliberately outside it, which is the same subset and the same sort key [`spec/audit.py`][audit-runner] uses. Who may bypass a ruleset is a per-repository human decision taken in the UI, no payload declares one, and [`repo-config/configure.sh`][repo-config] treats it that way in both modes, writing the live list back unchanged on `apply` and reporting it without asserting on `check`. Comparing it here would contradict that and report a ruleset finding against every repository that has any bypass actor, which is the field's normal state rather than a deviation:

```sh
```bash
# bypass_actors stays outside the projection, since no payload declares one and jq cannot sort the null that leaves.
# Rules sort on each rule's whole content, matching the key normalize_ruleset in audit.py sorts by.
# Sorting on .type alone leaves two rules of one type in input order, so a reordered pair would read as drift.
Expand Down Expand Up @@ -135,7 +135,7 @@ Run [`WORKFLOW.md`][workflow]'s methodology against the repo's **own** Actions:

- **Dependabot ecosystem coverage** - for each ecosystem the repo's tree implies, `.github/dependabot.yml` must declare it: `github-actions` when `.github/workflows/` is present (its workflows reference actions, and otherwise those versions go stale and a stood-up merge-bot has no action-update PRs to auto-merge), and `devcontainers` when a `.devcontainer` is present. The mechanical check (`spec/audit.py`) asserts each implied ecosystem's **presence**. A tree-implied ecosystem declared nowhere is a **drift finding** (the file exists, so its absence would instead be a file-presence letter). Then confirm **by inspection** that each declared ecosystem **dual-targets `main` + `develop`** per the [Branching Model][governance-branching-model], since the regex below cannot pair an ecosystem with its `target-branch`. Language ecosystems (`nuget`/`uv`/`npm`) are directory-scoped and audited by inspection too.

```sh
```bash
# Anchor to the line start (optional list dash) so a commented-out '# package-ecosystem:' is not counted.
dependabot_content=$(gh api "repos/<owner>/<repo>/contents/.github/dependabot.yml?ref=<ground>" --jq '.content') || exit 1
dependabot_yaml=$(base64 -d <<<"$dependabot_content") || exit 1
Expand Down
25 changes: 25 additions & 0 deletions scripts/tests/test_release_guards.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,31 @@ def test_audit_probes_fail_before_local_path_checks(self) -> None:
audit,
)

def test_audit_bash_blocks_are_not_labeled_as_posix_shell(self) -> None:
audit_lines = (REPO / "AUDIT.md").read_text(encoding="utf-8").splitlines()
bash_only = ("<(", "<<<", "$'", "[[")
mislabeled = []
fence_label = ""
fence_start = 0
fence_lines: list[str] = []

for number, line in enumerate(audit_lines, start=1):
stripped = line.strip()
if not fence_label and stripped.startswith("```"):
fence_label = stripped.removeprefix("```").split(maxsplit=1)[0]
fence_start = number
elif fence_label and stripped == "```":
if fence_label in {"sh", "shell"} and any(
token in "\n".join(fence_lines) for token in bash_only
):
mislabeled.append((fence_start, fence_label))
fence_label = ""
fence_lines = []
elif fence_label:
fence_lines.append(line)

self.assertEqual([], mislabeled)


if __name__ == "__main__":
unittest.main()