Pure Python 3.10+ · one dependency 36+ tests · fully offline

Every commit runs
the gauntlet.

// a miniature IP-regression platform

A DAG flow runner, EDA-style log parsing, waivers, and run-to-run diff — the core loop of a chip-design regression product, rebuilt small enough to read in an afternoon and honest enough to trust in CI.

5
status states, not 2
4
CLI commands
~3s
full test suite
Models the loop behind DAG scheduling · log-driven pass/fail · waivers as debt · regression deltas · SQLite history
The problem

A green build isn't the question.
The delta is.

In chip design, a nightly regression runs dozens of tools across a dependency graph, each spewing gigabyte logs where a tool can exit 0 and still have failed. What a team actually needs to know isn't "did tonight pass" — it's what broke that was working yesterday. GAUNTLET rebuilds that loop end to end.

Logs are the truth

A tool exits 0 but its log holds an unwaived ERROR → the step still FAILs. Exit codes under-report; the log is the source of record.

Waivers are debt, not a fix

A known failure becomes WAIVED — never silently green. It stays visible in every report with an owner and a JIRA reason.

The diff is the product

Run-to-run, GAUNTLET reports NEW FAILURES, FIXED, and STILL FAILING — and exits non-zero on regressions, so CI can gate on it.

How it works

One flow. A dependency graph.
Real subprocesses.

A flow is a YAML DAG. The scheduler submits a step only when every dependency has passed, runs it as a real subprocess in its own process group, then parses the log against a rulepack and applies waivers. Here's the smoke flow — lint gates two sims that run in parallel, then synth.

lint_rtlPASS
mock_lint · rulepack: lint
entry step · 4 warnings
sim_unitWAIVED
mock_sim · rulepack: sim
depends_on: lint_rtl
sim_integrationPASS
mock_sim · rulepack: sim
depends_on: lint_rtl
synth_quickPASS
mock_synth · rulepack: synth
depends_on: both sims

Threads, not asyncio

Workers spend their lives blocked in subprocess.wait(), where the GIL is released — a ThreadPoolExecutor gives real parallelism with none of the ceremony.

Timeouts kill the whole tree

Steps launch in their own process group. On timeout we signal the group, grace it, then hard-kill — so a shell wrapper can't die while its simulator lives on.

SKIPPED isn't a failure

When a dependency fails, dependents go SKIPPED — and the diff blames the upstream step that actually broke, not the ones it starved.

Streamed, never slurped

Logs are classified line by line against compiled regex rulepacks. Real EDA logs run to gigabytes; reading one whole is how you fall over in production.

The money shot

Introduce a flake. Run again.
Watch the delta surface.

Same seed, same flow — but a flake injects an overflow in u_fifo_tx. The waiver only matches u_fifo_rx, so it doesn't fire: the step FAILs. GAUNTLET doesn't just report the new run — it reports what changed.

run_…d84a  SEED=7 PASS · 7.0s
PASSlint_rtl4 warnings
WAIVEDsim_unit1 error waived
PASSsim_integration1 warning
PASSsynth_quickclean
run_…8a0b  SEED=7 FLAKE=0.3 FAIL · 5.0s
PASSlint_rtl4 warnings
FAILsim_unitSIM-ERR-007 · u_fifo_tx
PASSsim_integration1 warning
SKIPPEDsynth_quickupstream failure

gauntlet diff → NEW FAILURES (1): sim_unit

verdict: REGRESSION — 1 new failure the previous run didn't have.

exit code 1 → CI gate trips
What's in the box

Small surface. Real infrastructure behaviors.

No plugins, no async, no web server in the core — just the behaviors a regression platform actually needs, each one deliberate.

DAG scheduler with a ready-set executor

A step is submitted only when every dependency is PASS or WAIVED. Failures cascade SKIPPED transitively; --keep-going lets independent branches finish. Ordering is proven by tests, not vibes.

Rulepacks

Per-tool regex rulepacks compile once at load. A bad pattern is a config error at startup — exit 2 — never a surprise mid-run.

Waivers with an audit trail

rule-id + substring match, each carrying an owner and reason. Precise, not blanket — the same rule with a different signature still fails.

SQLite memory

history, report, and diff all work from the DB alone — long after the raw logs are cleaned up.

Shareable HTML reports

report <run> --html writes one self-contained page — step summary, per-step log links, violation tables — with zero new dependencies.

Retries that stay honest

A step that only passes on retry is marked flaky: it satisfies its dependents, but every report says so. A flaky pass is information, not a pass.

Run it yourself

Three commands to the money shot.

Clone it, install the one dependency, and watch a clean run, a regression, and the diff that catches it.

$ pip install -e ".[dev]" && pytest
$ GAUNTLET_SEED=7 gauntlet run flows/smoke.yaml --workers 4
$ gauntlet diff <run_1> <run_2>  # exits 1 → CI-usable
Have an idea? Let's talk.

Want to build something —
or break something interesting?

GAUNTLET is one project. There are 18 more (and a few jet engines) over at charanreddy.dev. If you're hiring, collaborating, or just curious how the diff works — the door's open.