Add single-source loading and version checks

This commit is contained in:
2026-09-06 20:03:22 +00:00
parent 21e3f633bc
commit cc41653bfd
13 changed files with 1022 additions and 38 deletions

View File

@@ -1,9 +1,9 @@
# Configuration Input Contract
This document owns ConfigKit's currently implemented source-input, YAML syntax,
schema, typed-decoding, and ownership contracts. The parser, owned document
representation, and decoder are private; a public typed loading lifecycle has not
been implemented yet.
schema, typed-decoding, loading, metadata, and ownership contracts. The parser,
owned document representation, and decoder remain private behind the public loading
lifecycle.
## Sources
@@ -14,7 +14,7 @@ ConfigKit exposes opaque sources through these constructors:
| `File(path)` | Requires a nonempty path, opens a regular file for each consumption, and closes only the library-owned file handle. Missing, inaccessible, unreadable, directory, and other nonregular inputs fail. |
| `Bytes(label, data)` | Requires a nonempty, non-secret diagnostic label and snapshots bytes during construction. Later caller mutation cannot affect the source. |
| `Reader(label, input)` | Requires a nonempty, non-secret label and non-nil reader. The reader is claimed once across all copies of the source, consumed but never closed, and cannot be reused. |
| `NoSource()` | Represents an explicit absence of input and creates no source record. Its defaults-only lifecycle behavior will become available with `Load`. |
| `NoSource()` | Selects defaults-only loading, creates no source record, and marks no paths explicit. |
The zero `Source` is invalid. Constructor argument failures are retained inside the
opaque source and become safe structured diagnostics when the source is consumed.
@@ -25,6 +25,65 @@ and override input when those layers are implemented. Reads request at most the
remaining allowance plus one byte so excess data is rejected rather than silently
truncated. Data returned together with a read error is not accepted as partial input.
## Single-source loading
`Load[T](source, options...)` accepts zero or one `Options[T]`. The configuration
root `T` must satisfy the application schema contract below. With no options it
starts from the Go zero value. A `Definition[T].Defaults` factory, when supplied,
runs exactly once after successful source parsing, schema construction, and optional
schema-version checking. Its result is detached before input is applied.
The current lifecycle is:
```text
validate options → parse one source → reject reserved directives → build schema
→ check the optional application version → create and clone defaults
→ strictly decode → snapshot configuration and metadata
```
`File`, `Bytes`, and `Reader` each contribute one base source with the deterministic
ID `source-1`. `NoSource` skips parsing, supplies an absent version to a configured
version checker, contributes no source, and continues through defaults, decode, and
projection. A missing file remains an error; it is never interpreted as no input.
The current `Options` boundary accepts a defaults factory and `VersionRule`.
Imports, active profiles, filesystem context, overrides, resolver and semantic
validator callbacks, and dynamic secret classification return an explicit
`options.unavailable` error because their lifecycle stages are not implemented.
The reserved profile spelling `default` selects no active profile. `NoSource`
additionally rejects imports, active profiles, and filesystem context as invalid
combinations. Input containing a reserved `_configkit` mapping key is rejected while
imports are disabled.
Every source, options, schema, version, defaults, or decode failure returns a nil
result and a `*LoadError`. Successful results contain caller-owned `Config`, detached
`Metadata`, a defensive source list, and defensive nonfatal diagnostics. Mutating
the returned configuration or inspection records cannot alter ConfigKit's private
snapshot. ConfigKit performs no logging and exposes no effective writer yet.
### Application schema versions
`VersionRule` declares a semantic path and non-nil checker. Before defaults or typed
decoding, the checker receives a `Version` preserving absent, explicit null, or
string input. Any other input kind is a `version.type` error and the checker does not
run. ConfigKit does not interpret the text or impose a universal version path.
The checker reports safe, value-free errors and warnings through `Validation.Error`
and `Validation.Warning`; ConfigKit resolves primary and related paths to exact or
nearest input locations. Reported errors fail loading. If the checker returns an
error without reporting one, ConfigKit emits a generic `version.hook_failed`
diagnostic and suppresses the returned error text. Warnings alone are retained on a
successful result. Callback panics remain programming failures.
### Base/default metadata
`Result.Metadata` describes every final canonical path. Exact surviving input paths
are explicit and have a base origin; omitted paths have a default origin. An explicit
parent mapping does not make omitted children explicit. Null state and available
source coordinates are retained. `History` records the Go-zero or application
default baseline followed by exact base definitions, including equal-valued input.
All metadata queries and returned nested records are detached.
## YAML documents
The private YAML boundary currently accepts exactly one nonempty document whose root
@@ -144,8 +203,8 @@ Paired text scalars decode only from strings through `encoding.TextUnmarshaler`,
codec failures are classified without exposing application error text or input
values.
This decoder is currently an internal lifecycle foundation, not a public conversion
API. Applications cannot call typed loading until `Load` is introduced.
This decoder is available only through `Load`; ConfigKit exposes no generic document
conversion API.
## Typed projection and ownership

