failproofai publish writes all three from the policy files in front of it, creates the release, and uploads them.
1. Write the policies
Start from something that already works rather than a template with blanks:<name>.mjs, and stops — no network, no git, nothing published. The file it writes is one policy that already blocks git push --force. It refuses to overwrite a file that exists.
Policies use the same API as any custom policy. Two extra fields matter for a pack:
defaultEnabled defaults to false when you omit it. A plain failproofai policies add switches on only what you marked — installing a stranger’s every policy unattended is not a decision the installer should make for its user.
A policy may also declare authority: "reviewable" with a reviewedBy list, which lets the Jev semantic evaluator clear its verdict on machines that configure Jev. failproofai publish copies both into the manifest, and a machine reads them from there; it refuses to build if a declaration would not be honored, such as a misspelled check name or, in a pack that declares Jev checks, a check it does not declare. Leave them out and the policy is hard. See Policy authority.
Jev checks in a pack
A pack can also carry Jev checks —semanticPolicies.add() — beside its policies, or on their own. A pack is the only way a Jev check reaches a machine: in a local policy file it is never asked. publish validates each one with the loader’s rules and writes them to the manifest’s semantic array.
- Limits. At most 24 checks per pack. Together, their questions must fit what one Jev request has room for, less what the 16
FailproofAI/jev-policieschecks take first where both are installed (about 9,100 characters are left) unless the repository is FailproofAI’s;publishrefuses a pack over that budget and prints the numbers. Other packs’ checks share the same room, so a check that does not fit beside them is not asked there:policies addnames it. - They are the only checks Jev asks. Failproof AI ships no Jev checks, so a machine asks exactly what its installed packs declare — yours, beside
FailproofAI/jev-policieswhere that is installed. Checks from several packs add up; when their questions overflow what one Jev request can carry, FailproofAI’s checks are kept first and the rest are dropped with a warning. A name two packs declare differently is honoured for neither — every policy naming it stays hard — while identical declarations of one name are fine. The 16FailproofAI/jev-policiesnames are reserved: declared by a pack not installed from a FailproofAI repository, that pack’s version is never asked, sopublishrefuses one there; pick names of your own. reviewedBynames the pack’s own checks. When the pack declares any,publishjudges everyreviewedByagainst those names only, so aFailproofAI/jev-policiesname the pack does not declare itself is refused. A pack with no checks of its own is judged against those sixteen names.- Set
--min-cli-version. A CLI too old for Jev checks ignores thesemanticarray and installs the rest, so pass--min-cli-version <version>for a pack that carries checks. It is written to the manifest asminCliVersion: an older CLI refuses to install the pack, and refuses to load it if it is already installed — which, for anenforcepack with policies, denies what those policies cover (see When a pack will not load). The value must be plain semver orpublishrefuses it; a CLI that cannot compare a stored value warns and ignores it. For a pack with checks it must be at least1.0.8-beta.0, the first release that runs a pack’s checks as published (1.0.7 ignores them, 1.0.7-beta.x replaces the built-in checks with them):publishrefuses a lower value, and writes1.0.8-beta.0when you pass none.
customPolicies.add) is refused by a CLI too old for Jev checks (“pack manifest declares no policies”) and ignored if already installed. If a machine refuses such a pack when loading it (a minCliVersion it does not meet, a missing or altered artifact), it reports why and denies nothing, because the pack blocks nothing without Jev. Older builds do not all agree: 1.0.7 loads one as an empty pack but denies every tool call if its artifact is missing or altered, and a Jev-capable prerelease before 1.0.8-beta.0 (such as 1.0.7-beta.2) denies every tool call whenever it refuses one, including for a minCliVersion above it. So before rolling a machine back, remove the pack (failproofai policies remove <id>); publish prints this reminder for a pack of Jev checks alone.
Write as many files as you like; one per category reads well. Every file in the directory that registers policies is bundled into the single artifact a pack has to be.
2. Try it here first
Before anyone else can see it, enforce the file on this machine:3. Publish it
- Finds the policy files here by content — those that import
failproofaiand callcustomPolicies.addorsemanticPolicies.add— rather than by filename, so it findsguards.mjsand ignores an unrelatedpolicies.mjs. It does not descend into subdirectories, so a test fixture is never swept up by accident. - Reads the repo from
git remote get-url origin, in the file’s directory rather than yours, and decides the version. - Finds your credential:
GITHUB_TOKEN,GH_TOKEN, orgh auth login. It needs release-write and nothing else, and is never printed. - Creates the repository if it does not exist. This happens before the build, so a pack refused in the next step can leave a new repository behind with no release in it.
- Builds the three assets, validating them with the loader’s own rules — the same code that decides what may install on a stranger’s machine — so a pack that could never install fails here, where you can still fix it.
- Creates or reuses the release and uploads, replacing assets of the same name.
The asset names are fixed — they are what a consumer’s CLI constructs its URLs from, with no API call and no discovery.
Refused at build time: an id that is not
publisher/name, a policy name containing /, a policy declaring alwaysOn, a missing description, category or match, an entry that registers nothing, an entry that imports local files, and a Jev check named after a built-in check unless the repository is FailproofAI’s.
Override anything it decided:
--id sets the pack id when it should differ from the repo, --tag sets the release’s tag, --notes replaces the generated release notes — which is where policies show --releases reads each release’s counts and commit from — --out chooses where the assets are written (default dist-pack), --min-cli-version sets the oldest CLI that may install the pack (above), and --dry-run builds them without publishing and needs no credential.
Anyone can now install it with failproofai policies add acme/support-agent. See policy packs for pinning a version and taking only part of one.
List it on the policy hub
Add thefailproofai-policies topic to the repository on GitHub. There is no submission form and no approval queue: the policy hub’s crawler picks the repository up on its next pass. The topic only puts it up for consideration — what lists it is a release whose manifest verifies against its own SHA256SUMS and parses under the same rules the CLI uses, which is exactly what failproofai publish produces.
How the version is decided
The version is the commit you are publishing from — its short sha, twelve characters:a1b2c3d4e5f6. There is nothing to pick and nothing to increment, and the version names exactly where the bytes came from, so publishing the same source twice gives the same version.
It is read from the tree in front of you, never from the repository’s releases, so a fresh clone and an air-gapped machine compute the same answer without asking GitHub what happened before.
Because the version names a commit, that commit has to exist. At a terminal, publish makes it for you: it initialises a repository when there is none, and commits changed policy files before it builds. It refuses instead — naming --version as the way out — when it runs without a terminal (a commit made on a CI runner would exist nowhere else), when files other than the policies are uncommitted, or in a checkout that has no commits yet. A tag on HEAD wins over the sha — someone who tagged v1.2.0 has said what this release is.
A sha carries no ordering of its own, so use failproofai policies show <owner>/<repo> --releases to see which release came first — newest at the top.
Shipping a new version
Commit the change and runfailproofai publish again — the new commit is the new version. Consumers run the same failproofai policies add. Without a terminal, or with a selection flag, they keep the subset they had chosen and a policy they turned off stays off; at a terminal with no flag, the picker opens pre-ticked with your defaults and their answer replaces their selection.
Changing a policy’s name is a breaking change: a machine that had turned it off is turning off a name that no longer exists, and the new name arrives at whatever defaultEnabled says.
What your users are trusting
SHA256SUMS lives in the same release as the artifact, so it proves the bytes are the ones you published — not who you are. Whoever can write to the repository can write both files. Your users’ protection is that the digest is pinned when they install, so what you shipped cannot change under them afterwards.
Publish from a repository whose write access you control, and treat a pack release like publishing a package.
The repository must also be public. Installs are anonymous HTTPS with no credential to offer, so an existing private repo is refused before anything is built or uploaded, and one publish creates is public for the same reason. --allow-private overrides that for somebody handing the three assets over another way, and says plainly that no policies add can reach them. Only the release matters: installs read releases/download/<tag>/<asset> and never touch your git tree.
Observe before you enforce
A manifest may declare"effect": "observe" — failproofai publish --effect observe is what sets it. Those policies run and their verdicts are recorded and discarded — nothing is blocked. An observe pack’s Jev checks are not asked at all, and neither are those of a pack installed with --cli for other agents. It is the way to measure a new rule against real traffic before it can interrupt anyone’s work.

