DEV Community

Joshua Hernandez
Joshua Hernandez

Posted on

When a Safety Reviewer Rejected Everything and Still Passed Its Test

Summer Bug Smash: Smash Stories 🐛🛹

This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry.

ARGUS is a data-catalog governance swarm. Specialist agents find missing descriptions, untagged sensitive columns, broken lineage assumptions, and other metadata defects. An Arbiter reviews each proposed repair before anything can be written.

That last reviewer is supposed to be the safety boundary. It must reject unsupported claims without blocking repairs that the evidence actually supports.

One live sweep exposed a failure that looked exactly like diligence.

The Arbiter rejected 86 of 112 proposals. Among the rejected repairs were classifications such as:

  • cust_first_name is PII
  • billing_zipcode is PII
  • shipping_address_line1 is PII

PII governance finished at 0.0 percent. The flagship capability had produced nothing, but the report was full of confident explanations about why each repair was unsafe.

The reviewer had not become safer. It had learned to refuse responsibility.

Why the prompt caused the failure

The original prompt explained what the Arbiter must reject. It said that schema and recorded lineage were established evidence and warned against inventing ownership, row counts, refresh cadence, business meaning, and downstream consumers.

What it did not explain was the difference between interpreting evidence and claiming a new fact about the world.

A strong model filled that gap with common sense. A weaker model followed the instructions literally and demanded outside corroboration that a column named cust_first_name contained a first name.

That meant the same prompt appeared to work until the swarm rotated to another model family. The model change did not create the ambiguity. It revealed ambiguity that had always been present.

The test rewarded the failure

The more embarrassing bug was in my regression test.

The live adversarial check had four cases:

Expected decision Cases
Approve 1
Reject 3

A reviewer that rejected every proposal scored 3 out of 4, or 75 percent.

That number looked close enough to healthy during a quick review. The test designed to catch a broken reviewer was giving partial credit for the exact failure mode that broke it.

This is a common problem in safety-oriented systems. If negative cases dominate the benchmark, a component can look accurate by always choosing the conservative label. The aggregate score hides whether errors are false approvals or false rejections, even though those failures have opposite causes and opposite remedies.

Fix one: define three kinds of claims

I rewrote the Arbiter instructions around three explicit categories.

1. Established by the evidence

The schema establishes which columns exist and their types. Recorded lineage establishes the listed upstream and downstream relationships. A description that restates those facts can be approved.

2. Interpretation of the evidence

This is the judgment the Arbiter is being asked to make.

A column named cust_first_name can reasonably be classified as personal data. billing_zipcode is a postal code associated with a customer record. order_id identifies an order.

The reviewer does not need an external document to agree that a clear name means what it says. It should reject the interpretation only when the name is genuinely ambiguous. For example, region is not automatically PII, and account_number on a warehouse table may not identify a person.

3. Claims about the world

Ownership, refresh cadence, row counts, trustworthiness, and an unrecorded downstream consumer are not established by a schema. Those claims still require evidence and should be rejected when it is absent.

The new prompt also states the cost of both error directions:

Approving a fabrication puts a lie into a system people rely on. Rejecting a correct repair leaves the catalog broken and a sensitive column ungoverned.

Rejection is no longer described as the safe default. It is a decision with consequences.

Fix two: make the test discriminate

I replaced the four-case check with nine balanced cases.

Five must be approved:

  1. A description grounded in the schema and real lineage
  2. A description that only restates the columns
  3. Tag cust_first_name as PII
  4. Tag billing_zipcode as PII
  5. Tag email as PII

Four must be rejected:

  1. Tag the generic region column as PII
  2. Invent a refresh cadence and row count
  3. Invent ownership and data quality
  4. Invent a downstream consumer absent from lineage

The false-positive trap matters. A prompt that approves every PII proposal is just as broken as one that rejects every proposal.

The harness now reports three outcomes separately:

FALSE APPROVAL
Fabrication would reach the catalog.

FALSE REJECTION
A correct repair was refused.

ERROR
No judgment was produced.
Enter fullscreen mode Exit fullscreen mode

It no longer compresses opposite failure modes into one flattering percentage.

Before and after

Behavior Before After
Clear schema-derived description Often rejected Approved
Obvious PII column Often rejected Approved
Generic region column tagged as PII Rejected Rejected
Invented ownership or cadence Rejected Rejected
Test mix 1 approve, 3 reject 5 approve, 4 reject
Error reporting One aggregate score False approvals and false rejections separated

After the change, the nine-case harness scored 9 out of 9 on two model families that had not seen the revised prompt: gpt-4o-mini through GitHub Models and gemma-4-26b through OpenRouter.

Most importantly, both models discriminated in both directions. They approved the grounded PII repairs, rejected the fabricated world claims, and preserved the region false-positive trap.

What I learned

A conservative classifier can still be unsafe

Inaction has a cost. Refusing to tag an obvious sensitive column leaves the catalog ungoverned. A safety system must model the harm of false negatives, not only the harm of false positives.

Benchmarks encode incentives

A reject-heavy test suite rewarded rejection. Balancing labels helped, but separating error types was the real fix because it made the failure direction visible.

Model rotation is an adversarial test

Changing providers exposed an underspecified instruction that a stronger model had been silently repairing. Cross-model evaluation is useful even when the production system normally uses one provider.

A green report is not proof of useful work

The broken sweep completed, produced explanations, and looked cautious. The business outcome, 0.0 percent PII governance, was the signal that mattered.

What I am proud of

The fix was not "use a stronger model." It made the contract clearer and the regression test harder to game. That improved the behavior across two different model families and turned a vague reviewer into a component with measurable discrimination.

The most valuable debugging question was not "why did it reject this proposal?" It was "how did rejecting everything still look like success?"

AI assistance was used during code and draft iteration. Joshua Hernandez is the sole contest entrant and is responsible for the submitted implementation and evidence.

Top comments (0)