Add maintained consumer migration examples
This commit is contained in:
@@ -585,6 +585,24 @@ introduce typed secret references.
|
||||
Descriptors are reused by typed decoding, projection, and cloning so those operations
|
||||
cannot acquire different field bindings.
|
||||
|
||||
### Canonical module options
|
||||
|
||||
Applications with a known module catalog represent each configured item as an object
|
||||
with an explicit module identifier and an options mapping. Ordinary typed structs,
|
||||
including a small union of optional option fields, use the same strict binding above;
|
||||
application validation decides which fields and combinations apply to each module.
|
||||
String shorthand, a library module registry, and custom structural decoder hooks are
|
||||
not supported alternatives.
|
||||
|
||||
When a runtime-selected catalog genuinely requires dynamic options,
|
||||
`map[string]any` retains ConfigKit's canonical dynamic scalars. In particular,
|
||||
integers are `int64`; application validation owns allowed keys, value constraints,
|
||||
range checks, and conversion to runtime `int` or catalog objects. ConfigKit does not
|
||||
provide generic subtree conversion, and consumers must not add a YAML marshal/decode
|
||||
round trip to exercise supported behavior. The
|
||||
[consumer adoption guide](consumers/README.md) contains compiled typed and dynamic
|
||||
examples.
|
||||
|
||||
## Defaults and typed decoding
|
||||
|
||||
The private decoder starts from an independently owned clone of the application's
|
||||
|
||||
98
docs/consumers/README.md
Normal file
98
docs/consumers/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Consumer Adoption Guide
|
||||
|
||||
ConfigKit removes parser setup, strict decoding, presence bookkeeping, import and
|
||||
profile 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`](../../examples/consumers) package demonstrates
|
||||
four 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 schema version, 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 |
|
||||
|
||||
The YAML under [`testdata`](../../examples/consumers/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.
|
||||
|
||||
## Happy path
|
||||
|
||||
An application defines ordinary types and passes one explicit source. Optional
|
||||
features appear only when the application needs them:
|
||||
|
||||
```go
|
||||
result, err := configkit.Load[Config](configkit.File(path), configkit.Options[Config]{
|
||||
Definition: rules,
|
||||
Imports: useDeclaredImports,
|
||||
Profile: selectedConfigProfile,
|
||||
Overrides: explicitlySetFlags,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return start(result.Config)
|
||||
```
|
||||
|
||||
Simple consumers use neither imports nor profiles. A complex consumer can opt into
|
||||
both without redefining their syntax. ConfigKit profiles are configuration overlays;
|
||||
they do not select or replace an application's LLM, model, pipeline, report, or
|
||||
execution profile concepts.
|
||||
|
||||
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.
|
||||
|
||||
## 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 are strings. Consumers with integer versions migrate their files
|
||||
instead of enabling another scalar convention.
|
||||
- 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.
|
||||
|
||||
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 roadmap examples
|
||||
|
||||
Every nontrivial API sketch from the feature roadmap 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`](../../example_test.go) |
|
||||
| Source/import/profile 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` |
|
||||
| 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.
|
||||
@@ -25,7 +25,7 @@ redacted effective rendering and historical value explanation. The
|
||||
| Documentation changes | [Documentation policy](policy/documentation.md) and the owning document |
|
||||
| Architecture decisions or future work | [Documentation policy](policy/documentation.md), [initial target design](roadmap/initial.md), and relevant ADRs listed below |
|
||||
| Code and test changes | [Architecture policy](policy/architecture.md), [testing policy](policy/testing.md), and owning contracts/tests as they are introduced |
|
||||
| Examples or copyable assets | The owning implemented contract and related maintained examples, once available |
|
||||
| Examples or copyable assets | The owning implemented contract and [consumer adoption guide](consumers/README.md) |
|
||||
| Release preparation | [Release policy](policy/release.md) and [release-readiness gaps](roadmap/release-readiness.md); the operational runbook will be added before release |
|
||||
|
||||
Accepted decisions:
|
||||
|
||||
@@ -11,6 +11,8 @@ govern every adaptation.
|
||||
| --- | --- | --- | --- |
|
||||
| Narratio | `b68435128be1e24b94fc7ec259d6e9093669fd99` | Clean `main` tracking `origin/main` | Primary parsing, composition, import, profile, presence, defaults, resolution, and inspection source |
|
||||
| WeatherReporter | `5d306a202471e10fe98923a1df27d0b0fce5c666` | Clean `main` tracking `origin/main` | Secrets bootstrap and simple-consumer/defaults/options evidence |
|
||||
| Notarius | `77ae132e5609916da40dcd4fcc749c000a63cbac` | Clean `main` tracking `origin/main` | Canonical binding, string-version, and dynamic-option migration evidence |
|
||||
| Seriatim | `0fc92f364316c874d366b00cdc884a33e1900b25` | Clean `main` tracking `origin/main` | Independent ordered speaker/autocorrect rule evidence |
|
||||
|
||||
These revisions were inspected on 2026-09-06. Paths below are relative to each
|
||||
application repository. Record later source revisions and adaptations here when
|
||||
@@ -369,10 +371,42 @@ Assigned to consumer-shaped examples and migration checks:
|
||||
semantics, registries, rendering, and module option validation remain application
|
||||
responsibilities.
|
||||
|
||||
Consumer-example implementation record (2026-09-06): at Narratio revision
|
||||
`b68435128be1e24b94fc7ec259d6e9093669fd99`, the maintained consumer cases adapt
|
||||
`TestMaintainedExtractionExamplesPreservePublishedContracts`,
|
||||
`TestConfigValidateAndShowPipelineOnlyAreSideEffectFree`, and the stable source-
|
||||
ownership tests into a sanitized partial-import/artifact fixture. ConfigKit replaces
|
||||
application composition envelopes and command output with `_configkit.imports`,
|
||||
typed defaults, recorded resolution, metadata, and explanation. Artifact meaning,
|
||||
model/pipeline profiles, commands, and generated-domain attribution remain in the
|
||||
example application layer; no production function was copied.
|
||||
|
||||
At WeatherReporter revision `5d306a202471e10fe98923a1df27d0b0fce5c666`,
|
||||
`TestDefaults`, explicit-empty-list cases, loaded/constructed consistency cases, and
|
||||
nonmutating module-option normalization were adapted into the WeatherReporter-shaped
|
||||
fixtures and tests in [`examples/consumers`](../../examples/consumers). ConfigKit's
|
||||
detached defaults replace source mutation, canonical module objects replace string
|
||||
shorthand, ordinary validation retains empty-list and option compatibility rules,
|
||||
and explicit `Override` records replace external configuration-field overlays. The
|
||||
startup example composes the separately implemented bootstrap report, typed secret
|
||||
lookup, and explanation APIs without copying WeatherReporter's registries or
|
||||
rendering/runtime modules.
|
||||
|
||||
Notarius revision `77ae132e5609916da40dcd4fcc749c000a63cbac` supplied
|
||||
binding/version/dynamic-option migration evidence only. The local example uses an
|
||||
ordinary canonical object, a string `VersionRule`, ConfigKit's `int64` dynamic
|
||||
integer convention, and application range checking; it does not copy a catalog or
|
||||
environment overlay. Seriatim revision
|
||||
`0fc92f364316c874d366b00cdc884a33e1900b25` supplied ordered rule-file evidence.
|
||||
The local examples load speaker and autocorrect files independently and keep their
|
||||
matching/replacement algorithms outside ConfigKit. Neither source is a build or test
|
||||
dependency.
|
||||
|
||||
## Application-only material
|
||||
|
||||
Do not extract application commands, filesystem discovery, logging, runtime
|
||||
initialization, remote clients, LLM/provider setup, audio/transcript fixtures,
|
||||
configuration digests, report rendering, campaign/session algorithms, or consumer
|
||||
resource validation into ConfigKit. Later consumer examples may use small synthetic
|
||||
types and fixtures to prove that these responsibilities remain straightforward.
|
||||
resource validation into ConfigKit. Maintained consumer examples use only small
|
||||
synthetic types and fixtures to prove that these responsibilities remain
|
||||
straightforward.
|
||||
|
||||
@@ -42,13 +42,17 @@ module and package documentation boundary. Its implemented public primitives are
|
||||
- frozen-snapshot redaction and canonical effective output in `render.go`;
|
||||
- bounded immutable input applications, safe historical replay, detached explanation
|
||||
records, and deterministic human rendering in `explain.go`, with the
|
||||
only upstream dumper integration isolated in `internal/yaml/write.go`.
|
||||
only upstream dumper integration isolated in `internal/yaml/write.go`; and
|
||||
- a separate maintained `examples/consumers` package with four sanitized consumer
|
||||
shapes, fixtures, domain validators, and runtime-boundary functions.
|
||||
|
||||
The [configuration contract](../config.md) owns implemented input, schema, decoding,
|
||||
loading lifecycle, path imports, composition, profile/CLI overlay, version checking,
|
||||
recorded resolution, typed secret references/bootstrap, semantic validation,
|
||||
metadata, secret classification, redacted effective rendering, historical
|
||||
explanation, and ownership behavior.
|
||||
The [consumer adoption guide](../consumers/README.md) owns current application-fit
|
||||
and migration guidance.
|
||||
|
||||
The source-extraction inventory records the application foundations assigned to
|
||||
future implementation work:
|
||||
|
||||
@@ -1437,7 +1437,56 @@ Acceptance:
|
||||
|
||||
## Stage 20 — Prove consumer ergonomics and migration contracts
|
||||
|
||||
Status: pending.
|
||||
Status: complete.
|
||||
|
||||
Completion evidence (2026-09-06):
|
||||
|
||||
- Added the maintained [`examples/consumers`](../../examples/consumers) package and
|
||||
sanitized local fixtures for four application shapes. The Narratio-like example
|
||||
uses fresh defaults, partial imports, artifacts, recorded inherited defaults, and
|
||||
an application model profile distinct from an optional ConfigKit profile. The
|
||||
Notarius-like example uses canonical object bindings, a string version rule,
|
||||
dynamic options, and range-checked `int64` conversion at the runtime boundary.
|
||||
- Added WeatherReporter-like defaults-only and file loads, report maps, canonical
|
||||
module objects, typed option unions, and shared loaded/constructed validation.
|
||||
Application validation—not the ConfigKit schema—rejects explicit empty module and
|
||||
path-template lists and null required module options. Recorded normalization acts
|
||||
on detached values without mutating caller-owned constructed options. Added
|
||||
independent ordered Seriatim-like speaker and autocorrect rule loaders while
|
||||
retaining matching/replacement algorithms in the example package.
|
||||
- Added executable examples for all four shapes and a WeatherReporter-shaped startup
|
||||
flow. The startup explicitly renders `SecretsReport`, demonstrates an existing
|
||||
empty variable blocking its file and a separate successful disposable lookup,
|
||||
translates only an explicitly set `--units` flag into an `Override`, and renders
|
||||
its explanation. It verifies ordinary `Load` preserves the process environment
|
||||
and never uses a real credential.
|
||||
- Generalized the assigned Narratio inspection/default/artifact outcomes and
|
||||
WeatherReporter defaults, explicit-empty, loaded/constructed consistency, and
|
||||
option-normalization cases. [`docs/internal/extraction.md`](../internal/extraction.md)
|
||||
records the rechecked clean revisions, adapted cases, intentional differences,
|
||||
and no-copy boundaries for Narratio `b68435128be1e24b94fc7ec259d6e9093669fd99`,
|
||||
WeatherReporter `5d306a202471e10fe98923a1df27d0b0fce5c666`, Notarius
|
||||
`77ae132e5609916da40dcd4fcc749c000a63cbac`, and Seriatim
|
||||
`0fc92f364316c874d366b00cdc884a33e1900b25`. No sibling checkout is a build or test
|
||||
dependency.
|
||||
- Added negative migration fixtures for integer versions, string module shorthand,
|
||||
explicit empty lists, explicit null required options, and strict unknown fields.
|
||||
They fail with stable `version.type`, `decode.type`, `validate.error`, and
|
||||
`decode.unknown_field` classifications at semantic paths without parser text.
|
||||
Dynamic options bind directly without YAML reserialization or a generic subtree
|
||||
conversion API.
|
||||
- Added the [consumer adoption guide](../consumers/README.md), current canonical
|
||||
module-options contract, README navigation, and component inventory. The guide
|
||||
maps every feature-roadmap API sketch to an executable example or focused test,
|
||||
describes removed plumbing versus retained domain code, and lists explicit
|
||||
empty/null, object, version, integer, strict-YAML, environment-overlay, CLI, and
|
||||
secret-reference migrations without claiming downstream repositories are migrated.
|
||||
- Confirmed the example package directly imports only ConfigKit and the standard
|
||||
library, then reviewed the exported surface and existing AST import-boundary test
|
||||
for aliases, generic constraints, callbacks, record fields, and safe error chains.
|
||||
Verified `go test ./...`, `go test -race ./...`, `go vet ./...`, `go build ./...`,
|
||||
`go mod tidy -diff`, and `git diff --check` with Go 1.26.7 on linux/amd64. No
|
||||
deviations were required; broader hardening remains later work.
|
||||
|
||||
Read: feature consumer-fit table, module options, external values, and both criteria.
|
||||
|
||||
|
||||
@@ -989,95 +989,10 @@ public records, replay ordering, redaction, rendering, and allocation bounds.
|
||||
|
||||
### Module-specific options
|
||||
|
||||
Use a canonical object entry with an explicit module identifier and options
|
||||
mapping. Shorthand strings and custom structural decoders are not required to
|
||||
express the domain information. For a known application module catalog, model
|
||||
the available option fields with ordinary typed structs; application validation
|
||||
selects the meaningful fields and rejects incompatible combinations.
|
||||
|
||||
Illustrative YAML for an application that supports two modules:
|
||||
|
||||
```yaml
|
||||
modules:
|
||||
- module: chunker
|
||||
options:
|
||||
window_units: 12
|
||||
- module: discussion
|
||||
options:
|
||||
sections: [short_term]
|
||||
```
|
||||
|
||||
The corresponding typed configuration and validator need no second parser:
|
||||
|
||||
```go
|
||||
type ModuleOptions struct {
|
||||
WindowUnits *int `yaml:"window_units"`
|
||||
Sections []string `yaml:"sections"`
|
||||
}
|
||||
|
||||
type ModuleSpec struct {
|
||||
Module string `yaml:"module"`
|
||||
Options ModuleOptions `yaml:"options"`
|
||||
}
|
||||
|
||||
type ModuleConfig struct {
|
||||
Modules []ModuleSpec `yaml:"modules"`
|
||||
}
|
||||
|
||||
func validateModules(cfg *ModuleConfig, v *configkit.Validation) {
|
||||
for index, spec := range cfg.Modules {
|
||||
path := configkit.Keys("modules").Index(index)
|
||||
switch spec.Module {
|
||||
case "chunker":
|
||||
if spec.Options.WindowUnits == nil || *spec.Options.WindowUnits < 1 {
|
||||
v.Error(path.Key("options").Key("window_units"), "must be a positive integer")
|
||||
}
|
||||
if spec.Options.Sections != nil {
|
||||
v.Error(path.Key("options").Key("sections"), "not supported by this module")
|
||||
}
|
||||
case "discussion":
|
||||
if spec.Options.WindowUnits != nil {
|
||||
v.Error(path.Key("options").Key("window_units"), "not supported by this module")
|
||||
}
|
||||
if len(spec.Options.Sections) == 0 {
|
||||
v.Error(path.Key("options").Key("sections"), "must contain at least one section")
|
||||
}
|
||||
default:
|
||||
v.Error(path.Key("module"), "unsupported module")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ReadModuleConfig(path string) (*configkit.Result[ModuleConfig], error) {
|
||||
return configkit.Load(configkit.File(path), configkit.Options[ModuleConfig]{
|
||||
Definition: configkit.Definition[ModuleConfig]{Validate: validateModules},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The pointer and nil-slice states above express optional domain values; this
|
||||
application treats explicit null as unselected for those optional alternatives.
|
||||
ConfigKit rejects unknown fields and invalid scalar types. The application owns
|
||||
which supported options apply to a module and their domain constraints. Larger
|
||||
catalogs may use nested typed option objects rather than one flat options struct.
|
||||
Neither shape requires a library registry, parser hook, or configurable merge rule.
|
||||
|
||||
For an application whose option schema is genuinely selected at runtime,
|
||||
`map[string]any` remains available. ConfigKit owns parsing, paths, and the single
|
||||
dynamic scalar representation; application validation owns the selected schema,
|
||||
including allowed keys and value constraints. Conversion into runtime module
|
||||
arguments is application code. Dynamic integers remain checked `int64`; existing
|
||||
`int` assertions must be adapted with range checks when converting to `int`.
|
||||
|
||||
The initial API does not add a generic subtree-conversion helper. Prefer typed
|
||||
configuration where option shapes are known, and direct domain validation where
|
||||
they are dynamic. Do not introduce a second YAML marshal/decode round trip as the
|
||||
blessed pattern. Reconsider a narrow conversion helper only if a concrete dynamic
|
||||
catalog demonstrates substantial duplicated type-binding infrastructure that
|
||||
ordinary models cannot remove. Such a helper would have to retain strictness and
|
||||
source attribution; that is a separate justified extension, not an escape hatch
|
||||
for legacy shorthand. Configuration inspection describes the validated input
|
||||
model, not the application's separately constructed runtime module objects.
|
||||
Canonical typed and dynamic module options are implemented through the ordinary
|
||||
schema. The [current module-options contract](../config.md#canonical-module-options)
|
||||
and [consumer adoption guide](../consumers/README.md) own the maintained object
|
||||
forms, validation boundary, and executable examples.
|
||||
|
||||
### Supported types and projection
|
||||
|
||||
|
||||
Reference in New Issue
Block a user