Featurevisor

Ship it

State files

State files remember what earlier builds decided, so the next build can keep each user in the same variation. They live in .featurevisor/, and your deployment workflow commits them after every successful build.

What they record

Every build writes two files:

  • existing-state.json records each feature's rules and how their percentages were divided between variations.
  • REVISION holds the revision number, which every build increments and writes into the datafiles.

For the pricing experiment, the state says that the everyone rule gives buckets 0 to 50,000 to control and 50,000 to 100,000 to treatment:

.featurevisor/existing-state.json, excerpt
{
"features": {
"pricing": {
"traffic": [
{
"key": "everyone",
"percentage": 100000,
"allocation": [
{ "variation": "control", "range": [0, 50000] },
{ "variation": "treatment", "range": [50000, 100000] }
]
}
]
}
}
}

Why they matter

When you raise a rule's percentage, the next build reads these ranges, keeps them, and divides only the new share. That is how a user stays in treatment as pricing rolls out further. Without the state, every build would divide the rollout from scratch, and users could move between variations each time. A user who switches mid experiment sees both versions of the page, and their behaviour counts towards both results. Variations shows the whole sequence.

Ranges are stored per rule key, so renaming a rule starts it from scratch on purpose.

Who commits them

The build that deploys your datafiles should also commit the state it wrote, so the next build starts from what is live. Do this in CI after a successful build, as Deployment shows.

Do not commit state you generated on your own machine. A local build can differ from the deployed one, and committing it would make the next deployment start from allocations that were never live. Leave .featurevisor/ out of your pull requests.

When a saved allocation is invalid

Before reusing a rule's saved ranges, the build checks that they name existing variations and cover the rule's percentage without gaps or overlaps. If not, it stops with invalid_saved_allocation, naming the feature, the rule and the state file, and links to this section:

Output
Invalid saved variation allocation for feature "pricing", rule "everyone".

The build never repairs state on its own. Either restore the state from the deployed revision, which keeps everyone's variation, or rename the rule to allocate it again, accepting that users may switch. Do not delete other features' state to fix one rule.

Lint rejects variation weights that do not add up to 100. Without that check, the first build would save ranges that cannot cover the rule, and the second build would stop here.

Location and format

  • The directory is .featurevisor/, or stateDirectoryPath.
  • With environments, each one has its own existing-state-<environment>.json.
  • prettyState indents the JSON, so its changes read well in a diff.
  • npx featurevisor build --no-state-files reuses the saved state but writes no state and does not advance the revision, for previews and one off exports.
Previous
Building datafiles