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
repository-drift-check.yaml has not run since 2026-08-07. Its two runs that day
(31171842926, 31171407346) both failed at the token step with "The permissions requested are
not granted to this installation", and the schedule: trigger was correctly removed so main
would not carry a permanently failing scheduled run. The missing grant is tracked in #144 and is
maintainer-only.
The consequence, measured 2026-09-02: repository-settings drift has been unchecked for 26 days,
and will stay unchecked for as long as the grant is outstanding. The control exists, is tested, and
detects nothing.
Problem and audience
The audience is whoever relies on deploy/ being the real source of repository configuration. The
check exists precisely because cluster state cannot answer this on its own — a Repository managed
resource whose writes are rejected still reports Synced=ReconcileSuccess, so a declaration that
never landed looks identical to one that did.
Today the check is all-or-nothing: it needs Administration: Read-only for seven merge-policy
fields, and without them gh omits those keys from the repository object, the comparison sees a
declared field with no live counterpart, and the script aborts 2 (fail-closed) before checking anything. So one missing permission costs coverage of every other declared field — description, homepage, topics, visibility, has_issues, default_branch and the rest —
all of which metadata: read can already see.
Hypothesis
Partial coverage now beats total coverage never. If the seven administration-gated fields are
treated as known-unverifiable under the current token rather than as a mapping break, the check
can run daily against everything else and report honestly on what it could not see.
Why this does not weaken the control
The current abort is correct design, not a bug: it refuses to pass a field it cannot observe, which
is the same "an empty filtered read is a claim about the filter" discipline used elsewhere. That
property must be preserved exactly.
The safe distinction is that the administration-gated set is fixed and already enumerated in repository-drift-check.yaml: allow_squash_merge, allow_merge_commit, allow_rebase_merge, allow_auto_merge, allow_update_branch, delete_branch_on_merge, web_commit_signoff_required.
Only those may be reported as unverifiable. Any other unmapped field must still abort 2, so a
genuine mapping break — the case the abort was written for — keeps failing closed.
Success signal
Baseline: 0 scheduled drift-check runs since 2026-08-07; 0 fields compared.
Target: a daily scheduled run completes, compares every non-administration declared field across
all repositories declared in deploy/, and exits 1 on real drift.
Add the fixed administration-gated field set to scripts/check-repository-drift.sh.
When such a field is absent from the live object, record it as unverifiable instead of unmapped; keep unmapped → abort 2 for everything else.
Report unverifiable fields in the output so the gap is visible rather than silent, and make the
exit code ignore them.
Restore the schedule: trigger.
Acceptance criteria
A declared administration-gated field missing from the live object yields an UNVERIFIABLE
line, not an abort.
A declared field outside that set missing from the live object still aborts 2 — pinned by a
test, so the fail-closed property cannot regress.
When every field is present (an administratively-scoped token), behaviour is byte-identical to
today.
The scheduled run is restored and completes.
Size: small — one script, its tests, and a workflow trigger.
Related: #144 remains the right long-term fix; this is what makes the control useful while that
grant is outstanding, and it becomes a no-op once granted.
Evidence
repository-drift-check.yamlhas not run since 2026-08-07. Its two runs that day(
31171842926,31171407346) both failed at the token step with "The permissions requested arenot granted to this installation", and the
schedule:trigger was correctly removed somainwould not carry a permanently failing scheduled run. The missing grant is tracked in #144 and is
maintainer-only.
The consequence, measured 2026-09-02: repository-settings drift has been unchecked for 26 days,
and will stay unchecked for as long as the grant is outstanding. The control exists, is tested, and
detects nothing.
Problem and audience
The audience is whoever relies on
deploy/being the real source of repository configuration. Thecheck exists precisely because cluster state cannot answer this on its own — a
Repositorymanagedresource whose writes are rejected still reports
Synced=ReconcileSuccess, so a declaration thatnever landed looks identical to one that did.
Today the check is all-or-nothing: it needs
Administration: Read-onlyfor seven merge-policyfields, and without them
ghomits those keys from the repository object, the comparison sees adeclared field with no live counterpart, and the script aborts
2(fail-closed) before checkinganything. So one missing permission costs coverage of every other declared field —
description,homepage,topics,visibility,has_issues,default_branchand the rest —all of which
metadata: readcan already see.Hypothesis
Partial coverage now beats total coverage never. If the seven administration-gated fields are
treated as known-unverifiable under the current token rather than as a mapping break, the check
can run daily against everything else and report honestly on what it could not see.
Why this does not weaken the control
The current abort is correct design, not a bug: it refuses to pass a field it cannot observe, which
is the same "an empty filtered read is a claim about the filter" discipline used elsewhere. That
property must be preserved exactly.
The safe distinction is that the administration-gated set is fixed and already enumerated in
repository-drift-check.yaml:allow_squash_merge,allow_merge_commit,allow_rebase_merge,allow_auto_merge,allow_update_branch,delete_branch_on_merge,web_commit_signoff_required.Only those may be reported as unverifiable. Any other unmapped field must still abort
2, so agenuine mapping break — the case the abort was written for — keeps failing closed.
Success signal
all repositories declared in
deploy/, and exits1on real drift.2. A run under a token thatdoes carry administrative read must check all fields, so this change becomes inert the moment
Grant the workflow App administrative read so the drift check can see repository settings #144 lands.
Smallest useful change
scripts/check-repository-drift.sh.unverifiableinstead ofunmapped; keepunmapped→ abort2for everything else.exit code ignore them.
schedule:trigger.Acceptance criteria
UNVERIFIABLEline, not an abort.
2— pinned by atest, so the fail-closed property cannot regress.
today.
Size: small — one script, its tests, and a workflow trigger.
Related: #144 remains the right long-term fix; this is what makes the control useful while that
grant is outstanding, and it becomes a no-op once granted.