Files
ConfigKit/docs/consumers

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
Mounted collections Entry-declared relative destinations, nested metadata/prompt/profile imports, local inheritance, and a version-only higher-tier override Collection IDs, profile meaning, and opening/interpreting prompt resources
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
Shared source-version checks One Definition.Version policy for accepted declarations across participating source documents, with optional or required declarations and sanitized evidence Choosing which declarations the application accepts; final assembled schema and domain validation
Identified prompt sequences Explicit item path/glob imports, helper-supplied IDs, reusable single-field registration, and main-tier replacement of a lower-priority collection Prompt meaning and final domain validation

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.

Shared source-version checks

LoadVersionedService demonstrates if-present and required modes with one Definition.Version rule across a mounted main input and an underlay. The example reads only sanitized SourceInfo.VersionCheck fields; its executable test is in layered_test.go. The configuration contract defines participation, source-local lookup and diagnostics, non-main header consumption, main-path protection, default behavior, and evidence disclosure. Accepted declarations do not replace final assembled decoding or application validation.

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.

LoadPromptBundle is the ordinary-root counterpart: its entry opts into imports through Load, imports metadata at the root, and places a prompt beneath prompts using YAML. The executable example and behavior test are in layered_test.go.

Input destinations and mounted collections

Main and underlay inputs use the same destination field. An import without a destination inherits its declaring document's effective destination; an explicit sequence of key strings appends a relative suffix. Nested imports append to their immediate parent's effective destination, while filesystem paths remain relative to the declaring file. Destinations are application mapping paths, independent of filesystem context. The configuration contract owns the complete behavior and validation rules.

The small Example_inputDestination mounts one mapping source beneath literal collection/prompt keys and loads a second input without a destination to show that root placement remains the default. Consumers pass paths, not filenames or namespace strings for ConfigKit to interpret.

LoadCollectionBundle assembles an embedded collection manifest mounted beneath a literal collection path. The entry's YAML places nested metadata imports at the collection root and prompt and profile peers beneath their own keys. A higher-priority input changes only one prompt's ordinary version field. The main document alone supplies the application schema version. A prompt's local extends relationship resolves after all tiers, so custom inherits the final brief content and fields while retaining its own profile selection.

The resolver follows each effective content_file origin to its declaring source context. This also works for custom's inherited resource. The application-owned ReadCollectionPromptContent demonstrates the same lookup after loading; ConfigKit does not open or interpret the resource. destinations_test.go checks source destinations and authored coordinates, presence, inherited values, relative resources, and strict peer conflicts. The configuration contract owns shared placement, import, source-location, and ownership behavior.

Identified prompt sequences

LoadPromptCatalog uses one definition registration for the literal prompts path and id field. Its main file imports one prompt by path and others by glob, placing each source as an item. A nested ordinary helper import supplies an item's ID. The main tier strictly appends the distinct records in contribution order; its completed sequence replaces the lower-priority default collection as one value.

The example's validatePromptCatalog remains application-owned. It checks that prompt names and message lists satisfy this consumer's requirements after ConfigKit has composed, projected, and decoded the configuration. It does not add recursive record merging, identity-based overlay, or final uniqueness checks after defaults and resolution. See the strict composition contract, import syntax, and ADR 0014 for the exact boundaries.

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 VersionRule accepts 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 int64 values. Runtime int conversion 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 Override records. Separate invocation state remains outside configuration.
  • Credential fields migrate to environment-name Secret references. Applications explicitly call LoadSecretsDir before Load when they choose file bootstrap; blocked empty variables remain blocked, and ordinary Load never 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
Literal input destinations, root-default compatibility, and mounted peer collections Example_inputDestination, LoadCollectionBundle, and destinations_test.go
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.