alfiz-verify is a static analysis tool that enforces Alfiz’s four-point wiring checklist at build time. It parses your TypeScript source files (no type-checker invocation needed) and scans for permission check and gate call sites, coverage gaps, and catalog convention violations. Running it in CI means a new server action with no gate, a typo’d permission key, or a visibility affordance used as a gate becomes a build failure, not a production bug.
Installation
Usage
alfiz-verify reads alfiz-verify.config.json in the current working directory. Pass --config <path> to use a different location.
The codegen subcommand
.ts module. Without --out it writes to stdout. --prefix names the emitted aliases (<Prefix>Key, <Prefix>Pattern, <Prefix>ScopeType, <Prefix>ScopeId); it defaults to Alfiz.
defineCatalog derives those unions from your catalog literal, so code importing the source module already has them. This is for everything that consumes the published document instead. A document is data rather than a literal, so its keys reach the type system only through codegen:
importedKeys<ZoomKey>({ ... }). Members are sorted, so regenerating diffs exactly the catalog change. A malformed document fails here, loudly, rather than emitting types from garbage.
The programmatic form
When you generate inside a build script instead of a shell step, callgenerateCatalogTypes directly. It returns the module source as a string, and writing it is yours to do.
What it checks
alfiz-verify runs six categories of checks on every scan. Every finding carries a rule name, the same name you use to suppress it per line, and the same name the VerifyIssue shape reports:
1. Typed key validation
Every string literal passed to a gate function (can, require, gateAction, etc.) that belongs to a known namespace is validated against the catalog. Unknown keys produce errors; near-misses (group paths where patterns are required) produce a specific suggestion:
2. Coverage linting
Warnings on catalog leaves that no gate or nav item references. Errors on exported async functions in server files that contain no gate call at all:alfiz_internal.* keys are exempt from coverage warnings, because they are gated inside Alfiz’s own admin surfaces, not your application code.3. Gate-shape linting
Errors when a visibility affordance (canAny, requireAny, or holds) is used directly as a gate in a server file. Visibility affordances tell you whether any matching key is held somewhere; they are not access control decisions. (heldKeys is a property access rather than a call, so it cannot read as a gate.)
4. Catalog linting
Convention violations detected by runninglintCatalog on the loaded catalog document:
5. Import validation
Permissions from a namespace your catalog neither owns nor imports are reported as implicit imports: an error when no import source is configured, a warning when one is. A reach beyond what an import does cover is always an error, with a did-you-mean drawn from the namespace owner’s published document:importSource and implicitImports, and point the CLI at your import manifest so it knows what you legitimately reference. Verification is offline and deterministic: it never fetches a catalog to grade your code.
6. The client-reach guard
Errors when an identifier you have named inforbidClientIdentifiers appears in a "use client" module. This is the guard that keeps service-key material out of the bundle:
Rule inventory
The command exits
1 when any error-severity issue is found. Warnings do not fail the build.
Emitting the catalog
Before runningalfiz-verify, emit your catalog to a JSON file that the CLI can load. Use catalog.toDocument():
--experimental-strip-types needs Node 22.6 or newer. On an older runtime, compile first and run the emitter against the built output.Exit codes
Output format
Each issue is printed to stderr in the format:
A summary line follows all issues:
File-level opt-out
Files that authenticate outside the catalog by design (system trust domains, deploy-key surfaces, internal health endpoints) can opt out of scanning with a file-header pragma:"use server" / "use client" / "use strict" directives all qualify. The reason text is required: an unexplained exemption in a security tool is how exemptions rot.
GitHub Actions example
Built-in gate and visibility names
alfiz-verify recognizes the following names as gates and visibility affordances out of the box. The gateNames and visibilityNames config fields are additive, so your custom wrappers extend the defaults; the defaults are always included via the CLI.
Default gate names: can, fresh, require, requirePermission, gateAction, apiRequirePermission
Default visibility names: canAny, requireAny, holds
Default server file patterns: app/.*route\.(t|j)sx?$, pages/api/. The first is anchored, so it matches only paths ending in route.ts(x) / route.js(x)