Focus the source roadmap on recursive underlays
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# Filesystem Sources and Explicit Layered Loading
|
# Filesystem Sources and Source-Backed Underlays
|
||||||
|
|
||||||
Status: proposed target for design agreement; not implemented. Significant API
|
Status: proposed target for design agreement; not implemented. Significant API
|
||||||
and semantic choices are collected under Open Decisions. A later implementation
|
and semantic choices are collected under Open Decisions. A later implementation
|
||||||
@@ -6,12 +6,13 @@ plan will own staged execution, detailed signatures, and acceptance commands.
|
|||||||
|
|
||||||
## Purpose and priorities
|
## Purpose and priorities
|
||||||
|
|
||||||
Implement the two prerequisites in the
|
Address the filesystem and layered-input needs in the
|
||||||
[PromptKit request](feature-request-promptkit.md) as one coherent extension:
|
[PromptKit request](feature-request-promptkit.md) as one coherent extension:
|
||||||
native `fs.FS` sources and public, explicitly ordered configuration layers,
|
native `fs.FS` sources and explicit source-backed underlays, including
|
||||||
including programmatic input. Neither capability should require temporary files,
|
programmatic input. ConfigKit's recursive combination model takes precedence
|
||||||
a generated root YAML document, or serialization through YAML merely to transfer
|
over PromptKit's requested whole-definition replacement. Neither capability
|
||||||
already available values.
|
should require temporary files, a generated root YAML document, or serialization
|
||||||
|
through YAML merely to transfer already available values.
|
||||||
|
|
||||||
Design priorities, in order:
|
Design priorities, in order:
|
||||||
|
|
||||||
@@ -125,17 +126,31 @@ configuration or remain valid after the caller closes their filesystem.
|
|||||||
|
|
||||||
### Explicit operations, not discovery order
|
### Explicit operations, not discovery order
|
||||||
|
|
||||||
Expose a public loading entry point for an explicit ordered set of sources and
|
Extend public loading with explicitly supplied lower-precedence configuration
|
||||||
operations, using the same typed definition/result lifecycle as ordinary `Load`.
|
sources, using the same typed definition/result lifecycle as ordinary `Load`.
|
||||||
Support native filesystem, host-file, byte, reader, and programmatic inputs.
|
Support native filesystem, host-file, byte, reader, and programmatic inputs.
|
||||||
The order is supplied by the caller; ConfigKit does not discover priorities or
|
The central workflow is bundled defaults overridden by application configuration,
|
||||||
know about PromptKit's catalog tiers.
|
not an unrestricted sequence of user-selectable merge algorithms. Underlays can
|
||||||
|
form explicitly ordered default tiers; ConfigKit does not discover priorities or
|
||||||
|
know about PromptKit's catalog tiers. Source kind does not confer priority:
|
||||||
|
programmatic data can supply the appropriate supported input role without being
|
||||||
|
misrepresented as CLI flags.
|
||||||
|
|
||||||
|
The precedence model, from lowest to highest, is:
|
||||||
|
|
||||||
|
**Typed application defaults < source underlays < strictly composed main
|
||||||
|
configuration < selected overlay < explicit CLI overrides.**
|
||||||
|
|
||||||
|
Typed defaults remain the existing Go-value baseline. Source underlays add
|
||||||
|
configuration-backed defaults with imports, filesystem context, and source
|
||||||
|
provenance; they are not a replacement for the typed-defaults facility.
|
||||||
|
|
||||||
The selected operation determines how values combine, not their source format
|
The selected operation determines how values combine, not their source format
|
||||||
or loading entry point. Equivalent file, embedded, byte/reader, and programmatic
|
or loading entry point. Equivalent file, embedded, byte/reader, and programmatic
|
||||||
input uses the same presence and combination rules. Explicitly choosing compose,
|
input uses the same presence and combination rules. Strictly composing independent
|
||||||
overlay, or replacement expresses different intentions; merely changing the input
|
contributions and intentionally overlaying precedence tiers express different
|
||||||
medium must not switch between them. This applies to both ordinary `Load` and
|
intentions; merely changing the input medium must not switch between them.
|
||||||
|
This applies to both ordinary `Load` and
|
||||||
the new layered-loading interface wherever they offer the same operation.
|
the new layered-loading interface wherever they offer the same operation.
|
||||||
The existing opt-in version-path ownership rule remains an explicit metadata
|
The existing opt-in version-path ownership rule remains an explicit metadata
|
||||||
constraint, independent of whether its owning source is a file or programmatic
|
constraint, independent of whether its owning source is a file or programmatic
|
||||||
@@ -147,15 +162,27 @@ Keep strict composition and intentional overriding visibly distinct:
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Compose independent contributions | Existing strict composition: recurse through mappings and report non-mapping conflicts with relevant source locations. |
|
| Compose independent contributions | Existing strict composition: recurse through mappings and report non-mapping conflicts with relevant source locations. |
|
||||||
| Overlay settings | Existing recursive mapping overlay: omitted children retain lower input; scalar, sequence, and null values replace. |
|
| Overlay settings | Existing recursive mapping overlay: omitted children retain lower input; scalar, sequence, and null values replace. |
|
||||||
| Replace a definition | Replace the selected input subtree as a unit, without carrying over omitted lower-input children. The inclusion and public expression of this operation are Decision 1. |
|
|
||||||
|
|
||||||
Each independently supplied root may bring its explicitly enabled imports and
|
Underlay is a precedence role, not another combination algorithm. Assemble the
|
||||||
source context. Its import graph is strictly composed before intentional
|
main document and all its imports by strict composition before applying that
|
||||||
precedence is applied. An overlay must not hide malformed YAML or a composition
|
result over the underlays using recursive overlay. A conflicting main/import
|
||||||
conflict in an earlier contribution. There is no automatic matching by an `id`
|
definition still fails, even if it would overwrite an underlay or be overwritten
|
||||||
field, implicit sequence merge, universal profile registry, or merge callback.
|
by the selected overlay. Never flatten main and imported documents into a
|
||||||
|
last-wins list.
|
||||||
|
|
||||||
Preserve version checking after base composition and before intentional overlays
|
An underlay source may bring its explicitly enabled imports and source context.
|
||||||
|
Independent contributions within an underlay tier compose strictly; intentional
|
||||||
|
precedence between explicitly ordered tiers uses recursive overlay. Discovery
|
||||||
|
order does not establish tiers or authorize overriding. Syntax, source, and
|
||||||
|
composition failures cannot be hidden by higher-priority input.
|
||||||
|
|
||||||
|
There is no whole-definition replacement mode, per-layer recursive/atomic switch,
|
||||||
|
collection-specific identity matching, implicit sequence merge, profile registry,
|
||||||
|
or merge callback in this feature. Existing atomic scalar, sequence, null, and
|
||||||
|
cross-kind replacement under overlay remains unchanged.
|
||||||
|
|
||||||
|
Preserve the main configuration's version check after its strict composition and
|
||||||
|
schema construction, before precedence assembly with underlays, selected overlay,
|
||||||
and CLI contributions. Assemble the effective input before typed defaults/decoding,
|
and CLI contributions. Assemble the effective input before typed defaults/decoding,
|
||||||
application resolution, secret resolution, semantic validation, and final
|
application resolution, secret resolution, semantic validation, and final
|
||||||
snapshotting. Each lifecycle phase runs once per load, not once per layer.
|
snapshotting. Each lifecycle phase runs once per load, not once per layer.
|
||||||
@@ -163,45 +190,50 @@ Higher layers may replace values that would not fit the final typed schema;
|
|||||||
effective typed validation is not validation of each intermediate snapshot.
|
effective typed validation is not validation of each intermediate snapshot.
|
||||||
Input syntax, source access, and composition remain validated at acquisition.
|
Input syntax, source access, and composition remain validated at acquisition.
|
||||||
|
|
||||||
The standard precedence relationship is typed defaults below assembled input,
|
Recorded application resolution follows input assembly and decoding. Named-overlay
|
||||||
then the existing selected-overlay convenience and explicit CLI overrides where
|
lookup has an unambiguous filesystem context, separate from version ownership: ordinary
|
||||||
those are used, followed by recorded application resolution. Named-overlay lookup
|
|
||||||
has an unambiguous filesystem context, separate from version ownership: ordinary
|
|
||||||
loading retains its source/root convention; a multi-context load explicitly
|
loading retains its source/root convention; a multi-context load explicitly
|
||||||
identifies the lookup context when that convenience is used. Other sources retain
|
identifies the lookup context when that convenience is used. Other sources retain
|
||||||
their own import and resource contexts. Callers do not manufacture a primary
|
their own import and resource contexts. Callers do not manufacture a primary
|
||||||
document merely to combine layers, and no overlay lookup context is needed when
|
document merely to combine layers, and no overlay lookup context is needed when
|
||||||
no named overlay is selected.
|
no named overlay is selected.
|
||||||
|
|
||||||
Replacing lower input does not mean resetting or deleting typed application
|
Higher-priority mappings retain omitted lower-input children. Empty mappings do
|
||||||
defaults. Existing defaults and null rules still apply during final decoding.
|
not clear those children; sequences, including empty sequences, replace as a
|
||||||
The distinction must be visible in examples, especially empty maps and partially
|
unit, and null remains explicit subject to the final schema. Typed defaults
|
||||||
specified definitions. No new reset/delete operator is implied by replacement.
|
continue to fill fields absent from the assembled input under existing rules.
|
||||||
For example, replacing input `{timeout: 10, retries: 2}` with `{timeout: 20}`
|
No reset/delete operator is added.
|
||||||
removes the lower input's `retries: 2`; a typed default of `retries: 3` can still
|
|
||||||
supply the final value. The metadata then attributes `retries` to defaults, not
|
For example, a bundled profile containing `{model: example-model, timeout: 30}`
|
||||||
to the replaced input. An empty replacement entry likewise removes lower-input
|
combined with application input `{model: another-model}` retains `timeout: 30`
|
||||||
children without suppressing applicable typed defaults.
|
from the bundle. Its model origin points to the application and its timeout
|
||||||
|
origin points to the underlay. PromptKit must validate that final profile rather
|
||||||
|
than assume the application entry discarded all bundled settings. This knowingly
|
||||||
|
differs from its requested identity-based whole-definition replacement. Consumer
|
||||||
|
examples should adopt the shared semantics, not implement a hidden replacement
|
||||||
|
pass after loading. A replacement operation could be considered separately if
|
||||||
|
future concrete needs justify it, but is not an open choice in this roadmap.
|
||||||
|
|
||||||
### Preserve existing schema-version semantics
|
### Preserve existing schema-version semantics
|
||||||
|
|
||||||
An application opts into version checking by configuring a version path. A key
|
An application opts into version checking by configuring a version path. A key
|
||||||
spelled `version` has no automatic special status. When checking is configured,
|
spelled `version` has no automatic special status. When checking is configured,
|
||||||
one designated input owns that declaration; fragments cannot contribute it and
|
one designated input owns that declaration; fragments cannot contribute it and
|
||||||
overlays or CLI overrides cannot alter it, even to an equal value. New layering
|
underlays, overlays, or CLI overrides cannot supply or alter it, even to an equal
|
||||||
operations must respect that ownership rather than bypass it through subtree
|
value. Preserve the current version-path and ancestor protections when integrating
|
||||||
replacement. Preserve the current version-path and ancestor protections.
|
the new input role.
|
||||||
|
|
||||||
Keep the checker at its existing lifecycle point: after base composition and
|
Keep the checker at its existing lifecycle point: after base composition and
|
||||||
schema construction, before intentional overlays, CLI overrides, defaults, and
|
schema construction, before underlay precedence assembly, selected overlays,
|
||||||
typed decoding. Preserve supported scalar forms, absent/null distinction,
|
CLI overrides, defaults, and typed decoding. Preserve supported scalar forms,
|
||||||
|
absent/null distinction,
|
||||||
diagnostics, failure propagation, and version-path integer-to-string decoding.
|
diagnostics, failure propagation, and version-path integer-to-string decoding.
|
||||||
An absent declaration remains absent for the checker; an invalid version type
|
An absent declaration remains absent for the checker; an invalid version type
|
||||||
fails without invoking it. Later input cannot mask a version-check failure.
|
fails without invoking it. Later input cannot mask a version-check failure.
|
||||||
|
|
||||||
Filesystem sources and layered loading do not require a different version
|
Filesystem sources and layered loading do not require a different version
|
||||||
policy. The layered API needs a way to identify the version-owning input when a
|
policy. The layered API needs a way to identify the version-owning input when a
|
||||||
`VersionRule` is configured; Decision 4 concerns that integration only. Without
|
`VersionRule` is configured; Decision 3 concerns that integration only. Without
|
||||||
version checking, no version owner or synthetic primary document is required.
|
version checking, no version owner or synthetic primary document is required.
|
||||||
Named-overlay lookup context is a separate concern, not a reason to impose
|
Named-overlay lookup context is a separate concern, not a reason to impose
|
||||||
version ownership on unversioned loads.
|
version ownership on unversioned loads.
|
||||||
@@ -218,7 +250,7 @@ Programmatic input has a non-secret source label and uses explicit structure to
|
|||||||
represent presence. Omitted mapping keys are absent; present zero, false, empty
|
represent presence. Omitted mapping keys are absent; present zero, false, empty
|
||||||
string, empty collection, and nil/null values remain explicit. Use ConfigKit's
|
string, empty collection, and nil/null values remain explicit. Use ConfigKit's
|
||||||
existing canonical scalar and collection conventions, not YAML round trips or
|
existing canonical scalar and collection conventions, not YAML round trips or
|
||||||
Go zero-value inference. Decision 2 selects the initial input representation.
|
Go zero-value inference. Decision 1 selects the initial input representation.
|
||||||
|
|
||||||
Do not label generic programmatic sources as CLI input or misuse synthetic flag
|
Do not label generic programmatic sources as CLI input or misuse synthetic flag
|
||||||
names to fit the existing override API. Preserve parser-independent conversion,
|
names to fit the existing override API. Preserve parser-independent conversion,
|
||||||
@@ -282,8 +314,9 @@ The target implementation:
|
|||||||
persistent-tree framework merely to meet an asymptotic target.
|
persistent-tree framework merely to meet an asymptotic target.
|
||||||
|
|
||||||
Use representative benchmarks and allocation measurements for broad keyed
|
Use representative benchmarks and allocation measurements for broad keyed
|
||||||
catalogs, many small layers, repeated replacement, imported filesystem defaults,
|
catalogs, many small underlay tiers, repeated scalar/sequence replacement,
|
||||||
and retained history. Compare growth as inputs scale, including small common
|
imported filesystem defaults, and retained history. Compare growth as inputs
|
||||||
|
scale, including small common
|
||||||
loads. Favor changes addressing demonstrated scaling problems; preserve stable
|
loads. Favor changes addressing demonstrated scaling problems; preserve stable
|
||||||
diagnostic order and ownership rather than trading them for a superficial O(n)
|
diagnostic order and ownership rather than trading them for a superficial O(n)
|
||||||
claim. No timing threshold or additional release gate is requested.
|
claim. No timing threshold or additional release gate is requested.
|
||||||
@@ -295,9 +328,9 @@ These are responsibility areas, not implementation stages:
|
|||||||
- Extend the root-owned source/context facade and private acquisition boundary
|
- Extend the root-owned source/context facade and private acquisition boundary
|
||||||
for `fs.FS`; adapt import, glob, overlay, identity, and resource-context handling
|
for `fs.FS`; adapt import, glob, overlay, identity, and resource-context handling
|
||||||
without exporting parser or engine types.
|
without exporting parser or engine types.
|
||||||
- Add explicit layered loading and programmatic input, sharing one engine path
|
- Add source-backed underlays and programmatic input, sharing one engine path
|
||||||
with ordinary loading rather than building a parallel configuration system.
|
with ordinary loading rather than building a parallel configuration system.
|
||||||
- Resolve the fixed operation and ownership decisions below, then extend source
|
- Resolve the representation and ownership decisions below, then extend source
|
||||||
records, histories, diagnostics, and rendering consistently across input kinds.
|
records, histories, diagnostics, and rendering consistently across input kinds.
|
||||||
- Integrate version-owner selection into layered loading while preserving the
|
- Integrate version-owner selection into layered loading while preserving the
|
||||||
existing version lifecycle and protections. Keep filesystem-context selection
|
existing version lifecycle and protections. Keep filesystem-context selection
|
||||||
@@ -313,11 +346,14 @@ These are responsibility areas, not implementation stages:
|
|||||||
- Compare equivalent logical inputs across supported source kinds and loading
|
- Compare equivalent logical inputs across supported source kinds and loading
|
||||||
interfaces. Assert consistent values, presence, operation outcomes, and origin
|
interfaces. Assert consistent values, presence, operation outcomes, and origin
|
||||||
meaning while allowing genuine location/label differences. Cover defaulted and
|
meaning while allowing genuine location/label differences. Cover defaulted and
|
||||||
derived values, replacement followed by typed defaults, and preservation of
|
derived values, recursively retained underlay fields, unchanged atomic-value
|
||||||
|
replacement, typed defaults, strict main/import conflicts, and preservation of
|
||||||
version ownership/check timing across both loading interfaces. Verify that
|
version ownership/check timing across both loading interfaces. Verify that
|
||||||
unversioned layered loads need no synthetic version-owning root.
|
unversioned layered loads need no synthetic version-owning root.
|
||||||
- Provide maintained simple and PromptKit-shaped examples: embedded defaults,
|
- Provide maintained simple and PromptKit-shaped examples: embedded defaults,
|
||||||
operator configuration from another context, and labeled programmatic input.
|
operator configuration from another context, and labeled programmatic input.
|
||||||
|
Show explicitly that a same-name profile recursively combines, rather than
|
||||||
|
replacing its whole lower definition.
|
||||||
Demonstrate exact operations and resource origins without provider calls or
|
Demonstrate exact operations and resource origins without provider calls or
|
||||||
recreating PromptKit itself. The consumer supplies sources, explicit operations,
|
recreating PromptKit itself. The consumer supplies sources, explicit operations,
|
||||||
types, and domain rules; it does not implement generic merging, presence
|
types, and domain rules; it does not implement generic merging, presence
|
||||||
@@ -346,30 +382,13 @@ defaults, and recorded domain resolution, and metadata distinguishes explicit
|
|||||||
zero/false from absence even when the typed values are equal. Benchmarks
|
zero/false from absence even when the typed values are equal. Benchmarks
|
||||||
demonstrate that adding inputs does not entail avoidable full-collection copying
|
demonstrate that adding inputs does not entail avoidable full-collection copying
|
||||||
or quadratic key lookup. Public contracts and examples clearly distinguish
|
or quadratic key lookup. Public contracts and examples clearly distinguish
|
||||||
composition, overlay, any selected replacement operation, and downstream domain
|
strict within-tier composition, recursive cross-tier overlay, and downstream domain
|
||||||
resolution. Equivalent-input tests and the maintained consumer example establish
|
resolution. Equivalent-input tests and the maintained consumer example establish
|
||||||
semantic consistency and reduced integration code, not merely API availability.
|
semantic consistency and reduced integration code, not merely API availability.
|
||||||
|
|
||||||
## Open Decisions
|
## Open Decisions
|
||||||
|
|
||||||
### 1. Whole-definition replacement
|
### 1. Initial programmatic representation
|
||||||
|
|
||||||
**Recommendation:** include a small fixed replacement operation alongside compose
|
|
||||||
and recursive overlay. For PromptKit-shaped catalogs, explicitly identify a
|
|
||||||
string-keyed collection path and replace each supplied entry as a whole; retain
|
|
||||||
unmentioned entries. Do not infer identities from object fields. An empty supplied
|
|
||||||
collection contributes no entries; an explicitly supplied empty entry replaces
|
|
||||||
that entry's lower input. Use the same rule for every consumer, not arbitrary
|
|
||||||
per-field policies. This meets the concrete identity-precedence need without
|
|
||||||
silently redefining ordinary overlay. Final typed defaults still apply.
|
|
||||||
|
|
||||||
**Alternative:** ship only existing composition/overlay semantics and leave
|
|
||||||
whole-definition selection downstream. This is smaller and closest to today's
|
|
||||||
surface, but may force consumers to parse/select definitions and reconstruct
|
|
||||||
origins outside ConfigKit, undermining the request's purpose. Confirm with the
|
|
||||||
combined consumer example before claiming this narrower scope meets the need.
|
|
||||||
|
|
||||||
### 2. Initial programmatic representation
|
|
||||||
|
|
||||||
**Recommendation:** labeled string-keyed mappings with the scalar/list/map value
|
**Recommendation:** labeled string-keyed mappings with the scalar/list/map value
|
||||||
set already supported by explicit overrides. Missing keys express omission and
|
set already supported by explicit overrides. Missing keys express omission and
|
||||||
@@ -383,7 +402,7 @@ bindings, masks, nulls, and typed zero values and a larger public contract. Do n
|
|||||||
infer omission from zero values. Add typed construction only if real examples
|
infer omission from zero values. Add typed construction only if real examples
|
||||||
show that the simple mapping representation imposes substantial repetitive work.
|
show that the simple mapping representation imposes substantial repetitive work.
|
||||||
|
|
||||||
### 3. How consumers obtain filesystem resource context
|
### 2. How consumers obtain filesystem resource context
|
||||||
|
|
||||||
**Recommendation:** a separate source-context query exposing standard filesystem
|
**Recommendation:** a separate source-context query exposing standard filesystem
|
||||||
access and a source-relative base location through root-owned API types, while
|
access and a source-relative base location through root-owned API types, while
|
||||||
@@ -398,19 +417,19 @@ serializable, but every consumer must maintain and correctly reconcile another
|
|||||||
registry. This is viable if limiting retained filesystem capabilities proves
|
registry. This is viable if limiting retained filesystem capabilities proves
|
||||||
more important than the additional integration machinery.
|
more important than the additional integration machinery.
|
||||||
|
|
||||||
### 4. Selecting the version-owning input for layered loads
|
### 3. Selecting the version-owning input for layered loads
|
||||||
|
|
||||||
**Recommendation:** let callers explicitly designate one of the supplied base
|
**Recommendation:** the designated main source owns the version when configuring
|
||||||
inputs as the version owner when configuring a `VersionRule`. It need not be a
|
a `VersionRule`, extending the existing selected-source role rather than letting
|
||||||
host file or a newly manufactured root document. Other contributions retain
|
an underlay win ownership by list position. It need not be a host file or a newly
|
||||||
fragment/override roles with respect to that path. Explicit designation avoids
|
manufactured root document. Its imports remain fragments and underlays cannot
|
||||||
making version ownership depend on incidental list order and preserves the
|
declare the application's version. No special version-owner designation is
|
||||||
existing selected-source model without changing version acceptance or timing.
|
needed when checking is not configured, including loads using only underlays.
|
||||||
No designation is needed when version checking is not configured.
|
|
||||||
|
|
||||||
**Alternative:** make the first base input the version owner when checking is
|
**Alternative:** allow explicit designation of a base input as the version owner
|
||||||
configured. This reduces API ceremony and naturally extends a main-source-plus-
|
separately from its main/underlay role. This supports bundles carrying the
|
||||||
fragments model, but couples ownership to ordering and can make reordering
|
application version, but adds another ownership choice and complicates the
|
||||||
otherwise independent contributions consequential. Either choice must preserve
|
otherwise clear distinction between defaults and application configuration.
|
||||||
the existing version contract; this decision does not reopen effective-version
|
Either choice must preserve the existing version contract; this decision does
|
||||||
|
not reopen effective-version
|
||||||
or per-document version validation.
|
or per-document version validation.
|
||||||
|
|||||||
Reference in New Issue
Block a user