skip to content

How does a ratcheting quality gate tell a new analyser violation from a baselined one?

level: middleimportance: must knowfreq 52%

answer

  1. Tolerance that moves one way only
  2. Identity, not position, in the source
  3. A list of entries versus a bare number
  4. Fix one, add one, count unchanged
  5. Down automatically, up only with review

basics

~20 s

By matching every finding against the recorded baseline. Findings are identified by rule, file and a fingerprint of the code they point at rather than by line number, so unrelated edits do not make old findings look new. Anything unmatched fails the build.

solid answer

~50 s

Two mechanisms are in use. An entry-list baseline stores one record per known finding - rule identifier, file, and a fingerprint derived from the code element or its surrounding text - and the gate fails on any finding whose fingerprint matches no entry. A count ratchet stores only a number per rule or module, allows the build while the current count is at or below it, and lowers the stored number whenever the count drops, never raising it. The list form is more precise: it catches a change that fixes one violation and introduces another, which a count ratchet reports as no change at all. Both forms deliberately avoid line numbers, because inserting a line above a finding would otherwise present it as new. Both are also defeated by the same habit - regenerating the record to unblock a red build.

code

pseudocode · 12 lines
pseudocode
stored = read_floor("analysis-floor")          # e.g. { "unchecked-cast": 137 }
current = count_by_rule(analyser.run(source_tree))

for rule in stored.keys() + current.keys():
    allowed = stored.get(rule, 0)
    found = current.get(rule, 0)
    if found > allowed:
        fail_build(rule + ": " + found + " findings, floor is " + allowed)
    if found < allowed:
        stored[rule] = found                    # tighten, never loosen

write_floor("analysis-floor", stored)

go deeper

for a junior

Know the shape of the idea: the gate compares today's findings with a stored record and fails on what it does not recognise, and the record is allowed to get smaller but not bigger.

for a middle

This is your level. Explain how a finding is identified, why line numbers cannot be part of that identity, and the difference in what an entry list and a bare count can each detect.

for a senior

Bring the operating experience: the fix-one-add-one blind spot, bulk unmatches after renames, conflicts between long branches, and the rule that the floor drops automatically but only rises through review.

for a principal

Set the policy across repositories - which design each pipeline uses, how fine-grained the counts are, who may accept an increase, and how the ratchet is prevented from becoming a formality that everyone regenerates around.

## What ratcheting means A ratchet is a gate whose tolerance can move in one direction only. The build is allowed to carry a known quantity of analyser findings; when that quantity falls the gate records the lower number and will not accept the higher one again. Nothing forces the team to improve, but improvement is locked in as it happens, and regression is blocked. The mechanism is what makes an adoption baseline more than a snapshot: without a ratchet, a baseline is a list that ages; with one, it is a floor that only ever descends. ## Identity: how a finding is matched Everything turns on deciding whether the finding in front of you is one you have already accepted. The obvious key - rule identifier plus file plus line number - is the wrong one, because a line number is not a property of the finding, it is a property of everything above it. Adding an import or a comment near the top of a file shifts every finding below it and the gate reports the whole file as new work. So tools fingerprint instead: the rule identifier, the file path, and a hash over the finding's code element or a small window of the surrounding source text, sometimes with an ordinal to separate several identical findings in the same file. Reformatting or renaming still breaks such a fingerprint, which is why the practical rule is that a large move, rename or reformat is handled as its own change with its own deliberate baseline update, never mixed into a feature change. ## The count ratchet, and what it cannot see The cheaper design stores no entries at all - just a number, per rule or per module: `unchecked-cast: 137`. The gate compares the current count with the stored one, passes at or below it, and rewrites the stored number downwards when the count falls. It merges cleanly, is easy to read, and survives file moves that would shred a fingerprint list. It also has a blind spot that interviewers like. On the stock-ledger service the ratchet allowed 137 findings from one rule. A change removed an unchecked read in the reporting path and, in the same change, introduced a new one in the ingest path. The count was 137 before and 137 after, so the gate passed - and the new site was the one that later failed as a schema-drift mismatch between the record shape written by ingest and the shape read by reporting. An entry-list baseline would have failed that build, because the fixed finding's fingerprint disappeared while an unrecognised one arrived. If a count ratchet is what you have, the mitigation is to keep the counts fine-grained - per rule and per module rather than one project-wide total - so the offsetting swap has to happen inside a much smaller window. ## Direction, and who is allowed to move it A ratchet that anyone can loosen is not a ratchet. Practically that means the recorded floor is updated automatically only downwards, in the same change that lowers it; raising it - accepting new findings - is a reviewed edit with an explanation, not a command anyone runs when the build goes red. The single most destructive habit in this whole area is making regeneration the standard response to a failing gate: after that, every violation is accepted at the moment it is written and the mechanism constrains nothing while still looking, in the pipeline configuration, like a quality gate. ## Practical wrinkles **Merge conflicts.** Two branches that both lower the floor will conflict on the record. Fine-grained per-rule entries conflict less badly than one global number, and a rebase that keeps the lower of the two values is the usual resolution. **Concurrency.** With a three-week release train and several teams merging daily, the floor drops in small steps from many changes. It is worth checking that the recorded value in the main line is the lowest observed, not whichever branch merged last. **Moves and renames.** Expect a bulk unmatch. Doing the move alone, updating the record in the same change, and saying so in the change description keeps the register credible. **Scope.** Some pipelines gate only the lines a change touches rather than comparing against a stored record at all; that is a different strategy with its own tradeoffs, and it is chosen at the pipeline level rather than being a property of the baseline. ## What to say in an interview Name the two designs, say why line numbers are unusable as identity, give the fix-one-add-one example that a count ratchet cannot see, and finish on direction: the floor moves down automatically and up only with a reviewed reason. That sequence answers the question and shows you have operated one rather than read about it.

  • Why is a raw line number a poor part of a finding's identity?
    Because it describes where the finding sits in the file rather than what the finding is. Adding an import, a comment or an unrelated method above it shifts every finding below, so the gate would unmatch them all and report a file of old, accepted issues as brand new work. A hash over the code element or the surrounding text survives those edits.
  • A change fixes one violation and introduces another of the same rule. Which design catches it?
    The entry-list baseline. It notices that a recorded fingerprint has vanished while an unrecognised one has appeared, and fails on the unrecognised finding. A count ratchet sees the same total before and after and passes, which is its main weakness; keeping counts per rule and per module shrinks the window in which such a swap can hide.
  • How should a large file rename or reformat be handled when the fingerprints all break?
    As its own change. Do the move or the reformat with no behaviour change, update the stored record in the same change, and say in the description that the entries were remapped rather than that new findings were accepted. Mixing a bulk remap into a feature change is how genuinely new findings slip in unnoticed.

saying these in an interview costs you the question

  • Keys baseline entries by file and line number
  • Thinks a count ratchet detects a fixed-one-added-one swap
  • Lets the gate raise its own floor when the count rises
  • Regenerates the record as the routine fix for a red build
  • Assumes a rename leaves fingerprints intact
  • Stores one project-wide total instead of per-rule counts

context