Skip to content

Commit e33ce65

Browse files
fix(rulesets): preserve existing policy and reject incomplete reads (#54)
The branch ruleset applier treated a failed ruleset lookup as permission to create `Base`, and a failed repository enumeration as a successful empty run. Updating a baseline also replaced review, scanning, deployment, bypass and branch-scope settings while retaining only status-check names. Repair an existing active repository-owned `Base` or `Optimus-Branch` additively. Preserve its full policy and other ruleset layers; reject failed, malformed, truncated, inherited or ambiguous reads. Missing baselines now require profile-specific provisioning instead of being created by the legacy hard-coded payload. Add `--repo NAME` for a bounded pilot, save the pre-change ruleset, recheck for concurrent edits, and verify the PUT by reading it back. An ambiguous write is reported without an automatic retry. Dry-run outcomes are reported separately from verified updates. Adopt the already-merged Governance reusable workflow at standards `a8f7249c3c5aef1ed6dfa16e637f13dfde815464`. This brings the consumer the upstream fixes for the missing lock wrapper and the separation of the tree allowlist check from credential-dependent administrative verification. The caller has no `HYPATIA_SCAN_PAT`: the live-policy advisory reports unavailable coverage, while the tree and lock gates remain enforced. An authenticated run of the authoritative live-policy checker independently passed for this repository (`allowed=all`, `sha_pin=true`). Package the maintenance CLI in `Containerfile.maintenance`, with an immutable Ubuntu base, current distribution updates, and the checksum-verified GitHub CLI 2.100.0 release. The distribution's older CLI failed the required `--slurp` capability check. Run as UID/GID 10001 and include only the maintenance CLI, its shared library, owner configuration and tests in the build context. This packages the maintenance component; it does not claim to package the separate Elixir TUI or provide a reproducible build of distribution packages. Validation: 29 offline cases pass, including zero-write failure paths, preservation of review rules/App-bound checks/bypass actors/scope, a baseline on the second API page, case-insensitive repository identity, reordered rules with full parameter checking, and an idempotent second apply. The original script fails the repository-enumeration negative control by exiting successfully after the API error. Bash syntax, ShellCheck, `git diff --check`, the authoritative packaging-policy check, and `gh actions-lock .github/workflows/governance.yml --no-fix` pass. The container builds, its entry point works, and all 29 tests pass as its runtime user with no network, a read-only root filesystem and dropped Linux capabilities. The test's temporary mount permits its executable `gh` test double. Governance CI runs the source tests, container build and restricted-runtime tests. Live pilot: this repository's Optimus-Branch ruleset #14936593 gained exactly the missing `non_fast_forward` rule. Full before/after comparison confirmed no rules removed, all other branch-policy fields retained, and tag ruleset #18111316 unchanged. A second live apply was a no-op. Pre-change snapshots and reports were retained in the execution ledger. This is the safe repair path for existing baselines. Profile selection, missing-baseline provisioning, policy changes to existing bypass actors, and estate-wide rollout remain separate work. The pre-PUT recheck narrows the concurrent-edit window; it is not an atomic compare-and-swap. --------- Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 81d902b commit e33ce65

7 files changed

Lines changed: 502 additions & 98 deletions

File tree

‎.dockerignore‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# The maintenance build sends only its runtime and test inputs to the builder.
3+
**
4+
!Containerfile.maintenance
5+
!scripts/
6+
!scripts/branch-protection-apply.sh
7+
!scripts/lib/
8+
!scripts/lib/common.sh
9+
!config/
10+
!config/owners.config
11+
!test/
12+
!test/branch-protection-apply-test.sh

‎.github/workflows/actions.lock‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ workflows:
66
'.github/workflows/codeql.yml':
77
- 'actions/checkout@v7.0.1'
88
- 'github/codeql-action@v4.37.9'
9-
'.github/workflows/governance.yml': []
9+
'.github/workflows/governance.yml':
10+
- 'actions/checkout@v7.0.1'
1011
'.github/workflows/hypatia-scan.yml': []
1112
'.github/workflows/label-triage.yml': []
1213
'.github/workflows/labels.yml': []

‎.github/workflows/governance.yml‎

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# SPDX-License-Identifier: MPL-2.0
21
# This workflow is managed by gh actions-lock.
2+
# SPDX-License-Identifier: MPL-2.0
33
name: Governance
44

55
on:
@@ -15,4 +15,30 @@ permissions:
1515

1616
jobs:
1717
governance:
18-
uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@fad242d35291de1898242d6737ba02b74a59a2f2
18+
uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@a8f7249c3c5aef1ed6dfa16e637f13dfde815464
19+
ruleset-regression:
20+
name: Ruleset repair regression
21+
runs-on: ubuntu-24.04
22+
timeout-minutes: 5
23+
permissions:
24+
contents: read
25+
steps:
26+
- uses: actions/checkout@v7.0.1
27+
with:
28+
persist-credentials: false
29+
- name: Check shell syntax and offline ruleset behaviour
30+
shell: bash
31+
run: |
32+
bash -n scripts/branch-protection-apply.sh test/branch-protection-apply-test.sh
33+
bash test/branch-protection-apply-test.sh
34+
- name: Build the maintenance CLI package
35+
run: docker build --file Containerfile.maintenance --tag git-scripts-maintenance:test .
36+
- name: Exercise the package without network or a writable root filesystem
37+
run: |
38+
# The suite creates an executable gh test double in /tmp.
39+
docker run --rm --network none --read-only --cap-drop ALL \
40+
--security-opt no-new-privileges \
41+
--tmpfs /tmp:rw,exec,nosuid,nodev,uid=10001,gid=10001 \
42+
--tmpfs /state:rw,nosuid,nodev,uid=10001,gid=10001 \
43+
--entrypoint bash git-scripts-maintenance:test \
44+
/opt/git-scripts/test/branch-protection-apply-test.sh

‎Containerfile.maintenance‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# A runnable package for the branch-policy maintenance CLI and its tests.
3+
FROM docker.io/library/ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90
4+
5+
RUN apt-get update \
6+
&& apt-get upgrade --no-install-recommends -y \
7+
&& apt-get install --no-install-recommends -y \
8+
bash ca-certificates coreutils curl diffutils findutils gawk git grep jq sed util-linux \
9+
&& rm -rf /var/lib/apt/lists/* \
10+
&& groupadd --gid 10001 maintenance \
11+
&& useradd --uid 10001 --gid 10001 --no-create-home --shell /usr/sbin/nologin maintenance \
12+
&& mkdir -p /state \
13+
&& chown 10001:10001 /state
14+
15+
# Ubuntu 24.04's gh 2.45 lacks the --slurp capability used by the applier.
16+
# Install the upstream immutable release and verify its published asset digest.
17+
RUN cli_arch="$(dpkg --print-architecture)" \
18+
&& case "${cli_arch}" in \
19+
amd64) cli_sha=698c8d88cc19cc92bfe96bad58d10b2a5b274c52433d6dc57799c81f6139d5fc ;; \
20+
arm64) cli_sha=33ccd2ad7ce639c927e1cb209e36555b0e1fbb89f7a38239c0568040ec758612 ;; \
21+
*) echo "Unsupported CLI architecture: ${cli_arch}" >&2; exit 1 ;; \
22+
esac \
23+
&& curl --fail --location --retry 3 \
24+
"https://github.com/cli/cli/releases/download/v2.100.0/gh_2.100.0_linux_${cli_arch}.deb" \
25+
--output /tmp/gh.deb \
26+
&& printf '%s %s\n' "${cli_sha}" /tmp/gh.deb | sha256sum --check - \
27+
&& dpkg --install /tmp/gh.deb \
28+
&& rm /tmp/gh.deb
29+
30+
WORKDIR /opt/git-scripts
31+
COPY scripts/branch-protection-apply.sh scripts/branch-protection-apply.sh
32+
COPY scripts/lib/common.sh scripts/lib/common.sh
33+
COPY config/owners.config config/owners.config
34+
COPY test/branch-protection-apply-test.sh test/branch-protection-apply-test.sh
35+
36+
ENV GS_BACKUP_DIR=/state/backups \
37+
GS_STATE_DIR=/state/snapshots \
38+
GS_LOCK_DIR=/state/locks \
39+
GS_REPORT_DIR=/state/reports \
40+
XDG_CACHE_HOME=/state/cache \
41+
XDG_CONFIG_HOME=/state/config
42+
USER 10001:10001
43+
44+
# Check the packaged files as the runtime user. The suite mocks every gh call.
45+
RUN bash -n scripts/branch-protection-apply.sh test/branch-protection-apply-test.sh \
46+
&& gh api --help | grep -q -- --slurp \
47+
&& bash test/branch-protection-apply-test.sh
48+
49+
ENTRYPOINT ["bash", "/opt/git-scripts/scripts/branch-protection-apply.sh"]
50+
CMD ["--help"]

‎README.adoc‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,37 @@ mix compile
4545
mix escript.build
4646
----
4747

48+
=== Branch-policy maintenance package
49+
50+
`Containerfile.maintenance` packages the Bash branch-policy repair CLI with its
51+
GitHub CLI, JSON and system-tool dependencies. It includes the offline regression
52+
suite and runs as UID/GID 10001. This package covers the maintenance CLI; the
53+
Elixir TUI build above remains a separate component. Guix is the estate's primary
54+
packaging route; this container provides a buildable maintenance environment on
55+
hosts where Guix is unavailable.
56+
57+
[source,bash]
58+
----
59+
podman build -f Containerfile.maintenance -t localhost/git-scripts-maintenance .
60+
podman run --rm --network none --read-only --cap-drop ALL \
61+
--security-opt no-new-privileges \
62+
--tmpfs /tmp:rw,nosuid,nodev,uid=10001,gid=10001 \
63+
--tmpfs /state:rw,nosuid,nodev,uid=10001,gid=10001 \
64+
localhost/git-scripts-maintenance --help
65+
----
66+
67+
For an authenticated dry run, allow network access and pass an existing
68+
`GH_TOKEN` with `--env GH_TOKEN`. Select a single repository with
69+
`--repo NAME --dry-run`. An actual repair needs repository Administration write
70+
permission and a persistent `/state` volume for its pre-change snapshots and
71+
reports. The CLI never creates missing baselines or changes tag rulesets.
72+
73+
The base image is pinned by digest. Distribution packages are resolved at build
74+
time, so rebuilding on another date can change package versions; the result is
75+
not claimed to be a reproducible build. Governance CI builds the image and runs
76+
its tests with networking disabled, a read-only root filesystem and no Linux
77+
capabilities. Docker can run the same commands and OCI image.
78+
4879
== Implementation Details
4980

5081
Each function is implemented as an Elixir module:

0 commit comments

Comments
 (0)