Verification ladder
"It compiles" and "it is secure" are different claims. The ladder makes the difference legible: five levels, each earned, so a project can state what it proves instead of what it hopes.
The five levels
Levels are cumulative. A level only counts when every level below it holds, so achieved is the highest rung with unbroken support underneath, and it is -1 when even L0 fails.
- L0 typed contract -
nifra checkpasses: the frontend and backend cannot have silently diverged. - L1 route assurance - every route is classified, and the enforcement a rule requires is actually installed.
- L2 capability lockfile - declared effects match a reviewed lockfile, so a route that starts writing to the database shows up in review.
- L3 route manifest - the committed trust artifact still matches the routes, evidence, effects, and response classification it was emitted from.
- L4 invariant-tested - contract-derived invariants ran against an isolated executor with nothing skipped.
Run it
# What does this project prove right now?
nifra levels
# Fail CI below a level the project already reached.
nifra levels --min 2
# The same report an agent reads over MCP (nifra_levels).
nifra levels --jsonL0 typed contract ok
L1 route assurance ok
L2 capability lockfile ok
L3 route manifest FAIL manifest drifted from routes: run `nifra manifest emit`
L4 invariant-tested FAIL no isolated invariant executor configured
achieved: 2Every level that does not hold explains itself, so the output is a work list rather than a verdict. --min <n> is the CI floor: pin it to the level you have reached and the build fails when a change quietly costs you a rung.
Wiring the levels
L0 needs nothing. L1 through L4 hang off nifra.assurance.ts, and each one you configure is a level you can start claiming.
// nifra.assurance.ts
// doc-check: skip - policy rules and the isolated executor are app-specific
import { defineAssuranceConfig } from "@nifrajs/core/assurance"
import { backend } from "./backend"
export default defineAssuranceConfig({
source: backend,
policy: { rules: [/* ... */] },
capabilities: { lockfile: "capabilities.lock.json", policy: { /* ... */ } },
manifest: { path: "nifra.manifest.json" },
invariants: {
// L4 fuzzes the contract, so it needs an app you are willing to throw hostile
// input at. Point this at an isolated instance, never a live one.
executor: (request) => backend.fetch(request),
},
})L4 requires an explicit executor. Invariant runs send deliberately hostile input, so Nifra will not guess which app to point them at; a level that fuzzed your live server by default would be a footgun, and a skipped route is reported rather than passed over in silence.
For coding agents
The nifra_levels MCP tool returns the same { achieved, levels[] } report. An agent can read what its change proved, and a level that regressed comes back with the reason it broke.