View File

@@ -1,10 +1,10 @@
# Development
This is the contributor entry point for ConfigKit. Read the task-specific owners
below before making changes. The repository is in early implementation: it has a
Go module and documented root package, but no loading behavior or executable
examples yet. The [component overview](internal/overview.md) owns the implementation
inventory.
below before making changes. The repository is in early implementation: its public
loader currently covers one strict source, typed defaults, schema-version checks,
and base/default metadata. The [component overview](internal/overview.md) owns the
implementation inventory.
## Initial orientation

View File

@@ -183,6 +183,15 @@ multi-error collection, and repeated-result isolation. Resolution, validation,
consumer defaults, and all campaign/session algorithms remain assigned to later
work.
Lifecycle integration record (2026-09-06): the public single-source `Load` boundary
combines the already extracted labeled-source and strict-decoding mechanisms with
Narratio's fresh-default and presence expectations. ConfigKit adds an application-
defined pre-decode string-version check, defaults-only `NoSource` operation,
detached base/default metadata and source reports, and explicit gates for every
later lifecycle option. No application loader or production line was copied.
Narratio's resolution, validation, import/profile, campaign/session, and command
behavior remains assigned to its later owning work.
### Secrets bootstrap
Assigned to the explicit bootstrap operation:

View File

@@ -16,12 +16,13 @@ module and package documentation boundary. Its implemented public primitives are
enforcement, and structural comparison in `projection.go`; and
- strict schema-driven decoding over detached defaults in `decode.go`, including
mapping/sequence semantics, null and numeric policy, safe codec errors, presence,
baseline/final trees, and initial default/input origins.
baseline/final trees, and initial default/input origins; and
- public single-source lifecycle coordination in `load.go`, version-report contexts
in `validation.go`, and detached base/default metadata assembly in
`load_metadata.go`.
The [configuration contract](../config.md) owns implemented input, schema, decoding,
and ownership behavior. ConfigKit intentionally exposes no typed loading API yet;
the decoder and its initial metadata artifacts remain private until that lifecycle
exists.
single-source lifecycle, version checking, metadata, and ownership behavior.
The source-extraction inventory records the application foundations assigned to
future implementation work:

View File

