You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: close F12 with the verified branch-protection contents
F12 asked four questions about `main` protection across the fleet and has been
open since the 2026-05-27 engagement because the contents were not reachable:
only the `protected` boolean was. They are now read directly, via an
authenticated gh api from the Vertex control plane, which is not subject to the
egress policy that blocks api.github.com from the session that raised this.
Both reads were read-only GETs recorded in the MCP broker log
(op-9a17fbde789036a9d16f658b, op-cfb3cb50b92ecd2341d0fc31).
The detail that made this hard to close, recorded first because it inverts the
obvious reading: protection is implemented as RULESETS, not legacy branch
protection. GET /repos/{owner}/{repo}/branches/main/protection returns 404 on
all eleven repositories. Taken alone that 404 says "main is unprotected", which
is the opposite of the truth.
The four answers:
no admin bypass verified. bypass_actors is [] on all eleven rulesets.
Nobody bypasses, including the owner.
required review not required anywhere. required_approving_review_count 0
and require_code_owner_review false on all eleven. A pull
request is required; an approval is not. The fleet-wide
CODEOWNERS files are therefore advisory, assigning a
reviewer without gating the merge. Deliberate
single-operator posture, now stated rather than assumed
from the files' presence.
required checks present on ten of eleven, listed per repository. ai-stack
has no required_status_checks rule at all.
signed commits not required anywhere. No required_signatures rule.
Also now on the record: every ruleset carries deletion + non_fast_forward, so
force-push and deletion of main are blocked server-side on all eleven. That is
the real control behind the .claude/settings.json force-push denies landed
earlier today, which are a client-side convenience by comparison.
Two contexts in infra's own required list are not job names and are called out
so a future reader does not prune them as stale: `Trivy` is the code-scanning
check from the upload-sarif step in ci.yml (category trivy-config), and
`CodeQL` / `Analyze (actions)` come from CodeQL default setup, which has no
workflow file in the repository. All sixteen reported on PR #62, which merged
under strict policy with an empty bypass list, so none is a phantom.
Matrix contexts are correctly expanded on automation, relay-shell and
6dof-ascent-sim. A required check named for an unexpanded matrix expression
would block merges forever; noted because the failure mode is silent and the
correct state is invisible.
The one gap, ai-stack, is carried forward as F13 rather than held inside F12.
Its Renovate path is already mitigated by platformAutomerge:false in
ai-stack/renovate.json5; the residual is that repository-level
allow_auto_merge:true lets a manually enabled auto-merge land an ordinary PR
with no CI gate. F13 records that the obvious fix, a trivial always-green job,
does not work: it would convert the gap into the appearance of a gate. The
effective shape is the ci-success aggregate that agents and aiops-mcp already
require, which means restructuring lint.yaml and is the maintainer's call.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QdProB4ZJuSaD8WZ84Sdy8
Copy file name to clipboardExpand all lines: BACKLOG.md
+116-1Lines changed: 116 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ moving it to **Resolved**.
10
10
11
11
| Id | Item | Origin | Why deferred | Next step |
12
12
|----|------|--------|--------------|-----------|
13
-
| F12 | Verify the *contents* of `main` branch protection (which checks are required, required review, no admin bypass, signed commits if intended) |[audit/2026-05-27-engagement.md](audit/2026-05-27-engagement.md) §8.1 (F12) | The branch-protection contents are still not exposed by any available tool; only the `protected` boolean is. Needs the repo-admin API/UI | An admin confirms the required-checks list and bypass posture, and records them under "F12 evidence" below |
13
+
| F13 | Give `ai-stack` a requireable aggregate status check, so the repository is not the one fleet member where a merge can land with no CI | F12 evidence 2026-08-13 (this file) | `ai-stack` is the only fleet repository with no `required_status_checks` rule, because every PR-triggered workflow is `paths`-filtered. The Renovate path is already mitigated by `platformAutomerge: false` in `ai-stack/renovate.json5`; the residual is that repository-level `allow_auto_merge: true` lets a manually enabled auto-merge land an ordinary PR with no CI gate. Fixing it properly means restructuring `lint.yaml`, which is a design change to the repository's primary CI and needs the maintainer's call | Adopt the `ci-success` aggregate pattern already used by `agents` and `aiops-mcp`: drop the workflow-level `paths` filter, add a `changes` job that computes changed paths, gate each existing job on it with `if:`, and finish with an `if: always()` aggregate that fails when any non-skipped job failed. Then require that one context on ruleset 15857143 |
14
14
15
15
### F12 evidence (2026-08-12)
16
16
@@ -59,9 +59,124 @@ Also unverified from here, and worth recording when an admin next has the UI
59
59
open: whether the ruleset bypass list is empty, and whether **Allow auto-merge**
60
60
is enabled per repository, since `platformAutomerge` is a no-op without it.
**404 on all eleven repositories**. Read alone, that 404 reads as "`main` is
74
+
unprotected", which is the opposite of the truth. The protection lives at
75
+
`GET /repos/{owner}/{repo}/rulesets/{id}`.
76
+
77
+
#### The four questions F12 asked
78
+
79
+
| Question | Answer | Confidence |
80
+
|---|---|---|
81
+
| No admin bypass? |**Yes, verified.**`bypass_actors` is `[]` on all eleven rulesets. Nobody bypasses, including the owner | Direct read |
82
+
| Required review? |**Not required anywhere.**`required_approving_review_count: 0` and `require_code_owner_review: false` on all eleven. A pull request *is* required (direct push to `main` is blocked), but no approval is | Direct read |
83
+
| Required checks? |**Configured on ten of eleven.**`ai-stack` has no `required_status_checks` rule at all. Full list below | Direct read |
84
+
| Signed commits? |**Not required anywhere.** No `required_signatures` rule on any of the eleven | Direct read |
85
+
86
+
`CODEOWNERS` files exist fleet-wide but are advisory: with
87
+
`require_code_owner_review: false` they assign a reviewer and do not gate the
88
+
merge. That is a deliberate single-operator posture, not an oversight, but it
89
+
should be stated rather than assumed from the file's presence.
90
+
91
+
#### Per-repository
92
+
93
+
Every ruleset is `enforcement: active`, targets `branch`, and carries
94
+
`deletion` + `non_fast_forward` (so branch deletion and force-push to `main`
95
+
are blocked server-side) plus a `pull_request` rule. All except
96
+
`renovate-config` also carry `required_linear_history`.
97
+
98
+
| Repository | Ruleset | Required status checks |`allow_auto_merge`|
`strict_required_status_checks_policy: true` (branch must be current with
114
+
`main` before merge); the other nine set it false.
115
+
116
+
Two of `infra`'s sixteen contexts are not job names and are worth recording so
117
+
a future reader does not delete them as stale: `Trivy` is the code-scanning
118
+
check produced by the `upload-sarif` step in `ci.yml` (category
119
+
`trivy-config`), and `CodeQL` / `Analyze (actions)` come from CodeQL **default
120
+
setup**, which has no workflow file in the repository. All sixteen reported on
121
+
PR #62, which merged under the strict policy with an empty bypass list, so none
122
+
of them is a permanently-blocking phantom context.
123
+
124
+
**Matrix contexts are correctly expanded.** A required check named for an
125
+
unexpanded matrix expression (`Molecule (${{ matrix.role }})`) would never
126
+
match a reported check and would block merges forever. `automation`,
127
+
`relay-shell` and `6dof-ascent-sim` all name the expanded values. No action
128
+
needed; noted because the failure mode is silent and the fix is invisible once
129
+
correct.
130
+
131
+
#### The one gap: `ai-stack`
132
+
133
+
`ai-stack` is the single repository where the preset's "automerge low-risk
134
+
updates once required checks pass" property is **not** backed by a required
135
+
check, which is exactly the case the 2026-08-12 evidence above predicted would
136
+
matter. It is also the one repository where that is a deliberate design:
137
+
138
+
- Every PR-triggered workflow (`docs.yaml`, `lint.yaml`,
139
+
`sync-image-artifacts.yml`) carries a `paths` filter, so no check is
140
+
guaranteed to run and any required check would permanently block a PR that
141
+
touches none of those paths.
142
+
- The live mitigation is in `ai-stack/renovate.json5`, which overrides
143
+
`platformAutomerge: false` for minor, patch, digest, pin and pinDigest with a
144
+
comment naming this exact hazard. Renovate therefore falls back to evaluating
145
+
the checks that did report, rather than handing the merge to GitHub.
146
+
147
+
So the Renovate path is covered. **The residual is the non-Renovate path**: with
148
+
`allow_auto_merge: true` at the repository level and no required check, anyone
149
+
enabling GitHub auto-merge on an ordinary `ai-stack` PR lands it with no CI
150
+
gate at all, subject only to `required_review_thread_resolution`. That is
151
+
tracked as **F13** below rather than held inside F12.
152
+
153
+
One trap worth recording, because it is the obvious fix and it does not work:
154
+
adding a trivial unconditional job that always exits 0 and requiring it gives
155
+
native auto-merge something to wait on, but that check goes green in seconds
156
+
regardless of the diff, so auto-merge still lands the PR without any chart
157
+
validation having run. It converts the gap into the appearance of a gate. The
158
+
effective shape is an **aggregate** check that is itself conditional on the
159
+
real jobs, which is what `agents` and `aiops-mcp` already require as
160
+
`ci-success`, and it means restructuring `lint.yaml` rather than adding a
161
+
file beside it.
162
+
163
+
#### Other observations, recorded not actioned
164
+
165
+
-**Signed commits are not required on any repository.** F12 asked about this
166
+
conditionally ("if intended"). Recorded as a decision, not a finding: adopting
167
+
it fleet-wide is a separate change with key-management consequences.
168
+
-**`renovate-config` lacks `required_linear_history`** while the other ten
169
+
carry it, and it is the only repository with `allow_auto_merge: false` and
170
+
`delete_branch_on_merge: false`. The auto-merge setting is consistent with its
171
+
deliberate no-automerge posture; the linear-history difference looks like
172
+
drift from it having been created later (2026-08-12) than the others.
173
+
-**`allow_auto_merge: true` on ten repositories** confirms `platformAutomerge`
174
+
is not a no-op there, which the 2026-08-12 evidence listed as unverified.
175
+
62
176
## Resolved
63
177
64
178
| Id | Item | Origin | Resolved by |
65
179
|----|------|--------|-------------|
180
+
| F12 | Verify the *contents* of `main` branch protection (which checks are required, required review, no admin bypass, signed commits if intended) |[audit/2026-05-27-engagement.md](audit/2026-05-27-engagement.md) §8.1 (F12) | "F12 evidence (2026-08-13)" above. All four questions answered by direct read of the eleven rulesets: bypass lists empty, no required approvals, required checks present on ten of eleven, signed commits not required. The `ai-stack` exception is carried forward as F13 |
66
181
| BL-1 | Execute the libvirt 0.9.x migration evaluation gates against a real lab host, then author the successor pin-bump ADR |[ADR-0009](docs/adr/0009-begin-libvirt-0.9-migration-evaluation.md) gates 2–5; [ADR-0012](docs/adr/0012-libvirt-0.9-schema-diff-inventory.md)|[ADR-0016](docs/adr/0016-migrate-libvirt-provider-to-0.9.md) (PR #29, merged 2026-06-04): gates 2–5 host-verified by the maintainer, pin bumped to `~> 0.9.0`, ADR-0002/0009/0012 superseded |
67
182
| BL-2 | Evaluate `siderolabs/talos` write-only secret arguments (`client_configuration_wo`, `machine_configuration_input_wo`) to keep rendered machine config out of state | audit 2026-05-31 |[ADR-0017](docs/adr/0017-adopt-talos-write-only-secret-arguments.md) (PR #38, 2026-06-09): adopted on `talos_machine_configuration_apply` + `talos_machine_bootstrap`; outcome noted in [ADR-0014](docs/adr/0014-pin-siderolabs-talos-provider.md)|
0 commit comments