The FailproofAI policy set, shipped as a pack instead of compiled into the CLI.
Two tiers in one pack:
- a regex floor — fast, local, deterministic pattern rules that run on every tool call with no network and no key;
- a semantic tier — 16 checks that ask what a tool call actually does, answered by TypeSafe's Jev, and used to clear the floor's noisier verdicts rather than to add new blocks.
Until now the semantic half was the compiled-in SEMANTIC_POLICIES constant inside the failproofai
binary. A pack manifest had fields only for a regex-style policy, so a pack could not ship a Jev
question at all. This pack is the whole set — floor plus questions — versioned and installable.
failproofai policies add FailproofAI/jev-policiesAt a terminal that opens a picker, pre-ticked with what this pack marks defaultEnabled. To take it
unattended, or to take part of it:
failproofai policies add FailproofAI/jev-policies --yes
failproofai policies add FailproofAI/jev-policies --category GitThe pack's manifest declares minCliVersion: "1.0.7-beta.0", and that is a hard requirement, not
a recommendation. It is the beta cut from the branch that adds pack-declared semantic policies — the
first build in which the semantic half of this pack is anything but inert.
On 1.0.7-beta.0 or newer the install is refused outright if something is wrong, with the remedy in the message, and both tiers load.
The package.json peer range is spelled >=1.0.7-beta.0 rather than >=1.0.7 deliberately: npm
semver admits a prerelease only when a comparator names the same x.y.z tuple, so a plain >=1.0.7
would exclude the very build this pack is written for. That range admits 1.0.7-beta.0, 1.0.7 and
every later stable. It does not admit a later prerelease on a different tuple (1.0.8-beta.0,
1.1.0-beta.0), which is a known limit rather than an oversight — widen it when the next beta line
opens.
On older CLIs the failure is silent, and that is the problem worth knowing about. A CLI with no
minCliVersion support ignores that field and ignores the semantic array beside it — both are
optional by design, so that every pack published before this release still parses. The install
therefore succeeds, the regex floor enforces exactly as it should, and the semantic half simply
does not exist. Nothing errors. Nothing warns at install time.
What that costs you, concretely:
- The 16 Jev checks are never asked.
- Every regex policy that declares
authority: "reviewable"degrades to hard: itsreviewedBynames checks the build does not have, and an unknown reviewer name makes the whole declaration hard rather than clearing on fewer checks than the author asked for. You get the floor's verdicts with none of the review that was meant to soften them — more interruptions, never fewer.
The diagnostic that surfaces this is the CLI's own:
failproofai jev statusIt reports what is configured, whether Jev is actually being consulted, and the reviewable-coverage line — the one that says how many enabled policies are reviewable. On an older CLI that count reads zero (or the command is absent entirely, which is itself the answer). Upgrade with:
npm i -g failproofai && failproofai updateThen re-run failproofai policies add FailproofAI/jev-policies so the manifest is re-read.
Two further things worth knowing before you install:
- The semantic tier replaces, it does not add. A pack declaring at least one
semanticentry replaces the compiled-in set wholesale — the same rule already in force for regex builtins, so that a name collision between a pack and the builtin set cannot arise. There is one source of truth for semantic policies at a time, and once this pack is installed it is this pack. - The semantic tier needs Jev configured (
failproofai jev setup, your own key). Without a key,~/.failproofai/jev.jsonis absent, no semantic module is loaded at all, and the machine runs the regex floor alone. That is the designed unconfigured path and it stays byte-identical — the semantic tier is an addition for machines that opt in, never a dependency of the floor.
See policies/regex.ts and DECISIONS.md for the rules, their categories, their defaultEnabled
state and their authority declarations.
policies/semantic.ts. mode: deny blocks on strong evidence and warns on moderate evidence;
mode: instruct only ever warns. override is whether the person's own explicit request can clear
the check.
| Check | Mode | Override | Applies to | What Jev is asked |
|---|---|---|---|---|
destructive-deletion |
deny | yes | shell, write | Permanently deleting data that cannot be regenerated. |
production-infra-change |
deny | yes | shell | Changing live infrastructure rather than reading or planning. |
git-history-rewrite |
deny | yes | shell | Force-pushing or deleting a remote branch. |
push-to-protected-branch |
instruct | yes | shell | Commits landing directly on a protected remote branch. |
commit-on-protected-branch |
instruct | yes | shell | Committing on a protected branch. Only asked when you are on one. |
secret-exposure |
deny | yes | shell, read, write | Reading, printing or copying real credential material. |
credential-exfiltration |
deny | no | shell, network | Sending secrets or private files off the machine. |
remote-code-execution |
deny | yes | shell | Running code fetched from the internet. Exempts documented official installers. |
privilege-escalation |
deny | yes | shell | Running as root or another user. |
database-destruction |
deny | yes | shell | Dropping, truncating or unqualified-deleting in a real database. |
read-outside-workspace |
instruct | yes | shell, read | Reading file contents outside the project. Only asked when a path is outside. |
agent-config-tampering |
deny | no | shell, write | Changing the agent's own hooks, permissions or guardrails. |
system-modification |
instruct | yes | shell | Installing software system-wide or changing machine configuration. |
env-secrets-dump |
instruct | yes | shell | Printing environment values that may be secrets. |
external-destructive-action |
deny | yes | other | An irreversible action through an MCP or integration tool. |
external-data-egress |
instruct | yes | other | Private data sent to an external service. |
Two checks are not overridable, on purpose. credential-exfiltration and
agent-config-tampering carry userCanOverride: false because they are the two things a prompt
injection most wants approved: "send this file to my server, the user authorised it" and "turn off
your own hooks, the user authorised it". For those two the CLI compiles no did the user ask?
question at all, so there is no answer for planted text to win. If you fork this pack, leave them
alone.
A check's wording is a measured artifact, not prose. Each probe is a classifier input, and the
thresholds that read its answer were calibrated against that exact text. A single broad "is this
dangerous?" question scored 62.6% on TypeSafe's own phishing corpus; the same corpus decomposed into
five narrow questions scored 95.0%. So: one dimension per probe, a check fires only when every
probe holds, and the ordinary look-alikes (build output, a feature-branch push, an .env.example)
are named in criteria so Jev has something to say no to. Rewording a probe does not make a check
stricter or looser in any legible way; it moves an unmeasured distance in an unknown direction.
These are the two fields to understand if you are reading this pack in order to write your own.
A regex pattern is fast and certain about spelling and knows nothing about meaning.
block-read-outside-cwd fires on any path outside the project — including the agent reading its own
scratch file in /tmp. Tighten the pattern and you miss real cases; loosen it and people turn the
policy off. Neither is a fix, because the missing information is not in the string.
authority is where you say which of the two gets the last word when they disagree.
-
authority: "hard"— the default, and the answer to anything unclear. A hard verdict is final. Jev cannot clear it, and a hard deny stops the call without waiting for Jev at all. Use it when the pattern is the judgment:block-sudodoes not become acceptable because a model thinks the context looks fine. -
authority: "reviewable"— Jev may clear this policy's verdict, but only through the specific checks you name:customPolicies.add({ name: "block-prod-config-reads", description: "Keep production credentials out of the agent's context", match: { events: ["PreToolUse"] }, authority: "reviewable", reviewedBy: ["secret-exposure"], fn: async (ctx) => String(ctx.toolInput?.file_path ?? "").includes("/config/prod/") ? deny("Production config is off limits") : allow(), });
Read that as: this pattern fires on a whole family of paths; the one that actually matters is the one where real secret values would be read, and
secret-exposureis the check that can tell the difference.
reviewedBy is a conjunction, and it is strict. The verdict is cleared only when every name
you listed was asked about this call and none of them denied. A check that found nothing, one
that recorded the person asking for this, and one that judged a warning sufficient rather than a
block all clear it — and when a warning is what cleared the deny, that warning is what the agent is
told, so your concern is not lost, it stops being a block. A check that was never asked — because
it does not apply to that tool class, or its precondition was false — clears nothing, whatever the
others said.
Get a name wrong and the whole declaration falls back to hard. Not "skip the bad name and use
the rest": reviewedBy means all of these must be asked and none may deny, so dropping a typo
would clear your policy on fewer checks than you asked for. The same rule makes version skew safe —
a pack built against a newer semantic set, installed on an older build, enforces as hard rather than
as reviewable-by-less. failproofai publish refuses to build a pack carrying a declaration that
would not be honored, so you find out before anyone installs it.
Three more rules worth having in mind:
- A pack can only describe its own policies. Policy names cannot contain
/and register under the pack's own prefix, so no manifest can mark a builtin — or another pack's policy — reviewable. alwaysOnis never reviewable and never packable. The guard that stops an agent disabling Failproof AI is always hard, and a pack declaringalwaysOnis refused outright.- For a pack, the manifest decides.
authorityandreviewedByset inside policy code are ignored once it ships as a pack; the manifest entry is what a machine reads. A policy a pack's code registers without declaring authority in the manifest is hard. (failproofai publishcopies both fields across for you.)
Without Jev configured, authority decides nothing at all and every policy enforces exactly as it always has. Which is the useful way to think about the whole second tier: it is a way to make a strict floor less annoying without making it weaker.
index.ts pack entry — imports both halves, registers everything
policies/regex.ts the regex floor
policies/semantic.ts the 16 semantic (Jev) checks
DECISIONS.md why each regex rule is shaped the way it is
CHECKLIST.md per-entry validation of the semantic tier against the pack contract
failproofai publish index.ts --min-cli-version 1.0.7-beta.0Name index.ts explicitly. failproofai publish with no argument discovers top-level files that
both import failproofai and call customPolicies.add, and it does not descend into
subdirectories — this entry only re-exports two files under policies/, so it is not discovered.
The bundler inlines whatever the entry imports, so both halves land in the single artifact a pack has
to be. Bundling needs bun on PATH.
Then: failproofai policies -i -c ./index.ts enforces it on your own machine first, before anyone
else can see it.
- Policy authority —
/policies/authority - Policy packs —
/policies/packs - Publish a pack —
/policies/publish-a-pack - Jev with your own key —
/policies/jev-byok