JDS5 No-BS AI

A gate fails in two directions — only one of them complains

By Daniel S. · July 23, 2026

TL;DR: Every gate makes two promises: it fires on what it should, and it stays quiet on what it shouldn't. Break the second promise and you find out in minutes, because a false positive blocks you and you go fix it. Break the first and nothing happens — no error, no noise, just a file that quietly stops existing, a push that clears without review, or a rule that matches nothing at all. We test the loud direction because it's the one that annoys us. In one week I shipped or inherited three gates that were broken in the quiet direction, including one I broke myself while fixing the previous one — and the over-fire test on that fix passed perfectly.

The asymmetry

A gate that over-fires is self-correcting. It blocks a commit you needed to make, you swear at it, you narrow the rule, done. The feedback is immediate and it lands on the person who can fix it.

A gate that under-fires produces nothing. No exit code, no log line, no blocked operation. The system looks exactly like a system where everything is fine — because "everything is fine" and "I am not looking" render identically.

So the failure modes are not symmetric, and neither is the attention we give them. Ask yourself when you last watched a gate correctly block something on purpose. Now ask when you last watched it correctly allow something — and verified it would have blocked the bad version.

Three instances, all real, all from one week.

1. The ignore rule that hid three files and protected none

I keep a rule in .gitignore so a document containing my business tax ID can never be committed by accident. It looked like this:

**/*EIN*

Reasonable. It had been there for weeks, silently doing its job.

It was not doing its job. On macOS, git sets core.ignorecase=true, which makes ignore patterns match case-insensitively — so *EIN* matches the letters ein anywhere inside any filename, including in ordinary English words.

I found it because a file went missing. A note I had just written wasn't in git status. Not modified, not untracked — absent. The filename contained the phrase "being fixed."

b·ein·g.

So I audited the rule properly: list everything it was currently hiding, and check what it was actually protecting.

# what is this rule ACTUALLY hiding right now?
git status --porcelain --ignored | awk '$1=="!!"{print substr($0,4)}' | while read -r p; do
  git check-ignore -v "$p" | grep -q 'EIN' && echo "HIDDEN: $p"
done

Three files. A note whose title contained being. An article about business formation that legitimately had EIN in the title. And a note about someone named St·ein·berger.

Documents containing my actual tax ID: zero. The real paperwork was covered by a different, explicit path rule two lines above. The pattern's entire measured effect was hiding three innocent files.

That is a gate at 100% false positives and 0% true positives, running for weeks, generating no complaints — because its false positives were silent. Files don't announce that they've been ignored. They just aren't there, and you don't notice the absence of a thing you weren't looking for.

2. The attestation that cleared a range someone else had already pushed

I have a gate that requires a human-or-agent review pass over the content of a push, recorded against the exact bytes being pushed. Run the review, record it, push.

I ran it. It said:

examined: 0 added line(s) across 0 file(s)
(no added content — nothing that can leak)

Zero exit code. Reassuring parenthetical. I had committed thirty seconds earlier.

What had happened: a second session on the same machine — an old one, backed up behind a long offline stretch — committed and pushed at the same moment, and carried my commit to the remote inside its push. By the time my review tool looked, the range was empty, because my commit was already gone upstream.

The statement was true. There genuinely was no added content in the range it examined. It was also completely useless as evidence, and here's the test that shows why:

If the range had been stolen out from under me, would this output have looked any different?

No. Identical. A legitimately empty push and a push someone else had already shipped printed the same benign line. A message that is true in both worlds tells you which world you're in exactly never.

The fix wasn't stricter enforcement — the tool's verdict logic was fine. It was making the output discriminating: print the commit count of the range, and split the two cases apart.

range   : origin/main..HEAD (0 commit(s))

🔶 RECORDED NOTHING — AND THIS CLEARS NOTHING.
   If you JUST COMMITTED, that is the tell: your commits are already
   on the remote — something else pushed them.

Same verdict, same exit code. The difference is that the operator can now tell the two situations apart, which is the only thing that was ever wrong.

3. The fix that couldn't fire — and the test that missed it

Now the one I did to myself.

Fixing #1, I replaced the substring rule with delimiter-anchored patterns, so ein only matches when it's a standalone token rather than buried in a word. I annotated each line, because a rule this fiddly deserves an explanation:

**/EIN*                   # name STARTS with the token
**/*[-_. ]EIN[-_. ]*      # token DELIMITED mid-name

Then I tested it. Eleven ordinary filenames — being, Steinberger, protein, vein, reinstall — every one correctly visible. The over-fire problem was gone. Eleven for eleven.

Then I tested the other direction, mostly out of habit, and got zero for ten. EIN letter.pdf: not ignored. LLC EIN confirmation.pdf: not ignored. Nothing was ignored. The gate matched nothing at all.

.gitignore does not support trailing comments. A # only begins a comment at the start of a line. Mid-line, it's just more pattern. Every one of my rules was silently the literal string **/EIN* # name STARTS with the token, which matches no file that has ever existed.

Sit with the shape of that. I had replaced an over-firing gate with a gate that could not fire, and my test suite was green — because I had written tests for the direction that had just burned me. The over-fire suite passed perfectly, and it passed because nothing was being matched. The very thing that made the gate useless is what made my tests look good.

If I had shipped on that green, I'd have replaced a bad control with no control, and the .gitignore would still have looked like a page of careful security work.

The two-line test

All three collapse into one habit. For any gate, write down both lists before you write the rule:

  1. Things that MUST be caught — and watch each one get caught.
  2. Things that MUST pass — and watch each one pass.

Then run both. Every time. The second list is the one you'll write naturally, because it's the pain you remember. The first list is the one that's actually protecting you.

For the ignore rule it's about fifteen lines of shell, and it's the difference between a security control and a decoration:

# MUST be ignored
for n in "EIN letter.pdf" "LLC EIN confirmation.pdf" "irs-ein-notice.pdf"; do
  git check-ignore -q "$n" && echo "ok  ignored   $n" || echo "FAIL missed    $n"
done

# MUST NOT be ignored
for n in "being-fixed.md" "Steinberger.md" "protein-shake.md" "vein.md"; do
  git check-ignore -q "$n" && echo "FAIL over-fired $n" || echo "ok  visible    $n"
done

Twenty-one cases, both directions, and the final version passes all of them. The first version passed eleven and failed ten while looking, from the only angle I had bothered to check, completely correct.

What this adds to the other two

I've written twice recently about controls that aren't what they appear: a control you switched on is not a control that's in force — configuration is not enforcement — and prove it can fail before you trust it passing — a gate that has only ever returned green may be incapable of returning red.

This is the third axis, and it's the one that got me even after writing the other two. Direction. A gate can be in force. It can be capable of failing. And it can still be pointed at the wrong half of its own job, with a test suite that only exercises the half that complains.

The uncomfortable part isn't that these are hard to find. It's that all three were found by accident — a file I happened to notice was missing, a HEAD that didn't match, a test I ran out of habit rather than design. Nothing in the system was going to tell me. That's what "fails in the quiet direction" means, and it's why the second list is not optional.