12 KiB
Consumer Adoption Guide
ConfigKit removes parser setup, strict decoding, presence bookkeeping, import and selected-overlay traversal, precedence, provenance, secret lookup, and safe inspection from applications. Consumers still own their configuration structs, defaults, semantic validation, schema-version policy, CLI parsing, runtime matching and conversion, resource checks, remote acquisition, and service initialization.
The maintained examples/consumers package demonstrates
sanitized shapes using only ConfigKit and the Go standard library:
| Shape | Demonstrated fit | Application code deliberately retained |
|---|---|---|
| Narratio-like | Fresh defaults, explicit partial imports, artifact maps, recorded inherited defaults, normalization, and source ownership | Artifact meaning and the separate model_profile domain field |
| Notarius-like | Canonical object bindings, a string-backed schema version accepting string or integer YAML, strict dynamic options, and int64 dynamic integers |
Catalog selection, allowed option keys, range-checked conversion to runtime int |
| WeatherReporter-like | Defaults-only loading, report maps, canonical module objects, typed option unions, explicit CLI records, and typed secret startup | Report/module compatibility, rejection of explicitly empty domain lists, CLI parsing, and startup ordering |
| Seriatim-like | Independent strict ordered speaker and autocorrect rule files | First-match and ordered replacement algorithms; no central application configuration file |
| Bundled file | Simple native fs.FS source through ordinary Load |
Selection and lifetime of the application-owned embedded filesystem |
| Layered catalog | Embedded defaults with an import, a mapping underlay, an independent host main file, explicit false/zero input, and source-relative resource access | Domain validation and opening/interpreting the template resource |
| Named presets | One-load sibling inheritance, explicit false, nested destination resolution, and source/derivation inspection | Choosing and executing a preset |
| Effective configuration diff | Same-type frozen effective comparisons for two selected files or base-versus-overlay impact | Source/options selection and consumer-owned safe presentation |
The YAML under testdata is loaded by the
executable examples and ordinary tests. It contains no private application data,
credentials, network calls, or dependency on a sibling checkout.
The Notarius-shaped fixtures deliberately load both version: 4 and
version: "4" into the same string-backed application field. Its version checker
still owns the supported identifier; fractional numeric input and unsupported
integer identifiers remain negative examples.
Happy path
An application defines ordinary types and passes one explicit source. Optional features appear only when the application needs them:
result, err := configkit.Load[Config](configkit.File(path), configkit.Options[Config]{
Definition: rules,
Imports: useDeclaredImports,
Overlay: selectedConfigOverlay,
Overrides: explicitlySetFlags,
})
if err != nil {
return err
}
return start(result.Config)
Simple consumers use neither imports nor selected overlays. A complex consumer can opt into both without redefining their syntax. ConfigKit overlays are configuration overlays; they do not select or replace an application's LLM, model, pipeline, report, or execution profile concepts.
Effective configuration diffs
CompareFiles loads two caller-selected files
with one application definition, handles each load error, calls configkit.Diff,
and presents the safe records through consumer-owned FormatDifferences.
CompareRolloutOverlay loads the same base
once without an overlay and once with the selected overlay, so omitted lower
priority values remain present. Its resolver records a derived endpoint URL after
overlay application, and its nested typed secret reference is classified before
the resulting records are presented.
The maintained behavior tests and executable examples
use temporary files and disposable secret values. Presenters should use
ValueSnapshot.Present for absence, record membership for change, and
HiddenChanged for a withheld change indicator. Do not parse the redaction marker
or add separately rendered full contexts that lack combined sensitivity. The
configuration contract owns shared
equality, granularity, and disclosure semantics.
Named sibling inheritance
LoadPresets loads the maintained
presets.yml in one
call. custom inherits enabled: false from base, while its nested
settings.primary changes the inherited settings.secondary result at the
custom destination. The application struct has no extends field; ConfigKit
consumes the relationship before strict typed decoding.
The executable example reads
Metadata.IsExplicit for the inherited false value, then follows
Origin.Inheritance back to the parent path while Origin.Locations identifies
the declaring source. Applications still choose which preset to execute and
validate domain meaning. The configuration contract
owns exact lookup, precedence, cancellation, and migration rules.
Native filesystem and layered input
LoadBundled keeps the simple Load shape:
it selects a file in an application-owned embedded filesystem and needs no
underlay setup. LoadCatalog uses
LoadLayered with an imported embedded-defaults group, a separate
Mapping group, and a host main file supplied by its caller. The groups
compose strictly within themselves and recursively across precedence boundaries;
the main file's explicit false and 0 replace lower values without being
mistaken for omissions. Its validator owns allowed modes and resource-name policy.
ReadWelcome follows the effective field's
origin location to a source ID, queries Result.SourceContext, and opens the
resource relative to that context's BaseDir. It does not assume the main file
owns a retained underlay value. A composite origin needs an application choice;
ConfigKit does not guess a directory. The context query is passive, while later
resource reads observe the caller-owned filesystem or a freshly opened host root
and may fail independently of the completed configuration load. The
configuration contract owns exact
lifetime and precedence rules.
Known module catalogs should use canonical object entries with a module identifier
and typed options. A small union struct is sufficient when the available option
fields are known; validation selects which fields apply to each module. Runtime-
selected catalogs may retain map[string]any, whose integers are int64; applications
validate keys and ranges and convert only at their runtime boundary. Neither shape
needs YAML reserialization, a parser hook, a ConfigKit registry, or a generic subtree
conversion API.
Source-aware application path policy
LoadNotariusPaths is the maintained
path-bearing example. Its application resolver uses Resolution.InputInfo and
Resolution.Source before normalizing: relative CLI input uses an explicitly
supplied invocation directory, retained defaults use the canonical main source,
and a file-authored scalar uses its one declaring source. Absolute paths remain
unchanged. Its executable test covers the matching effective YAML and explanation
history, source-file ownership, and a symlink opened through an alias; canonical
target-file directories, rather than the alias directory or process working
directory, are the selected bases.
The selected example policy leaves a relative value unchanged and emits a warning when a CLI base is absent or file provenance is missing or composite. That is an application choice, not a ConfigKit fallback: consumers can instead reject it or choose another explicit base. The example intentionally performs neither target existence checks nor confinement checks.
Intentional migration changes
The examples preserve domain meaning while adopting one configuration convention:
- Explicit empty strings, lists, zeroes, false values, and nulls remain intentional. ConfigKit accepts an empty list structurally; WeatherReporter-shaped validation rejects an explicitly or programmatically supplied empty module/template list as a domain rule. Loaded and constructed values use the same validator.
- Canonical module and object bindings replace string shorthand. Unknown fields and incompatible option shapes fail at their semantic paths.
- Schema versions remain string-backed in application types. A configured
VersionRuleaccepts string or strict decimal-integer YAML at its exact path and supplies the spelling to both the checker and a plain string destination. - Integers inside dynamic options are portable
int64values. Runtimeintconversion is explicit and range checked. - Strict YAML intentionally rejects duplicate keys, aliases, merge keys, unsupported tags/scalars, trailing documents, and unknown typed fields. Diagnostics expose stable classifications and safe paths, not copied parser text.
- General environment-variable configuration overlays retire. Application CLI
parsers translate only explicitly set configuration flags into
Overriderecords. Separate invocation state remains outside configuration. - Credential fields migrate to environment-name
Secretreferences. Applications explicitly callLoadSecretsDirbeforeLoadwhen they choose file bootstrap; blocked empty variables remain blocked, and ordinaryLoadnever changes the process environment.
Selected-overlay migration
To adopt the current selected-overlay API, rename Options.Profile,
LayerProfile, and CodeProfile* references to their Overlay equivalents; move
selected files from profiles/<name>.yml to overlays/<name>.yml; update any
handling of the overlay layer and overlay.* diagnostic strings; then rebuild.
The configuration contract is the canonical
behavioral reference. Application-owned profile concepts, such as model_profile,
are unchanged, and any CLI flag rename remains the application's decision.
These examples are migration evidence, not completed changes to Narratio, Notarius, WeatherReporter, or Seriatim. Updating those repositories, their deployed YAML, CLI wiring, runtime catalogs, and operational documentation remains separate downstream work.
Maintained API examples
Every nontrivial initial API sketch has a compiled example or a focused behavioral owner:
| Roadmap surface | Maintained owner |
|---|---|
Load, NoSource, validation, versions, resolution, SecretEnv, overrides, and explanation |
Executable examples in example_test.go |
| Source/import/selected-overlay pipeline rules and artifact defaults | Narratio-shaped executable example and fixture |
| Canonical module objects and dynamic option handling | WeatherReporter- and Notarius-shaped examples and migration tests |
LoadSecretsDir, bootstrap reporting, typed lookup, CLI explanation |
Example_weatherStartup |
| Native filesystem, grouped underlays, programmatic mapping, retained resource context | Example_nativeFilesystem, Example_layeredCatalog, and their focused consumer tests |
| Named sibling inheritance, presence, and derivation | Example_inheritedPresets and TestPresetCatalogOneLoadAndDerivation |
| Effective configuration diffs and consumer-owned presentation | ExampleDiff, ExampleCompareFiles, and ExampleCompareRolloutOverlay |
| Results, metadata, diagnostics, effective output, and writer failures | Focused root-package load, provenance, rendering, and explanation tests |
The package-level import-boundary test covers the consumer examples too: supported usage names no YAML dependency, parser type, parser option, or parser error chain.