Why fail closed

Every system that asks "is this allowed?" has to decide what happens when it cannot get an answer. There are only two choices.

For a chat feature, failing open is often fine. For an AI agent that can delete files, deploy to production, send money or change permissions, it is not.

Why agents make this worse

A person who cannot reach the approval system waits. An agent keeps going. It retries, picks another tool, or rationalises: "the policy service timed out, so I will proceed." Every code path that turns an error into permission is a path an agent will eventually find.

The classic fail-open bugs are small and look reasonable:

try {
  allowed = await checkPolicy(action);
} catch {
  allowed = true; // "don't block the agent on a flaky check"
}

That catch quietly converts "we could not check" into "yes".

Four outcomes, not a boolean

A boolean cannot tell a refusal from a breakdown. SensCheck keeps them apart:

OutcomeMeaningAction runs?
ALLOWEverything checked outYes
DENYPolicy or authority said noNo
REQUIRE_APPROVALA human, who is not the agent, must approve this exact actionNo
FAIL_CLOSEDGovernance could not be establishedNo

You respond differently to each. A DENY is working as intended. A FAIL_CLOSED is an incident to look at: something that should have answered did not.

What counts as "could not be established"

All of these resolve to FAIL_CLOSED: no policy configured, no authority provider, a provider that throws, times out or returns something malformed, authority that has expired, policies that disagree, an invalid or unknown-risk effect, a receipt that cannot be written, and an attempt to replay an effect that already ran.

UNKNOWN is not ALLOW.

The rule behind it

If something in the chain is missing, stale, conflicting, malformed, unavailable, timed out or unverifiable, the action does not run. Not "probably". Not "this once".

Try it: the simulator on the home page runs fifteen failure scenarios through the real engine.

← All lessons