negative-invariant-testing · git:20261019.5842640 · 2026-10-19 · sha256 26b7a955ef3ee6ad
negative-invariant-testing git:20261019.5842640A
Immutable. This exact content never changes and is served at /api/v1/blob/26b7a955ef3ee6ad.
---
name: negative-invariant-testing
description: >
Use when a system has a load-bearing "never" constraint — never holds funds,
never logs secrets, never calls a forbidden API, never writes to a table,
never blocks the event loop. Behavioural tests can't observe a capability
that isn't exercised yet, so write a STRUCTURAL test that trips on the mere
existence of the forbidden mechanism. Triggers on "never does X", "must
not", "forbidden", "invariant", "safety constraint", "guardrail", "should
not be possible", and hardening a property a future edit could quietly
violate.
---
# Negative-Invariant Testing
**Type:** Open-source — client-agnostic methodology, no project-specific detail.
**Created by akbarsha — https://github.com/iamakbarsha1**
Distilled from a case where a load-bearing "the system never does X" constraint
had no test, so a future edit adding the forbidden construct would break it
invisibly.
**Licence:** Released under CC BY 4.0 — share and adapt for any purpose with
credit. Full text: `LICENSE` at the repository root.
**Feedback & Support:** If a rule here proves wrong or needs sharpening,
open an issue on the repository or contact the author at the profile link
above. If the problem is the agent not following a rule below rather than
the rule itself, that's an execution failure — acknowledge and correct it.
## The core rule
A constraint of the form "the system NEVER does X" can't be protected by a
behavioural test, because a behavioural test can only observe behaviour that
is exercised — and the whole point is that X is never exercised today. The
danger is a future edit that ADDS the forbidden capability; nothing runs it,
so nothing goes red. Protect a negative invariant with a STRUCTURAL test that
fails on the mere existence of the mechanism — introspect the schema, the AST,
the exports, the dependency list — so the guard trips the moment someone
introduces X, before it's ever called.
## When this fires
**Where:** Hardening a property stated as a "never" — never holds funds,
never logs secrets, never calls that API, never writes to that table — where
the risk is a future edit adding the capability, not today's code using it.
**The case this came from.** A design rested on the system never taking
custody of funds — it was the reason the thing needed no money-transmitter
posture at all. The test suite was thorough and every test passed, and none of
them touched the property, because nothing in the code could hold a balance
for a test to exercise. The guarantee the whole design leaned on was the one
thing with no test behind it, and that only surfaced when someone asked which
test would fail if it stopped being true.
**Another shape it takes.** *(Illustrative: A service promises it never writes
to the analytics database. A refactor imports the analytics client "just for
reads", the write method comes along with it, and six months later a cleanup
job uses it — no test could have gone red before that call existed.)*
## Checks
- **Assert on existence, not on execution.** Write the test against the
structure that would have to exist for X to be possible: a forbidden import
or export, a schema column that would let the system hold state it shouldn't,
a function signature, a config key, a dependency. The test passes because
the mechanism is absent and fails the instant it appears. *(A "never holds
custody of funds" property had no test; a structural check that fails if a
balance-holding field or transfer-out capability is ever added would trip on
the first line of the violation, not the first execution.)*
- **Name the invariant in the failure message.** A structural test that fires
is cryptic without context ("unexpected export found"). State the invariant
it protects and why, so the engineer who tripped it understands they've
crossed a designed line, not hit a lint quirk. *(Illustrative: A guard against
reintroducing a banned network call fired with only "forbidden pattern
matched"; the engineer read it as flaky lint noise, silenced the check, and
the banned call shipped on the next release.)*
- **Prefer the narrowest structural signal.** Trip on the specific forbidden
construct (this import, this column, this capability), not a broad proxy
that also flags legitimate code. A negative-invariant test that cries wolf
gets deleted, and then the invariant is unprotected again. *(Illustrative: A "never opens
a raw socket" guard matched any file containing the word "socket"; it
flagged a legitimate client-library import, got dismissed as noise, and was
deleted, leaving the real invariant unprotected.)*
## Pre-flight check — before you rely on a "never" constraint
- [ ] The invariant has a STRUCTURAL test (schema/AST/exports/deps), not only
a behavioural one.
- [ ] The test fails on the EXISTENCE of the forbidden mechanism, before it is
ever executed.
- [ ] The failure message names the invariant and why it matters.
- [ ] The structural signal is narrow enough not to flag legitimate code.
If any box is unchecked, the "never" is a comment, not a guard — go make it
structural.