Complete integer schema version consumer readiness
This commit is contained in:
@@ -81,6 +81,21 @@ exponent numbers, booleans, mappings, and sequences produce `version.type`, and
|
|||||||
checker does not run. ConfigKit does not interpret the text or impose a universal
|
checker does not run. ConfigKit does not interpret the text or impose a universal
|
||||||
version path.
|
version path.
|
||||||
|
|
||||||
|
Integer versions use the strict grammar `^-?(0|[1-9][0-9]*)$`. Their spelling is
|
||||||
|
preserved exactly, including `-0`, and application policy decides which identifiers
|
||||||
|
are supported. A numeric version has no built-in `int64` range limit. Existing parser
|
||||||
|
policy still rejects leading zeroes, leading plus signs, hexadecimal notation, and
|
||||||
|
other non-JSON scalar forms before version checking.
|
||||||
|
|
||||||
|
At exactly the configured semantic path, accepted integer input can decode into a
|
||||||
|
plain Go string or named string without a text codec, including pointer wrappers.
|
||||||
|
This is a load-local exception: siblings and unrelated fields, dynamic values,
|
||||||
|
integer and float destinations, durations, `Secret`, and text codecs retain ordinary
|
||||||
|
strict decoding. The retained source and history stay numeric while typed projection
|
||||||
|
and effective YAML contain the resulting string; explanation records this through
|
||||||
|
the normal decoding reconciliation event. Fragment, profile, and CLI ownership
|
||||||
|
protections apply before this conversion.
|
||||||
|
|
||||||
The checker reports safe, value-free errors and warnings through `Validation.Error`
|
The checker reports safe, value-free errors and warnings through `Validation.Error`
|
||||||
and `Validation.Warning`; ConfigKit resolves primary and related paths to exact or
|
and `Validation.Warning`; ConfigKit resolves primary and related paths to exact or
|
||||||
nearest input locations. Reported errors fail loading. If the checker returns an
|
nearest input locations. Reported errors fail loading. If the checker returns an
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ The YAML under [`testdata`](../../examples/consumers/testdata) is loaded by the
|
|||||||
executable examples and ordinary tests. It contains no private application data,
|
executable examples and ordinary tests. It contains no private application data,
|
||||||
credentials, network calls, or dependency on a sibling checkout.
|
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
|
## Happy path
|
||||||
|
|
||||||
An application defines ordinary types and passes one explicit source. Optional
|
An application defines ordinary types and passes one explicit source. Optional
|
||||||
|
|||||||
@@ -42,9 +42,11 @@ git show cb5b435:docs/roadmap/remediation.md
|
|||||||
Release-candidate, publication, and verification work follows the operational
|
Release-candidate, publication, and verification work follows the operational
|
||||||
[release procedure](release.md).
|
[release procedure](release.md).
|
||||||
|
|
||||||
Planned feature work: [string and integer schema versions](roadmap/integer-schema-versions.md)
|
The completed string-and-integer schema-version work remains recorded in its
|
||||||
and its [sequential implementation plan](roadmap/implementation.md).
|
[feature roadmap](roadmap/integer-schema-versions.md) and
|
||||||
These describe future behavior, not the current configuration contract.
|
[sequential implementation plan](roadmap/implementation.md) until release
|
||||||
|
preparation retires them. The [configuration contract](config.md) owns current
|
||||||
|
behavior.
|
||||||
|
|
||||||
## Baseline validation
|
## Baseline validation
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
# Integer schema versions: implementation plan
|
# Integer schema versions: implementation plan
|
||||||
|
|
||||||
Status: in progress. The [feature roadmap](integer-schema-versions.md) owns the
|
Status: implementation complete (2026-09-12). The
|
||||||
intended behavior and architecture; this document owns execution and progress.
|
[feature roadmap](integer-schema-versions.md) records the feature scope, the
|
||||||
|
[configuration contract](../config.md) owns implemented behavior, and this document
|
||||||
|
owns execution and progress.
|
||||||
|
|
||||||
## Execution rules
|
## Execution rules
|
||||||
|
|
||||||
@@ -127,7 +129,7 @@ and release-readiness validation without publishing a release.
|
|||||||
|
|
||||||
## Stage 4 — Complete consumer evidence and release readiness
|
## Stage 4 — Complete consumer evidence and release readiness
|
||||||
|
|
||||||
Status: not started. Depends on: Stage 3.
|
Status: complete (2026-09-12). Depends on: Stage 3.
|
||||||
|
|
||||||
Scope: maintained Notarius-shaped examples/fixtures, consumer documentation,
|
Scope: maintained Notarius-shaped examples/fixtures, consumer documentation,
|
||||||
documentation consistency review, and validation handoff. No downstream app edits.
|
documentation consistency review, and validation handoff. No downstream app edits.
|
||||||
@@ -167,3 +169,22 @@ CONFIGKIT_NEWER_TOOLCHAIN=go1.26.7 \
|
|||||||
|
|
||||||
Rerun exact-commit release checks after
|
Rerun exact-commit release checks after
|
||||||
any later candidate or roadmap-retirement changes.
|
any later candidate or roadmap-retirement changes.
|
||||||
|
|
||||||
|
Completion record: maintained Notarius fixtures now accept integer `4` and quoted
|
||||||
|
`"4"` with the same string-backed result. Unsupported integer `5` reaches domain
|
||||||
|
policy, fractional `4.0` remains `version.type`, and dynamic option integers remain
|
||||||
|
`int64`. Current documentation and Go examples were audited; canonical behavior is
|
||||||
|
owned by the configuration contract and consumer guide, while historical release,
|
||||||
|
extraction, and ADR records were left unchanged. The focused consumer/example
|
||||||
|
commands, `./scripts/check.sh`, and the full release checker pass on Linux with
|
||||||
|
Go 1.25.5 and Go 1.26.7.
|
||||||
|
|
||||||
|
Compatibility handoff: exported API shapes are unchanged, but integer input at a
|
||||||
|
configured version path intentionally changes from structural rejection to invoking
|
||||||
|
application policy and, for plain string destinations, successful decoding. The
|
||||||
|
conservative release choice is therefore a new pre-v1 minor version, subject to
|
||||||
|
maintainer selection and unused-version checks. No version, note, candidate, or tag
|
||||||
|
was created. Release preparation must still select the version, create and approve
|
||||||
|
matching notes, retire these completed roadmap files, rerun Linux and native macOS
|
||||||
|
checks on the exact candidate commit, push and guard that commit, publish the exact
|
||||||
|
tag, and verify clean consumer retrieval.
|
||||||
|
|||||||
@@ -1,142 +1,17 @@
|
|||||||
# String and integer schema versions
|
# String and integer schema versions
|
||||||
|
|
||||||
Status: planned; not implemented. This document owns the intended end state.
|
Status: implemented on 2026-09-12; retained until release preparation. The
|
||||||
The [implementation plan](implementation.md) owns sequencing and
|
[implementation plan](implementation.md) records validation and stage handoffs.
|
||||||
progress. Current behavior remains defined by the [configuration contract](../config.md).
|
|
||||||
|
|
||||||
## Motivation and scope
|
ConfigKit now accepts strings and strict decimal-integer syntax at the exact path
|
||||||
|
configured by `Definition.Version`. The application checker and a plain
|
||||||
|
string-backed destination receive the preserved text, while all unrelated decoding
|
||||||
|
remains strict. Composition ownership, source history, explanation reconciliation,
|
||||||
|
and secret redaction retain their existing behavior.
|
||||||
|
|
||||||
Notarius historically declares `version: 4`. ConfigKit currently rejects this at
|
The [configuration contract](../config.md#application-schema-versions) is the
|
||||||
the version-check boundary and independently rejects assigning an integer to a Go
|
canonical definition of the implemented behavior. The
|
||||||
string during decoding. Consumers should be able to retain an integer version
|
[consumer adoption guide](../consumers/README.md) links maintained Notarius-shaped
|
||||||
declaration while using a string for application version comparison and storage.
|
string and integer examples. This roadmap chooses no release version and grants no
|
||||||
|
publication authority; compatibility review and roadmap retirement follow the
|
||||||
Support this through the existing `VersionRule`, not a new parser mode, global
|
[release procedure](../release.md) when separately authorized.
|
||||||
coercion option, public union type, or application-side YAML rewrite. Applications
|
|
||||||
continue to decide which version identifiers are supported. No downstream
|
|
||||||
repository changes or automatic schema migrations are included.
|
|
||||||
|
|
||||||
## Target contract
|
|
||||||
|
|
||||||
The following is intended future behavior, not a description of today's release.
|
|
||||||
|
|
||||||
### Version checking
|
|
||||||
|
|
||||||
Only the semantic path designated by `Definition.Version.Path` receives version
|
|
||||||
handling. There is no special behavior based on a field being named `version`.
|
|
||||||
An absent rule leaves all existing decoding behavior unchanged.
|
|
||||||
|
|
||||||
| Input at the configured path | Checker input |
|
|
||||||
| --- | --- |
|
|
||||||
| Absent | `Version{Present: false}` |
|
|
||||||
| Explicit null | `Version{Present: true, Null: true}` |
|
|
||||||
| String, including empty string | Present, non-null; `Text` preserves the string |
|
|
||||||
| Number with integer syntax | Present, non-null; `Text` preserves the validated decimal spelling |
|
|
||||||
| Fractional/exponent number, boolean, mapping, or sequence | `CodeVersionType`; checker not called |
|
|
||||||
|
|
||||||
Integer syntax means the existing strict decimal integer grammar
|
|
||||||
`^-?(0|[1-9][0-9]*)$`. Thus `4` and `"4"` both supply text `"4"`.
|
|
||||||
`-0` supplies `"-0"`; do not silently canonicalize it to `"0"`. Negative and zero
|
|
||||||
identifiers are structurally accepted; application policy may reject them.
|
|
||||||
`4.0` and `4e0` are not integer syntax, even though their mathematical values are
|
|
||||||
integral. Quoted versions such as `"4.0"` remain strings and remain accepted.
|
|
||||||
The parser's existing rejection of leading zeroes, hexadecimal, leading plus,
|
|
||||||
and other unsupported scalar spellings stays unchanged.
|
|
||||||
|
|
||||||
Use the validated number text directly, without conversion through `int`, `int64`,
|
|
||||||
or floating point. Textual versions need no machine-integer range limit beyond
|
|
||||||
existing input resource limits. Numeric Go destinations retain their normal range
|
|
||||||
checks. Preserve the public `Version` and `VersionRule` shapes: no new kind flag
|
|
||||||
or opt-in field is needed.
|
|
||||||
|
|
||||||
Checking remains before selected-profile application, defaults, typed decoding,
|
|
||||||
resolution, and secret lookup. Callback warnings, errors, absent/null handling,
|
|
||||||
safe diagnostics, and source attribution retain their current behavior.
|
|
||||||
|
|
||||||
### Typed decoding
|
|
||||||
|
|
||||||
At exactly the configured version path, an integer node may decode into a plain
|
|
||||||
Go string descriptor, including a named string without custom text decoding and
|
|
||||||
pointer wrappers around those types. The resulting string is the same text the
|
|
||||||
version checker receives. This permits both integer and quoted version YAML when
|
|
||||||
the application stores its version as a string.
|
|
||||||
|
|
||||||
Other destination types retain ordinary decoding: an integer field can still
|
|
||||||
decode integer YAML, but quoted strings are not coerced into integers. Dynamic
|
|
||||||
`any` retains its usual scalar types and numeric bounds. This feature does not
|
|
||||||
force every destination to become a string. Custom text codecs, durations, and
|
|
||||||
secret-reference types gain no integer conversion, even if their path is the
|
|
||||||
configured version path. Missing/null values and strict unknown-field validation
|
|
||||||
also retain normal typed semantics; passing the checker does not bypass decoding.
|
|
||||||
|
|
||||||
Unrelated fields, sibling fields with the same Go type, ancestors, descendants,
|
|
||||||
and fields merely named `version` receive no coercion. Semantic path matching must
|
|
||||||
respect escaped keys and contextual numeric tokens using existing path machinery.
|
|
||||||
Do not alter pointer-versus-key/index distinctions by comparing field names alone.
|
|
||||||
|
|
||||||
### Composition, inspection, and secrets
|
|
||||||
|
|
||||||
Version ownership remains unchanged: imported fragments cannot supply the
|
|
||||||
configured version, profiles cannot replace it or a covering ancestor, and CLI
|
|
||||||
overrides cannot overlap it. No conversion may hide a composition conflict or
|
|
||||||
weaken those checks.
|
|
||||||
|
|
||||||
Keep the parsed input and retained history numeric when integer syntax was used.
|
|
||||||
For a Go string destination, typed projection and effective YAML show a string;
|
|
||||||
historical explanation should expose the existing decoding reconciliation event
|
|
||||||
between numeric input and string output. Presence, source locations, origins,
|
|
||||||
historical paths, and detached ownership remain intact. Do not represent this
|
|
||||||
conversion as application resolution or invent a synthetic source.
|
|
||||||
|
|
||||||
Existing sensitivity classification and redaction apply to both original and
|
|
||||||
decoded snapshots. Conversion performs no environment access, invokes no codecs
|
|
||||||
that previously required strings, and introduces no raw-value diagnostic text.
|
|
||||||
|
|
||||||
## Intended architecture
|
|
||||||
|
|
||||||
The current entry points are `checkVersion` in `validation.go`,
|
|
||||||
`decodeLoadInput` in `load_result.go`, and `decodeConfiguration`/`decodeValue` in
|
|
||||||
`decode.go`. The lifecycle in `load.go` already supplies the definition to both
|
|
||||||
phases; retain its ordering.
|
|
||||||
|
|
||||||
1. Share a small private integer-version text predicate/extractor between checking
|
|
||||||
and decoding, reusing the existing integer grammar. Do not extend the YAML
|
|
||||||
adapter or expose its node types.
|
|
||||||
2. Carry an optional, per-load version-path capability into typed decoding through
|
|
||||||
private decode context. Follow it through recursive decoding, including
|
|
||||||
pointer, map, and dynamic-structure paths. Default private decoder callers
|
|
||||||
should remain strict unless they explicitly receive that capability.
|
|
||||||
3. Apply the exception only when the current semantic path matches the configured
|
|
||||||
path, the source is an integer number, and the destination descriptor is a
|
|
||||||
plain string. All other cases use existing dispatch and errors.
|
|
||||||
4. Let normal typed projection produce the final string representation. Retain
|
|
||||||
original input for provenance and explanation; avoid rewriting an assembled
|
|
||||||
document or cloning the entire tree solely to convert one scalar.
|
|
||||||
|
|
||||||
Do not attach path-specific conversion to a shared type descriptor: descriptors
|
|
||||||
can be reused by unrelated fields of the same Go type. Keep the capability local
|
|
||||||
to one decoding operation, not global state or a public schema extension. Exact
|
|
||||||
private context names/signatures are implementation choices, not API commitments.
|
|
||||||
|
|
||||||
## Compatibility and completion
|
|
||||||
|
|
||||||
Existing accepted strings keep their meaning. Newly accepted integers intentionally
|
|
||||||
change the previous `version.type` rejection contract and cause application version
|
|
||||||
checkers to run on inputs they previously never saw. Release review must account
|
|
||||||
for that, rather than describing the change as entirely behavior-neutral. A
|
|
||||||
pre-v1 minor release is the conservative choice under the
|
|
||||||
[release policy](../policy/release.md); this roadmap chooses no tag and grants no
|
|
||||||
patch-policy exception or publication authority.
|
|
||||||
|
|
||||||
Completion requires end-to-end integer/string equivalence for a string-backed
|
|
||||||
Notarius-shaped configuration, strict rejection outside the version exception,
|
|
||||||
correct original/effective inspection, and retained version-ownership and secret
|
|
||||||
safety guarantees. Update Go reference comments, the configuration contract, and
|
|
||||||
consumer migration guidance as their behavior lands. Historical ADR rationale and
|
|
||||||
published release notes remain unchanged. If an accepted ADR actually conflicts
|
|
||||||
with the implementation decision, supersede it using the documentation policy;
|
|
||||||
do not rewrite its accepted rationale.
|
|
||||||
|
|
||||||
Once implemented, move target contracts to their canonical owners and replace
|
|
||||||
duplicated roadmap text with links. Retire completed roadmap/plan documents during
|
|
||||||
release preparation, preserving the execution record in Git history.
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ type ObjectBinding struct {
|
|||||||
Options map[string]any `yaml:"options"`
|
Options map[string]any `yaml:"options"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotariusConfig demonstrates string versions and dynamic catalog options.
|
// NotariusConfig demonstrates string-backed versions and dynamic catalog options.
|
||||||
type NotariusConfig struct {
|
type NotariusConfig struct {
|
||||||
Version string `yaml:"version"`
|
Version string `yaml:"version"`
|
||||||
Bindings []ObjectBinding `yaml:"bindings"`
|
Bindings []ObjectBinding `yaml:"bindings"`
|
||||||
@@ -93,7 +93,7 @@ type NotariusConfig struct {
|
|||||||
func LoadNotarius(path string) (*configkit.Result[NotariusConfig], error) {
|
func LoadNotarius(path string) (*configkit.Result[NotariusConfig], error) {
|
||||||
return configkit.Load(configkit.File(path), configkit.Options[NotariusConfig]{
|
return configkit.Load(configkit.File(path), configkit.Options[NotariusConfig]{
|
||||||
Definition: configkit.Definition[NotariusConfig]{
|
Definition: configkit.Definition[NotariusConfig]{
|
||||||
Version: &configkit.VersionRule{Path: configkit.Keys("version"), Check: requireVersion("v1")},
|
Version: &configkit.VersionRule{Path: configkit.Keys("version"), Check: requireVersion("4")},
|
||||||
Validate: validateNotarius,
|
Validate: validateNotarius,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func Example_notarius() {
|
|||||||
result, err := consumers.LoadNotarius(fixture("notarius", "config.yml"))
|
result, err := consumers.LoadNotarius(fixture("notarius", "config.yml"))
|
||||||
window, ok := consumers.NotariusWindow(result.Config, 0)
|
window, ok := consumers.NotariusWindow(result.Config, 0)
|
||||||
fmt.Println(result.Config.Version, result.Config.Bindings[0].Object, window, ok, err)
|
fmt.Println(result.Config.Version, result.Config.Bindings[0].Object, window, ok, err)
|
||||||
// Output: v1 summary 12 true <nil>
|
// Output: 4 summary 12 true <nil>
|
||||||
}
|
}
|
||||||
|
|
||||||
func Example_weatherReporter() {
|
func Example_weatherReporter() {
|
||||||
@@ -100,6 +100,10 @@ func TestMaintainedConsumerFixturesPreserveDomainOutcomes(t *testing.T) {
|
|||||||
if _, ok := notarius.Config.Bindings[0].Options["window_units"].(int64); !ok {
|
if _, ok := notarius.Config.Bindings[0].Options["window_units"].(int64); !ok {
|
||||||
t.Fatalf("dynamic integer type = %T", notarius.Config.Bindings[0].Options["window_units"])
|
t.Fatalf("dynamic integer type = %T", notarius.Config.Bindings[0].Options["window_units"])
|
||||||
}
|
}
|
||||||
|
integerNotarius, err := consumers.LoadNotarius(fixture("notarius", "integer-version.yml"))
|
||||||
|
if err != nil || integerNotarius.Config.Version != notarius.Config.Version || integerNotarius.Config.Version != "4" {
|
||||||
|
t.Fatalf("Notarius version forms = %#v / %#v, %v", notarius.Config, integerNotarius, err)
|
||||||
|
}
|
||||||
|
|
||||||
weather, err := consumers.LoadWeather(configkit.File(fixture("weather", "config.yml")))
|
weather, err := consumers.LoadWeather(configkit.File(fixture("weather", "config.yml")))
|
||||||
if err != nil || weather.Config.Reports["daily"].Modules[1].Options.Sections[0] != "short_term" {
|
if err != nil || weather.Config.Reports["daily"].Modules[1].Options.Sections[0] != "short_term" {
|
||||||
@@ -124,10 +128,21 @@ func TestMigrationFixturesFailWithStableCodesAndPaths(t *testing.T) {
|
|||||||
path string
|
path string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "unsupported integer schema version",
|
name: "unsupported application version",
|
||||||
load: func() error { _, err := consumers.LoadNotarius(fixture("notarius", "integer-version.yml")); return err },
|
load: func() error {
|
||||||
|
_, err := consumers.LoadNotarius(fixture("notarius", "unsupported-version.yml"))
|
||||||
|
return err
|
||||||
|
},
|
||||||
code: configkit.CodeVersionError, path: "/version",
|
code: configkit.CodeVersionError, path: "/version",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "non-integer numeric version",
|
||||||
|
load: func() error {
|
||||||
|
_, err := consumers.LoadNotarius(fixture("notarius", "noninteger-version.yml"))
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
code: configkit.CodeVersionType, path: "/version",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "module shorthand",
|
name: "module shorthand",
|
||||||
load: func() error {
|
load: func() error {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: v1
|
version: "4"
|
||||||
bindings:
|
bindings:
|
||||||
- object: summary
|
- object: summary
|
||||||
options:
|
options:
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
version: 1
|
version: 4
|
||||||
bindings: []
|
bindings:
|
||||||
|
- object: summary
|
||||||
|
options:
|
||||||
|
window_units: 12
|
||||||
|
format: markdown
|
||||||
|
|||||||
2
examples/consumers/testdata/notarius/noninteger-version.yml
vendored
Normal file
2
examples/consumers/testdata/notarius/noninteger-version.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
version: 4.0
|
||||||
|
bindings: []
|
||||||
2
examples/consumers/testdata/notarius/unsupported-version.yml
vendored
Normal file
2
examples/consumers/testdata/notarius/unsupported-version.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
version: 5
|
||||||
|
bindings: []
|
||||||
Reference in New Issue
Block a user