Skip to content

Let the repository drift check run without administrative read, reporting the gated fields as unverifiable #179

Description

@devantler

🤖 Generated by the Agentic Engineer

Evidence

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.
  • Guardrail: an unmapped field outside the seven-field set still exits 2. A run under a token that
    does 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

  1. Add the fixed administration-gated field set to scripts/check-repository-drift.sh.
  2. When such a field is absent from the live object, record it as unverifiable instead of
    unmapped; keep unmapped → abort 2 for everything else.
  3. Report unverifiable fields in the output so the gap is visible rather than silent, and make the
    exit code ignore them.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions