143 lines
7.9 KiB
Markdown
143 lines
7.9 KiB
Markdown
# String and integer schema versions
|
|
|
|
Status: planned; not implemented. This document owns the intended end state.
|
|
The [implementation plan](implementation.md) owns sequencing and
|
|
progress. Current behavior remains defined by the [configuration contract](../config.md).
|
|
|
|
## Motivation and scope
|
|
|
|
Notarius historically declares `version: 4`. ConfigKit currently rejects this at
|
|
the version-check boundary and independently rejects assigning an integer to a Go
|
|
string during decoding. Consumers should be able to retain an integer version
|
|
declaration while using a string for application version comparison and storage.
|
|
|
|
Support this through the existing `VersionRule`, not a new parser mode, global
|
|
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.
|