Add initial documentation skeleton
This commit is contained in:
861
CONFIGKIT_DESIGN_PROPOSAL.md
Normal file
861
CONFIGKIT_DESIGN_PROPOSAL.md
Normal file
@@ -0,0 +1,861 @@
|
||||
# ConfigKit — Initial Design and Architecture
|
||||
|
||||
## Status
|
||||
|
||||
**Design note for initial implementation.**
|
||||
|
||||
Audience: future maintainers of ConfigKit, including LLM coding agents assisting with implementation.
|
||||
|
||||
This document captures the current architectural and policy decisions for ConfigKit. The goal is to establish a sufficiently opinionated v1 design that can be implemented consistently across the application suite without prematurely becoming a general-purpose configuration framework.
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
ConfigKit is a shared Go library for configuration loading and resolution across applications such as Seriatim, Audita, Narratio, WeatherFeeder, WeatherReporter, Distributor, and related tools.
|
||||
|
||||
Its purpose is to provide a consistent, blessed configuration lifecycle for applications that use YAML configuration files.
|
||||
|
||||
ConfigKit should solve recurring infrastructure concerns once:
|
||||
|
||||
- file loading;
|
||||
- YAML parsing and strictness;
|
||||
- deterministic imports / `conf.d` composition;
|
||||
- duplicate-definition detection;
|
||||
- intentional higher-precedence overlays such as profiles;
|
||||
- presence tracking;
|
||||
- source/provenance tracking;
|
||||
- typed decoding;
|
||||
- standardized diagnostics;
|
||||
- effective-configuration inspection;
|
||||
- support for application defaults, derived defaults, and validation.
|
||||
|
||||
ConfigKit is **not** intended to become a universal configuration framework.
|
||||
|
||||
---
|
||||
|
||||
## 2. Design Principles
|
||||
|
||||
### 2.1 YAML is syntax; ConfigKit defines semantics
|
||||
|
||||
The YAML parser must not implicitly define ConfigKit behavior for:
|
||||
|
||||
- composition;
|
||||
- precedence;
|
||||
- duplicate keys;
|
||||
- sequence merging;
|
||||
- `null`;
|
||||
- imports;
|
||||
- defaults;
|
||||
- profile overlays.
|
||||
|
||||
These behaviors must be explicit ConfigKit policy.
|
||||
|
||||
### 2.2 Preserve information until late in the pipeline
|
||||
|
||||
ConfigKit should retain the parsed YAML tree and source metadata through composition and overlay processing.
|
||||
|
||||
Do **not** immediately reduce configuration to `map[string]any` or typed Go structs.
|
||||
|
||||
The implementation should preserve enough information to answer:
|
||||
|
||||
> Why does this effective value have this value, and where did it come from?
|
||||
|
||||
### 2.3 Keep application-facing Go types ordinary
|
||||
|
||||
Applications should normally use idiomatic types:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Enabled bool
|
||||
Workers int
|
||||
Timeout time.Duration
|
||||
}
|
||||
```
|
||||
|
||||
ConfigKit should not require pervasive `*bool`, `*int`, or `Optional[T]` fields merely to distinguish absence from explicit zero values.
|
||||
|
||||
Parser-level presence is metadata owned by ConfigKit.
|
||||
|
||||
Application-level optionality should remain explicit in the application's Go type where it is genuinely part of the application's semantics.
|
||||
|
||||
---
|
||||
|
||||
## 3. Responsibility Boundary
|
||||
|
||||
### ConfigKit owns
|
||||
|
||||
- configuration source loading;
|
||||
- YAML parsing;
|
||||
- YAML policy enforcement;
|
||||
- import discovery;
|
||||
- deterministic import ordering;
|
||||
- strict composition;
|
||||
- overlay application;
|
||||
- duplicate-definition detection;
|
||||
- source location tracking;
|
||||
- presence tracking;
|
||||
- definition history / provenance;
|
||||
- typed decoding;
|
||||
- standard diagnostics;
|
||||
- effective-config rendering;
|
||||
- config source reporting;
|
||||
- config fingerprinting, if implemented;
|
||||
- redaction mechanisms for secrets.
|
||||
|
||||
### Consuming applications own
|
||||
|
||||
- their `Config` Go types;
|
||||
- static default values;
|
||||
- derived/defaulted values;
|
||||
- semantic validation;
|
||||
- schema meaning;
|
||||
- schema migrations;
|
||||
- runtime initialization;
|
||||
- runtime resource validation;
|
||||
- hot reload and resource replacement.
|
||||
|
||||
---
|
||||
|
||||
## 4. Configuration Resolution Model
|
||||
|
||||
The conceptual lifecycle is:
|
||||
|
||||
```text
|
||||
source discovery
|
||||
↓
|
||||
YAML parsing
|
||||
↓
|
||||
YAML policy validation
|
||||
↓
|
||||
strict base composition
|
||||
↓
|
||||
profile overlay
|
||||
↓
|
||||
static defaults / typed decode
|
||||
↓
|
||||
derived defaults / normalization
|
||||
↓
|
||||
semantic validation
|
||||
↓
|
||||
immutable effective configuration
|
||||
```
|
||||
|
||||
A useful precedence model is:
|
||||
|
||||
```text
|
||||
LOW PRECEDENCE
|
||||
|
||||
application defaults
|
||||
↓
|
||||
base configuration
|
||||
├─ main file
|
||||
└─ imports / conf.d ← strict composition
|
||||
↓
|
||||
selected profile ← intentional overlay
|
||||
|
||||
HIGH PRECEDENCE
|
||||
```
|
||||
|
||||
Future layers such as environment-variable or CLI value overrides may be added later, but are not required for v1.
|
||||
|
||||
---
|
||||
|
||||
## 5. YAML Policy
|
||||
|
||||
YAML remains the configuration format.
|
||||
|
||||
ConfigKit should support a conservative YAML subset rather than arbitrary YAML semantics.
|
||||
|
||||
Recommended v1 policy:
|
||||
|
||||
- one YAML document per file;
|
||||
- mappings, sequences, and scalars;
|
||||
- string mapping keys;
|
||||
- comments;
|
||||
- unique mapping keys;
|
||||
- reject unknown fields when decoding into typed configuration;
|
||||
- reject custom YAML tags;
|
||||
- reject YAML merge keys (`<<`);
|
||||
- strongly consider rejecting anchors / aliases;
|
||||
- reject ambiguous or unsupported constructs rather than guessing.
|
||||
|
||||
The intended model is approximately:
|
||||
|
||||
> JSON's data model, with comments and more pleasant human syntax.
|
||||
|
||||
The underlying YAML implementation must remain an internal implementation detail. Public ConfigKit APIs should not expose parser-specific node or error types.
|
||||
|
||||
---
|
||||
|
||||
## 6. Presence Semantics
|
||||
|
||||
ConfigKit must distinguish at least these states:
|
||||
|
||||
| State | Meaning |
|
||||
|---|---|
|
||||
| key absent | no opinion; a lower-precedence/default value may apply |
|
||||
| `key: false`, `key: 0`, `key: ""` | explicitly configured value |
|
||||
| `key: null` | explicitly null; not equivalent to absent |
|
||||
| ordinary nonzero value | explicitly configured value |
|
||||
|
||||
Presence information must survive configuration resolution even after the final typed Go value is produced.
|
||||
|
||||
Applications should be able to determine, when necessary, whether an effective value was explicitly configured or defaulted.
|
||||
|
||||
Conceptual metadata API:
|
||||
|
||||
```go
|
||||
result.Config
|
||||
result.Metadata.IsExplicit("transcription.concurrency")
|
||||
result.Metadata.Origin("transcription.concurrency")
|
||||
result.Metadata.History("transcription.concurrency")
|
||||
```
|
||||
|
||||
Exact API names are not yet fixed.
|
||||
|
||||
---
|
||||
|
||||
## 7. Defaults
|
||||
|
||||
### 7.1 Static defaults
|
||||
|
||||
Applications define static defaults, for example:
|
||||
|
||||
```go
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
Enabled: true,
|
||||
Workers: 4,
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Explicit YAML values such as `false` and `0` must override those defaults.
|
||||
|
||||
Absent YAML values leave defaults intact.
|
||||
|
||||
### 7.2 Derived defaults
|
||||
|
||||
Defaults that depend on other resolved fields should be applied by an application-owned resolver after typed decoding.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
func ResolveConfig(cfg *Config, meta configkit.Metadata) error {
|
||||
if !meta.IsExplicit("timeout") {
|
||||
cfg.Timeout = cfg.PollInterval / 10
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
ConfigKit should provide a standard lifecycle hook for this but should not own application-specific derived-default logic.
|
||||
|
||||
---
|
||||
|
||||
## 8. `null`
|
||||
|
||||
`null` is **not** equivalent to absence.
|
||||
|
||||
Recommended v1 rule:
|
||||
|
||||
> `null` is an explicit value and is valid only where the application schema permits null.
|
||||
|
||||
For ordinary scalar fields such as:
|
||||
|
||||
```go
|
||||
Workers int
|
||||
Enabled bool
|
||||
```
|
||||
|
||||
this should be invalid:
|
||||
|
||||
```yaml
|
||||
workers: null
|
||||
```
|
||||
|
||||
Nullable application semantics may be represented using suitable Go types such as pointers.
|
||||
|
||||
Do not use `null` in v1 to mean "reset this value to the application default."
|
||||
|
||||
If profile-level reset-to-default semantics are eventually required, add an explicit ConfigKit mechanism rather than overloading YAML `null`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Strict Composition
|
||||
|
||||
Base configuration may be assembled from a main file plus imported fragments, including Debian-style `conf.d` directories.
|
||||
|
||||
Composition means:
|
||||
|
||||
> Multiple files contribute non-conflicting portions of one logical configuration.
|
||||
|
||||
A duplicate semantic value path across composed files is an error.
|
||||
|
||||
Example allowed:
|
||||
|
||||
```yaml
|
||||
# 10-server.yaml
|
||||
server:
|
||||
host: localhost
|
||||
```
|
||||
|
||||
```yaml
|
||||
# 20-server.yaml
|
||||
server:
|
||||
port: 8080
|
||||
```
|
||||
|
||||
These define different paths:
|
||||
|
||||
```text
|
||||
server.host
|
||||
server.port
|
||||
```
|
||||
|
||||
Example rejected:
|
||||
|
||||
```yaml
|
||||
# A
|
||||
server:
|
||||
port: 8080
|
||||
```
|
||||
|
||||
```yaml
|
||||
# B
|
||||
server:
|
||||
port: 9000
|
||||
```
|
||||
|
||||
Both define:
|
||||
|
||||
```text
|
||||
server.port
|
||||
```
|
||||
|
||||
The diagnostic should identify both source locations.
|
||||
|
||||
### 9.1 Mapping behavior
|
||||
|
||||
Mappings compose recursively by semantic path.
|
||||
|
||||
### 9.2 Sequence behavior
|
||||
|
||||
Sequences do not implicitly compose or merge.
|
||||
|
||||
If two composed files define the same sequence path, that is a collision.
|
||||
|
||||
When configuration needs independently composable named entries, prefer mappings keyed by stable identifiers rather than lists.
|
||||
|
||||
---
|
||||
|
||||
## 10. Intentional Overlay
|
||||
|
||||
Overlay is a separate operation from composition.
|
||||
|
||||
Overlay means:
|
||||
|
||||
> A higher-precedence layer intentionally replaces values from a lower-precedence layer.
|
||||
|
||||
For mappings, overlay recursively by key.
|
||||
|
||||
For scalar values, the higher-precedence value wins.
|
||||
|
||||
For sequences, the higher-precedence sequence replaces the lower-precedence sequence in full.
|
||||
|
||||
Do not implement implicit sequence append, union, or index-wise merging in v1.
|
||||
|
||||
---
|
||||
|
||||
## 11. Profiles
|
||||
|
||||
Profiles are the first intended overlay mechanism.
|
||||
|
||||
Example layout:
|
||||
|
||||
```text
|
||||
config/
|
||||
├── narratio.yml
|
||||
├── conf.d/
|
||||
│ ├── 10-transcription.yml
|
||||
│ ├── 20-artifacts.yml
|
||||
│ └── 30-distribution.yml
|
||||
└── profiles/
|
||||
├── production.yml
|
||||
└── testing.yml
|
||||
```
|
||||
|
||||
Default invocation loads only the composed base configuration.
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
narratio
|
||||
```
|
||||
|
||||
Explicit profile selection:
|
||||
|
||||
```text
|
||||
narratio --config-profile=testing
|
||||
```
|
||||
|
||||
produces:
|
||||
|
||||
```text
|
||||
application defaults
|
||||
↓
|
||||
main config + imports
|
||||
↓
|
||||
profiles/testing.yml
|
||||
```
|
||||
|
||||
### 11.1 `default` profile
|
||||
|
||||
"Default" should mean **no profile overlay**.
|
||||
|
||||
Do not require a literal `profiles/default.yml`.
|
||||
|
||||
### 11.2 Profile names
|
||||
|
||||
`--config-profile` should accept a profile name, not an arbitrary path.
|
||||
|
||||
Profile names should use a constrained character set, e.g.:
|
||||
|
||||
```text
|
||||
[a-zA-Z0-9_-]+
|
||||
```
|
||||
|
||||
The selected profile should resolve deterministically relative to the configuration root.
|
||||
|
||||
### 11.3 Profiles should not redefine the configuration graph
|
||||
|
||||
Profiles should modify application configuration values, not change how ConfigKit discovers configuration.
|
||||
|
||||
Profile overlays should therefore not introduce new imports or redefine import behavior.
|
||||
|
||||
The base configuration graph is discovered and composed first; the selected profile is then applied to the resulting configuration tree.
|
||||
|
||||
---
|
||||
|
||||
## 12. Import Policy
|
||||
|
||||
Import behavior must be deterministic and explicitly specified.
|
||||
|
||||
Recommended v1 rules:
|
||||
|
||||
- relative imports resolve relative to the file containing the import;
|
||||
- glob results are sorted deterministically;
|
||||
- duplicate semantic values across imported fragments are errors;
|
||||
- explicit missing imports are errors;
|
||||
- an optional empty `conf.d` glob may resolve to zero files;
|
||||
- import cycles are errors;
|
||||
- cycle diagnostics should show the full cycle;
|
||||
- define file identity carefully with respect to symlinks;
|
||||
- consider constraining imports to an allowed configuration root;
|
||||
- do not support remote/network imports in v1.
|
||||
|
||||
Remote imports create separate concerns around availability, caching, authentication, integrity, startup latency, and freshness and should remain out of scope.
|
||||
|
||||
---
|
||||
|
||||
## 13. Provenance and Definition History
|
||||
|
||||
Provenance is a first-class design requirement.
|
||||
|
||||
ConfigKit should retain, for each effective semantic value path:
|
||||
|
||||
- whether it was absent or explicitly configured;
|
||||
- its effective value source;
|
||||
- source file;
|
||||
- line and column where practical;
|
||||
- configuration layer;
|
||||
- previous definitions overridden by higher-precedence layers.
|
||||
|
||||
Conceptually:
|
||||
|
||||
```go
|
||||
type Definition struct {
|
||||
Layer Layer
|
||||
Source Source
|
||||
Value NodeRef
|
||||
}
|
||||
|
||||
type FieldHistory struct {
|
||||
Definitions []Definition
|
||||
}
|
||||
```
|
||||
|
||||
Possible layers:
|
||||
|
||||
```go
|
||||
const (
|
||||
LayerDefault Layer = iota
|
||||
LayerBase
|
||||
LayerProfile
|
||||
)
|
||||
```
|
||||
|
||||
Future layers may include environment or CLI overrides.
|
||||
|
||||
This metadata should enable diagnostics such as:
|
||||
|
||||
```text
|
||||
transcription.concurrency = 1
|
||||
|
||||
history:
|
||||
application default 4
|
||||
conf.d/10-transcription.yml:14 8
|
||||
profiles/testing.yml:7 1
|
||||
|
||||
effective source:
|
||||
profiles/testing.yml:7
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Typed Decoding
|
||||
|
||||
After composition and overlays produce one effective presence-aware configuration tree, ConfigKit decodes it into the application's typed Go `Config`.
|
||||
|
||||
Requirements:
|
||||
|
||||
- unknown fields are errors;
|
||||
- type mismatches are errors;
|
||||
- diagnostics should preserve source location where possible;
|
||||
- parser-specific errors should be converted into ConfigKit diagnostics.
|
||||
|
||||
Do not expose raw YAML parser types through the public API.
|
||||
|
||||
---
|
||||
|
||||
## 15. Semantic Validation
|
||||
|
||||
Application-specific validation remains application-owned.
|
||||
|
||||
ConfigKit should provide a standard validation hook and diagnostic type.
|
||||
|
||||
Validation should normally collect multiple independent errors rather than aborting after the first.
|
||||
|
||||
Example output:
|
||||
|
||||
```text
|
||||
configuration invalid:
|
||||
|
||||
weather.sources.nws.timeout
|
||||
must be > 0
|
||||
config.yml:19:5
|
||||
|
||||
outputs.public
|
||||
duplicate definition
|
||||
config.yml:42:3
|
||||
conf.d/90-output.yml:8:3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. Schema Versioning
|
||||
|
||||
Applications should support explicit configuration schema versions.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
schema_version: narratio.v1
|
||||
```
|
||||
|
||||
The version identifies the application's configuration schema, not the ConfigKit library version.
|
||||
|
||||
ConfigKit may later provide lifecycle support for application-provided migrations, but automatic migration is not required for the initial implementation.
|
||||
|
||||
The design should leave a clean pipeline location for schema-version checking and migration.
|
||||
|
||||
---
|
||||
|
||||
## 17. Secrets and Redaction
|
||||
|
||||
ConfigKit will eventually process configuration containing secrets.
|
||||
|
||||
Redaction must be considered before implementing:
|
||||
|
||||
- effective-config output;
|
||||
- diagnostics;
|
||||
- config diffs;
|
||||
- logging;
|
||||
- config fingerprinting.
|
||||
|
||||
Potential application annotation:
|
||||
|
||||
```go
|
||||
APIKey string `yaml:"api_key" configkit:"secret"`
|
||||
```
|
||||
|
||||
Exact mechanism is TBD.
|
||||
|
||||
Human-facing tooling must never accidentally emit raw secrets.
|
||||
|
||||
If fingerprints include secret values, the raw values must still never be exposed.
|
||||
|
||||
---
|
||||
|
||||
## 18. Diagnostics and Operability
|
||||
|
||||
ConfigKit should establish recognizable diagnostics across the application suite.
|
||||
|
||||
Potential standardized commands enabled by ConfigKit:
|
||||
|
||||
```text
|
||||
<app> config validate
|
||||
<app> config effective
|
||||
<app> config sources
|
||||
<app> config explain <path>
|
||||
```
|
||||
|
||||
These commands do not all need to exist in the initial skeleton, but the metadata model should make them straightforward to implement later.
|
||||
|
||||
`config effective` must support secret redaction.
|
||||
|
||||
---
|
||||
|
||||
## 19. Non-Goals for v1
|
||||
|
||||
Do not include these in the initial implementation unless a concrete application requirement appears:
|
||||
|
||||
- hot reload;
|
||||
- runtime resource replacement;
|
||||
- remote/network imports;
|
||||
- arbitrary configuration providers;
|
||||
- TOML/JSON/HCL/CUE support;
|
||||
- Vault/S3 configuration providers;
|
||||
- sophisticated environment-variable overlays;
|
||||
- arbitrary CLI field overrides;
|
||||
- sequence append/union merge strategies;
|
||||
- arbitrary custom merge policies;
|
||||
- profile inheritance;
|
||||
- reset-to-default operators;
|
||||
- application runtime validation such as checking whether an S3 bucket exists.
|
||||
|
||||
ConfigKit should remain opinionated.
|
||||
|
||||
---
|
||||
|
||||
## 20. Initial Internal Architecture
|
||||
|
||||
A reasonable internal model is:
|
||||
|
||||
```text
|
||||
Source
|
||||
↓
|
||||
Document
|
||||
├─ parsed tree
|
||||
└─ source locations
|
||||
↓
|
||||
Composer
|
||||
strict combination
|
||||
duplicate definitions = error
|
||||
↓
|
||||
Overlay
|
||||
higher layer wins
|
||||
mappings recurse
|
||||
sequences replace
|
||||
↓
|
||||
Metadata / Provenance
|
||||
presence
|
||||
origins
|
||||
definition history
|
||||
↓
|
||||
Decoder[T]
|
||||
effective tree → typed Config
|
||||
↓
|
||||
Resolver[T]
|
||||
application-derived defaults / normalization
|
||||
↓
|
||||
Validator[T]
|
||||
application semantic validation
|
||||
↓
|
||||
Result[T]
|
||||
```
|
||||
|
||||
Conceptual result:
|
||||
|
||||
```go
|
||||
type Result[T any] struct {
|
||||
Config T
|
||||
Metadata Metadata
|
||||
Sources []Source
|
||||
Diagnostics []Diagnostic
|
||||
Fingerprint string // optional / future
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 21. Possible Package Skeleton
|
||||
|
||||
Exact names are not settled, but an initial repository could look roughly like:
|
||||
|
||||
```text
|
||||
configkit/
|
||||
├── configkit.go
|
||||
├── source.go
|
||||
├── document.go
|
||||
├── compose.go
|
||||
├── overlay.go
|
||||
├── metadata.go
|
||||
├── decode.go
|
||||
├── diagnostic.go
|
||||
├── profile.go
|
||||
├── import.go
|
||||
├── schema.go
|
||||
├── secret.go
|
||||
└── internal/
|
||||
└── yaml/
|
||||
```
|
||||
|
||||
Public API should remain small.
|
||||
|
||||
A future happy path might resemble:
|
||||
|
||||
```go
|
||||
result, err := configkit.Load[Config](
|
||||
configkit.File(path),
|
||||
configkit.WithImports(),
|
||||
configkit.WithProfile(profile),
|
||||
configkit.WithDefaults(DefaultConfig()),
|
||||
configkit.WithResolver(ResolveConfig),
|
||||
configkit.WithValidator(ValidateConfig),
|
||||
)
|
||||
```
|
||||
|
||||
This is illustrative only. Do not prematurely lock in option names or signatures.
|
||||
|
||||
---
|
||||
|
||||
## 22. Suggested Initial Implementation Sequence
|
||||
|
||||
### Stage 1 — Core document model
|
||||
|
||||
Implement:
|
||||
|
||||
- local file source;
|
||||
- YAML parsing;
|
||||
- conservative YAML validation;
|
||||
- source locations;
|
||||
- semantic path traversal;
|
||||
- internal presence-aware tree representation.
|
||||
|
||||
### Stage 2 — Strict composition
|
||||
|
||||
Implement:
|
||||
|
||||
- recursive mapping composition;
|
||||
- duplicate semantic-path detection;
|
||||
- deterministic import ordering;
|
||||
- useful duplicate diagnostics.
|
||||
|
||||
### Stage 3 — Profile overlay
|
||||
|
||||
Implement:
|
||||
|
||||
- constrained profile name resolution;
|
||||
- recursive mapping overlay;
|
||||
- scalar replacement;
|
||||
- sequence replacement;
|
||||
- provenance history across layers.
|
||||
|
||||
### Stage 4 — Typed decoding
|
||||
|
||||
Implement:
|
||||
|
||||
- generic `Load[T]` or equivalent;
|
||||
- strict unknown-field rejection;
|
||||
- typed errors converted to ConfigKit diagnostics.
|
||||
|
||||
### Stage 5 — Defaults and resolution hooks
|
||||
|
||||
Implement:
|
||||
|
||||
- static application defaults;
|
||||
- explicit-value preservation for `false`, `0`, and empty strings;
|
||||
- metadata query for explicitness;
|
||||
- derived-default / resolver hook.
|
||||
|
||||
### Stage 6 — Semantic validation
|
||||
|
||||
Implement:
|
||||
|
||||
- application validation hook;
|
||||
- multi-error diagnostics.
|
||||
|
||||
### Stage 7 — Introspection
|
||||
|
||||
Implement or prototype:
|
||||
|
||||
- source listing;
|
||||
- effective-config rendering;
|
||||
- `explain(path)`;
|
||||
- secret redaction.
|
||||
|
||||
---
|
||||
|
||||
## 23. Tests That Define Policy
|
||||
|
||||
ConfigKit should have policy-focused tests, not merely implementation tests.
|
||||
|
||||
At minimum, test:
|
||||
|
||||
- absent bool receives default;
|
||||
- explicit `false` overrides `true` default;
|
||||
- absent integer receives default;
|
||||
- explicit `0` overrides nonzero default;
|
||||
- explicit empty string remains explicit;
|
||||
- `null` differs from absent;
|
||||
- duplicate key within one YAML file fails;
|
||||
- duplicate semantic path across composed files fails;
|
||||
- sibling mapping paths compose successfully;
|
||||
- profile scalar overrides base scalar;
|
||||
- profile mapping recursively overrides only specified children;
|
||||
- profile sequence replaces base sequence;
|
||||
- base sequence collision across composed files fails;
|
||||
- profile cannot redefine import graph;
|
||||
- imports are deterministically ordered;
|
||||
- import cycles fail with useful diagnostics;
|
||||
- unknown typed-config fields fail;
|
||||
- provenance identifies effective source;
|
||||
- provenance retains overridden definitions;
|
||||
- defaults are distinguishable from explicit equal-valued configuration;
|
||||
- secrets are redacted from introspection output.
|
||||
|
||||
These tests should become executable documentation for ConfigKit policy.
|
||||
|
||||
---
|
||||
|
||||
## 24. Key Decisions Summary
|
||||
|
||||
For v1:
|
||||
|
||||
1. YAML remains the configuration language.
|
||||
2. ConfigKit defines a conservative YAML policy.
|
||||
3. Parsed configuration remains presence-aware until after composition and overlays.
|
||||
4. Application-facing Go structs remain idiomatic and strongly typed.
|
||||
5. Absent, explicit zero/false/empty, and `null` are distinct.
|
||||
6. Base imports use strict composition.
|
||||
7. Duplicate semantic values across composed files are errors.
|
||||
8. Profiles use explicit higher-precedence overlay semantics.
|
||||
9. Mapping overlays recurse.
|
||||
10. Sequence overlays replace.
|
||||
11. "Default profile" means no profile overlay.
|
||||
12. Profiles are names, not arbitrary file paths.
|
||||
13. Profiles do not redefine the import graph.
|
||||
14. Static defaults are application-owned.
|
||||
15. Derived defaults are application-owned and presence-aware.
|
||||
16. Provenance and definition history are first-class metadata.
|
||||
17. Semantic validation is application-owned but follows a common lifecycle.
|
||||
18. Parser implementation details remain private.
|
||||
19. Secrets must be redactable.
|
||||
20. Hot reload, remote providers, generalized merge strategies, and broad extensibility are non-goals until concrete requirements exist.
|
||||
|
||||
The core architectural objective is:
|
||||
|
||||
> **Build one deterministic, explainable configuration resolution model for the entire application suite.**
|
||||
Reference in New Issue
Block a user