Plan configuration input destination implementation

This commit is contained in:
2026-10-14 12:48:08 +00:00
parent 6353598bd6
commit d3d468a8b4
3 changed files with 761 additions and 123 deletions

View File

@@ -0,0 +1,432 @@
# Implement configuration input destinations
Status: planned. Execute Stage 1 through Stage 12 in order, one focused stage
per Luna implementation prompt. The [feature roadmap](input-destinations.md)
owns the approved behavior and rationale; the
[consumer request](promptkit-feature-request.md#public-acceptance-scenarios)
supplies the public acceptance scenarios. This document owns execution and
completion evidence. Planning baseline: `6353598`.
## Working instructions
- Start each stage with `AGENTS.md`, [development.md](../development.md), the
applicable policies, and working-tree inspection. Use graph-first code
discovery. Preserve unrelated changes and previously completed stages.
- Implement only that stage and its supporting tests/documentation. Mark it
complete here with actual commands and evidence, then commit the nonempty
validated unit with a short plain-English message. Subsequent stages remain
planned. A stage is not complete solely because its narrow tests pass.
- Keep early mechanics private until the complete engine behavior is ready.
Root destination fields land in Stage 10. Ordinary root loading must remain
working throughout. Do not publish a partially supported public placement API.
- Follow the existing engine/document/YAML boundaries. Reuse strict composition,
recursive overlay, sibling inheritance, paths, history, and sensitivity.
No dependencies, toolchain changes, alternate mergers, mount registry,
per-fragment lifecycle, import-destination syntax, resource discovery, or new
release gates are needed. These are scope boundaries, not new prohibitions
on separately approved future work.
- Update current-behavior documentation when public behavior lands; keep future
claims here or in the roadmap until then. Add the ADR in Stage 1 under the
documentation policy's accepted-decision exception.
- Use focused offline tests while iterating; run `./scripts/check.sh` before
committing production changes. Documentation-only stages need whitespace and
local-link checks. Stage 12 adds full ownership/race validation. Tests protect
observable behavior, not wrapper allocation or helper-call counts. Reuse
coverage rather than repeat an entire loading matrix at every layer.
- No benchmark campaign or public complexity guarantee is required. Prefer
straightforward bounded walks and wrapping each acquired contribution once;
avoid rescanning all preceding contributions for every new input.
- Do not push, tag, publish, update downstream dependencies, or retire unfinished
records. Release compatibility review is separate from publication.
## Resolved implementation decisions
1. Add root-owned `Input.Destination Path` and `SourceInfo.Destination Path`,
mirrored privately in engine input/source records. Extend `loadInput` and
explicit root/engine conversion and cloning. No new loading entry point,
public wrapper type, option, or origin field is needed. Ordinary `Load`
continues to supply root placement.
2. The zero path means root. Validate `Path.valid()` and reject every explicit
index segment, including a negative index. Normalize parsed tokens to literal
mapping-key segments: `/0` means the key `0`, not sequence element zero.
Empty, dot, dot-dot, slash-containing, tilde-containing, and numeric keys keep
existing semantic-path meaning. Do not split dots, interpret filesystem
traversal, enforce identifier grammar, or add namespace uniqueness.
3. Reject destination segments equal to `configkit` or `extends`: placement
would introduce a reserved mapping key with a mapping value. Substrings such
as `myextends` are ordinary keys. This preserves the approved structural
rules rather than adding an application naming policy. Source-root import
directives are still processed before placement.
4. Validate destination shape/reservations for all declared inputs during
grouped option preflight, before reading any source. Reuse
`CodeOptionsInvalid` for invalid destination declarations; include the
offending semantic path when representable, with no fabricated physical
location. An invalid source still follows existing source-option validation.
A valid destination on `NoSource` contributes no nodes, source record, or
budget charges. No extra error type or exact message contract is introduced.
5. Separate acquired source syntax from application placement. Retain original
parsed source metadata for filesystem/directive handling. Strip source-root
directives, then wrap each application body at the normalized destination
before fragment version checks and strict composition. Imports inherit the
same destination transitively. Do not wrap both a source application and its
recomposed result; every retained application must already use effective
coordinates. Content roots remain mappings under existing source contracts.
6. Wrapping a body at `Keys("a", "b")` creates `{a: {b: body}}` as private
document mappings. Actual body nodes and entries keep their physical source
coordinates. Introduced mappings/entries use the contributing document's root
location as a documented source fallback, not purported authored coordinates.
They have the same contributing source ID and create no synthetic source.
Retained history trees remain immutable and separate from assembly mutation.
7. A present empty body creates explicit destination mappings, including an
import-only source whose application body is empty. It still cannot clear
lower mappings. A missing optional import and `NoSource` remain absent. Empty
source text remains the existing invalid document, not an empty mapping.
8. `VersionRule.Path` stays an effective application path. Check main once after
its own imports compose and before underlays/overlay/CLI, at the existing
phase. Imported and underlay claims are checked against placed application
content, including scalar/null/sequence ancestors replacing a protected path.
A mapping ancestor with no protected descendant is not a version claim.
Mounted domain `version` fields are ordinary data. Existing inheritance,
selected-overlay, CLI protection and exact-path numeric conversion remain;
no nested blanket conversion or additional fragment checker is introduced.
9. Placement does not alter physical source identity, filesystem namespace,
source IDs, acquisition order, imported-from locations, or resource contexts.
Repeated physical sources in one strict group still fail even at different
destinations. The same physical source in separate tiers retains the existing
independent source applications/records and each record's own destination.
Overlay and CLI source records have root `Destination`; they are not mounted
merely because main is mounted. Active layered overlays retain their explicit
independent filesystem selection and application-root meaning.
10. Value-level paths use effective coordinates. Keep genuine source-local
import-directive diagnostics and parse/read failures at their original
locations; do not invent a logical child path when parsing cannot identify
one. Prefix existing application-relative diagnostic paths exactly once
where available. Preserve structured codes, related physical locations,
deterministic ordering, and value-free messages.
11. Use current depth/node limits, not a new destination cap. With root depth
zero, each destination segment adds one level and, under `countInputNodes`,
one mapping node plus one key. Retain current raw input accounting (including
directives); charge only the additional `2 * segmentCount` synthetic nodes
per acquired body, once, to the shared input budget. Placement adds no bytes,
acquired sources, import nesting, or glob visits. Validate placed application
depth and per-document node bounds before composition; share accounting
across inputs and imports. Precheck wrapper depth/count before allocating
wrappers. Use existing source/import limit diagnostics in their acquisition
contexts; retain projection, expansion, and history bounds independently.
Root placement adds no charge or new rejection. NoSource adds none either.
12. Cloned source records include independently owned destination paths at every
existing publication boundary: results, load errors, metadata/inspection,
callback input information, and root conversion. Passive resource contexts
remain keyed by the real source identity; inspection must work without an
open configuration handle after load. This does not extend caller-owned
filesystem or callback resource lifetimes.
## Stage 1 — Record the decision and capture the baseline
Status: planned. Scope: decision record and evidence, no feature code.
1. Read the roadmap, request, relevant policies, and ADRs 0001, 0003, 0006,
0008, and 0009. Inspect the owners identified in the roadmap and the function
map below. Confirm the baseline and worktree before starting.
2. Run `./scripts/check.sh`; record the commit, Go version, and results here.
Locate existing coverage for root loading, strict underlays, version ownership,
repeated sources, history, resource contexts, and detached records.
3. Write `docs/adr/0011-input-destinations.md` after confirming the number is
unused. Record the approved input-placement decision as Accepted, with the
roadmap owning implementation status. Follow the repository ADR format.
Focus on ConfigKit, put rejected options only in Alternatives Considered,
and avoid negative parallelism elsewhere. No PromptKit-specific semantics.
4. Record the compatibility assessment under the release policy: adding fields
preserves zero-value/keyed-literal use, but callers using unkeyed literals
for `Input` or `SourceInfo` must migrate to keyed literals. Treat the feature
as minor-release work; select a version and write its note only during
separately authorized release preparation.
Validate: local-link test and whitespace checks. Done: baseline evidence and
the decision are recorded; implemented contracts remain unchanged.
## Stage 2 — Add private destination normalization and wrapping
Status: planned. Scope: pure private mechanics with bounded unit tests.
1. Add a small engine helper file for normalized mapping-only destinations and
constructing placed document bodies. Follow decisions 2–7; keep helpers
private and avoid introducing a second path abstraction.
2. Validate invalid paths, explicit indices, and exact reserved tokens. Normalize
parsed numeric tokens into keys, preserving literal text and pointer escaping.
3. Build wrappers without mutating the supplied tree. Preserve body locations;
give introduced entries/mappings the source-root fallback. Implement depth and
node measurement/prechecks usable with existing limits, including the extra
synthetic-node count; aggregate charging is wired in Stage 7.
4. Cover root identity/equivalence, nested empty mapping, literal/escaped/numeric
keys, invalid index/path, exact versus substring reserved keys, owned paths,
unchanged body coordinates, and bounded wrapper checks. Do not count helper
invocations or fix an allocation strategy in tests.
Validate: focused destination/path tests and the everyday check. Done: the
mechanism is usable privately; no loader accepts a new public field yet.
## Stage 3 — Integrate private non-import input placement
Status: planned. Scope: engine request plumbing and ordinary acquired bodies.
1. Add destination fields to engine `Input`, `loadInput`, and `SourceInfo`.
Preserve ownership through engine `LoadLayered` request construction,
`cloneSourceInfo`, and source-record copies. Keep the root facade unchanged.
2. Extend grouped preflight to validate/normalize destinations before source
reads. Use the existing diagnostic accumulator and options-invalid category.
Ordinary `Load` supplies root placement through its existing request adapter.
3. Thread placement into `assembleBaseInputTracked` without confusing raw
`parsedSource` with effective `assembledInput.document`. Preserve the root
path through existing wrappers/callers. For imports-disabled acquisition,
validate disabled directives on raw content, place the body, and retain the
destination on the acquired source record. NoSource still short-circuits.
4. Use engine loads to cover mounted main and underlay bodies, omitted versus
explicit root destinations, empty mapping versus NoSource, and preflight
rejection without consuming a test-owned reader. Reuse current source-kind
acquisition tests; full source equivalence is verified publicly in Stage 10.
Validate: destination, grouped-load, source, and existing ordinary-load tests;
the everyday check. Done: private non-import placement reaches assembly, with
root behavior unchanged. Imports are the next private integration step.
## Stage 4 — Carry placement through import acquisition
Status: planned. Scope: source-local directives and placed imported applications.
1. Thread a normalized destination through `loadImportedDocumentsTracked`,
`loadImportedWithAccessTracked`, and `importTraversal`, retaining root defaults
for current private callers. Place the selected body's application content
after `splitImportDirectives`; do the same for every imported body.
2. Set placement on selected/imported source records, including error records
once a source has been acquired. Keep `ImportedFrom` at the physical directive.
Run imported `fragmentVersionClaim` against placed bodies and retain placed
bodies in `state.documents` before `composeDocuments` builds applications.
3. Leave filesystem joins, containment, glob traversal, optional missing inputs,
active cycles, visited identities, and acquisition IDs/order unchanged.
Destination must not become part of the repeated-source identity key.
4. Test native and host transitive imports, nested source-root directives,
import-only bodies, and mounted imported protected versus ordinary version
paths. Verify a repeated file at two destinations in one group still fails,
including an import/direct-input alias. Reuse cycle/containment regressions.
Validate: focused destination/import/glob/grouped tests and everyday check.
Done: every retained imported contribution has effective placement and physical
source identity, with no double wrapping or application-level directive leakage.
## Stage 5 — Complete strict tiers and main version ownership
Status: planned. Scope: assembly and version interactions, not a new merger.
1. Review `acquireUnderlayGroup`: version checks must use placed content, and
documents taken from `acquired.composition.applications` must already be
placed. Recompose strict peers with ordinary `composeDocuments`; preserve
per-source applications, origins, UnderlayIndex, and input accounting.
2. In `executeLoadRequest`, main's version check uses its placed main/import
composition at the existing point. `assembleLayeredInput` recursively overlays
completed underlay groups beneath main, selected overlay, and CLI as before.
Do not mount the final assembly or prepend main's destination to upper tiers.
3. Test disjoint same-destination peers, different destinations, equal and unequal
conflicts, ancestor/descendant destinations, and a strict conflict despite an
upper override. Verify recursive mapping overlays, atomic sequences, null,
explicit false/zero, and version-only domain overrides retaining lower fields.
4. Exercise mounted main with a nested VersionRule, absent root declarations,
imported/underlay direct and ancestor claims, and nonclaiming mapping ancestors.
Check the hook runs once and does not inspect each fragment independently.
Preserve exact configured-path integer conversion and ordinary nested typing.
Validate: destination/composition/overlay/version/grouped tests and everyday
check. Done: placed and authored equivalents agree on values and conflicts;
main ownership and lifecycle timing are preserved.
## Stage 6 — Reconcile diagnostic coordinate systems
Status: planned. Scope: useful paths without fictional source locations.
1. Audit early acquisition, import, fragment-version, and composition failures.
Use one narrow path-prefix translation where diagnostics genuinely identify
application content; do not blanket-prefix every diagnostic or double-prefix
diagnostics already produced by placed trees.
2. Preserve syntax/read/directive locations and related import sites. Value
conflicts and later typed/resolver/validation failures use effective paths
but original content coordinates. Invalid introduced structure identifies
the destination declaration rather than a nonexistent source wrapper line.
3. Add focused assertions for a mounted conflict with two real sources, a mounted
decode or validation error, an imported version error, invalid destination,
and malformed source-local directives. Test stable codes/paths/locations and
nil results, not whole messages or callback choreography.
Validate: destination/diagnostic/import/version/decode tests and everyday check.
Done: diagnostics consistently distinguish application paths from source syntax.
## Stage 7 — Complete shared placement accounting
Status: planned. Scope: resource bounds with no new public limit system.
1. Wire Stage 2's prechecks and synthetic-node charges into both tracked ordinary
acquisition and import traversal, using `importAccounting` and current limit
structures. Charge each source body once, including empty import bodies.
2. Preserve existing raw byte/source/node accounting and imported directive
inspection. Additional wrappers count in per-body depth/node checks and
shared aggregate nodes; recomposition must not charge them again. Ensure
selected overlays and CLI start from totals including placement charges.
3. Test below/at/above relevant bounds using small private limit fixtures where
existing seams permit; add a narrow private seam if necessary. Cover deep
body plus destination, many shallow mounted inputs, imported wrappers, and
empty mappings. Verify root placement and NoSource do not add charges.
Preserve history/expansion limits; do not turn fixture values into defaults.
Validate: destination/limit/import/override tests and everyday check. Done:
placement cannot bypass current budgets and introduces no unrelated caps.
## Stage 8 — Preserve provenance, history, and source resource contexts
Status: planned. Scope: engine inspection and retained evidence.
1. Audit `compositionOrigins`, `completeCompositionOrigins`, application replay,
metadata construction/finalization, and source-record cloning against placed
applications. Prefer feeding placed trees into existing processing rather
than adding another path-offset index or rewriting final metadata afterward.
2. Verify explicit destination containers, actual leaves, composite origins,
superseded/removed descendants, and root/source fallbacks. Historical and
effective indexes keep independently owned paths and existing shape semantics.
3. Preserve source-ID resource contexts through imports, surviving lower-tier
values, and inheritance. Test completed-result and resolution-time context
queries against real relative files, without making ConfigKit open the
referenced application resource or retain loading handles.
4. Exercise the same physical source in separate tiers with different placement,
inspect each source record, and verify independent destination paths. Repeat
inspection after comparison and independent loads to detect retained mutation.
Validate: destination/metadata/provenance/history/resource tests,
`go test -race ./internal/engine`, and everyday check. Done: inspection explains
effective placement and physical origin without consumer reconstruction.
## Stage 9 — Verify final inheritance, sensitivity, and lifecycle integration
Status: planned. Scope: complete private engine behavior before facade exposure.
1. Resolve sibling inheritance only after full source precedence using the
existing expansion path. Cover a child and parent supplied by separate peers,
nested destination-local siblings, cancellation, and an inherited relative
resource retaining its declaring context. Cross-collection search is unchanged.
2. Reuse existing inheritance-version, overlay, and CLI guards; add targeted
mounted cases for actual protected paths and their ancestors. Do not reject
unrelated inheritance merely because it shares a mapping ancestor.
3. Test effective-path secret predicates, field tags and recorded dependencies,
superseded classified values, safe effective output/history/explanation, and
bilateral diff redaction using disposable canaries. Compare mounted versus
explicitly wrapped loads for equal effective values and no differences.
4. Verify defaults, decoding, resolution, secret lookup, and validation execute
once on the final shape; failures return no usable result. Reuse existing
ordinary-load lifetime/ownership coverage and add mounted protection where
placement creates a distinct risk.
Validate: destination/inheritance/secret/sensitivity/explain/diff/lifecycle tests
and everyday check. Done: all approved engine semantics work together without
per-fragment loading or a post-return processing pass.
## Stage 10 — Expose the root-owned facade and public acceptance tests
Status: planned. Scope: the two public fields and complete consumer behavior.
1. Add `Input.Destination Path` and `SourceInfo.Destination Path` to root types.
Extend `toEngineInput`, `fromEngineSourceInfo`, `toEngineSourceInfo`, and all
source publication paths with owned conversion. Run existing public/import
boundary tests; expose no engine nodes, aliases, or bridge methods.
2. Document zero/root behavior, mapping-only paths, main/underlay parity,
inherited import placement, source-coordinate fallback, and source ownership
beside declarations. Update the configuration contract for implemented input
destinations, preserving canonical ownership of shared semantics.
3. Add external-package public-load coverage for equivalent host file, native
fs.FS file, byte, reader, and Mapping sources. Cover default/root destinations,
literal escaped/numeric keys, empty mapping, NoSource, and invalid declarations.
Include one public mounted main case, not just underlays.
4. Add representative public tests tying final configuration to metadata,
source placement, history, resources, callbacks, and safe diffs. Mutate returned
record copies and repeat queries/loads to confirm detachment. Reuse engine
matrices; public tests demonstrate supported integration rather than every
private branch. Confirm failed loads expose detached source destinations.
Validate: `go test . -run 'Destination|Facade|PublicFacadeBoundary|DependencyImportBoundary'`,
`go test -race ./...`, and everyday check. Done: the requested workflow is usable
entirely through ConfigKit's supported import path and documented surface.
## Stage 11 — Add maintained simple and collection-style consumer examples
Status: planned. Scope: executable adoption examples with consumer-owned types.
1. Add a minimal mounted-fragment example using `Input.Destination` in an
ordinary layered load. Show literal keys and root-default compatibility
without introducing consumer parsing or generic merge helpers.
2. Add one representative collection assembly under `examples/consumers`: peer
prompt/profile fragments, collection metadata with ordinary `version`, a
source-root import, local sibling inheritance, and an intentional higher-tier
version-only override retaining the body. Keep the application VersionRule
main-owned. Use a controlled native filesystem or temporary files.
3. Demonstrate effective-path inspection plus source-relative resource lookup
during resolution and from the result, including an inherited/surviving field.
Application code owns opening/interpreting the resource and any qualified IDs.
4. Add tests for final values, presence, actual source placement/coordinates,
resource context, and expected conflict behavior. Link maintained examples
from the consumer guide; add a small executable root example if useful for
discovery without duplicating the complete scenario.
Validate: `go test ./examples/consumers`, applicable `Example` tests, local links,
and everyday check. Done: simple and representative consumers substantiate the
same configuration model and need no compensating structural pass.
## Stage 12 — Reconcile documentation and audit the complete implementation
Status: planned. Scope: final integration audit and evidence, not release work.
1. Update README navigation, consumer guidance, the component overview, and
development reading/test maps. Add ADR 0011 to navigation. Update architecture
policy only as needed to recognize placement within existing assembly and
source ownership, linking precise behavior to the configuration contract.
Do not change unrelated policies, deferred boundaries, or published notes.
2. Audit every resolved decision, roadmap behavior, and request acceptance row
against implementation and tests. Record the corresponding test names here;
for reused coverage explain the behavior it proves. Do not mark complete
based on green tests that do not exercise mounting.
3. Review the complete baseline-to-candidate diff for accidental strengthening:
extra naming rules, namespace uniqueness, main-only restrictions, changed
merge/version semantics, invented source-read ordering guarantees, new limits,
or expanded secret policies. Remove temporary helpers/unsupported branches
left by staged integration. Confirm original source coordinates remain real.
4. Run `./scripts/check.sh` and
`GOTOOLCHAIN=local GOPROXY=off go test -race ./...`. Review source-kind and
placement ownership coverage, error disclosure, complete-on-return handling,
and root/private boundaries. Record commit, Go version, results, and any
limitations. No release command, CI waiting, benchmark, or downstream upgrade.
5. Mark this plan and the roadmap implemented only when code, public examples,
and owning contracts agree. Retain completed planning evidence until separately
authorized release preparation.
Done: one complete layered load supports input destinations with coherent values,
diagnostics, inspection, limits, and source ownership across the approved surface.
## Implementation owner map
Use graph tools to locate these symbols rather than assume line numbers:
- Public input forwarding: `layered.go`, `records.go`, `conversion.go`
(source conversions), and existing facade tests.
- Private request and lifecycle: `internal/engine/layered.go`, `load_request.go`
(`loadInput`, `acquireUnderlayGroup`, `assembleLayeredInput`), `load.go`
(`executeLoadRequest`), and `load_input.go` (`assembleBaseInputTracked`).
- Directives and imports: `directives.go` (`splitImportDirectives`,
`fragmentVersionClaim`), and `imports.go` (`visitPath`).
- Shared document semantics: `compose.go`, `document_walk.go`
(`countInputNodes`), `path.go`, `parse.go`, and `value_operation.go` limits.
- Inspection: `metadata.go`, `history.go`, `explain.go`, `resource_context.go`,
`inheritance_origin.go`, `render.go`, and `diff.go`; source copies live in
engine `records.go`. Confirm current filenames through the component overview.
## Open Questions
None required for implementation. The four policy choices are approved; this
plan selects API spelling, validation timing/error category, coordinate handling,
and accounting within them. Surface a newly discovered incompatibility or scope
change with evidence rather than resolving it by silently narrowing the feature
or strengthening an unrelated contract.

View File

@@ -1,10 +1,10 @@
# Configuration Input Destinations Roadmap # Configuration Input Destinations Roadmap
Status: design decisions approved; implementation has not started. Execute the Status: design decisions approved; implementation has not started. This roadmap
stages below in order. This roadmap responds to the responds to the [PromptKit feature request](promptkit-feature-request.md) and owns
[PromptKit feature request](promptkit-feature-request.md) and owns the target the target state and selected design decisions. The
state, delivery sequence, and selected design decisions. Public API spelling and [implementation plan](implementation.md) owns sequencing, implementation details,
diagnostic details remain to be finalized during implementation planning. and completion evidence.
## Targeted end state ## Targeted end state
@@ -20,9 +20,9 @@ For example, a fragment defining `summarize.alerts` can contribute beneath
local key can contribute beneath another collection without conflict. Collection local key can contribute beneath another collection without conflict. Collection
and prompt names remain ordinary application keys, not ConfigKit namespaces. and prompt names remain ordinary application keys, not ConfigKit namespaces.
The intended public surface is per-input placement, illustratively an optional The public surface adds an optional `Destination Path` field on `Input`, not a
`Destination Path` field on `Input`, not a new loading entry point or an operation new loading entry point or an operation on completed results. Content-bearing
on completed results. Content-bearing main inputs and underlay inputs support main inputs and underlay inputs support
the same destination model; root placement remains the default. the same destination model; root placement remains the default.
The following behavior defines the target: The following behavior defines the target:
@@ -120,119 +120,6 @@ or a second configuration model. The
consistency constraints; [deferred scope](deferred.md) continues to govern the consistency constraints; [deferred scope](deferred.md) continues to govern the
unrequested features above. unrequested features above.
## Ordered implementation plan
### 1. Settle placement semantics and the public contract
Finalize public API spelling using the Selected Design Decisions below for
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 approved 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.
## Selected Design Decisions ## Selected Design Decisions
### Main-input destinations and absent inputs ### Main-input destinations and absent inputs
@@ -246,8 +133,8 @@ across its roles and preserves absence without per-fragment version checks.
### Public placement inspection and synthetic ancestors ### Public placement inspection and synthetic ancestors
Expose effective placement on existing source records, Expose effective placement as `SourceInfo.Destination Path` on existing source
illustratively `SourceInfo.Destination`, including inherited import placement. records, including inherited import placement.
Use original source identity for introduced ancestor mappings, documenting that Use original source identity for introduced ancestor mappings, documenting that
any root-location fallback identifies the contributing document rather than a any root-location fallback identifies the contributing document rather than a
physically authored wrapper. Actual content keeps its real coordinates. This physically authored wrapper. Actual content keeps its real coordinates. This

View File

@@ -0,0 +1,319 @@
# PromptKit Feature Request: Configuration Input Destinations
Status: consumer feature request; not accepted or implemented.
## Requested Capability
Provide a public API through which a consumer can select a destination mapping
path for each configuration input. The input's application content contributes
beneath that path while retaining ConfigKit's normal composition, precedence,
inheritance, validation, diagnostics, and inspection behavior.
PromptKit needs this capability for its proposal 3, **Collections With Mounted
Configuration Fragments**, recorded in PromptKit's
`docs/roadmap/configuration-collections-mounted-fragments.md`. This request
describes the public API needs and observable acceptance criteria. It does not
prescribe ConfigKit's internal design or an implementation plan.
The reviewed public baseline is ConfigKit v0.7.1. The current
[layered-loading contract](../config.md#layered-loading) lets consumers choose
sources, import policy, filesystem context, and precedence groups, but does not
provide an input destination. Existing behavior remains authoritative until an
extension is adopted.
## Consumer Problem
PromptKit applications assemble independently maintained collections of prompts,
inference profiles, and backends. Collections can contain identical local names,
such as `summarize.alerts`, without identifying the same resource. PromptKit wants
an effective configuration organized by collection, while letting authors omit
repeated collection and resource-container wrappers from individual files.
A file selected from a collection's `prompts` directory would contain:
```yaml
summarize.alerts:
version: "1"
default_profile: provider/brief
messages:
- role: user
content: Summarize the weather alerts.
output:
format: text
validation_mode: none
```
Given destination `[collections, weatherreporter, prompts]`, its contribution
would appear in the final application configuration as:
```yaml
collections:
weatherreporter:
prompts:
summarize.alerts:
version: "1"
default_profile: provider/brief
messages:
- role: user
content: Summarize the weather alerts.
output:
format: text
validation_mode: none
```
Another collection can contribute `summarize.alerts` beneath
`[collections, homewatch, prompts]` without conflicting. After final handoff,
PromptKit interprets these as `weatherreporter/summarize.alerts` and
`homewatch/summarize.alerts`. The profile reference is illustrative and must be
supplied separately by the application configuration.
PromptKit will support one installed definition per prompt. Its optional flat
`version` is ordinary domain metadata, not a merge identity. This request does
not need identified-record sequences, sequence-root documents, or a custom
merge policy.
Authors could write the complete wrappers today, but that repeats assembly
details in every fragment. Consumer-side parsing and wrapping would put generic
source processing back into PromptKit and complicate source attribution.
PromptKit instead wants to select sources and destinations, then receive one
final configuration through ConfigKit's supported loading lifecycle.
## Minimum Public API
### Per-Input Destination
Allow a destination to be attached to each `Input` passed to `LoadLayered`.
One possible public spelling is an optional `Destination Path` field on `Input`.
The name and exact signature are suggestions; equivalent public API designs
are welcome.
Illustrative consumer code using the proposed field, which does not exist in
v0.7.1:
```go
promptInput := configkit.Input{
Source: configkit.File("/usr/local/share/collections/weatherreporter/prompts/alerts.yml"),
Destination: configkit.Keys("collections", "weatherreporter", "prompts"),
}
peers := configkit.Underlay{
Inputs: []configkit.Input{promptInput},
}
```
The consumer would include `peers` in its existing `LayeredOptions` and invoke
`LoadLayered` with its normal root main input and application `Definition`.
This is a partial API sketch, not an executable example or a proposed new
loading entry point. A single public layered load is sufficient for PromptKit;
it does not require a separate mounting operation on completed results.
The API must let the consumer:
- Express a destination relative to the application configuration root using
literal mapping-key segments. Dots, slashes, and numeric-looking text inside
a key are not implicit path separators or array indexes. PromptKit's namespace
grammar remains its own domain rule.
- Omit the destination to retain existing root placement. An explicitly empty
destination should have the same meaning. Mapping destinations are sufficient;
sequence-index destinations are not needed by this request.
- Assign different destinations to inputs in the same strict composition group,
and assign the same destination to inputs in separate precedence groups.
- Use the destination with existing content-bearing source kinds: host files,
native filesystem files, bytes, readers, and mapping sources, subject to their
existing acquisition and ownership contracts.
- Retain the existing import opt-in and filesystem context independently of the
destination. A configuration destination is not a filesystem root or base
directory.
PromptKit can continue to enumerate an explicitly selected directory and attach
the same destination to its returned inputs. ConfigKit does not need a new
directory scanner, namespace registry, filename convention, or PromptKit schema.
The namespace comes from the application's assembly declaration, not from an
implicit interpretation of a directory basename.
### Representative Assembly
These are application choices, not ConfigKit-specific reserved paths:
| Selected inputs | Destination key segments |
| --- | --- |
| Application main document | Root |
| `weatherreporter/collection.yml` | `collections`, `weatherreporter` |
| YAML files under `weatherreporter/prompts` | `collections`, `weatherreporter`, `prompts` |
| YAML files under `homewatch/prompts` | `collections`, `homewatch`, `prompts` |
| YAML files under `provider/profiles` | `collections`, `provider`, `profiles` |
| YAML files under `provider/backends` | `collections`, `provider`, `backends` |
Peer collection contributions belong to a single strict underlay group when the
application wants conflict detection among them. Intentional operator overrides
use later groups or main. Destination selection must not establish precedence
or make source traversal order authorize replacement.
## Required Observable Behavior
### Composition And Precedence
Mounted and explicitly wrapped contributions must have equivalent application
values and conflict behavior at their effective destinations. Actual source
locations naturally differ and must remain accurate.
Retain the [strict composition](../config.md#strict-document-composition) and
[layering](../config.md#layered-loading) rules:
- Disjoint mapping entries at the same destination combine.
- Overlapping non-mapping values within a strict group conflict, including equal
scalars, nulls, and sequences. Different destinations do not collide merely
because the source documents share a local key.
- Later precedence layers recursively override mappings and replace other
values. A strict conflict cannot be repaired by an upper layer.
- Empty mappings do not clear lower definitions; ordinary arrays remain atomic.
In particular, `messages` does not acquire sequence-merging behavior.
- Overlapping destination paths and contributions at their ancestors follow
those same rules. No namespace-level uniqueness restriction is requested.
Changing only a prompt's `version` in a later layer changes that field while
retaining omitted lower-layer fields. ConfigKit must not treat a domain version
label as a request to replace an entire definition.
### Imports And Existing Structural Directives
An input with imports enabled must be able to use its existing source-root
`configkit.imports` directive while contributing application data at a nested
destination. The directive remains structural configuration, not an application
field below that destination.
For the minimum API, imports inherit their importing input's destination,
including transitive imports. Import paths continue to resolve relative to
their declaring source's filesystem context. A mounted document must not send
its imported application fields back to the configuration root accidentally.
Existing opt-in, containment, missing-input, cycle, and repeated-source behavior
must remain applicable. A different destination is not a request to bypass the
current rejection of a repeated physical source within a strict group.
Sibling `extends` must resolve against mappings at their effective destinations.
For example, two independently supplied profile fragments mounted beneath the
same collection's `profiles` mapping can establish `extends: brief` once they
compose. Inheritance remains ConfigKit-owned; PromptKit does not perform another
relationship-resolution pass.
### Protected Specification Paths
The distinction between source-local field names and effective application
paths is essential. PromptKit's main document will declare `version: 1` at the
configuration root. A separate collection metadata file can contain:
```yaml
version: "2.4.0"
```
Mounted at `[collections, weatherreporter]`, that field is an ordinary collection
release label. It must not be rejected as an attempt to contribute the protected
root specification field. The same applies to a prompt's nested optional
version. Only the main-owned specification declaration uses the application's
configured [VersionRule](../config.md#application-schema-versions).
Conversely, placement must not permit an underlay or imported fragment to claim
or replace the actual protected specification path or an ancestor that would
replace it. The public contract must define protection in terms of effective
application paths, rather than reserving the spelling `version` in every source.
Protection must remain valid with imports, inheritance, overlays, and explicit
CLI overrides.
PromptKit needs one main-owned specification check with the existing lifecycle.
It does not need main mounted away from the root, integer-to-string conversion
for every nested `version`, or separate compatibility checks for every fragment.
### Typed Loading, Diagnostics, And Inspection
The destination must be visible consistently through the ordinary public
loading and inspection surface:
- Final typed configuration and application callbacks use effective destination
paths. Defaults, resolution, and validation retain the existing single-load
lifecycle; fragments are not independently validated as whole applications.
- Metadata and presence queries distinguish omission, explicit null, and other
explicit values at the mounted paths.
- Diagnostics identify the effective conflicting or invalid path and the real
contributing source locations. Consumers can determine which source and
placement produced a value without recreating that relationship themselves.
- Effective rendering, explanations, history, and configuration comparison agree
with the final configuration. Existing sensitivity and disclosure rules apply
to mounted fields, including application path-based secret classification.
- Unsupported destination forms and destination-related loading failures use
the normal structured error boundary. Exact new diagnostic codes, if needed,
are for ConfigKit to define.
PromptKit does not require a separate mount registry or new inspection facade.
Existing inspection APIs are sufficient if they expose accurate effective paths,
source attribution, and placement information. ConfigKit should document how
source-local locations relate to effective paths.
### Source-Relative Resources And Lifecycle
For a mounted field such as `content_file: ./summary.md`, obtaining its effective
origin and calling `Result.SourceContext` or `Resolution.SourceContext` must
still identify the declaring source's filesystem and base directory. The same
holds when that field survives an override or is inherited into another mapping.
Its configuration destination must not change the meaning of the relative file
reference.
Retain existing source ownership, load completion, result lifetime, containment,
and resource-access behavior. Destinations must participate in documented load
limits without creating a way to bypass them; this request does not prescribe
new limit values or their accounting machinery.
## Optional Follow-Ups, Not Prerequisites
**Import destinations in YAML.** A destination on an import directive could let
publishers describe assembly in configuration rather than Go. Proposal 3
recommends destinations relative to the importing document's destination, with
omission retaining that destination. This is useful, but per-input placement
through the public API is sufficient for initial PromptKit adoption. No new
directive spelling is selected by this request.
**Independent specification headers.** A collection entry document might later
declare its own supported specification version. That needs a distinct contract
for checking the header and determining its role in the composed configuration.
Mounting collection release metadata does not supply this capability. Keep it a
separate request if PromptKit decides it is necessary.
**Cross-collection inheritance.** Nested collection profiles are not immediate
siblings across collections. Current sibling inheritance remains sufficient for
the initial proposal, with authored definitions flattened where necessary.
Generic cross-path inheritance could be considered separately if consumer needs
justify revisiting the [deferred scope](deferred.md). Mounting must not silently
reinterpret `extends: provider/brief` as a cross-collection reference.
PromptKit also does not request collection installation, automatic directory
discovery, remote fetching, namespace ownership enforcement, dependency solving,
live reload, filename-derived identity, or arbitrary document transformations.
Its qualified resource IDs, optional prompt version semantics, collection
inventory, and execution APIs remain PromptKit responsibilities.
## Public Acceptance Scenarios
Evaluate the extension through supported public loading and inspection APIs:
| Scenario | Expected consumer-visible result |
| --- | --- |
| Destination omitted or empty | Existing root-placement behavior |
| Same fragment shape mounted under two distinct collection keys from independent sources | Two separate effective resources |
| Disjoint inputs at the same destination in one group | Mapping contributions combine |
| Equal or unequal overlapping scalar fields in one group | Structured composition conflict with both source locations |
| Same destination across deliberate precedence tiers | Existing recursive overlay behavior |
| Version-only prompt override | Label changes; omitted prompt body fields survive |
| Nested mounted import without a destination directive | Imported fields contribute at the inherited destination |
| Local profile inheritance across mounted peer fragments | Parent resolves at the effective sibling scope |
| Collection metadata containing `version` mounted below root | Ordinary nested field, independent of root VersionRule |
| Mounted/imported contribution actually targeting the protected version path | Rejected under version ownership rules |
| Mounted field using source-relative content, including inheritance | Original declaring resource context remains available |
| Mounted explicit null and omitted field | Distinct effective presence information |
| Mounted validation error or secret field | Effective paths, physical locations, and existing disclosure rules agree |
| File, native filesystem, byte, reader, or mapping input with equivalent content | Equivalent application values, subject to existing source contracts |
| Unsupported destination or failed load | Normal structured failure, no usable partial result |
The minimum usable outcome is a consumer that selects collection sources and
destinations, performs one ordinary layered load, and receives final typed
configuration with reliable diagnostics and inspection. It should need no
consumer-side source rewriting, generic merging, or provenance reconstruction.