Files
ConfigKit/docs/roadmap/input-destinations.md

172 lines
10 KiB
Markdown

# Configuration Input Destinations Roadmap
Status: implemented. This roadmap records the approved behavior and rationale
for the [PromptKit feature request](promptkit-feature-request.md). The
[implementation plan](implementation.md) records the completed sequence and
audit evidence; the [configuration contract](../config.md#input-destinations)
owns current behavior.
## Targeted end state
A consumer can assign a mapping destination to a configuration input and perform
one ordinary `LoadLayered` call. The input's application content contributes
beneath that destination while retaining normal composition, precedence,
inheritance, typed loading, diagnostics, and inspection. PromptKit can assemble
collections without parsing and rewriting sources, merging values, or rebuilding
provenance itself.
For example, a fragment defining `summarize.alerts` can contribute beneath
`collections`, `weatherreporter`, `prompts`. An independent source with the same
local key can contribute beneath another collection without conflict. Collection
and prompt names remain ordinary application keys, not ConfigKit namespaces.
The public surface adds an optional `Destination Path` field on `Input`, not a
new loading entry point or an operation on completed results. Content-bearing
main inputs and underlay inputs support
the same destination model; root placement remains the default.
The following behavior defines the target:
- Omitted and empty destinations retain root placement. Destinations consist of
literal mapping keys, including dots, slashes, and numeric-looking text.
Explicit sequence-index destinations are outside the requested capability.
- A valid destination on `NoSource` contributes nothing. An actual empty
application mapping materializes its destination mappings as explicit input,
matching authored wrappers, without clearing lower-tier children.
- Existing structural reservations apply to introduced mappings. Destinations
that materialize `configkit` or a mapping-valued `extends` contrary to existing
contracts fail through the structured loading-error boundary. Keys merely
containing those words remain ordinary keys.
- Host files, native filesystem files, bytes, readers, and mapping sources all
support placement under their existing acquisition and ownership contracts.
Filesystem context, import opt-in, and precedence remain independent choices.
- Mounted contributions have the same application values and conflict behavior
as explicitly wrapped contributions, with accurate original source locations.
Disjoint mappings combine; overlapping non-mapping values within a group
conflict even when equal. A later tier cannot repair a strict-group conflict.
- Different destinations can coexist in one group, and the same destination can
occur in different tiers. Ancestor/descendant destinations follow ordinary
composition and overlay rules without namespace uniqueness restrictions.
Later tiers recursively overlay mappings and replace other values. Empty
mappings do not clear lower values; sequences remain atomic.
- A prompt's ordinary `version` override changes that field and retains omitted
lower fields. It neither identifies a merge record nor replaces a whole prompt.
- Source-root `configkit.imports` remains structural. Imported application data
inherits the input destination transitively. Import paths still resolve from
their declaring source; containment, optionality, cycles, and repeated-source
rules remain applicable. Placement does not change physical source identity.
- Sibling inheritance runs after assembly in the effective destination scope.
Peer fragments can supply a child and its sibling parent. Qualified PromptKit
references do not acquire cross-collection inheritance meaning.
- Version ownership is enforced at effective application paths. A collection's
mounted `version` is ordinary data, while underlay/import contributions to the
actual protected path, or replacements of its ancestors, remain invalid.
There is one main-owned version check and no blanket nested numeric conversion.
- Defaults, decoding, resolution, secret lookup, and validation run once on the
assembled application, not independently on fragments. Callbacks and value
diagnostics use effective paths. Failures use structured load errors and yield
no usable partial result.
- Presence, provenance, history, explanations, effective rendering, and diffs
agree with the final values. Sensitivity is evaluated at effective application
paths under the existing historical and dependency rules.
- Existing source records expose placement, including inherited import
placement. Introduced ancestor mappings retain the contributing source's
identity; any root-location fallback identifies that document, not an authored
wrapper. Actual content retains its real source coordinates.
- Original source locations and passive resource contexts survive placement,
overrides, and inheritance. `Result.SourceContext` and
`Resolution.SourceContext` still identify the declaring filesystem and base
directory for a field such as `content_file: ./summary.md`.
- Destinations and the structure they introduce participate in existing load
limits; placement cannot bypass depth or node accounting. Source ownership,
complete-on-return loading, and detached result behavior remain intact.
Import-destination syntax, independent collection specification headers, and
cross-collection inheritance are separate possible follow-ups. This work does
not add directory discovery, collection installation, remote acquisition,
namespace ownership enforcement, dependency solving, live reload, filename-based
identity, arbitrary transformations, or new merge strategies. PromptKit owns its
collection inventory, qualified IDs, and execution semantics.
## Implementation baseline (v0.7.1)
The request reviewed v0.7.1. At that baseline, the relevant boundaries were:
- [Input](../../layered.go) holds a source, import opt-in, and filesystem context,
but no destination. Root types delegate to the engine through the existing
[facade boundary](../internal/overview.md).
- [Layered loading](../config.md#layered-loading) already provides strict groups,
ordered precedence, shared acquisition budgets, and one final lifecycle.
[Underlay acquisition](../../internal/engine/load_request.go) gathers source
applications and recomposes them into their strict group.
- [Input acquisition](../../internal/engine/load_input.go) and
[import traversal](../../internal/engine/imports.go) process source-root
directives and source identity before final assembly. Imported version claims
are checked during traversal; underlay claims are checked before group
composition. These currently operate without a destination.
- [Version claim checks](../../internal/engine/directives.go) inspect a document
using the configured application path. Wrapping only an already acquired
result would leave early fragment checks and diagnostic paths incorrect.
- [Sibling inheritance](../adr/0009-sibling-inheritance.md) already runs after
source precedence and preserves source-to-destination derivation. Placement
must feed that existing mechanism rather than resolve fragments independently.
- Existing source records identify acquisition and precedence but do not expose
input placement. Existing history retains source applications, so moving only
the final tree would be insufficient for coherent inspection.
The architectural fit is an extension of input assembly, not a parser extension
or a second configuration model. The
[architecture policy](../policy/architecture.md) supplies the ownership and
consistency constraints; [deferred scope](deferred.md) continues to govern the
unrequested features above.
## Selected Design Decisions
### Main-input destinations and absent inputs
Allow the same destination model for content-bearing main inputs and underlays,
with `VersionRule.Path` remaining an effective application path and any
specification declaration still owned by main. The checker retains its existing
handling of an absent declaration and its existing lifecycle phase. `NoSource`
contributes nothing even with a valid destination. This keeps `Input` coherent
across its roles and preserves absence without per-fragment version checks.
### Public placement inspection and synthetic ancestors
Expose effective placement as `SourceInfo.Destination Path` on existing source
records, including inherited import placement.
Use original source identity for introduced ancestor mappings, documenting that
any root-location fallback identifies the contributing document rather than a
physically authored wrapper. Actual content keeps its real coordinates. This
allows consumers to connect placement to sources without a new query system.
Preserve that relationship through inherited fields, composite origins, and the
same source selected in separate tiers. Placement is per source application;
consumers can inspect it without inferring it from final paths.
### Reserved structural keys in destinations
Use existing `Path` conventions for literal keys and parsed
numeric tokens, reject explicit index segments, and preserve current structural
reservations in the effective tree. Reject destination segments that would
materialize `configkit` or a mapping-valued `extends` contrary to those contracts,
with a clear structured destination error. Ordinary keys containing those words,
dots, or slashes remain ordinary keys. Source-root directives are processed before
placement and are not rejected simply because their application body is mounted.
Mounted and authored wrappers therefore retain the same structural meaning.
Destination diagnostics identify placement as the cause of invalid introduced
structure, using existing error categories where suitable.
### Empty mappings and presence of introduced containers
Mounting an actual empty application mapping materializes its
destination mappings as explicit contributions, matching authored wrappers.
This preserves the request's equivalence principle and lets consumers inspect
an intentionally empty collection. Such mappings still do not clear lower-tier
children. An absent source contributes nothing.
This applies equally to empty imported application bodies and inputs containing
only import directives. Presence follows the contribution's meaning rather than
whether an implementation happened to allocate wrapper nodes.