Skip to main content

Schema versioning

Every file in a flag namespace declares its own schema_version. This page is the reference for that field, the compatibility model, and the migration tooling.


The schema_version field

Every TOML file in a manifest — namespace.toml, every flags/<flag-key>.toml, and every segments/<segment-key>.toml — MUST contain a top-level schema_version key, declared early in the file by convention.

schema_version = "0.1"

Field rules

PropertyRule
TypeTOML string.
Format<major>.<minor> — two non-negative integers separated by a single . (e.g., "0.1", "1.0", "1.3", "2.0").
RequiredYes, on every file.
PositionTop-level. MUST NOT be nested under any TOML table.
Linter code on missing/invalidE001.

The linter accepts any string whose components parse as non-negative integers separated by exactly one dot. Patch-level versions ("1.0.1"), prefix qualifiers ("v1.0"), trailing build metadata ("1.0+abc"), and non-numeric components ("1.x", "abc") all fail validation.

Major 0 is reserved for pre-stable iteration (0.1, 0.2, …). The current schema is 0.1. The first stable wire-format major will be 1.0.


What the linter validates

The linter validates the shape of the version string. It does NOT enforce that the major matches a server-supported value — a file declaring "99.0" passes lint locally but is rejected at upload time with schema_version_mismatch. The server is the authority on which majors it accepts.

Use exd lint --schema-major <N> to opt into a local major check.


Compatibility model

Minor versions are additive

A newer minor version within the same major is forward-compatible: a file declaring schema_version = "1.3" MUST be accepted by any implementation that supports "1.0" or higher in the 1.x line. This is what allows a team to upgrade their CLI before the central server, or vice versa, without breaking uploads.

A flag namespace MAY mix minor versions across files (for example, namespace.toml at "1.0" and a single flag file at "1.2"). The linter does NOT require every file to declare the same version. Mismatched minors within one flag namespace are accepted with W008 so authors notice the drift.

Major versions are not compatible

A file whose major version differs from the server's supported major is rejected at upload with schema_version_mismatch (HTTP 422). Mixing majors within a manifest is unsupported; uploading such a manifest fails with the same code.

The linter does NOT gate on major-version mismatch; it accepts any well-formed major.minor. This is intentional — local linting against the wrong version is useful information ("file parses, but the wire format will reject it"), and gating would force the linter to track the server's supported version.

Forward-compat policy

Forward compatibility — silently accepting unknown fields a newer minor version may have introduced — is opt-in per site, not a blanket default. Most tables in the schema have a closed set of recognized fields; an unrecognized field raises E016. This catches typos that would otherwise silently break evaluation (default_varient = "off", stat = 0) and forces every minor-version field addition to be reflected in the spec.

Forward-compatible sites in the current schema:

SiteBehavior
Env values under [namespace.environments] (each <env-slug> = { ... })Unknown fields are silently ignored.

Every other table is strict. The full set of recognized fields per table is enumerated in E016.


Server acceptance policy

ConditionServer response
All files declare a major matching the server's current majorUpload accepted (subject to lint and authorization).
Any file's major is below the server's minimum supported major422 schema_version_mismatch.
Any file's major is above the server's current major422 schema_version_mismatch.
Any minor version within a supported majorAlways accepted.
schema_version missing or malformed on any fileLinter rejects with E001; server returns manifest_lint_failed.

Mismatched versions inside one flag namespace

namespace.tomlflag fileResult
"1.0""1.0"Accepted, no diagnostics.
"1.0""1.3"Accepted with W008. Server (current major 1) accepts the upload.
"1.0""2.0"Linter passes each file's shape check; server rejects with schema_version_mismatch.
"1.0""1.x"Linter raises E001 on the flag file; upload rejected.

Migration

Minor upgrades within a major

exd manifest migrate --from 1.0 --to 1.3 ./payments/

Rewrites every file in the flag namespace to advance the schema_version field and add or rename any optional fields introduced between source and target. Migration is mechanical and lossless: files written by exd manifest migrate parse cleanly under both source and target versions, with schema_version set to the target.

The command refuses to run if any file already declares a higher version than the target, and refuses to run across major boundaries.

Major upgrades

exd manifest upgrade --to 2.0 ./payments/

The only supported path between major versions. Unlike migrate, upgrade MAY rewrite semantics, prompt for human decisions on ambiguous mappings, and produce a manifest that is not readable by older clients. A migration guide is published alongside each major version bump.


Common diagnostics

ScenarioDiagnostic
schema_version missingE001
schema_version = 1 (integer, not string)E001
schema_version = "1" (no minor component)E001
schema_version = "1.0.0" (patch component)E001
schema_version = "v1.0" (prefix)E001
schema_version = "1.x" (non-numeric minor)E001
schema_version = ""E001
schema_version = "0.1"accepted (pre-stable)
schema_version = "99.0"accepted by linter; server rejects with schema_version_mismatch
Namespace and flag declare different minors within the same majoraccepted with W008
Namespace and flag declare different majorslinter passes; server rejects

  • Default to the current schema major. Today that is "0.1". The first stable wire-format major will be "1.0".
  • Keep all files on the same minor. Mixing is allowed, but uniform versions keep git blame honest about which version your flag namespace runs on.
  • Run exd manifest migrate instead of hand-editing schema_version. The migrate command also adds or renames fields the new minor introduces — a hand edit would miss those.
  • Pin a CI lint job to the same exd version as production. A divergent CLI may accept a manifest the server rejects for major-version reasons; pinning eliminates that drift.

See also