@@ -2,7 +2,7 @@
## Status and ownership
Status: implementation in progress; Stages 1–6 are complete and later stages are
Status: implementation in progress; Stages 1–7 are complete and later stages are
pending.
Audience: a `gpt-5.6-terra` coding agent implementing one stage per prompt, in
@@ -619,7 +619,37 @@ Acceptance:
## Stage 7 — Assemble single-source loading and schema-version checks
Status: pending.
Status: complete.
Completion evidence (2026-09-06):
- Added the public generic `Load`, `Definition`, `Options`, `Result`, version,
filesystem-context, override, validation-context, and reserved resolution
declarations in [`load.go`](../../load.go) and [`validation.go`](../../validation.go).
The active lifecycle validates options, parses one source, rejects reserved
directives, builds the schema, checks an optional application version, creates
fresh defaults, decodes, and captures detached state.
- Added absent/null/string version handling, non-string rejection, exact/nearest
primary and related input locations, safe application reports, warning retention,
and generic value-free hook failure only when no explicit error was reported.
Defaults run only after version success and exactly once for a continuing load.
- Populated effective base/default `Metadata` and chronological definition history
in [`load_metadata.go`](../../load_metadata.go), with explicit and null state,
synthetic default origins, base coordinates, equal-valued input records, and
defensive result/source/diagnostic copies. `NoSource` performs the same schema,
version, defaults, decode, and projection lifecycle without a source record.
- Added explicit `options.unavailable` gates for resolution, semantic validation,
imports, active profiles, filesystem context, overrides, and secret classification;
invalid `NoSource` combinations and `_configkit` directives fail rather than being
ignored. The [current single-source contract](../config.md#single-source-loading),
package documentation, README, and component inventory own the implemented API.
- Integration tests cover File/Bytes/Reader/NoSource, schema-version states and
warnings, related locations, raw hook-error suppression, fresh-default ordering,
metadata agreement and detachment, source lists, unavailable options, reserved
directives, and nil results for source/schema/version/decode failures. 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; resolution and semantic validation remain later work.
Read: feature [lifecycle](initial.md#resolution-lifecycle),
[version hook](initial.md#schema-checking-and-secret-classification),

View File

@@ -425,15 +425,16 @@ configuration is never returned as a successful, usable result.
Source ownership, the supported YAML subset, scalar grammar, fixed input limits,
and safe failure behavior are implemented. The [current configuration input
contract](../config.md) now owns those definitions. Typed scalar conversion into
application values remains target work in [typed decoding](#typed-decoding).
contract](../config.md) now owns those definitions, including typed scalar
conversion through the current single-source lifecycle.
## Paths and presence
Semantic path construction, JSON Pointer parsing, and current metadata query
declarations are implemented. Their current contracts are owned by the Go
documentation in [`path.go`](../../path.go) and [`metadata.go`](../../metadata.go).
The remaining presence behavior below is target lifecycle work.
Semantic path construction, JSON Pointer parsing, and base/default metadata
population are implemented. Their current contracts are owned by the Go
documentation in [`path.go`](../../path.go), [`metadata.go`](../../metadata.go),
and the [single-source configuration contract](../config.md#base-default-metadata).
Layered replacement and resolution behavior below remains target lifecycle work.
Distinguish absent, explicitly null, explicitly zero/false/empty, and other
explicit values. An explicit value equal to its default remains explicit.
@@ -672,8 +673,11 @@ Automatic migration is deferred.
## Consumer API design
The following is a proposed public API, not implemented Go reference documentation
or a frozen naming commitment. Declarations omit method bodies and private fields.
The core declarations and single-source loading subset are implemented; their
current contract is owned by [configuration input](../config.md#single-source-loading)
and Go reference comments. Remaining declarations and behavior in this section are
target design, not implemented Go reference documentation or a frozen naming
commitment. Declarations omit method bodies and private fields.
Consumer snippets assume the indicated standard-library imports and a `configkit`
import using the module path selected in the [implementation plan](implementation.md#implementation-decisions).
They are illustrative target examples, not currently runnable examples. The
@@ -682,6 +686,13 @@ and verification against consumer models.
### Entry point and reusable application definition
`Load`, `Definition`, `Options`, `FilesystemContext`, `Override`, `VersionRule`,
`Version`, `Validation`, and `Result` are declared. The current loader accepts only
the defaults and version-checking subset described by the
[single-source contract](../config.md#single-source-loading); later-feature fields
fail explicitly rather than being ignored. The complete lifecycle and option
behavior below remains the target as its owning stages land.
```go
func Load[T any](source Source, options ...Options[T]) (*Result[T], error)
@@ -795,11 +806,11 @@ func ReadWorkerDefaults() (*configkit.Result[Config], error) {
### Paths and metadata
The path API, detached metadata records, and empty/unknown query behavior are now
implemented; [`path.go`](../../path.go), [`records.go`](../../records.go), and
[`metadata.go`](../../metadata.go) own their current Go contracts. Populating
effective presence, origins, and history belongs to the loading stages. Callback
path checking and safe historical values remain target behavior below.
The path API, detached metadata records, empty/unknown query behavior, and
base/default population are now implemented; [`path.go`](../../path.go),
[`records.go`](../../records.go), [`metadata.go`](../../metadata.go), and the
[metadata configuration contract](../config.md#base-default-metadata) own current
behavior. Layered callback history and safe historical values remain target behavior.
### Resolution and validation callbacks
@@ -970,6 +981,12 @@ potentially sensitive configured values.
### Schema checking and secret classification
`VersionRule`, `Version`, and the version-checking use of `Validation` are
implemented. The [current version contract](../config.md#application-schema-versions)
owns absent/null/string handling, report locations, warnings, failure behavior, and
raw-error suppression. Secret classification and later composed-source placement
checks remain target work.
```go
type VersionRule struct {
Path Path
@@ -1030,6 +1047,12 @@ applications own secret-producing transformations not described by those records
### Results, errors, and inspection
`Result.Config`, `Metadata`, `Sources`, and `Diagnostics`, together with
`LoadError` and `WriteDiagnostics`, are implemented at the single-source boundary.
The [current loading contract](../config.md#single-source-loading) and Go reference
comments own their behavior. Effective rendering and richer layered inspection
below remain target work.
```go
type Result[T any] struct {
Config T