Skip to content
docuconf
docuconf on GitHub

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:

  1. Metadata the host cannot express — descriptions, secret, constraints with no host equivalent.
  2. Validation the host does not do, run after the host has parsed.
  3. 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 as op run or doppler 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.yml or Config.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:

  1. 1
    Idiomatic declaration. Cover every variable type, file type and field, in the host library’s own style.
  2. 2
    Validate 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.
  3. 3
    Export 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.
  4. 4
    Load 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.
  5. 5
    Support 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.
  6. 6
    Report 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.
  7. 7
    Expose typed values. A struct, class or inferred type — never a string map.
  8. 8
    Check 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.
  9. 9
    Honour reload: watch. Reload watched files (Kubernetes swaps a symlink), or reject watch at declaration time.
  10. 10
    Ignore undeclared variables. HOSTNAME, KUBERNETES_* and the like. The unknown-name check applies only to platform values.
  11. 11
    Support DOCUCONF_FILE_ROOT. A directory prepended to every absolute file path and every overlay path, for local runs and tests.
  12. 12
    Offer 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.
  13. 13
    Pass 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.
  14. 14
    Parse 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.
  15. 15
    Support keySet. Expose a key set’s keys in the order the platform gave them, and check their count and length at boot.

And should:

  1. 1
    Export 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.
  2. 2
    Framework integration. A Railtie, ValidateOnStart in .NET, a Next.js or NestJS adapter, a Spring auto-configuration.
  3. 3
    Warn on feature-flag names. Variables matching ^(FF|FEATURE|FEATURE_FLAG|ENABLE)_ are probably flags.
  4. 4
    Detect unresolved injection references. Fail with invalid_type when a secret still holds an injector reference (vault:, op://, ref+) because the injector did not run.
  5. 5
    Warn on Unicode regex classes. Where the host’s \d, \w, \s, \b are Unicode-aware but RE2’s are ASCII.
  6. 6
    Warn 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.
  7. 7
    Offer 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.

Boot error codes
CodeMeaning
missing_requiredA required variable or file input has no value.
invalid_typeThe 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_rangeOutside 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_mismatchA string or text file does not match its pattern.
not_in_enumNot one of the enum’s values.
invalid_schemeA URL with a scheme not in schemes.
too_few_itemsA list shorter than minItems, or a key set with fewer keys than minKeys.
too_many_itemsA list longer than maxItems, or a key set with more keys than maxKeys.
file_missingA file input’s path does not exist.
file_unreadableThe path exists but cannot be read.
file_too_largeLarger than maxSize.
file_malformedDoes 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_mismatchA config file or json value does not match its schema.
certificate_invalidA PEM certificate that does not parse, expired, not yet valid, a disallowed key algorithm, or a broken chain.
certificate_expiringValid, but with less than minRemaining left.
certificate_name_mismatchDoes not cover every name in dnsNames.
key_mismatchThe private key does not match the certificate.
keystore_unreadableThe keystore does not open with its password.

The SDKs today

What each docuconf SDK supports, from each repository's main branch, checked 2026-10-08.
SDKHost libraryPackageVariable typesListsDurationsConfig filesKeystoresreload: watchProfilesExportConformanceExample
Gocaarlos0/env v11github.com/docuconf/docuconf-goall 9csvgojson, yamlPKCS#12all file types—docuconf export -pkg -type316 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 9csvgojson, yamlPKCS#12 (JKS: format check)all file types—npx docuconf-t3 export src/env.ts316 of 316, none skipped (beta suite)T3 Env
TypeScript (NestJS)@nestjs/config + class-validator@docuconf/nestjs (npm)all 9csvgojson, yamlPKCS#12 (JKS: format check)all file types—npx docuconf-nestjs export src/orders.config.ts316 of 316, none skipped (beta suite)NestJS
.NETOptions pattern + appsettingsDocuconf.Options (NuGet)all 9indexedtimespanjsonPKCS#12tls, caBundle, keystore, binaryappsettings.{Environment}.jsondotnet app.dll docuconf export316 of 316, none skipped (beta suite)ASP.NET Core
Pythonpydantic-settings 2docuconf-pydantic (PyPI)all 9json (csv opt-in)iso8601json, yaml, tomlPKCS#12all file types—docuconf export mod:Settings316 of 316, none skipped (beta suite)http.server
Rubyanyway_config 2docuconf-anyway (RubyGems)all 9csviso8601json, yaml, tomlPKCS#12 (JKS: format check)all file typesRails YAML (RAILS_ENV)rails docuconf:export316 of 316, none skipped (beta suite)Rack
Java (Spring Boot)Spring Boot 3 / 4 @ConfigurationPropertiesdev.docuconf:docuconf-spring (Maven)all 9csviso8601json, yaml, tomlPKCS#12, JKSall file typesapplication-{profile}.ymlannotation processor at compile time316 of 316, none skipped (beta suite)Spring Boot
KotlinHoplite 3dev.docuconf:docuconf-hoplite (Maven)all 9csviso8601json, yaml, tomlPKCS#12, JKSfile inputs declared Watched<…>; not overlays—Gradle plugin: docuconfExport316 of 316, none skipped (beta suite)JDK HttpServer
Rustfigment + serdedocuconf (crates.io)all 9jsongojson, yaml, tomlPKCS#12file inputs declared Watched<T>; not overlaysfigment profilesdocuconf::export()316 of 316, none skipped (beta suite)std::net
Swiftswift-configurationDocuconf (SwiftPM)all 9csvsecondsjson, yamlPKCS#12, JKS (integrity check)when the app consumes changes—app docuconf-export316 of 316, none skipped (beta suite)POSIX sockets
Elixirconfig/runtime.exsdocuconf (Hex)all 9csvgojson (yaml, toml via a decoder)PKCS#12, JKS (integrity check)with Docuconf.Watcher—mix docuconf.export316 of 316, none skipped (beta suite):httpd
Gleamenvoy + gleam/dynamic/decodedocuconf_gleam (Hex)all 9csvgojson (others via a parser)format check onlyin a declaration; not in contract-first mode—docuconf.write_contract()316 of 316 on Erlang and JavaScript, none skipped (beta suite)wisp
C++CLI11docuconf (CMake FetchContent)all 9csvgojson, yaml, tomlPKCS#12, JKS (integrity check)file inputs bound to Watched<T>—app --docuconf-export316 of 316, none skipped (beta suite)cpp-httplib
PHP (Laravel)Laravel config + vlucas/phpdotenvdocuconf/docuconf (Packagist)all 9csvgojson, yaml, tomlPKCS#12, JKS (integrity check)file inputs; not overlays—php artisan docuconf:export316 of 316, none skipped (beta suite)Laravel
PHP (Symfony)Symfony config + %env()% processorsdocuconf/docuconf (Packagist)all 9csvgojson, yaml, tomlPKCS#12, JKS (integrity check)file inputs; not overlays—bin/console docuconf:export316 of 316, none skipped (beta suite)Symfony
COBOLGnuCOBOL copybooks + docuconf execdocuconf-cobol (Go module)all 9csvgojson, yaml (checked by docuconf exec)PKCS#12 (checked by docuconf exec)not offered—docuconf-cobol generate316 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 #Render once 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_ROOT to 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.yaml describes a declaration that uses every variable type, file type and field. Each SDK declares it in its own language, exports it, and runs docuconf 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.