SettingSpec Specification
Version: 0.1
Status: Released
Authors: Arijit Basu and SettingSpec Contributors
Repository: https://github.com/sayanarijit/settingspec
1. Overview and Goals
1.1 Purpose
Modern software development requires managing configuration across multiple environments (such as development, staging, testing, and production), various programming languages (such as Python, Node.js, Rust, Go, Lua), and nested modules. Traditional approaches frequently split configurations across multiple files (common.toml, dev.toml, prod.toml), leading to:
- Mental overhead: Developers must mentally merge configurations and guess override precedence.
- Silent configuration drift: Missing keys in production often remain undetected until deployment failure or runtime crashes.
- Language lock-in: Configurations written for one language (e.g., Python config or YAML) require duplication or custom converters for other tools in polyglot projects.
- Secret handling friction: Secrets are either inadvertently committed to source control or detached from configuration schemas altogether.
SettingSpec establishes a unified, single-source-of-truth configuration format (settingspec.toml). It provides declarative profile definitions, compile-time/load-time strict environment validation, language-agnostic export capabilities, and seamless secret sourcing.
1.2 Conformance and Key Words
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
1.3 Terminology
- Configuration File: The primary TOML file declaring the specification and settings (by default
settingspec.toml). - Profile: A named runtime environment or operational context (e.g.,
dev,stage,prod). - Default Profile (
default): A fallback profile selector denoting settings applicable across all profiles unless explicitly overridden. - Setting Key: A hierarchical dot-delimited identifier (e.g.,
key1,database.host,group1.auth.timeout). - Directive: A terminal attribute defining the origin or property of a setting (
val,env,null,tags). - Active Profile: The single profile selected for the current execution context.
- Export Target: A file or standard output stream where resolved settings are serialized.
2. File Format and Document Structure
2.1 File Location and Syntax
- A SettingSpec configuration MUST be a valid TOML v1.0.0 document.
- The configuration file name is
settingspec.toml, located in the root of the project directory. There is no way to overwrite it. - A SettingSpec document consists of two top-level tables:
[spec]: Metadata, profile declarations, environment file sources, and export definitions (OPTIONAL).[settings]: Setting definitions and per-profile value declarations (REQUIRED).
2.2 TOML Key Representations
SettingSpec uses dotted keys to represent hierarchical settings, target profiles, and directives.
[settings]
key1.default.val = "val1"
3. The [spec] Section
The [spec] section configures profile resolution, environment variable file loading, and export definitions.
[spec]
profile.key = "SETTINGSPEC_PROFILE"
profile.options = ["dev", "stage", "prod"]
profile.default = "dev"
envfile.default = ".env"
envfile.prod = "-"
[spec.export]
mode = 0x600
keep = false
stdout = "toml"
[spec.export.file]
"settings.toml" = true
"settings.json".group1 = true
3.1 Profile Configuration (spec.profile)
The profile sub-table configures active profile selection and validation rules:
| Field | Type | Default | Description |
|---|---|---|---|
profile.key | String | "SETTINGSPEC_PROFILE" | The name of the environment variable inspected to determine the active profile. |
profile.options | Array of Strings | [] (empty / unset) | The closed list of valid profile names. Enables strict coverage validation. |
profile.default | String | None (unset) | Fallback profile to activate when the environment variable is not defined or empty. |
3.1.1 Profile Validation Rules
- If
profile.optionsis specified:- The active profile MUST be an element of
profile.options. If the resolved active profile is not inprofile.options, execution MUST fail immediately with an error. - Any profile name used within the
[settings]table MUST either be an element ofprofile.optionsor the reserved identifierdefault. Any unknown profile name MUST cause configuration loading to fail. - The reserved name
defaultMUST NOT be included as an element inprofile.options. - Strict Coverage Rule: For every setting declared in
[settings], if adefaultdeclaration is absent, a declaration MUST exist for every profile listed inprofile.options. If any profile is unmapped, loading MUST fail.
- The active profile MUST be an element of
3.2 Environment Files (spec.envfile)
SettingSpec can load environment variable definition files (dotenv files) prior to resolving settings. This is declared under spec.envfile:
envfile.default: Environment file loaded by default for all profiles.envfile.<profile>: Environment file loaded specifically when<profile>is active.
[spec]
envfile.default = ".env"
envfile.prod = "-"
3.2.1 Sourcing Rules
- If an environment file is specified for the active profile (
envfile.<profile>), it takes precedence overenvfile.default. - Sourcing occurs before setting values are resolved.
- The special value
"-"denotes reading environment variables in dotenv format (KEY=VALUE) from standard input (stdin). - Sourced environment variables populate the execution environment for the duration of setting resolution and child command execution.
3.3 Export Configuration (spec.export)
The export sub-table specifies how resolved settings are output to files, stdout, stdin, or environment variables.
3.3.1 File Mode (export.mode)
- Type: Integer (POSIX permission bits).
- Default:
0x600(octal0600, read/write by owner only). - Description: All files created by
settingspec exportorsettingspec runMUST be created or chmod-ed to this mode to protect configuration secrets against unauthorized local access.
3.3.2 File Retention (export.keep)
- Type: Boolean.
- Default:
false. - Description: Controls whether exported target files are retained after
settingspec runfinishes execution.- When
false(default), SettingSpec automatically cleans up and deletes all generated export files after the child command finishes execution. - When
true, SettingSpec retains all exported files on disk after the child command finishes execution.
- When
3.3.3 Standard Output (export.stdout)
- Type: Boolean or String (
"toml","json","yaml","env"etc. supported formats). - Default: None.
- Description: Emits the resolved settings directly to standard output in the specified serialization format.
- Setting
export.stdout = trueorexport.stdout = ""emits resolved settings formatted as TOML (default format). - Setting
export.stdout = "<format>"(e.g.,"json","yaml","toml","env") emits resolved settings formatted in the specified serialization format. - Setting
export.stdout = false(or omittingexport.stdout) disables stdout export.
- Setting
3.3.4 Target Files (export.file)
The export.file table maps target file paths to export filter expressions:
[spec.export.file]
"settings.toml" = true
"settings.yaml" = { key1 = true, group1 = true, group2.subgroup = true, group3.subgroup = false, "#tag1" = true }
- Setting
export.file = truedefaults to exporting all settings tosettings.toml. - Setting
export.file = falsedisables exporting to files. - If no export configuration is declared in
[spec], the default behavior MUST be:[spec.export.file] "settings.toml" = true - Environment variable files (
.env,.env.*,env.*,*.env) are exported inenvformat. - If a target file’s format is unrecognizable (e.g.,
"aaa","output.xyz"), SettingSpec MUST fail with an unsupported export format error.
3.3.5 Environment Variables (export.env)
- Type: Boolean or String.
- Default: None (disabled).
- Description: Exports resolved settings directly as environment variables into the child process when executing
settingspec run.- Setting
export.env = trueorexport.env = ""exports resolved settings using their uppercase, underscore-delimited key names (e.g.,database.host->DATABASE_HOST). - Setting
export.env = "PREFIX_"exports resolved settings with the specified prefix prepended to the key names (e.g.,database.host->PREFIX_DATABASE_HOST). - Setting
export.env = false(or omittingexport.env) disables exporting settings as environment variables. - Setting keys with
nullvalues are omitted from exported environment variables.
- Setting
3.3.6 Standard Input (export.stdin)
- Type: Boolean or String (
"toml","json","yaml","env"etc. supported formats). - Default: None (disabled).
- Description: Passes resolved settings directly to standard input (stdin) of the child process when executing
settingspec run.- Setting
export.stdin = trueorexport.stdin = ""passes resolved settings formatted as TOML. - Setting
export.stdin = "<format>"(e.g.,"json","yaml","toml","env") passes resolved settings formatted in the specified serialization format. - Setting
export.stdin = false(or omittingexport.stdin) disables passing settings via stdin.
- Setting
3.3.7 Git Integration (export.skip_gitignore)
- Type: Boolean.
- Default:
false. - Description: When executing inside a git repository, SettingSpec automatically appends all exported file targets to
.gitignore(adjacent to the git root) before eachrunorexportexecution if not already present. Settingspec.export.skip_gitignore = truedisables this automatic behavior.
4. The [settings] Section
The [settings] section defines configuration keys, their values, environment bindings, tags, and profile-specific overrides.
4.1 Declaration Grammar
Each declaration in [settings] follows the canonical syntax:
{key}.{profile}.{directive} = {value}
Where:
{key}: A dot-delimited identifier representing the setting name or namespace path (e.g.,key1,database.host,app.server.port).{profile}: A valid profile name defined inspec.profile.optionsor the reserved identifierdefault.{directive}: One of the reserved terminal keywords:val,env,null, ortags.{value}: The directive value.
Because TOML treats dot-delimited keys as table hierarchies, the declaration parser identifies:
- The terminal segment as the directive (
val,env,null, ortags). - The penultimate segment as the profile identifier (
defaultor profile name). - All preceding segments as the hierarchical setting key path.
4.2 Setting Directives
4.2.1 val
Supplies a static, literal value for the setting.
- Supported Types: String, Integer, Float, Boolean, Datetime (RFC 3339), Array, Inline Table.
- Example:
app.name.default.val = "MyApp" app.port.dev.val = 8080 app.port.prod.val = 80
4.2.2 env
Binds the setting to an environment variable name.
- Type: String (the name of an environment variable).
- Semantics: At resolution time, SettingSpec attempts to read the named environment variable from the runtime environment (including variables loaded via
spec.envfile). - Example:
database.password.default.env = "DB_PASSWORD"
4.2.3 Coexistence of val/null and env (Default with Env Override)
A setting profile declaration MAY specify both val/null and env. In such cases, the following resolution rules apply:
secret2.default.val = "defaultvalue"
secret2.default.env = "SECRET2"
secret2.prod.env = "PRODSECRET"
- If the environment variable specified by
envexists, its value MUST be used. - If the environment variable does not exist, the value specified by
val/nullMUST be used as the fallback default. - If a setting has an
envdirective without avalornulldirective, and the environment variable is absent at runtime, resolution MUST fail with a missing variable error.
4.2.4 null
Explicitly declares that a setting has a null or unset value for the specified profile.
- Type: Boolean (
true). - Example:
debug_banner.default.val = "My App BETA 0.0.1" debug_banner.prod.null = true - Target Export Semantics:
- For formats with native null representations (JSON, YAML, Python
None, Luanil), the setting MUST be exported as null/None/nil. - For formats without native null representations (TOML 1.0, shell
.env), the setting key MUST be omitted from the exported target. nullcan only betrueand cannot co-exist withvalfor the same profile declaration. If both are present, configuration loading MUST fail.
- For formats with native null representations (JSON, YAML, Python
4.2.5 tags
Associates a list of string tags with a setting.
- Type: Array of Strings.
- Example:
api_key.default.tags = ["sensitive", "auth"] - Semantics: Tags are metadata and are NOT exported as values of the setting. They are used by export filters to include or exclude tagged settings (e.g.,
#auth).
5. Resolution and Validation Semantics
5.1 Active Profile Resolution Order
When executing SettingSpec, the active profile MUST be determined using the following precedence (highest priority first):
- Environment Variable: The value of the environment variable specified by
spec.profile.key(default:SETTINGSPEC_PROFILE). - Specification Default: The value specified by
spec.profile.default. - Resolution Failure: If none of the above are set:
- If
spec.profile.optionsis defined, execution MUST terminate with an error stating that no profile was selected. - If
spec.profile.optionsis not defined, execution MAY continue using default-only resolution.
- If
5.2 Setting Resolution Precedence
For a given setting key K and active profile P, the resolved value V(K, P) MUST be resolved using the following order:
flowchart TD
Start["Resolve Key K for Profile P"] --> HasProfileDecl{"Has declaration for Profile P?"}
HasProfileDecl -- Yes --> CheckProfileEnv{"Profile P has env declared?"}
CheckProfileEnv -- Yes --> CheckEnvExists{"Is env var set?"}
CheckEnvExists -- Yes --> ReturnEnv["Return env var value"]
CheckEnvExists -- No --> CheckProfileVal{"Profile P has val/null declared?"}
CheckProfileEnv -- No --> CheckProfileVal
CheckProfileVal -- Yes --> ReturnProfileVal["Return Profile P val/null"]
CheckProfileVal -- No --> CheckDefault
HasProfileDecl -- No --> CheckDefault{"Has default declaration?"}
CheckDefault -- Yes --> CheckDefaultEnv{"Default has env declared?"}
CheckDefaultEnv -- Yes --> CheckDefaultEnvExists{"Is default env var set?"}
CheckDefaultEnvExists -- Yes --> ReturnDefaultEnv["Return default env var value"]
CheckDefaultEnvExists -- No --> CheckDefaultVal{"Default has val/null declared?"}
CheckDefaultEnv -- No --> CheckDefaultVal
CheckDefaultVal -- Yes --> ReturnDefaultVal["Return default val/null"]
CheckDefaultVal -- No --> ResolutionError["Error: Required value missing"]
CheckDefault -- No --> StrictCheck{"spec.profile.options defined?"}
StrictCheck -- Yes --> MissingCoverError["Error: Key missing coverage for Profile P"]
StrictCheck -- No --> KeyOmitted["Key omitted"]
5.3 Static Validation Rules
During initialization, SettingSpec MUST validate the configuration document prior to performing any export or execution:
- Option Membership: If
spec.profile.optionsis set, every profile referenced in[settings]MUST be present inspec.profile.optionsor bedefault. - Exhaustive Profile Coverage: If
spec.profile.optionsis set, every setting keyKdefined in[settings]MUST satisfy at least one of:- A
defaultdeclaration exists forK. - An explicit declaration exists for
Kacross all profiles listed inspec.profile.options.
- A
- Directive Validity: A declaration MUST contain
envor at least one ofvalornull = true, along with optionaltags. Unknown directives MUST be rejected with an error.
6. Export Target Filtering and Generation
6.1 Filter Expressions and Types
Export target specifications under spec.export.file select subsets of resolved settings for each target file. SettingSpec supports two filter representations:
6.1.1 Boolean Filter (true / false)
true: Exports all resolved settings into the target file.false: Disables exporting to this target file (no file is created).
[spec.export.file]
"settings.toml" = true
6.1.2 Table Filter (Fine-Grained Selector Map)
When finer control over inclusion and exclusion is required, an export target can be defined as an inline table or sub-table mapping selectors (exact keys, namespace prefix paths, or tags) to boolean values (true to include, false to exclude):
[spec.export.file]
"settings.yaml" = { key1 = true, group1 = true, group2.subgroup = true, group3.subgroup = false, "#tag1" = true }
Or using standard TOML table syntax:
[spec.export.file."settings.yaml"]
key1 = true
group1 = true
group2.subgroup = true
group3.subgroup.key1 = false
"#tag1" = true
Exclusion rules take precedence over inclusion rules.
6.2 Table Filter Semantics and Resolution Rules
In a table filter, each key is a selector (exact key, group path, or tag) and each value is a boolean flag (true or false).
6.2.1 TOML Syntax and Non-Conflicting Rules
SettingSpec configurations MUST be valid TOML v1.0.0 documents. In TOML:
- No scalar/table conflicts: A key cannot be assigned a scalar value and subsequently reopened as a table. Syntax such as:
is syntactically invalid because# INVALID TOML: Fails with parse error group2 = true group2.subgroup = falsegroup2cannot simultaneously be a boolean scalar and a table containingsubgroup. - No duplicate keys: Defining duplicate keys in the same scope (e.g.,
key = trueandkey = false) is forbidden by TOML.
Consequently, table filters do NOT allow or require conflicting hierarchical rules on the exact same key path. Instead, SettingSpec interprets scoped rules hierarchically.
6.2.2 Filter Evaluation and Precedence Rules
SettingSpec MUST evaluate table filters using the following precedence and scoping rules:
-
Exclusion Preferred Over Inclusion: Exclusion (
false) always takes precedence over inclusion (true). If a setting matches an applicable exclusion rule (whether by exact key, namespace prefix, or tag), it MUST NOT be exported, even if it also matches an inclusion rule. -
Group and Subgroup Inclusion (
= true): Setting a group or subgroup selector totrueincludes all settings under that namespace:- If
group1 = true, all settings undergroup1(e.g.,group1.key1,group1.sub.key2) are included. - If
group1.subgroup1 = true, all settings undergroup1.subgroup1(e.g.,group1.subgroup1.key1) are included.
- If
-
Exact Key Inclusion (
= true): Setting an individual key totrue(e.g.,key1 = trueorgroup2.subgroup2.key = true) includes only that specific setting:- If
group2.subgroup2.key = true, onlygroup2.subgroup2.keyis included. Sibling keys withingroup2.subgroup2are NOT included unless explicitly selected.
- If
-
Implicit Parent Inclusion via Child Exclusion (
= false): Because TOML syntax does not permit writinggroup = truealongsidegroup.subgroup = false, setting a child path or key tofalsein the absence of sibling inclusion rules implicitly includes the enclosing parent namespace while excluding that specified child:- If
group3.subgroup = false, all settings fromgroup3are included, excluding those undergroup3.subgroup. - If
group2.subgroup2.key = false(orgroup1.subgroup2.key = false), all settings from the parent namespace are included, excludinggroup2.subgroup2.key.
- If
-
Mixed Inclusion and Exclusion in the Same Scope: If a namespace specifies both inclusion (
true) and exclusion (false) rules:- For example, if
group2.subgroup2.key1 = trueandgroup2.subgroup2.key2 = false:- Only
group2.subgroup2.key1is included. group2.subgroup2.key2is explicitly excluded.- Unmentioned sibling keys (e.g.,
group2.subgroup2.key3) are NOT included. Explicit inclusion rules (key1 = true) establish the candidate set for that scope, so the presence of an exclusion rule (key2 = false) does not implicitly include unselected siblings. Exclusion is preferred over inclusion.
- Only
- For example, if
-
Tag Selectors (
"#tag"): Tag selectors cross-cut hierarchy:- If
"#tag1" = true, any setting declaringtags = ["tag1"]is included (unless excluded by an exclusion rule). - If
"#tag2" = false, any setting declaringtags = ["tag2"]is excluded, even if its parent group was set totrue.
- If
-
Base Inclusion Scope:
- Explicit Inclusion Mode: If the table filter contains one or more inclusion rules (or implicit parent inclusions from child exclusion rules), only settings matching those inclusions are considered candidates for export. Settings in other unmentioned namespaces are excluded.
- Pure Exclusion Mode: If the table filter contains only top-level exclusion rules (e.g.,
group1 = false,"#draft" = false), all resolved settings are candidates by default, except those matching any exclusion rule.
6.2.3 Summary of Filter Patterns
| Pattern | Example | Resolved Behavior |
|---|---|---|
| Group Inclusion | group1 = true | Include all settings from group1. |
| Subgroup Inclusion | group1.subgroup1 = true | Include all settings from group1.subgroup1. |
| Exact Key Inclusion | group2.subgroup2.key = true | Include only group2.subgroup2.key. |
| Subgroup Exclusion | group3.subgroup = false | Include all from group3 except group3.subgroup. |
| Key Exclusion | group2.subgroup2.key = false | Include all from parent namespace except group2.subgroup2.key. |
| Mixed in Same Scope | group2.subgroup2.key1 = truegroup2.subgroup2.key2 = false | Include only group2.subgroup2.key1. |
| Tag Exclusion Override | group1 = true"#sensitive" = false | Include all from group1 except settings tagged #sensitive. |
6.3 Supported File Formats and Code Generators
Export formats are inferred from the destination file extension:
| Extension | Format | Description |
|---|---|---|
.toml | TOML 1.0 | Standard TOML format. Keys with null values are omitted. |
.json | JSON | Standard JSON. Keys with null values are serialized as null. |
.yaml, .yml | YAML | Standard YAML. Keys with null values are serialized as null or ~. |
.py | Python Module | Python source file defining nested classes or top-level variables. null maps to None. |
.js, .mjs | JavaScript Module | ECMAScript module (export default { ... }). null maps to null. |
.ts | TypeScript Module | TypeScript module with typed interfaces and export default { ... }. |
.lua | Lua Table | Lua module returning a table (return { ... }). null maps to nil. |
.env, .env.*, env.*, *.env | Shell Environment | Key-value pairs (KEY=VALUE). Keys with null values are omitted. |
7. Command-Line Interface (CLI) Specification
SettingSpec provides a unified command-line tool named settingspec.
7.1 Common Options
All subcommands accept the following common options:
-h, --help: Print help information.-V, --version: Print version information.
7.2 Subcommand: export
Generates all configured export files and/or writes formatted output to stdout.
settingspec export [OPTIONS]
7.3 Subcommand: run
Executes a command with resolved configuration available as exported files.
settingspec run [OPTIONS] -- <COMMAND> [ARGS...]
7.3.1 Execution Lifecycle
sequenceDiagram
autonumber
participant CLI as settingspec run
participant Disk as File System
participant Child as Target Program
CLI->>Disk: Write to stdout and/or all target export files with mode spec.export.mode (0x600)
CLI->>Child: Spawn child process
Child-->>CLI: Child process completes (exit code or signal)
alt If spec.export.keep is false (default)
CLI->>Disk: Delete exported files listed in spec.export.file
else If spec.export.keep is true
CLI->>Disk: Retain exported files on disk
end
CLI->>CLI: Exit with child process exit code
- File Generation: SettingSpec resolves settings for the active profile and writes all target export files declared in
spec.export.fileusing permission modespec.export.mode(default0x600). - Execution: The child process
<COMMAND> [ARGS...]is spawned. Sourced environment variables fromspec.envfileare set, ifspec.export.envis configured, resolved settings are exported as environment variables into the child process, and ifspec.export.stdinis configured, resolved settings are passed to the child process via standard input (stdin). Signals (e.g.,SIGINT,SIGTERM) MUST be forwarded to the child process. - Cleanup:
- If
spec.export.keepisfalse(default): All exported target files are deleted upon completion. - If
spec.export.keepistrue: Exported target files are retained on disk.
- If
- Exit Code Propagation: SettingSpec MUST exit with the same exit status code as the child process.
7.4 Subcommand: check
Validates configuration syntax, profile completeness, and environment variable requirements without writing any files to disk.
settingspec check [OPTIONS]
- Returns exit code
0on validation success. - Returns non-zero exit code with diagnostic errors if syntax is invalid, profiles are incomplete, or required environment variables are missing.
8. Security Considerations
- File Permission Enforcement: All generated export files containing configuration or secrets MUST be created with restricted file permissions (
spec.export.mode, default0x600). On POSIX systems, this prevents reading by other unprivileged users on the same host. - Ephemeral Secrets: Applications that utilize
settingspec runbenefit from ephemeral file lifetimes. By default (spec.export.keep = false), SettingSpec guarantees cleanup of exported files upon process exit, even in the event of child errors or termination signals. - Source Control Hygiene: Configuration authors SHOULD add generated targets (e.g.,
settings.toml,settings.jsonetc.) to.gitignoreto prevent inadvertent commits of decrypted secrets. When inside a git repository, SettingSpec automatically appends exported target files to.gitignore(adjacent to the git root) before eachrunorexportexecution if not already present, unless disabled viaspec.export.skip_gitignore = true. - Standard Input for Secrets: When pairing with secret managers (e.g., SecretSpec, 1Password CLI, Vault), users SHOULD pass secrets via environment variables or stdin (
envfile.prod = "-") to avoid writing raw secrets to persistent disk.