Z6 is a deterministic, high-performance, auditable load testing tool written in Zig. Built with Tiger Style philosophy: safety, performance, developer experience.
- Language: Zig (0.11.0+)
- Architecture: Single-threaded, async I/O, deterministic scheduler
- Philosophy: Tiger Style (TigerBeetle-inspired discipline)
- Status: Pre-alpha, ~10,300 lines across 17 modules
src/ # Core implementation (17 modules)
tests/ # Unit, integration, fuzz tests
docs/ # Technical specifications
scripts/ # Development tooling & pre-commit hooks
examples/ # Working demonstrations
zig build # Build project
zig build test # Run all tests
zig fmt src/ tests/ # Format codeThis project uses GitHub Issues for task management. Always check context before working:
# Check current branch
git branch --show-current
# Check open issues assigned to you
gh issue list --assignee @me
# Check open PRs
gh pr list --state open
# View specific issue
gh issue view <issue-number>| Type | Format | Example |
|---|---|---|
| Feature | feat/TASK-XXX-description |
feat/TASK-301-vu-execution-engine |
| Bug fix | fix/TASK-XXX-description |
fix/TASK-201-http-parser-overflow |
| Docs | docs/TASK-XXX-description |
docs/TASK-600-api-documentation |
| Refactor | refactor/TASK-XXX-description |
refactor/TASK-150-memory-pools |
| Test | test/TASK-XXX-description |
test/TASK-500-fuzz-infrastructure |
<type>(<scope>): <subject> (TASK-XXX)
<body>
<footer>
Types: feat, fix, docs, style, refactor, perf, test, chore
Examples:
feat(http): implement HTTP/2 frame parser (TASK-203)
Add RFC 7540 compliant frame parsing with header compression.
Includes 28 unit tests and fuzz test coverage.
Closes #62
-
Before creating PR:
- Ensure branch is up to date with main
- All tests pass locally (
zig build test) - Pre-commit hook passes
- Code formatted (
zig fmt)
-
PR Title Format:
feat: Description (TASK-XXX) -
Required sections in PR description:
- Problem (what issue does this solve?)
- Solution (approach and key decisions)
- Testing (tests added/run)
- Tiger Style Compliance checklist
-
Link to issue: Include
Fixes #<issue-number>orCloses #<issue-number>
- Simplicity over complexity - The simple solution is easier to verify
- Do it right the first time - Zero technical debt policy
- Test before implement - Write failing tests, then make them pass
- Fail fast - Assertions downgrade bugs into liveness issues
- Explicit over implicit - No hidden state, no magic
Minimum 2 assertions per function:
fn process(data: []const u8) !Result {
assert(data.len > 0); // precondition
assert(data.len <= MAX_SIZE); // bound check
const result = try compute(data);
assert(result.isValid()); // postcondition
assert(result.size <= data.len); // invariant
return result;
}- Assert all function arguments (preconditions)
- Assert all return values (postconditions)
- Assert invariants throughout execution
- Test files are exempt from assertion density requirements
All loops MUST be provably bounded:
// GOOD: Bounded by slice length
for (items) |item| { ... }
// GOOD: Bounded by explicit limit
var i: usize = 0;
while (i < MAX_ITERATIONS) : (i += 1) { ... }
// GOOD: Event loop with unreachable
while (true) {
// event loop body
}
unreachable;
// FORBIDDEN: Unbounded without assertion
while (condition) { ... } // Must have explicit bound- No recursion - All executions must be provably bounded
- Prefer
forloops with explicit iteration counts - Event loops (
while (true)) must be followed byunreachableorassert
All errors MUST be explicit - no silent failures:
// FORBIDDEN - Silent error suppression
something() catch {};
// REQUIRED - Explicit handling
something() catch |err| {
log.err("Failed: {}", .{err});
return error.OperationFailed;
};
// REQUIRED - Propagation
try something();- Use
tryto propagate errors up the call stack - Define specific error sets, not
anyerror - Document all error cases in function comments
- Use
errdeferfor partial cleanup
// Pair allocations with deallocations
const buffer = try allocator.alloc(u8, size);
defer allocator.free(buffer);- All allocations must be bounded - No unbounded growth
- Document memory ownership explicitly
- Use
deferfor cleanup immediately after allocation - Check allocation results - never assume success
- Specify maximum memory limits for all data structures
- Single-threaded by design for core execution
- Use logical ticks, not wall-clock time
- All operations must be deterministic given same seed
- PRNG must be seeded explicitly
- No system calls that introduce non-determinism in core logic
- Avoid
@ptrCastunless absolutely necessary - Never use
@ptrFromIntwith arbitrary values - Use slices over raw pointers - Slices carry length information
- Always check allocation results
- Initialize all memory - Uninitialized memory is UB
- Use
deferfor RAII-like cleanup
// Use explicit overflow handling
const result = @addWithOverflow(a, b);
if (result[1] != 0) return error.Overflow;
// Use sized integers, not usize
const count: u32 = @intCast(value);
// Use saturating arithmetic where appropriate
const safe_value = @min(calculated_value, MAX_ALLOWED);- Use explicit sized types:
u32,i64(avoidusizeunless necessary) - Validate all enum values from external sources
- Use optionals (
?T) for "might not exist" - Use error unions (
!T) for "might fail" - Use
std.mem.eqlfor slice equality (not==)
comptime {
assert(@sizeOf(Header) == 24);
assert(@alignOf(Header) == 8);
}- Use
comptimefor validation at compile time - Type-level programming for zero-cost abstractions
- Document comptime parameters clearly
- Validate all external input - Never trust user data
- Use constant-time comparison for secrets:
std.crypto.utils.timingSafeEql - Clear sensitive data:
std.crypto.utils.secureZero - Check buffer sizes to prevent overflows
- Limit resource usage to prevent DoS
- Write tests FIRST - Create failing test
- Run tests - Verify they fail (
zig build test) - Implement feature - Make tests pass
- Refactor - Clean up while tests stay green
- Minimum 90% test coverage target
- Test happy path AND error paths
- Test boundary conditions (min, max, zero, one-off)
- Property-based tests for complex logic
- Fuzz tests for all parsers (minimum 1M inputs)
tests/
unit/ # Unit tests for individual modules
integration/ # Integration tests for component interaction
fuzz/ # Fuzzing tests for parsers
fixtures/ # Test data files
The pre-commit hook enforces Tiger Style. All checks must pass:
- Code formatting -
zig fmt --check - Assertion density - Minimum 2 per function
- Bounded loops - No unbounded
while(true)without assertion - Explicit error handling - No
catch {} - Build success -
zig build - All tests pass -
zig build test
To install hooks:
./scripts/install-hooks.shBypass is FORBIDDEN except for:
- Emergency hotfixes (with follow-up PR)
- Documentation-only changes (no code)
| Practice | Reason |
|---|---|
catch {} |
Silent error suppression |
while (true) without unreachable |
Unbounded loop |
| Recursion | Unbounded execution |
usize for data sizes |
Architecture-dependent |
| Functions with < 2 assertions | Insufficient invariant checking |
| Magic numbers | Use named constants |
| Global mutable state | Hidden state |
| Implicit allocations | Hidden memory behavior |
@as to bypass type safety |
Find proper solution |
catch unreachable on fallible ops |
Hides potential failures |
- Use
zig fmt- formatting must be consistent - Descriptive variable names (no single letters except loop counters)
- Comments explain WHY, not WHAT
- Group related functionality
- One struct per file for major types
- Use
pubintentionally - minimal public API
- Deterministic Reproducibility - Same seed = identical execution
- Immutable Event Log - All actions logged as fixed-size (272B) events
- Logical Ticks - Not wall-clock time
- Bounded Resources - 100K VUs, 10M events, 16GB RAM max
- Protocol Abstraction - Pluggable handlers (HTTP/1.1, HTTP/2, gRPC)
| Phase | Status | Focus |
|---|---|---|
| Phase 0-3 | Complete | Foundation, Core, HTTP, Execution |
| Phase 4 | ~85% | Metrics (HDR Histogram, Reducers) |
| Phase 5 | Pending | Testing (Fuzz, Integration, Property) |
| Phase 6 | Pending | Polish (Docs, Limits, Benchmarks) |
| Phase 7 | Pending | Release v1.0.0 |
| File | Purpose |
|---|---|
src/main.zig |
CLI entry point |
src/scheduler.zig |
Deterministic microkernel |
src/event.zig |
Immutable event model |
src/protocol.zig |
Protocol interface |
src/http1_handler.zig |
HTTP/1.1 implementation |
docs/MANIFESTO.md |
Design philosophy |
ROADMAP.md |
Task tracking |
git checkout main && git pull
gh issue list --assignee @me
git checkout -b feat/TASK-XXX-descriptionzig fmt src/ tests/
zig build test
git add .
git commit -m "feat(scope): description (TASK-XXX)"git fetch origin
git rebase origin/main
zig build test
git push -u origin feat/TASK-XXX-description
gh pr create --title "feat: Description (TASK-XXX)" --body "..."When making technical decisions, ask:
- Does this preserve determinism?
- Can this be audited and replayed?
- Is the memory behavior explicit and bounded?
- Does this make the system simpler or more complex?
- Can we prove this is correct?
If the answer to any is "no" or "I don't know," the feature is not ready.
Tiger Style: Do it right the first time.