293 lines
17 KiB
Markdown
293 lines
17 KiB
Markdown
# Configuration Input Destinations Roadmap
|
|
|
|
Status: proposed; implementation has not started. Execute the stages below in
|
|
order. This roadmap responds to the
|
|
[PromptKit feature request](promptkit-feature-request.md) and owns the target
|
|
state, delivery sequence, and unresolved design choices. Public spelling and
|
|
recommendations below are proposals until selected explicitly.
|
|
|
|
## 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 intended public surface is per-input placement, illustratively an optional
|
|
`Destination Path` field on `Input`, not a new loading entry point or an operation
|
|
on completed results. The minimum supported workflow has a root main input and
|
|
mounted underlay inputs; broader main placement is an open decision below.
|
|
|
|
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.
|
|
- 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.
|
|
- 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.
|
|
|
|
## Current baseline
|
|
|
|
The request reviewed v0.7.1. The current worktree has these relevant boundaries:
|
|
|
|
- [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.
|
|
|
|
## Ordered implementation plan
|
|
|
|
### 1. Settle placement semantics and the public contract
|
|
|
|
Resolve the Open Decisions before their dependent implementation. Select the
|
|
public API spelling, main/NoSource behavior, placement inspection, reserved-key
|
|
handling, and empty-contribution semantics. Define destination validation and
|
|
structured errors, including when validation occurs relative to source access.
|
|
Use existing error categories where suitable rather than inventing a parallel
|
|
error system.
|
|
|
|
Record the significant assembly decision in an ADR. Review compatibility under
|
|
the release policy, including exported struct changes and unchanged zero-value
|
|
behavior. Keep unimplemented claims in the roadmap; update canonical contracts
|
|
when implementation lands. Do not turn unrelated deferred features into work.
|
|
|
|
Completion: selected semantics cover the request's public acceptance scenarios
|
|
and a simple consumer as well as PromptKit's multi-collection workflow.
|
|
|
|
### 2. Carry destinations through acquisition and imports
|
|
|
|
Thread the selected destination through root-to-engine conversion and private
|
|
load requests. Preserve its ownership and semantic key representation. Separate
|
|
source-local directive processing from application placement: extract directives
|
|
at their original root, inherit placement through traversal, and place application
|
|
content before checks and composition that require effective paths.
|
|
|
|
Make version protection destination-aware for underlay roots and imported
|
|
fragments, preserving the main check's lifecycle position. Ensure diagnostics
|
|
produced before final composition identify effective application paths where
|
|
available while preserving actual source coordinates. Structural directive
|
|
errors still need their genuine source-local declaration locations.
|
|
|
|
Retain repeated-source detection across the whole strict group, independent of
|
|
destination. Preserve filesystem namespace, resource contexts, and traversal
|
|
ordering. Account for destination depth and introduced nodes using existing
|
|
budgets; distinguish them from acquired source bytes and physical source count.
|
|
|
|
Completion: all content-bearing source kinds and transitive imports contribute
|
|
at the intended destination, with correct version protection and source identity.
|
|
|
|
### 3. Integrate composition, provenance, and the final lifecycle
|
|
|
|
Apply ordinary composition to placed contributions and ordinary overlay between
|
|
tiers. Retain each source application at its effective placement for history;
|
|
carry real source attribution and the selected synthetic-container convention
|
|
through origin construction. Avoid a separate merge algorithm or a mount registry.
|
|
|
|
Exercise overlapping destinations, empty mappings, and a version-only domain
|
|
override. Resolve sibling inheritance after the full precedence assembly, then
|
|
use existing defaults, typed decoding, resolution, secrets, and validation.
|
|
Check protection of the specification path through inheritance, selected
|
|
overlays, and CLI overrides as well as direct contributions.
|
|
|
|
Completion: mounted and explicitly wrapped equivalents agree on values,
|
|
conflicts, presence, and lifecycle results; physical locations remain accurate.
|
|
|
|
### 4. Complete inspection and public consumer workflows
|
|
|
|
Expose placement through the selected existing inspection surface, with detached
|
|
records. Verify effective paths throughout metadata, explanation replay, history,
|
|
rendering, and diffs. Apply the existing sensitivity rules to mounted and inherited
|
|
fields, including superseded values. Do not fabricate coordinates for wrappers
|
|
that were never written in a source file.
|
|
|
|
Demonstrate source-relative resource lookup from both resolution and completed
|
|
results, including inherited fields and lower-tier fields surviving an override.
|
|
Add executable public examples for one mounted fragment and a representative
|
|
multi-collection assembly with peer composition and an intentional override.
|
|
Applications select sources and destinations; the examples should require no
|
|
consumer-side parsing, generic merging, or provenance reconstruction.
|
|
|
|
Completion: the supported root API is sufficient for the requested end-to-end
|
|
workflow, including accurate placement and source-relative resource inspection.
|
|
|
|
### 5. Validate the full feature and update owning documentation
|
|
|
|
Use engine tests for placement mechanics and public tests for observable loading
|
|
and inspection. Map every scenario in the request's acceptance table to a test
|
|
or executable example, reusing existing coverage where it proves the same
|
|
behavior. Include focused cases for:
|
|
|
|
- omitted/empty destinations, literal escaped and numeric keys, invalid index
|
|
destinations, and the selected reserved-key and main-input rules;
|
|
- different destinations, shared destinations, ancestor overlaps, equal and
|
|
unequal conflicts, explicit null, omitted fields, and empty contributions;
|
|
- recursive precedence, atomic sequences, and domain-version-only overrides;
|
|
- inherited import destinations, source-root directives, repeated physical
|
|
sources at different destinations, and unchanged cycle/containment behavior;
|
|
- nested ordinary version fields versus actual protected-path and ancestor
|
|
claims, including mounted imported content;
|
|
- local inheritance across peers and source contexts after inheritance/overlay;
|
|
- effective diagnostic paths with real locations, callback paths, history,
|
|
rendering, diffs, and classified values protected with disposable canaries;
|
|
- equivalent content across every supported source kind, invalid loads with no
|
|
usable result, introduced depth/node limits, and detached inspection ownership.
|
|
|
|
Follow the [testing policy](../policy/testing.md): protect observable contracts
|
|
without fixing incidental wrapper allocation or helper-call sequences. Run
|
|
focused tests, then the everyday check and ownership race validation in the
|
|
[development guide](../development.md). Validate local documentation links.
|
|
No independent performance benchmark runs are required for this roadmap.
|
|
|
|
Update declaration documentation, the configuration contract, consumer guidance,
|
|
and the component overview as their behavior lands. Document how source-local
|
|
coordinates relate to effective paths and how placement is inspected. Review
|
|
release compatibility separately from publication; feature completion does not
|
|
authorize a release.
|
|
|
|
Completion: all requested public scenarios pass, owning documentation describes
|
|
implemented behavior, and consumers can perform one complete layered load with
|
|
no compensating source-rewriting or post-load structural pass.
|
|
|
|
## Open Decisions
|
|
|
|
### Main-input destinations and absent inputs
|
|
|
|
Recommendation: allow the same destination model for content-bearing main inputs
|
|
as for 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. Treat `NoSource` as no contribution,
|
|
even with a valid destination. This keeps `Input` coherent and avoids a role-only
|
|
restriction. PromptKit can continue using root main; broader main placement must
|
|
not add per-fragment checks or move the existing version-check phase.
|
|
|
|
Alternative: initially support non-root destinations only for underlay inputs
|
|
and reject non-root main placement explicitly. This meets PromptKit's minimum
|
|
need and reduces the first implementation's version-check interactions, but adds
|
|
a role-dependent rule to a shared public type. Adopt that restriction only as an
|
|
explicit scope choice, not because threading main placement is inconvenient.
|
|
For `NoSource`, rejecting a nonempty destination could catch mistaken assembly
|
|
declarations, but makes an otherwise absent contribution less composable.
|
|
|
|
### Public placement inspection and synthetic ancestors
|
|
|
|
Recommendation: expose effective placement on existing source records,
|
|
illustratively `SourceInfo.Destination`, 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.
|
|
|
|
Alternative: add placement to origin records or a dedicated lookup, and represent
|
|
synthetic ancestors without a physical location. This can distinguish authored
|
|
and synthetic structure more precisely, but expands per-value metadata or adds
|
|
an API when placement is fundamentally per source application. Evaluate the
|
|
choice against inherited fields, composite origins, and the same source selected
|
|
in separate tiers; do not infer placement from a final path by guesswork.
|
|
|
|
### Reserved structural keys in destinations
|
|
|
|
Recommendation: 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.
|
|
|
|
Alternative: permit structural spellings as literal synthetic wrapper keys by
|
|
distinguishing them from authored directives. This enables additional application
|
|
paths but introduces an exception where mounted and explicitly wrapped inputs
|
|
can have different meanings. It would require an explicit change to reserved-key
|
|
semantics and is not needed for the requested collection paths. Running existing
|
|
directive validation on the placed tree is another viable error mechanism, but
|
|
must still identify that the invalid structure came from destination selection.
|
|
|
|
### Empty mappings and presence of introduced containers
|
|
|
|
Recommendation: 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.
|
|
|
|
Alternative: treat an empty mounted body as no contribution. This avoids empty
|
|
containers for fragments containing only imports or no application fields, but
|
|
loses equivalence with explicitly wrapped empty mappings and erases an observable
|
|
presence distinction. If selected, document that exception and its effect on
|
|
history and empty imported bodies rather than deriving it accidentally from
|
|
whether the implementation allocated wrapper nodes.
|