feat: fleet visibility — gate outcomes readable across installed repos - #41
Merged
Conversation
Overrides and gate outcomes were already recorded per repo: the accepted-risk labels,
the two ledgers, the PR timeline. Nothing aggregated them, so across a portfolio nobody
could answer whether the gates are firing, being routinely waved through, or quietly
switched off.
The third question is the one that needed building. A gate has two halves — the check,
and the rule requiring it to pass. Remove the rule and the check still runs, still
reports, and looks entirely normal on the pull request; a red run simply merges anyway.
From outside that repo, a disarmed gate and a gate that never caught anything produce
identical evidence, and no amount of counting separates them. check_standard.py catches
this in our own repo; nothing caught it in a client's.
So each repo commits a weekly rails-telemetry.json with three sections — what ran, every
override by name, and the comparison of what branch protection REQUIRES against what the
workflows DECLARE. The schema ships fixed at version 1 before the install wave, because
a schema retrofitted across live repos is a migration nobody schedules.
Two constraints held throughout. It stays inside the client's tenancy: the workflow reads
the repo's own history through the platform's own API and writes into the same repo, with
no external calls — that is what makes it approvable rather than a negotiation. And it
counts gates, never people: overrides are reported against merged changes so they read as
a rate, and there is no per-author breakdown anywhere in the file.
Where live branch protection cannot be read the workflow falls back to the committed
ruleset and RECORDS WHICH, because that reading describes intent rather than what the
platform enforces. Presenting the two as equivalent would be the same silent-green
failure one layer up.
scripts/collect_rails_telemetry.py is operator tooling, deliberately not part of kit/ and
never installed. It reads the committed files worst-first and exits non-zero on a high
finding OR an unreadable repo — silence is not health. Two things that shaped it:
- Discovery is keyed on .git, not on the report file. Keyed on the report, it would
only ever find repos already reporting, so one where the workflow was never installed
would be silently absent — the exact failure being built against, reproduced one level
up in the tool meant to catch it.
- An empty --root is a finding with its own message, not the usage error argparse gives
for a missing flag. Sending someone to re-type a flag they already typed is how a tool
earns a reputation for being broken.
The jq that assembles the report was exercised against synthetic data before shipping,
including the disarmed-gate case; 19 new tests inject real drift and assert it is caught,
following test_check_standard.py's discipline that a checker which has stopped noticing
is worse than no checker.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #12.
Overrides and gate outcomes were already recorded per repo — the accepted-risk labels, the two ledgers, the PR timeline. Nothing aggregated them, so across a portfolio nobody could answer whether the gates are firing, being routinely waved through, or quietly switched off.
The third question is the one that needed building
A gate has two halves: the check, and the rule requiring it to pass. Remove the rule and the check still runs, still reports, and looks entirely normal on the pull request — a red run simply merges anyway.
From outside that repo, a disarmed gate and a gate that never caught anything produce identical evidence. No amount of counting separates them.
check_standard.pycatches this in our repo; nothing caught it in a client's.So each repo commits a weekly
rails-telemetry.jsonwith three sections — what ran, every override by name, and the comparison of what branch protection requires against what the workflows declare.Two constraints held throughout
Where live branch protection can't be read it falls back to the committed ruleset and records which, because that reading describes intent rather than what the platform enforces. Presenting the two as equivalent would be the same silent-green failure one layer up.
The schema ships fixed at version 1 before the install wave — a schema retrofitted across live repos is a migration nobody schedules. That was the sequencing constraint in #12.
The collector
scripts/collect_rails_telemetry.pyis operator tooling — deliberately not part ofkit/, never installed into a client repo. Reads the committed files worst-first; exits non-zero on a high finding or an unreadable repo, because silence is not health.Two things that shaped it:
.git, not on the report file. Keyed on the report it would only ever find repos already reporting, so one where the workflow was never installed would be silently absent — the exact failure being built against, reproduced inside the tool meant to catch it.--rootis a finding with its own message, not the usage error argparse gives for a missing flag. Sending someone to re-type a flag they already typed is how a tool earns a reputation for being broken.Test plan
python -m pytest scripts/tests -q— 65 passing (19 new). Tests inject real drift and assert it is caught, followingtest_check_standard.py: a checker that has stopped noticing is worse than no checker.python scripts/check_standard.py— no drift🤖 Generated with Claude Code