Prove what your flagd targeting rules can actually decide.
Flagfold is a local, deterministic CLI for flagd JSON. It finds unreachable targeting branches, distinguishes impossible conditions from branches shadowed by earlier rules, identifies variants that can never be served, and generates concrete evaluation contexts that witness every reachable decision. diff shows the smallest contexts where a candidate configuration changes a baseline decision.
It does not connect to a provider, upload configuration, execute embedded code, or silently sample. Unsupported semantics fail with an exact JSON path.
git clone https://github.com/KanadeK/flagfold.git
cd flagfold
uv sync --all-groups
# Clean configuration: exits 0
uv run flagfold analyze examples/baseline.flagd.json
# Known findings: prints evidence and exits 1
uv run flagfold analyze examples/shadowed.flagd.jsonThe second command reports:
Flagfold analysis (schema 1)
checkout: 5 contexts
rule-1: reachable -> pro when {"plan":"enterprise"}
rule-2: shadowed -> beta
rule-3: impossible -> beta
fallback: reachable -> control when {}
dead variants: beta, unused
Summary: 4 semantic findings
Compare two configurations:
uv run flagfold diff examples/baseline.flagd.json examples/candidate.flagd.jsoncheckout: 9 contexts
treatment -> control when {"plan":"pro"}
That witness says the old config served treatment to a pro user with no country attribute, while the candidate falls back to control.
Download the wheel from the latest GitHub Release, then:
py -3.11 -m venv .venv
.\.venv\Scripts\python -m pip install .\flagfold-0.1.0-py3-none-any.whl
.\.venv\Scripts\flagfold --versionFlagfold has no runtime dependencies. Python 3.11 or newer is required.
flagfold analyze INPUT [--format text|json] [--max-contexts N]
flagfold diff BASELINE CANDIDATE [--format text|json] [--max-contexts N]
flagfold --version
--format json emits stable schemaVersion: 1 output for CI. Full field definitions are in docs/formats.md.
| Exit | Meaning | Typical CI action |
|---|---|---|
| 0 | Analysis is clean, or diff has no changed flags | Pass |
| 1 | Semantic findings or changed decisions exist | Review evidence |
| 2 | File, JSON, or flagd structure is invalid | Fix the path named in the error |
| 3 | Targeting uses unsupported semantics | Rewrite to the supported subset or do not claim proof |
| 4 | Exact enumeration exceeds --max-contexts |
Raise the reviewed budget or simplify the rule |
Flagfold supports a deliberately finite JSON Logic subset:
- ordered
ifbranches with direct variant-name ornullresults; and,or, and!;- strict equality
===and inequality!==with at most onevaroperand; inwith a literal list on the right;varwith one non-empty top-level attribute name (no dotted nested path);- JSON scalar literals.
For every referenced attribute it builds equivalence-preserving values from rule literals, plus an absent state and one distinct other value. It then evaluates the full Cartesian product. Within this subset, the result is exhaustive—not a random sample.
Loose equality (==/!=), var-to-var comparison, dotted nested var paths, numeric ordering, regular expressions, string containment, nested result expressions, object/array attributes, fractional rollouts, custom flagd operators, and provider-specific formats are not supported in v0.1. Flagfold rejects them instead of guessing.
OpenFeature provides vendor-neutral evaluation APIs and an official CLI for flag manifests. Flagfold stays narrower: it analyzes the semantics of flagd's concrete targeting JSON and complements structural manifest tooling rather than replacing it.
- name: Prove flag decisions
run: flagfold analyze config/flags.flagd.json --format jsonExit 1 is intentional when the report contains a shadowed/impossible branch or dead variant. If you only want an artifact, capture the exit code explicitly and review the JSON instead of appending a blanket || true.
uv sync --all-groups
uv run pytest --cov=flagfold --cov-branch --cov-fail-under=90
uv run ruff format --check .
uv run ruff check .
uv run mypy src tests scripts
uv build
uv run python scripts/check.pyThe last command is the release-equivalent gate: it runs static checks and tests, builds both archives, inspects their contents, installs the wheel into an isolated environment, and executes real clean/finding/diff/failure paths.
- Specification
- Input and report formats
- Troubleshooting and repair flows
- ADR-001: exact finite-domain analysis
- Security policy and threat model
- Contributing
The default 5 MiB input cap and 100,000-context budget bound local resource use. Increasing the context budget can consume substantial CPU and memory; Flagfold prints the exact required count before enumeration. Findings prove behavior of the supplied supported rule—not business correctness, production exposure, or safety of a rollout.
Apache-2.0. See LICENSE.