Skip to main content

Directory layout

A flag namespace is a directory tree. This page is the reference for the tree's structure: where files go, what they're named, and the file-system requirements implementations rely on.


Tree structure

A flag namespace's manifest is rooted at a directory whose name doubles as the namespace slug. The full layout:

<namespace-slug>/
namespace.toml # optional
flags/ # zero or more flag files
<flag-key>.toml
...
segments/ # optional; only when at least one segment is defined
<segment-key>.toml
...

The smallest valid flag namespace is a directory containing one flags/<key>.toml file. Everything else — namespace.toml, segments/, the env list, per-env flag blocks — is scaffolding that earns its place when the flag namespace grows.

Concrete example

payments/
namespace.toml
flags/
checkout-redesign.toml
express-checkout.toml
payment-retry-v2.toml
segments/
beta-users.toml
checkout-redesign-rollout-10.toml
internal-employees.toml

Optional entries

  • namespace.toml — OPTIONAL. When absent, the flag namespace runs in untyped-env mode with every namespace-level field at its default and slug inferred from the directory name. See 04-namespace.md.
  • flags/ — OPTIONAL as a directory but expected in any non-empty flag namespace. Its absence emits W011 once any flag declaration would otherwise be expected.
  • segments/ — OPTIONAL. Only needed when at least one rule references a segment by name. Rules may also carry an inline predicate; see predicates.

Forbidden entries

What's forbiddenWhyDiagnostic
Files at the flag-namespace root other than namespace.toml and .exdignoreKeeps the root surface minimal(silently ignored; not packaged)
Subdirectories under flags/ or segments/One file per entity is the contractW009
Symbolic links anywhere in the treeSymlinks compromise reproducibility — the same tree may resolve differently on different machinesE018

File naming

Flag files

Each flag lives in its own file at flags/<flag-key>.toml.

  • The filename without the .toml extension is the flag key. There is no flag.key field in schema 0.1; the filename is the single source of truth.
  • The filename stem MUST match the key pattern: [a-z][a-z0-9_-]*, max 63 characters. Violations raise E031 and the file is dropped before per-file lint.
  • The filename MUST end in the lowercase extension .toml. Files with any other extension (e.g., .TOML, .tom, .toml.bak) are silently ignored.
  • Flag-key uniqueness falls out of the filesystem: two regular files in the same directory cannot share a stem.

Segment files

Same rules as flag files: segments/<segment-key>.toml, lowercase .toml, key pattern, unique stems. Violations raise E032.

Namespace file

The namespace descriptor lives at namespace.toml. Its filename is fixed; renaming it has the same effect as deleting it (the linter falls back to defaults and infers the slug from the directory name).

Files the linter ignores

The linter operates only on regular files inside flags/ or segments/ whose names end in .toml. Everything else is silently ignored:

  • Non-.toml extensions (README.md, .DS_Store, flag.toml.bak).
  • Subdirectories (even if their name ends in .toml).
  • Files at the flag-namespace root other than namespace.toml.

This lets teams keep auxiliary artifacts (READMEs, ownership docs, ignore files) alongside the manifest without affecting linting.


Identifier patterns

Two character classes are used throughout the manifest:

ClassPatternUsed for
slug[a-z][a-z0-9-]*tenant slug, namespace slug, environment slug
key[a-z][a-z0-9_-]*flag key, segment key, variant key

Rules:

  • Lowercase ASCII only. Unicode letters, uppercase letters, dots, slashes, and other punctuation are forbidden.
  • First character MUST be a lowercase ASCII letter. First character MUST NOT be a digit, hyphen, or underscore.
  • Maximum length is 63 characters for every slug and every key.
  • Underscores are permitted in keys (flag, segment, variant) but MUST NOT appear in slugs (tenant, namespace, environment).
  • All comparisons are byte-exact. The slug pattern is lowercase-only, so two slugs differing only in case can never both be valid.

TOML dialect

Every file in a manifest is a TOML 1.0.0 document.

AspectRule
EncodingUTF-8. A leading BOM (U+FEFF) is accepted but ignored.
Line endingsLF (\n) or CRLF (\r\n). Mixed within one file is accepted but discouraged.
CommentsPermitted anywhere TOML allows them. Comments are NOT preserved when the server canonicalizes a manifest; they live only in the source files.
Duplicate keysForbidden by TOML. The parser rejects them; the linter records the parse error as E001.
Inline vs. table syntaxBoth forms are accepted wherever TOML permits them. The choice carries no semantic meaning.

File-system requirements

Case sensitivity

Flag keys, segment keys, and environment slugs are case-sensitive. Production and production are different identifiers. On case-insensitive file systems (macOS APFS, NTFS, FAT32) two files whose names differ only in case will collide before the linter ever sees them — that's a file-system error, not a manifest error. The slug/key patterns enforce lowercase, so the question never arises if you follow them.

Permissions

The reference linter requires only read permission. Write tools (exd manifest push, exd manifest pull) require the usual read/write permissions for the directory and its parent. No special UNIX permission bits or extended attributes are relied on.

Maximum file count

No hard maximum. The server's archive size limits (50 MB uncompressed, 5 MB compressed; see packaging) are the only ceilings.

Maximum file size

Each individual .toml file MUST NOT exceed 256 KB. Larger files are rejected with E019. Typical flag and segment files are a few hundred bytes to a few kilobytes; the cap exists to prevent runaway JSON variant payloads.


Traversal order

When the linter (or any tool) walks flags/ or segments/, entries are sorted by lexicographic byte order of the path. Deterministic ordering is what makes "previous file" pointers in diagnostics reproducible across runs.

Tools that emit diff or report output across versions also use lexicographic order so two runs against the same flag namespace produce identical output.


Why one file per entity?

Three reasons (informative):

  • Agent context efficiency. An agent asked to modify flags/checkout-redesign.toml can fetch exactly that file and receive only the relevant content. A monolithic config file containing hundreds of flags wastes context on unrelated entries and increases the risk of hallucinated edits to adjacent flags.
  • git blame and ownership. Each file has its own complete commit history. git blame flags/checkout-redesign.toml shows who changed each field, without noise from unrelated flag edits. PR reviews are scoped: a reviewer of a checkout change is not presented with a diff touching an unrelated payments-retry flag.
  • Minimal diffs. Changing a single rule on one flag produces a diff touching exactly one file. PR review is fast, merge conflicts are rare, and CI gets a deterministic file path as a cache key.

Empty flag namespace

A flag namespace whose flags/ and segments/ directories are both empty (or absent) is a valid manifest. If namespace.toml is also absent, the linter passes with no diagnostics: slug = directory name, every namespace field at its default, untyped-env mode in effect. If namespace.toml is present, it MUST be well-formed.


See also