TigerCheck - Deterministic safety and conformance enforcement for Zig.
The goal of tigercheck is to turn NASA Power of 10, TigerStyle, and TigerBeetle guidance into repeatable static checks with stable rule IDs and CI-friendly output.
WARNING: tigercheck is in the early stages of development, expect interface changes, rules modification, feature additions/removals/modifications.
- Deterministic checks: every rule maps to an explicit enforcement mechanism.
- Strict-by-default behavior: warnings and critical diagnostics fail CI.
- Global core policy: one strict mode with optional rule-specific offs.
- Precision gates: track FP/FN deltas per rule against a committed baseline.
tigercheck follows a strict TigerBeetle-style correctness boundary:
- Internal analyzer invariant violations are programmer bugs and fail fast (
panic/unreachable). - Code-under-analysis violations are emitted as diagnostics with stable rule IDs.
- Invalid CLI/user input is handled as regular usage errors (not internal invariant panics).
If you hit a panic with message prefix internal invariant violated:, report it as an analyzer bug with the command, target path, and the panic text.
Use the repo-pinned Zig toolchain so the zig binary and lib directory stay in sync with CI:
# macOS/Linux
./zig/download.sh
# Windows (PowerShell)
./zig/download.win.ps1If you plan to contribute, we recommend using these scripts rather than a global Zig install.
./zig/zig build
./zig/zig build run -- src/Common commands:
# Explain policy actions/suppressions
./zig/zig build run -- --explain-policy ./tigerbeetle/src
# Explain strict rewrites
./zig/zig build run -- --explain-strict ./src
# Dump call graph
./zig/zig build run -- --dump-graph ./srcDiagnostic shape:
[CRITICAL] src/foo.zig:88:17 [N02_BOUNDED_LOOPS] all loops must have static bounds; loop bound depends on runtime input
rewrite: clamp bound with explicit max and assert the cap
Machine-readable output:
./zig/zig build run -- --format json ./srcCLI diagnostics use the IDs below. This is the canonical catalog for NASA, TigerStyle, and TigerBeetle checks.
| Family | Rules | Status |
|---|---|---|
| NASA | 10 | Enforced |
| TigerStyle | 26 | Enforced |
| TigerBeetle | 5 | Enforced |
NASA Rules (N01-N10)
| ID | Enforcement Mechanism | Status |
|---|---|---|
N01_CONTROL_FLOW |
AST ban list (goto/setjmp/longjmp) + whole-program cycle detection |
Enforced |
N02_BOUNDED_LOOPS |
Interprocedural loop-bound proof with taint propagation | Enforced |
N03_STATIC_MEMORY |
Phase-colored allocation analysis (INIT=green, RUN=red, MIXED=amber) |
Enforced |
N04_FUNCTION_SIZE |
Normalized logical-line counter with hard function-length ceiling | Enforced |
N05_ASSERTION_DENSITY |
Per-function assertion density and side-effect safety checks | Enforced |
N06_SCOPE_MINIMIZATION |
Scope-width analysis, global audit, declaration-locality checks | Enforced |
N07_RETURN_AND_PARAM_CHECKS |
Unchecked non-void return detection + parameter-guard coverage | Enforced |
N08_PREPROCESSOR_OR_COMPTIME_BUDGET |
Comptime complexity budget + hidden-control-flow pattern bans | Enforced |
N09_POINTER_DISCIPLINE |
Pointer dereference-depth limits + function-pointer policy checks | Enforced |
N10_PEDANTIC_PIPELINE |
Strict compiler-warning gate plus analyzer integration | Enforced |
TigerStyle Rules (TS01-TS26)
| ID | Enforcement Mechanism | Status |
|---|---|---|
TS01_SIMPLE_FLOW |
Reuse N01_CONTROL_FLOW + explicit-flow style checks |
Enforced |
TS02_EXPLICIT_BOUNDS |
Reuse N02_BOUNDED_LOOPS + bounded-queue checks |
Enforced |
TS03_FIXED_WIDTH_TYPES |
Detect architecture-sized integer leakage (usize/isize) at boundaries |
Enforced |
TS04_ASSERTIONS |
Assertion presence for argument/return/invariant contracts | Enforced |
TS05_PAIR_ASSERT |
Pair-invariant enforcement across independent paths | Enforced |
TS06_POS_NEG_ASSERT |
Positive-space and negative-space assertion coverage | Enforced |
TS07_MEMORY_PHASE |
Reuse N03_STATIC_MEMORY startup-only allocation policy |
Enforced |
TS08_SCOPE |
Reuse N06_SCOPE_MINIMIZATION for smallest declaration scope |
Enforced |
TS09_FUNCTION_SHAPE |
TigerStyle hard limit function-size policy (70-line default limit) | Enforced |
TS10_PEDANTIC |
Zero-warning compiler policy gate | Enforced |
TS11_PACED_CONTROL |
Reject direct external-event mutation without batching boundary | Enforced |
TS12_PLANE_BOUNDARY |
Control-plane/data-plane boundary checks and complexity ceilings | Enforced |
TS13_BOOLEAN_SPLIT |
Reject compound boolean density in critical branches | Enforced |
TS14_POSITIVE_INVARIANTS |
Prefer positive invariant forms in boundary checks | Enforced |
TS15_ERROR_HANDLING |
Detect silent catch suppression and discarded fallible results | Enforced |
TS16_EXPLICIT_OPTIONS |
Detect default-option reliance in sensitive call sites | Enforced |
TS17_SNAKE_CASE |
Function/variable/file naming policy + structural naming checks | Enforced |
TS18_ACRONYM_CASE |
Acronym capitalization policy | Enforced |
TS19_UNIT_SUFFIX_ORDER |
Units/qualifiers suffix-order policy | Enforced |
TS20_NO_ABBREVIATION |
Abbreviation denylist with domain exceptions | Enforced |
TS21_CALLBACK_LAST |
Callback-last signature policy | Enforced |
TS22_STRUCT_ORDER |
Struct declaration order policy (fields, types, methods) | Enforced |
TS23_LARGE_ARG_POINTER |
By-value large-argument enforcement (*const preference) |
Enforced |
TS24_IN_PLACE_INIT |
Large-struct return-by-value detection + out-pointer recommendation | Enforced |
TS25_IF_BRACES |
If-brace safety policy (single-line exception) | Enforced |
TS26_LINE_LENGTH |
100-column line-length ceiling after normalization | Enforced |
TigerBeetle Rules (TB01-TB05)
| ID | Enforcement Mechanism | Status |
|---|---|---|
TB01_ALIASING |
Pointer-parameter overlap risk detector | Enforced |
TB02_ASSERT_ALIAS |
Ban qualified std.debug.assert; require local assert alias |
Enforced |
TB03_COPY_API |
Ban raw copy APIs; require explicit stdx copy helpers |
Enforced |
TB04_CONTEXT_BUNDLE |
Require context bundling for repeated walker plumbing signatures | Enforced |
TB05_TAG_DISPATCH |
Prefer direct tag dispatch over repeated tag-set guards | Enforced |
- Build steps:
./zig/zig build run -- ./src./zig/zig build run -- --allow-findings ./src(report findings but exit 0)./zig/zig build run -- --format json ./src ./build.zig./zig/zig build run -- --gates policy,perf ./src./zig/zig build bench
- Build options:
-Danalyze-path=<path>(bench target path, default:./src)-Doff-rules=RULE_ID,RULE_ID-Dperf-budget-ms=<ms>(default:6000in Debug,1000in Release*)
Corpus audit options:
./zig/zig build corpus-audit -- tests/corpus --min-cases-per-kind 2- Add
--strict-min-casesto fail on minimum-depth coverage violations.
Perf benchmark options:
./zig/zig build bench -- --runs 5./zig/zig build bench -- --runs 5 --json
Add a tigercheck step to your build.zig, then make your check step depend on it:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Example compile check step.
const exe_check = b.addExecutable(.{
.name = "my-project-check",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
const tigercheck_exe = b.option(
[]const u8,
"tigercheck-exe",
"Path to tigercheck binary",
) orelse "tigercheck";
const tigercheck_target = b.option(
[]const u8,
"tigercheck-target",
"Path to analyze",
) orelse "src";
const tigercheck_cmd = b.addSystemCommand(&.{
tigercheck_exe,
"--format",
"text",
tigercheck_target,
});
const tigercheck_step = b.step("tigercheck", "Run tigercheck");
tigercheck_step.dependOn(&tigercheck_cmd.step);
const check = b.step("check", "Compile + tigercheck");
check.dependOn(&exe_check.step);
check.dependOn(tigercheck_step);
}Usage examples:
zig build tigercheckzig build checkzig build check -Dtigercheck-exe=../tiger/zig-out/bin/tigercheckzig build check -Dtigercheck-target=.
For CI and long-lived repos, pin the tigercheck binary like TigerBeetle pins docs tooling:
tigerbeetle/src/docs_website/build.zig.zonpinspandocandvaleby.url+.hash.tigerbeetle/src/docs_website/build.zigresolves the host tool withb.lazyDependency(...).tigerbeetle/src/docs_website/src/page_writer.zigcomputes SHA-256 for inline scripts (CSP hash).tigerbeetle/src/docs_website/build.zigpassesgit-commitas the service-worker cache name.
We recommend the same pattern for tigercheck: add per-platform release assets to your build.zig.zon with a fixed URL and content hash, then execute that pinned binary from build.zig.
Use zig fetch <url> to obtain the content hash for each release artifact.
.{
.name = .my_project,
.version = "0.0.0",
.dependencies = .{
.tigercheck_linux_x86_64 = .{
.url = "https://github.com/enfipy/tiger/releases/download/0.1.0/tigercheck-x86_64-linux.zip",
.hash = "<hash-from-zig-fetch>",
.lazy = true,
},
},
.paths = .{"."},
}
const host = b.graph.host.result;
const dep_name = switch (host.os.tag) {
.linux => switch (host.cpu.arch) {
.x86_64 => "tigercheck_linux_x86_64",
else => @panic("unsupported linux arch"),
},
else => @panic("unsupported host"),
};
const dep = b.lazyDependency(dep_name, .{}) orelse @panic("missing tigercheck dependency");
const tigercheck_rel = switch (host.os.tag) {
.windows => "tigercheck.exe",
else => "tigercheck",
};
const tigercheck_bin = dep.path(tigercheck_rel);
const tigercheck_cmd = b.addSystemCommand(&.{});
tigercheck_cmd.addFileArg(tigercheck_bin);
tigercheck_cmd.addArgs(&.{ "--format", "text", "src" });This gives you reproducible toolchain behavior, avoids accidental analyzer drift, and makes local + CI diagnostics consistent.
name: safety
on: [push, pull_request]
jobs:
tigercheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v1
with:
version: 0.16.0-dev
- name: Build checker
run: ./zig/zig build
- name: Safety analysis
run: ./zig/zig build run -- .Core gates:
./zig/zig build testvalidates unit coverage + corpus pass/fail contracts../zig/zig build precision-checkenforces rule-level FP/FN deltas vstests/corpus/precision-baseline.json../zig/zig build run -- --gates policy,perf ./src/libtigercheckenables policy + perf gates for strict-core conformance.
Repository CI workflow (.github/workflows/safety.yml) runs:
./zig/download.sh./zig/zig build test./zig/zig build precision-check./zig/zig build run -- --gates policy,perf ./src/libtigercheck./zig/zig build --release=fast run -- ./src
If you see stdlib errors like invalid builtin function: '@Type', your Zig binary and lib directory are out of sync. Use ./zig/zig ... to force a matched toolchain.
tigercheck is focused on one outcome: strict, deterministic Zig conformance in CI.
- Rule contracts: make each rule's violation semantics explicit and testable.
- Corpus discipline: maintain pass/fail/edge coverage per rule prefix.
- Precision tracking: keep baseline-driven FP/FN regression gates for every rule.
- Determinism and performance: hold strict and bench lanes stable with release headroom.
- CI UX: keep local and CI gates identical, concise, and reproducible.
Release tooling is scripted in src/tools/release.zig and enforces deterministic inputs:
- source SHA (
--sha, defaults togit rev-parse HEAD) - fixed target order (
x86_64-linux,aarch64-linux,x86_64-windows,aarch64-macos) - mandatory quality gates (
test,precision-check,run -- --gates policy,perf ./src/libtigercheck) - metadata artifacts (
RELEASE_METADATA,RELEASE_NOTES.md,SHA256SUMS)
Local dry-run (build + package + checksums + metadata, no publish):
./zig/zig build release -- --version 0.1.0 --dry-run
Release validation:
- explicit target:
./zig/zig build release-validate -- --tag 0.1.0 --sha <commit>
Recovery playbook:
- Draft created but assets missing: rerun release workflow with the same version after deleting the incomplete draft release.
- Checksum mismatch: stop publication, rebuild from the same source SHA, and compare
SHA256SUMSbefore re-upload. - Post-release validation failure: keep release tagged but publish a follow-up patch release with corrected artifacts and validation evidence.
