Complete input destination implementation audit

This commit is contained in:
2026-10-14 14:06:38 +00:00
parent 2d8df22dd1
commit 945e111c4e
9 changed files with 107 additions and 20 deletions

View File

@@ -68,12 +68,14 @@ The [architecture goals](docs/policy/architecture.md#success-criteria) explain
these priorities and how genuine new needs can justify extending the shared these priorities and how genuine new needs can justify extending the shared
model. The [deferred scope](docs/roadmap/deferred.md) records deliberate exclusions. model. The [deferred scope](docs/roadmap/deferred.md) records deliberate exclusions.
The [configuration contract](docs/config.md) describes implemented behavior. The The [configuration contract](docs/config.md) describes implemented behavior,
[consumer adoption guide](docs/consumers/README.md) provides maintained simple including [input destinations](docs/config.md#input-destinations). The
native-filesystem, layered, and sibling-inheritance examples alongside the migration shapes. [consumer adoption guide](docs/consumers/README.md) provides maintained simple,
See the [development guide](docs/development.md) for repository orientation and the layered, and mounted-collection examples alongside the migration shapes. The
[release procedure](docs/release.md) for maintainer-only candidate and publication accepted placement decision is recorded in [ADR 0011](docs/adr/0011-input-destinations.md).
steps. See the [development guide](docs/development.md) for repository orientation and
the [release procedure](docs/release.md) for maintainer-only candidate and
publication steps.
ConfigKit code is licensed under the [LGPL v3.0 or later](COPYRIGHT). ConfigKit code is licensed under the [LGPL v3.0 or later](COPYRIGHT).

View File

@@ -111,6 +111,11 @@ lifetime and precedence rules.
## Input destinations and mounted collections ## Input destinations and mounted collections
Main and underlay inputs use the same destination field; imported application
contributions inherit the declaring input's destination. A destination is an
application mapping path, independent of filesystem context. The configuration
contract owns the complete behavior and validation rules.
The small [`Example_inputDestination`](../../examples/consumers/destinations_test.go) The small [`Example_inputDestination`](../../examples/consumers/destinations_test.go)
mounts one mapping source beneath literal collection/prompt keys and loads a mounts one mapping source beneath literal collection/prompt keys and loads a
second input without a destination to show that root placement remains the second input without a destination to show that root placement remains the

View File

@@ -39,6 +39,7 @@ public behavior.
| Task | Read | | Task | Read |
| --- | --- | | --- | --- |
| Repository orientation or component responsibility | [Component overview](internal/overview.md) | | Repository orientation or component responsibility | [Component overview](internal/overview.md) |
| Input destinations | [Configuration contract](config.md#input-destinations), [ADR 0011](adr/0011-input-destinations.md), and the destination tests below |
| Documentation changes | The owning document | | Documentation changes | The owning document |
| Licensing, dependencies, attribution, or release-document changes | [Project grant](../COPYRIGHT) and [attribution](oss/attribution.md); release changes also follow the release procedure | | Licensing, dependencies, attribution, or release-document changes | [Project grant](../COPYRIGHT) and [attribution](oss/attribution.md); release changes also follow the release procedure |
| Architecture decisions or future work | [Deferred scope](roadmap/deferred.md) and relevant ADRs listed below | | Architecture decisions or future work | [Deferred scope](roadmap/deferred.md) and relevant ADRs listed below |
@@ -55,6 +56,7 @@ Accepted decisions:
- [Explicit overrides and safe explanation](adr/0005-explicit-overrides-and-safe-explanation.md) - [Explicit overrides and safe explanation](adr/0005-explicit-overrides-and-safe-explanation.md)
- [Keep the public facade root-owned](adr/0006-public-facade.md) - [Keep the public facade root-owned](adr/0006-public-facade.md)
- [Compare frozen effective configuration snapshots](adr/0010-effective-configuration-diff.md) - [Compare frozen effective configuration snapshots](adr/0010-effective-configuration-diff.md)
- [Place inputs at literal application mapping paths](adr/0011-input-destinations.md)
For cross-cutting changes, follow every applicable row. Completed implementation For cross-cutting changes, follow every applicable row. Completed implementation
and remediation plans are preserved in Git history at `cb5b435`; use current and remediation plans are preserved in Git history at `cb5b435`; use current
@@ -164,6 +166,7 @@ code changes. Use the component overview to find the corresponding implementatio
| Composition, selected overlays, CLI precedence | [Composition](../internal/engine/compose_test.go), [overlay](../internal/engine/overlay_test.go), [selected overlays](../internal/engine/selected_overlay_test.go), [overrides](../internal/engine/override_test.go) | `go test ./internal/engine -run 'Compose\|Overlay\|Override'` | | Composition, selected overlays, CLI precedence | [Composition](../internal/engine/compose_test.go), [overlay](../internal/engine/overlay_test.go), [selected overlays](../internal/engine/selected_overlay_test.go), [overrides](../internal/engine/override_test.go) | `go test ./internal/engine -run 'Compose\|Overlay\|Override'` |
| Sibling inheritance, provenance, history, sensitivity | [Expansion](../internal/engine/inheritance_test.go), [destination scopes](../internal/engine/inheritance_walk_test.go), [origins](../internal/engine/inheritance_origin_test.go), [history](../internal/engine/inheritance_history_test.go), [secrets](../internal/engine/inheritance_secret_test.go), [public loading](../facade_inheritance_test.go) | `go test ./internal/engine -run 'Inheritance\|Inherited'` and `go test . -run '^TestFacadeInheritance\|^TestFacadeInheritedSecrets'` | | Sibling inheritance, provenance, history, sensitivity | [Expansion](../internal/engine/inheritance_test.go), [destination scopes](../internal/engine/inheritance_walk_test.go), [origins](../internal/engine/inheritance_origin_test.go), [history](../internal/engine/inheritance_history_test.go), [secrets](../internal/engine/inheritance_secret_test.go), [public loading](../facade_inheritance_test.go) | `go test ./internal/engine -run 'Inheritance\|Inherited'` and `go test . -run '^TestFacadeInheritance\|^TestFacadeInheritedSecrets'` |
| Layered loading and source equivalence | [Grouped lifecycle](../internal/engine/load_request_test.go), [layered facade](../facade_layered_test.go), [equivalence](../facade_equivalence_test.go) | `go test ./internal/engine -run '^TestGrouped'`, `go test . -run '^TestFacadeLayered'`, and `go test . -run '^TestEquivalentSourceForms'` | | Layered loading and source equivalence | [Grouped lifecycle](../internal/engine/load_request_test.go), [layered facade](../facade_layered_test.go), [equivalence](../facade_equivalence_test.go) | `go test ./internal/engine -run '^TestGrouped'`, `go test . -run '^TestFacadeLayered'`, and `go test . -run '^TestEquivalentSourceForms'` |
| Input destinations, mounted sources, and source ownership | [Destination engine tests](../internal/engine/destination_test.go), [tier and import tests](../internal/engine/destination_tiers_test.go), [integration and provenance tests](../internal/engine/destination_integration_test.go), [public facade tests](../facade_destination_test.go), and [consumer examples](../examples/consumers/destinations_test.go) | `go test ./internal/engine -run 'Destination|Mounted'`, `go test . -run 'Destination|FacadeMounted'`, and `go test ./examples/consumers -run 'CollectionBundle|CollectionPeer'` |
| Schema, defaults, projection, cloning | [Schema](../internal/engine/schema_test.go), [decoding](../internal/engine/decode_test.go), [projection](../internal/engine/projection_test.go) | `go test ./internal/engine -run 'Schema\|Decode\|Projection\|Clone\|Inline\|CompareDocument'` | | Schema, defaults, projection, cloning | [Schema](../internal/engine/schema_test.go), [decoding](../internal/engine/decode_test.go), [projection](../internal/engine/projection_test.go) | `go test ./internal/engine -run 'Schema\|Decode\|Projection\|Clone\|Inline\|CompareDocument'` |
| Lifecycle, resolution, validation, callback ownership | [Loading](../internal/engine/load_test.go), [resolution](../internal/engine/resolution_test.go), [validation](../internal/engine/validation_test.go), [ownership](../internal/engine/callback_ownership_test.go), [independence](../internal/engine/independence_test.go) | `go test ./internal/engine -run 'Load\|Resolution\|Validation\|Validator\|Hook\|Callback\|Parallel'` | | Lifecycle, resolution, validation, callback ownership | [Loading](../internal/engine/load_test.go), [resolution](../internal/engine/resolution_test.go), [validation](../internal/engine/validation_test.go), [ownership](../internal/engine/callback_ownership_test.go), [independence](../internal/engine/independence_test.go) | `go test ./internal/engine -run 'Load\|Resolution\|Validation\|Validator\|Hook\|Callback\|Parallel'` |
| Paths, metadata, provenance | [Paths](../internal/engine/path_test.go), [metadata](../internal/engine/metadata_test.go), [provenance](../internal/engine/provenance_test.go) | `go test ./internal/engine -run 'Path\|Metadata\|Provenance\|Queries'` | | Paths, metadata, provenance | [Paths](../internal/engine/path_test.go), [metadata](../internal/engine/metadata_test.go), [provenance](../internal/engine/provenance_test.go) | `go test ./internal/engine -run 'Path\|Metadata\|Provenance\|Queries'` |

View File

@@ -8,8 +8,10 @@ private state; public signatures and fields do not expose engine types.
Implemented component owners: Implemented component owners:
- `internal/engine` owns host and native-filesystem source acquisition, - `internal/engine` owns host and native-filesystem source acquisition,
grouped strict composition, recursive precedence assembly, and destination-local grouped strict composition, recursive precedence assembly, per-input mapping
sibling inheritance before typed decoding. It also owns schema placement within that assembly, and destination-local sibling inheritance
before typed decoding. It retains source placement alongside physical source
identity and owns schema
inspection, lifecycle coordination, callback auditing, inspection, lifecycle coordination, callback auditing,
provenance/history, passive source-context retention, secret resolution and provenance/history, passive source-context retention, secret resolution and
bootstrap, safe inspection rendering, and effective-value comparison over bootstrap, safe inspection rendering, and effective-value comparison over
@@ -41,11 +43,13 @@ these constraints and rejects exported aliases, fields, or signatures involving
internal types. internal types.
The [configuration contract](../config.md) owns implemented YAML and lifecycle The [configuration contract](../config.md) owns implemented YAML and lifecycle
semantics, including [effective configuration diffs](../config.md#effective-configuration-diffs). semantics, including [input destinations](../config.md#input-destinations) and
[effective configuration diffs](../config.md#effective-configuration-diffs).
The [consumer adoption guide](../consumers/README.md) owns application guidance. The [consumer adoption guide](../consumers/README.md) owns application guidance.
The [architecture policy](../policy/architecture.md) owns contributor The [architecture policy](../policy/architecture.md) owns contributor
boundaries; [ADR 0006](../adr/0006-public-facade.md) records the facade decision boundaries; [ADR 0006](../adr/0006-public-facade.md) records the facade decision
and [ADR 0010](../adr/0010-effective-configuration-diff.md) records the diff and [ADR 0010](../adr/0010-effective-configuration-diff.md) records the diff
decision; [ADR 0011](../adr/0011-input-destinations.md) records the placement
decision. See the [development guide](../development.md) for validation commands decision. See the [development guide](../development.md) for validation commands
and [release procedure](../release.md) for source-candidate gates. [ADR and [release procedure](../release.md) for source-candidate gates. [ADR
0009](../adr/0009-sibling-inheritance.md) records why inheritance is a built-in 0009](../adr/0009-sibling-inheritance.md) records why inheritance is a built-in

View File

@@ -99,6 +99,13 @@ types, field meanings, defaults, normalization, semantic validation, and schema
version policy. Applications also own main-file search conventions, CLI wiring, version policy. Applications also own main-file search conventions, CLI wiring,
remote acquisition, runtime initialization, and resource checks. remote acquisition, runtime initialization, and resource checks.
The engine applies each input's mapping destination within the existing source
assembly and retains that placement with source identity and inspection records;
the root facade owns the public fields and conversions. The
[configuration contract](../config.md#input-destinations) defines the observable
placement behavior, and [ADR 0011](../adr/0011-input-destinations.md) records the
accepted decision.
Library code must not depend on consuming applications or initialize their Library code must not depend on consuming applications or initialize their
runtime resources. It must not perform implicit network access, discover runtime resources. It must not perform implicit network access, discover
application-specific paths, or change process-global state during ordinary `Load`. application-specific paths, or change process-global state during ordinary `Load`.

View File

@@ -1,8 +1,7 @@
# Implement configuration input destinations # Implement configuration input destinations
Status: planned. Execute Stage 1 through Stage 12 in order, one focused stage Status: complete. Stages 1–12 are implemented and audited. The
per Luna implementation prompt. The [feature roadmap](input-destinations.md) [feature roadmap](input-destinations.md) owns the approved behavior and rationale; the
owns the approved behavior and rationale; the
[consumer request](promptkit-feature-request.md#public-acceptance-scenarios) [consumer request](promptkit-feature-request.md#public-acceptance-scenarios)
supplies the public acceptance scenarios. This document owns execution and supplies the public acceptance scenarios. This document owns execution and
completion evidence. Planning baseline: `6353598`. completion evidence. Planning baseline: `6353598`.
@@ -504,7 +503,7 @@ structural pass.
## Stage 12 — Reconcile documentation and audit the complete implementation ## Stage 12 — Reconcile documentation and audit the complete implementation
Status: planned. Scope: final integration audit and evidence, not release work. Status: complete. Scope: final integration audit and evidence, not release work.
1. Update README navigation, consumer guidance, the component overview, and 1. Update README navigation, consumer guidance, the component overview, and
development reading/test maps. Add ADR 0011 to navigation. Update architecture development reading/test maps. Add ADR 0011 to navigation. Update architecture
@@ -529,6 +528,24 @@ Status: planned. Scope: final integration audit and evidence, not release work.
and owning contracts agree. Retain completed planning evidence until separately and owning contracts agree. Retain completed planning evidence until separately
authorized release preparation. authorized release preparation.
Audit evidence for the approved decisions and request acceptance surface:
| Behavior reviewed | Evidence and what it proves |
| --- | --- |
| Root-owned `Input.Destination` and `SourceInfo.Destination`; detached conversions and publication | `TestSourceInfoDestinationConversionsAndCopies`, `TestFacadeFailedLoadExposesDetachedSourceDestination`, and `TestFacadeMountedMainAndUnderlayInspectionResourcesAndDiff` verify root/engine conversion, error records, and returned record detachment. `TestPublicFacadeBoundary` and `TestDependencyImportBoundary` verify no engine types cross the facade. |
| Literal mapping keys, parsed numeric keys, reserved structural tokens, and invalid-path preflight | `TestNormalizeDestinationUsesLiteralMappingKeys`, `TestNormalizeDestinationRejectsIndicesAndReservedKeys`, and `TestLayeredLoadPreflightsDestinationsBeforeReadingSources` cover mapping-only normalization and rejection before reader consumption; `TestFacadeDestinationRootEmptyMappingNoSourceAndInvalidPaths` verifies the public error boundary. |
| Placement of acquired bodies, empty mappings versus absence, imports, all source kinds, physical identity, and authored coordinates | `TestFacadeInputDestinationsAcrossSourceKinds` covers host/native/bytes/reader/mapping sources; `TestFacadeSameFragmentMountsAtSeparateCollectionDestinations` proves independent public collections can retain the same local key. `TestLayeredImportOnlyInputMaterializesDestination`, `TestLayeredHostImportsInheritInputDestinationTransitively`, `TestLayeredNativeFilesystemImportsInheritInputDestination`, and `TestLayeredDestinationsDoNotChangeRepeatedSourceIdentity` cover inherited import placement and unchanged identity. `TestPlaceDocumentWrapsEmptyMappingAndPreservesSourceCoordinates`, `TestPlaceDocumentPreservesAuthoredEntryCoordinates`, and `TestLayeredDestinationDiagnosticsUseEffectivePathsAndAuthoredLocations` distinguish fallback wrapper locations from authored coordinates. |
| Strict composition, explicit-wrapper equivalence, precedence, empty values, and domain `version` overrides | `TestLayeredDestinationStrictCompositionBoundaries`, `TestLayeredDestinationMatchesExplicitlyWrappedInput`, `TestEmptyMountedMappingRetainsLowerChildren`, and `TestLayeredDestinationOverlayRetainsOrdinaryValueSemantics` prove existing group and overlay semantics are retained; the latter includes ordinary field-only updates rather than whole-record replacement. |
| Main-owned version checks and imported, underlay, inheritance, overlay, CLI, and ancestor protection | `TestLayeredDestinationChecksVersionOnceOnComposedMain`, `TestLayeredDestinationVersionClaimsUsePlacedApplicationPaths`, `TestLayeredImportedVersionClaimsUseEffectiveDestination`, and `TestLayeredMountedVersionProtectionCoversInheritanceAndUpperTiers` cover effective paths, one main check, protected-path claims/ancestors, and nonclaiming mapping ancestors. `TestLoadVersionDestinationTypesKeepOrdinaryDecoding` preserves exact configured-path conversion and ordinary nested decoding. |
| Final-shape sibling inheritance, no cross-collection lookup, one lifecycle, and failure without a usable partial result | `TestLayeredDestinationInheritanceUsesFinalSiblingScopes`, `TestLayeredDestinationInheritanceDoesNotSearchOtherCollections`, `TestLayeredDestinationLifecycleAndSensitiveInspectionUseFinalShape`, and `TestLayeredDestinationDecodeFailureReturnsNoResultOrLaterCallbacks` exercise these behaviors. Reused inheritance, lifecycle, and callback tests cover generic behavior that destination placement does not alter. |
| Effective paths, presence, provenance/history, safe rendering, sensitivity, bilateral diff, and source-relative resources | `TestLayeredDestinationsRetainOriginsHistoryAndDetachedRecords`, `TestLayeredDestinationLifecycleAndSensitiveInspectionUseFinalShape`, `TestLayeredDestinationDiffUsesBilateralSensitivity`, `TestLayeredImportedInheritedValuesRetainResourceContexts`, and `TestFacadeMountedMainAndUnderlayInspectionResourcesAndDiff` connect mounted values to inspection. The imported-inheritance test specifically proves the inherited value keeps the declaring source's passive resource context. |
| Shared depth/node limits and unchanged root/absence accounting | `TestPlaceDocumentRootAndBounds`, `TestDestinationPlacementCountsAgainstSharedNodeBudget`, `TestMultipleMountedInputsSharePlacementCharges`, `TestImportedEmptyBodiesChargeEachDestinationWrapper`, `TestSelectedOverlayAndCLIUsePlacementNodeCharges`, and `TestNoSourceAndRootPlacementAddNoWrapperCharges` cover per-document and aggregate limits without adding a new limit class. |
| Maintained consumer usage and strict peer behavior | `Example_inputDestination`, `TestCollectionBundleAssemblesMountedPeersAndResources`, and `TestCollectionPeerPromptConflictsRemainStrict` verify the simple and representative collection examples against actual values, resources, source records, and conflict behavior. |
The wider baseline-to-candidate review covered all 38 changed files (`6353598..2d8df22`): production changes are limited to the root facade fields/conversions, private input/import assembly and accounting, source record ownership, and the inheritance version-claim correction described above. The contract and tests do not impose identifier grammar, namespace uniqueness, main-only placement, source-read ordering, new budgets, nested version conversion, or expanded secret policy. The implementation wraps only acquired application bodies after processing source-root directives; placement does not change physical source identity, authored node locations, filesystem resolution, or later-tier semantics. Stage evidence above reuses existing general composition, resource, inheritance, version, lifecycle, sensitivity, and boundary tests where those behaviors are unchanged; mounted-specific cases are named separately.
Completion record: audited source candidate `2d8df22dd1cb9fde8e60853da3e2260731acf2bb`. On `go1.26.8 linux/amd64`, `./scripts/check.sh` passed, including formatting, whitespace, documentation links, tests, vet, and build. `GOTOOLCHAIN=local GOPROXY=off go test -race ./...` also passed with dependency downloads disabled. No remaining implementation limitations were found; release preparation, CI, benchmark campaigns, and downstream upgrades remain separately scoped.
Done: one complete layered load supports input destinations with coherent values, Done: one complete layered load supports input destinations with coherent values,
diagnostics, inspection, limits, and source ownership across the approved surface. diagnostics, inspection, limits, and source ownership across the approved surface.

View File

@@ -1,10 +1,10 @@
# Configuration Input Destinations Roadmap # Configuration Input Destinations Roadmap
Status: design decisions approved; implementation has not started. This roadmap Status: implemented. This roadmap records the approved behavior and rationale
responds to the [PromptKit feature request](promptkit-feature-request.md) and owns for the [PromptKit feature request](promptkit-feature-request.md). The
the target state and selected design decisions. The [implementation plan](implementation.md) records the completed sequence and
[implementation plan](implementation.md) owns sequencing, implementation details, audit evidence; the [configuration contract](../config.md#input-destinations)
and completion evidence. owns current behavior.
## Targeted end state ## Targeted end state
@@ -88,9 +88,9 @@ namespace ownership enforcement, dependency solving, live reload, filename-based
identity, arbitrary transformations, or new merge strategies. PromptKit owns its identity, arbitrary transformations, or new merge strategies. PromptKit owns its
collection inventory, qualified IDs, and execution semantics. collection inventory, qualified IDs, and execution semantics.
## Current baseline ## Implementation baseline (v0.7.1)
The request reviewed v0.7.1. The current worktree has these relevant boundaries: 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, - [Input](../../layered.go) holds a source, import opt-in, and filesystem context,
but no destination. Root types delegate to the engine through the existing but no destination. Root types delegate to the engine through the existing

View File

@@ -153,6 +153,36 @@ func TestFacadeImportedSourcesRetainInputDestination(t *testing.T) {
} }
} }
func TestFacadeSameFragmentMountsAtSeparateCollectionDestinations(t *testing.T) {
weatherDestination := configkit.Keys("collections", "weather", "prompts")
homeDestination := configkit.Keys("collections", "homewatch", "prompts")
result, err := configkit.LoadLayered(
configkit.Input{Source: configkit.NoSource()},
configkit.LayeredOptions[map[string]any]{Underlays: []configkit.Underlay{{Inputs: []configkit.Input{
{Source: configkit.Bytes("weather", []byte("summarize.alerts: {title: weather}\n")), Destination: weatherDestination},
{Source: configkit.Bytes("homewatch", []byte("summarize.alerts: {title: home}\n")), Destination: homeDestination},
}}}},
)
if err != nil {
t.Fatalf("LoadLayered: %v", err)
}
for _, test := range []struct {
path []string
want string
}{
{path: []string{"collections", "weather", "prompts", "summarize.alerts", "title"}, want: "weather"},
{path: []string{"collections", "homewatch", "prompts", "summarize.alerts", "title"}, want: "home"},
} {
if got := facadeDestinationValue(t, result.Config, test.path...); got != test.want {
t.Errorf("value at %v = %#v, want %q", test.path, got, test.want)
}
}
sources := result.Sources()
if len(sources) != 2 || sources[0].Destination.String() != weatherDestination.String() || sources[1].Destination.String() != homeDestination.String() {
t.Fatalf("independent collection source placements = %#v", sources)
}
}
type destinationCountingReader struct{ reads int } type destinationCountingReader struct{ reads int }
func (reader *destinationCountingReader) Read([]byte) (int, error) { func (reader *destinationCountingReader) Read([]byte) (int, error) {

View File

@@ -131,6 +131,25 @@ func TestLayeredDestinationMatchesExplicitlyWrappedInput(t *testing.T) {
} }
} }
func TestEmptyMountedMappingRetainsLowerChildren(t *testing.T) {
t.Parallel()
destination := Keys("collections", "weather")
loaded, err := LoadLayered(
Input{Source: Bytes("empty-main", []byte("{}\n")), Destination: destination},
testSecretBinding(),
LayeredOptions[map[string]any]{Underlays: []Underlay{{Inputs: []Input{{
Source: Bytes("lower", []byte("prompt: {title: retained}\n")), Destination: destination,
}}}}},
)
if err != nil {
t.Fatalf("LoadLayered: %v", err)
}
if got := layeredMapValue(t, testLoaded(loaded).Config, "collections", "weather", "prompt", "title"); got != "retained" {
t.Fatalf("lower child beneath empty mounted mapping = %#v, want retained", got)
}
}
func TestLayeredDestinationOverlayRetainsOrdinaryValueSemantics(t *testing.T) { func TestLayeredDestinationOverlayRetainsOrdinaryValueSemantics(t *testing.T) {
t.Parallel() t.Parallel()