Draft, not final
v1beta1 does not exist yet. These pages describe the changes proposed for it in docuconf-go pull requests #27, #28, #29 and #30, which are not merged. Anything here can still change. Keep contracts on v1alpha1 until the format freeze lands. See the versioning policy and the draft SPEC.md.
SDK requirements
A docuconf SDK has one job: let an app declare its configuration in the way its ecosystem already does, and add what is missing for a platform contract. There are 16 SDKs today: Go, TypeScript (T3 Env and NestJS config), .NET, Python, Ruby, Java, Kotlin, Rust, Swift, Elixir, Gleam, C++, PHP (Laravel and Symfony) and COBOL. Each has a Get started page.
Build on the host library
Every language already has a configuration library teams trust. An SDK must extend it, not replace it. The host keeps loading, parsing and binding; its users keep its API, docs and idioms. The SDK adds only:
- Metadata the host cannot express — descriptions,
secret, constraints with no host equivalent. - Validation the host does not do, run after the host has parsed.
- Contract export, from the same declaration, so the contract cannot drift from the code.
Each SDK sets the contract's encoding fields to whatever its host parses, so the host never needs a custom parser for platform-rendered values. Where the host conflicts with a rule in the spec (for example, empty-string handling), the SDK adapts the host with a pre-check.
Cloud-native by default
Every SDK must work however the configuration arrives:
- Environment variables set in the pod spec — literals and Kubernetes references.
- Values injected at runtime — Bank-Vaults'
vault-env, wrappers such asop runordoppler run, operators and webhooks. The SDK reads the process environment when the process starts, after injection, and validates injected values exactly like any other. It never resolves secret references itself and never reads configuration at build time. - Files mounted or written at runtime — Secrets, ConfigMaps, CSI volumes, cert-manager certificates, Vault Agent templates — checked at boot and, with
reload: watch, reloaded when they rotate. - Config-file overlays on hosts that layer files — a platform-mounted
appsettings.Production.json,application.ymlorConfig.toml, loaded between the baked-in files and the environment.
See Inputs for how the contract and the platform describe each case.
Requirements
A conforming SDK must:
- 1Idiomatic declaration. Cover every variable type, file type and field, in the host library’s own style.
- 2Validate the declaration. At definition time: name format, description length, details not blank and at most 4000 characters, default against constraints, no default on required, RE2-only patterns, no watch where reload is unsupported.
- 3Export a conforming contract. Deterministic, sorted by name, canonical durations (1h30m), encodings set to what the host parses, narrow integer ranges exported as min/max, full-match patterns anchored as ^(?:p)$. The shared export fixture, declared in the SDK’s language, must match conformance/export/golden.cue, compared as data by docuconf conformance export.
- 4Load from the process environment, at process start. Read the real environment when the process starts, after any injection (Bank-Vaults, Vault Agent, wrappers such as op run), and validate injected values exactly like any other. Never resolve secret references itself, and never read configuration at build time. A .env file is opt-in for development, and real variables override it.
- 5Support config-file overlays (hosts that layer files). Declare overlay files in the contract, load them in the documented order (base file < profile file < platform overlay < environment variables), and reload them when declared watch. Applies to .NET, Spring, Rails, figment and Hoplite; other hosts reject overlays at declaration time.
- 6Report every violation at boot. All together, each with a stable error code, never printing a secret value; also to /dev/termination-log. Length limits count characters (Unicode code points), and a value outside them, or an empty key in a key set, is out_of_range.
- 7Expose typed values. A struct, class or inferred type — never a string map.
- 8Check every file input at boot. Existence, readability and size; config files parse and bind; TLS key match, validity window, minRemaining, DNS names, key algorithm and chain; CA bundle count; keystore opens; text constraints.
- 9Honour reload: watch. Reload watched files (Kubernetes swaps a symlink), or reject watch at declaration time.
- 10Ignore undeclared variables. HOSTNAME, KUBERNETES_* and the like. The unknown-name check applies only to platform values.
- 11Support DOCUCONF_FILE_ROOT. A directory prepended to every absolute file path and every overlay path, for local runs and tests.
- 12Offer a contract-first mode. Validate an environment against a contract.json with no in-language declaration, parsing every list and duration encoding. The conformance runner uses it.
- 13Pass the conformance suite. Every case in docuconf-go’s conformance/cases.json, run through the contract-first mode, including files, profiles, overlays, key sets, deprecated inputs and strict parsing. Only cases tagged int64 or json-schema may be skipped, by a host that lacks that capability.
- 14Parse exactly. Accept exactly the strings the spec allows for each type, whatever the host library accepts on its own: values are never trimmed, a bool is only true or false, an int is base 10, a float has no hex, inf or NaN. A pre-check rejects the extra forms with invalid_type.
- 15Support keySet. Expose a key set’s keys in the order the platform gave them, and check their count and length at boot.
And should:
- 1Export details. From the language’s natural doc location, beside the description. docuconf docs generates the documentation from the contract; an SDK needs no generator of its own.
- 2Framework integration. A Railtie, ValidateOnStart in .NET, a Next.js or NestJS adapter, a Spring auto-configuration.
- 3Warn on feature-flag names. Variables matching ^(FF|FEATURE|FEATURE_FLAG|ENABLE)_ are probably flags.
- 4Detect unresolved injection references. Fail with invalid_type when a secret still holds an injector reference (vault:, op://, ref+) because the injector did not run.
- 5Warn on Unicode regex classes. Where the host’s \d, \w, \s, \b are Unicode-aware but RE2’s are ASCII.
- 6Warn on deprecated inputs. Log a warning at boot for each deprecated input that is set, naming the input and its message, never its value. It still loads and is still checked.
- 7Offer key set helpers. A constant-time contains(candidate), and a helper that tries every key with a check the caller supplies, such as an HMAC comparison.
Boot-time error codes
Every violation found at boot is reported with one of these codes, so platforms and alerting can act on them the same way in every language. Length limits use out_of_range; an expired certificate, a disallowed key algorithm or a broken chain is certificate_invalid; a CA bundle with too few certificates is file_malformed.
| Code | Meaning |
|---|---|
missing_required | A required variable or file input has no value. |
invalid_type | The value does not parse as its type under the exact parsing rules: values are never trimmed, and forms a host accepts on its own (1 for a bool, 0x10 for an int) are rejected. |
out_of_range | Outside min/max or the 64-bit range, a list item outside itemMin/itemMax, a string, url, json value, list item, key or text file outside its length limits (counted in characters), or an empty key in a key set. |
pattern_mismatch | A string or text file does not match its pattern. |
not_in_enum | Not one of the enum’s values. |
invalid_scheme | A URL with a scheme not in schemes. |
too_few_items | A list shorter than minItems, or a key set with fewer keys than minKeys. |
too_many_items | A list longer than maxItems, or a key set with more keys than maxKeys. |
file_missing | A file input’s path does not exist. |
file_unreadable | The path exists but cannot be read. |
file_too_large | Larger than maxSize. |
file_malformed | Does not parse in its format; a tls.crt, tls.key or CA bundle with no PEM certificate or key at all; or a CA bundle with too few certificates. |
schema_mismatch | A config file or json value does not match its schema. |
certificate_invalid | A PEM certificate that does not parse, expired, not yet valid, a disallowed key algorithm, or a broken chain. |
certificate_expiring | Valid, but with less than minRemaining left. |
certificate_name_mismatch | Does not cover every name in dnsNames. |
key_mismatch | The private key does not match the certificate. |
keystore_unreadable | The keystore does not open with its password. |
The SDKs today
| SDK | Host library | Package | Variable types | Lists | Durations | Config files | Keystores | reload: watch | Profiles | Export | Conformance | Example |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Go | caarlos0/env v11 | github.com/docuconf/docuconf-go | all 9 | csv | go | json, yaml | PKCS#12 | all file types | — | docuconf export -pkg -type | 316 of 316, none skipped (beta suite, docuconf-go PR #29; main runs the 134-case v1alpha1 suite) | net/http |
| TypeScript (T3 Env) | T3 Env + Zod 4 | @docuconf/t3 (npm) | all 9 | csv | go | json, yaml | PKCS#12 (JKS: format check) | all file types | — | npx docuconf-t3 export src/env.ts | 316 of 316, none skipped (beta suite) | T3 Env |
| TypeScript (NestJS) | @nestjs/config + class-validator | @docuconf/nestjs (npm) | all 9 | csv | go | json, yaml | PKCS#12 (JKS: format check) | all file types | — | npx docuconf-nestjs export src/orders.config.ts | 316 of 316, none skipped (beta suite) | NestJS |
| .NET | Options pattern + appsettings | Docuconf.Options (NuGet) | all 9 | indexed | timespan | json | PKCS#12 | tls, caBundle, keystore, binary | appsettings.{Environment}.json | dotnet app.dll docuconf export | 316 of 316, none skipped (beta suite) | ASP.NET Core |
| Python | pydantic-settings 2 | docuconf-pydantic (PyPI) | all 9 | json (csv opt-in) | iso8601 | json, yaml, toml | PKCS#12 | all file types | — | docuconf export mod:Settings | 316 of 316, none skipped (beta suite) | http.server |
| Ruby | anyway_config 2 | docuconf-anyway (RubyGems) | all 9 | csv | iso8601 | json, yaml, toml | PKCS#12 (JKS: format check) | all file types | Rails YAML (RAILS_ENV) | rails docuconf:export | 316 of 316, none skipped (beta suite) | Rack |
| Java (Spring Boot) | Spring Boot 3 / 4 @ConfigurationProperties | dev.docuconf:docuconf-spring (Maven) | all 9 | csv | iso8601 | json, yaml, toml | PKCS#12, JKS | all file types | application-{profile}.yml | annotation processor at compile time | 316 of 316, none skipped (beta suite) | Spring Boot |
| Kotlin | Hoplite 3 | dev.docuconf:docuconf-hoplite (Maven) | all 9 | csv | iso8601 | json, yaml, toml | PKCS#12, JKS | file inputs declared Watched<…>; not overlays | — | Gradle plugin: docuconfExport | 316 of 316, none skipped (beta suite) | JDK HttpServer |
| Rust | figment + serde | docuconf (crates.io) | all 9 | json | go | json, yaml, toml | PKCS#12 | file inputs declared Watched<T>; not overlays | figment profiles | docuconf::export() | 316 of 316, none skipped (beta suite) | std::net |
| Swift | swift-configuration | Docuconf (SwiftPM) | all 9 | csv | seconds | json, yaml | PKCS#12, JKS (integrity check) | when the app consumes changes | — | app docuconf-export | 316 of 316, none skipped (beta suite) | POSIX sockets |
| Elixir | config/runtime.exs | docuconf (Hex) | all 9 | csv | go | json (yaml, toml via a decoder) | PKCS#12, JKS (integrity check) | with Docuconf.Watcher | — | mix docuconf.export | 316 of 316, none skipped (beta suite) | :httpd |
| Gleam | envoy + gleam/dynamic/decode | docuconf_gleam (Hex) | all 9 | csv | go | json (others via a parser) | format check only | in a declaration; not in contract-first mode | — | docuconf.write_contract() | 316 of 316 on Erlang and JavaScript, none skipped (beta suite) | wisp |
| C++ | CLI11 | docuconf (CMake FetchContent) | all 9 | csv | go | json, yaml, toml | PKCS#12, JKS (integrity check) | file inputs bound to Watched<T> | — | app --docuconf-export | 316 of 316, none skipped (beta suite) | cpp-httplib |
| PHP (Laravel) | Laravel config + vlucas/phpdotenv | docuconf/docuconf (Packagist) | all 9 | csv | go | json, yaml, toml | PKCS#12, JKS (integrity check) | file inputs; not overlays | — | php artisan docuconf:export | 316 of 316, none skipped (beta suite) | Laravel |
| PHP (Symfony) | Symfony config + %env()% processors | docuconf/docuconf (Packagist) | all 9 | csv | go | json, yaml, toml | PKCS#12, JKS (integrity check) | file inputs; not overlays | — | bin/console docuconf:export | 316 of 316, none skipped (beta suite) | Symfony |
| COBOL | GnuCOBOL copybooks + docuconf exec | docuconf-cobol (Go module) | all 9 | csv | go | json, yaml (checked by docuconf exec) | PKCS#12 (checked by docuconf exec) | not offered | — | docuconf-cobol generate | 316 of 316 under docuconf exec, none skipped (beta suite) | batch job |
Known gaps
- Go: caarlos0/env parses int as 32-bit, so that range is exported. No TOML config files, no JKS keystores.
- TypeScript (T3 Env): ESM (.mjs) and CommonJS both supported. int is limited to the safe-integer range. Tested with Zod 4; other Standard Schema validators (Valibot, ArkType) are untested.
- TypeScript (NestJS): ESM and CommonJS builds; NestJS 11 (CommonJS) and 12 (ESM). int is limited to the safe-integer range.
- .NET: Platform-mounted appsettings overlays load through AddDocuconfOverlays<T>(). Export runs at runtime; a build-time source generator is planned.
- Python: No JKS keystores. No profiles.
- Ruby: Rails credentials must be excluded explicitly. TOML needs the tomlrb gem.
- Java (Spring Boot): Several active profiles at once (a,b) are not supported yet. The dev.docuconf Maven namespace is not verified on Maven Central yet.
- Kotlin: JVM target only so far; the core module is Kotlin Multiplatform-ready. Overlays are read once: reload: watch is rejected on an overlay.
- Rust: reload: watch is not offered on overlays. No JKS keystores.
- Swift: Built and tested on Linux; macOS and iOS builds are untested. No TOML, no profiles.
- Elixir: Watched files reload only while the app supervises Docuconf.Watcher.
- Gleam: Keystores are not opened with their password yet. On the JavaScript target an int holds ±(2^53 − 1); BigIntValue holds the full 64-bit range. The Hex package is `docuconf_gleam`: `docuconf` is the Elixir SDK's.
- C++: No profiles or config-file overlays in the declaration; contract-first mode reads both. JKS keystores are checked by their integrity digest; their entries are not parsed.
- PHP (Laravel): No profiles or config-file overlays. No reload: watch on overlays. Run php artisan config:cache when the container starts, not in the image build.
- PHP (Symfony): No profiles or config-file overlays. No reload: watch on overlays. No Flex recipe yet: register the bundle by hand.
- COBOL: Patterns, URL schemes, JSON Schemas and file contents are checked by docuconf exec, not by the loader. A PIC X field holds bytes and contract lengths count characters: leave room for multi-byte UTF-8, or restrict values to ASCII. A value loses its trailing spaces.
Conformance suite
The shared suite lives in conformance/ in docuconf-go. Cases are written once, in YAML, and expanded into cases.json, which every SDK runs in CI through its contract-first mode. For v1beta1 it covers every input, not only variables:
- Values cases start from typed platform values, rendered with the meta-schema's
#Renderonce per list and duration encoding, so one case checks every encoding. - Env cases give the raw environment the app might see anyway, with the exact error codes every SDK must report. An env case may also give files, by path: config files in every format, TLS key pairs, CA bundles, PKCS#12 keystores and text files. The runner writes them under a new directory and sets
DOCUCONF_FILE_ROOTto it. - Profiles and overlays are tested in the documented layer order: base default, profile, overlay, environment.
- Certificates, keys and keystores come from
conformance/fixtures, generated deterministically. Valid certificates run for 50 years, and the suite fails 60 days before one expires. - Export is checked with a shared fixture:
conformance/export/fixture.yamldescribes a declaration that uses every variable type, file type and field. Each SDK declares it in its own language, exports it, and runsdocuconf conformance export --golden conformance/export/golden.cue, which compares the two as data. It ignores what the SDK sets for its host (metadata.generator,encoding,configKey); everything else must match.
Capability tags. A case may require int64 (the host holds every 64-bit integer) or json-schema (the SDK validates json values against their schema); an SDK that lacks one skips those cases and documents the gap. The other tags, key-set, deprecated, strict-parsing, files, profiles and overlays, are transitional: they let older SDKs keep a green suite while they catch up, and they are dropped when v1beta1 is frozen, so every SDK must support them by then. A runner that does not know a tag skips the case rather than run it.
Two SDKs that disagree on a case cannot both be right, so a failing case is a bug in an SDK or in the spec. How each SDK does today is on SDK tiers. See SPEC.md §12.