From 7ee9e92ef5d7ec3f7988df1a613ec8599de3fc60 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Tue, 27 Oct 2026 15:00:04 +0000 Subject: [PATCH] Clarify resolution metadata and sensitivity names --- docs/roadmap/implementation.md | 4 ++-- examples/consumers/consumers.go | 4 ++-- examples/consumers/destinations.go | 2 +- examples/consumers/source_version.go | 2 +- facade_candidate_test.go | 2 +- facade_context_test.go | 2 +- facade_destination_test.go | 6 +++--- facade_diff_test.go | 6 +++--- facade_equivalence_test.go | 2 +- facade_import_lifecycle_test.go | 4 ++-- facade_inheritance_test.go | 8 ++++---- facade_layered_test.go | 4 ++-- facade_resolution_presence_test.go | 2 +- facade_sequence_test.go | 4 ++-- facade_source_version_mode_test.go | 6 +++--- facade_underlay_origins_test.go | 2 +- lifecycle.go | 29 ++++++++++++++-------------- startup_bench_test.go | 4 ++-- 18 files changed, 47 insertions(+), 46 deletions(-) diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index e53d4ff..809061e 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # Pre-v1 Public API Cleanup Implementation Plan -Status: planned; Stages 1–11 are pending. +Status: in progress; Stage 1 is complete and Stages 2–11 are pending. Baseline: `07e4ae5d9236935389e81b207d4a8f00543fafce`. Audience: a Luna coding agent executing the stages sequentially. @@ -97,7 +97,7 @@ it is not a reason to leave obvious neighboring comments wrong. ## Stage 1 — Clarify Resolution Entry Metadata And Sensitivity -Status: pending. Implements D1 and D2. +Status: complete. Implements D1 and D2. 1. In [lifecycle.go](../../lifecycle.go), rename root `Resolution.InputInfo` to `EntryInfo` and `Definition.Secret` to `Sensitive`, without old aliases. diff --git a/examples/consumers/consumers.go b/examples/consumers/consumers.go index 6203db8..6d2fb9b 100644 --- a/examples/consumers/consumers.go +++ b/examples/consumers/consumers.go @@ -167,7 +167,7 @@ func LoadNotariusPaths(path, invocationDir string, overrides ...configkit.Overri resolveNotariusReference(configuration, resolution, invocationDir) return nil }, - Secret: func(path configkit.Path) bool { return path.String() == "/sensitive" }, + Sensitive: func(path configkit.Path) bool { return path.String() == "/sensitive" }, }, }) } @@ -177,7 +177,7 @@ func resolveNotariusReference(configuration *NotariusPathConfig, resolution *con if configuration.Reference == "" || filepath.IsAbs(configuration.Reference) { return } - info, found := resolution.InputInfo(path) + info, found := resolution.EntryInfo(path) if !found { resolution.Warning(path, "relative reference has no entry provenance") return diff --git a/examples/consumers/destinations.go b/examples/consumers/destinations.go index 6f3c184..27268dc 100644 --- a/examples/consumers/destinations.go +++ b/examples/consumers/destinations.go @@ -86,7 +86,7 @@ func resolveCollectionResources(configuration *CollectionBundleConfig, resolutio continue } contentPath := configkit.Keys("collections", collectionID, "prompts", promptID, "content_file") - info, found := resolution.InputInfo(contentPath) + info, found := resolution.EntryInfo(contentPath) if !found || len(info.Origin.Locations) != 1 { return fmt.Errorf("prompt resource has no single declaring source") } diff --git a/examples/consumers/source_version.go b/examples/consumers/source_version.go index 8058143..064d1f6 100644 --- a/examples/consumers/source_version.go +++ b/examples/consumers/source_version.go @@ -47,7 +47,7 @@ func LoadVersionedService(mode configkit.SourceVersionMode, overrides ...configk return nil }, }, - Secret: func(path configkit.Path) bool { + Sensitive: func(path configkit.Path) bool { return path.String() == "/service/version" }, } diff --git a/facade_candidate_test.go b/facade_candidate_test.go index 3bb66ba..0934816 100644 --- a/facade_candidate_test.go +++ b/facade_candidate_test.go @@ -203,7 +203,7 @@ func TestFacadeLoadBridgesLifecycleCallbacks(t *testing.T) { if !resolution.IsExplicit(configkit.Keys("input")) { resolution.Error(configkit.Keys("input"), "input is required") } - input, ok := resolution.InputInfo(configkit.Keys("input")) + input, ok := resolution.EntryInfo(configkit.Keys("input")) if !ok || !input.Explicit { resolution.Error(configkit.Keys("input"), "missing input provenance") } diff --git a/facade_context_test.go b/facade_context_test.go index 30b388a..a8cf968 100644 --- a/facade_context_test.go +++ b/facade_context_test.go @@ -24,7 +24,7 @@ func TestFacadeInactiveContextsPreserveClosedFailure(t *testing.T) { call func() }{ {"IsExplicit", func() { state.resolution.IsExplicit(path) }}, - {"InputInfo", func() { state.resolution.InputInfo(path) }}, + {"EntryInfo", func() { state.resolution.EntryInfo(path) }}, {"Source", func() { state.resolution.Source("source") }}, {"SourceContext", func() { state.resolution.SourceContext("source") }}, {"Record", func() { state.resolution.Record(path, configkit.ChangeNormalization, "reason") }}, diff --git a/facade_destination_test.go b/facade_destination_test.go index 6ddb75b..e16cbee 100644 --- a/facade_destination_test.go +++ b/facade_destination_test.go @@ -352,7 +352,7 @@ func TestFacadeMountedMainAndUnderlayInspectionResourcesAndDiff(t *testing.T) { Resolve: func(configuration *map[string]any, resolution *configkit.Resolution) error { callbacks.resolve++ resourcePath := configkit.Keys("catalog", "entries", "item", "resource") - input, ok := resolution.InputInfo(resourcePath) + input, ok := resolution.EntryInfo(resourcePath) if !ok || len(input.Origin.Locations) != 1 { return fmt.Errorf("mounted resource input has no single source origin") } @@ -373,8 +373,8 @@ func TestFacadeMountedMainAndUnderlayInspectionResourcesAndDiff(t *testing.T) { resolution.Record(configkit.Keys("catalog", "entries", "item", "resource_text"), configkit.ChangeNormalization, "read source-relative summary", resourcePath) return nil }, - Validate: func(*map[string]any, *configkit.Validation) { callbacks.validate++ }, - Secret: func(candidate configkit.Path) bool { return candidate.String() == tokenPath.String() }, + Validate: func(*map[string]any, *configkit.Validation) { callbacks.validate++ }, + Sensitive: func(candidate configkit.Path) bool { return candidate.String() == tokenPath.String() }, }, }) if err != nil { diff --git a/facade_diff_test.go b/facade_diff_test.go index a755fb7..60d2c24 100644 --- a/facade_diff_test.go +++ b/facade_diff_test.go @@ -29,7 +29,7 @@ func TestFacadeDiffKeepsPublicSequenceChangesWithHistoricalDescendants(t *testin t.Helper() result, err := configkit.Load(configkit.Bytes("historical-sequence.yml", []byte("items: [{secret: disposable-canary}, "+public+"]\n")), configkit.Options[configuration]{Definition: configkit.Definition[configuration]{ - Secret: func(path configkit.Path) bool { return path.String() == "/items/0/secret" }, + Sensitive: func(path configkit.Path) bool { return path.String() == "/items/0/secret" }, Resolve: func(current *configuration, resolution *configkit.Resolution) error { current.Items[0] = "public scalar" resolution.Record(configkit.Keys("items"), configkit.ChangeNormalization, "normalize first item") @@ -71,7 +71,7 @@ func TestFacadeDiffKeepsPublicScalarAcrossHistoricalAbsence(t *testing.T) { Dynamic map[string]any `yaml:"dynamic"` } options := configkit.Options[configuration]{Definition: configkit.Definition[configuration]{ - Secret: func(path configkit.Path) bool { return path.String() == "/dynamic/entry/token" }, + Sensitive: func(path configkit.Path) bool { return path.String() == "/dynamic/entry/token" }, Resolve: func(current *configuration, resolution *configkit.Resolution) error { delete(current.Dynamic, "entry") resolution.Record(configkit.Keys("dynamic", "entry"), configkit.ChangeNormalization, "remove entry") @@ -281,7 +281,7 @@ func TestFacadeDiffUsesFrozenFilesAndRunsCallbacksOnlyDuringLoad(t *testing.T) { func TestFacadeDiffCombinesSecretSensitivityAndLoadErrors(t *testing.T) { t.Setenv("FACADE_DIFF_SECRET", "credential-one") - secretOptions := configkit.Options[facadeDiffSecretConfig]{Definition: configkit.Definition[facadeDiffSecretConfig]{Secret: func(path configkit.Path) bool { + secretOptions := configkit.Options[facadeDiffSecretConfig]{Definition: configkit.Definition[facadeDiffSecretConfig]{Sensitive: func(path configkit.Path) bool { return path.String() == "/secret" }}} left, err := configkit.Load[facadeDiffSecretConfig](configkit.Bytes("left.yml", []byte("secret: FACADE_DIFF_SECRET\n")), secretOptions) diff --git a/facade_equivalence_test.go b/facade_equivalence_test.go index 1aea88e..1261dff 100644 --- a/facade_equivalence_test.go +++ b/facade_equivalence_test.go @@ -132,7 +132,7 @@ func TestReplacedSecretRemainsAbsentFromSafeOutput(t *testing.T) { } result, err := configkit.LoadLayered(configkit.Input{Source: configkit.Bytes("main", []byte("secret: new-secret-canary\n"))}, configkit.LayeredOptions[config]{ Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.Mapping("lower", map[string]any{"secret": "old-secret-canary"})}}}}, - Definition: configkit.Definition[config]{Secret: func(path configkit.Path) bool { return path.String() == "/secret" }}, + Definition: configkit.Definition[config]{Sensitive: func(path configkit.Path) bool { return path.String() == "/secret" }}, }) if err != nil || result.Config.Secret != "new-secret-canary" { t.Fatalf("secret load = %#v, %v", result, err) diff --git a/facade_import_lifecycle_test.go b/facade_import_lifecycle_test.go index 601dcf4..18038be 100644 --- a/facade_import_lifecycle_test.go +++ b/facade_import_lifecycle_test.go @@ -42,7 +42,7 @@ entries: Definition: Definition[map[string]any]{ Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }}, SequenceIdentities: []SequenceIdentity{SequenceIdentityAt(Keys("services", "records"), "id")}, - Secret: func(candidate Path) bool { + Sensitive: func(candidate Path) bool { return candidate.String() == "/services/records/0/version" }, }, @@ -107,7 +107,7 @@ entries: Definition: Definition[map[string]any]{ Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }}, SequenceIdentities: []SequenceIdentity{SequenceIdentityAt(Keys("services", "records"), "version")}, - Secret: func(candidate Path) bool { + Sensitive: func(candidate Path) bool { return candidate.String() == "/services/records/0/version" }, }, diff --git a/facade_inheritance_test.go b/facade_inheritance_test.go index 0972ea3..12c1d78 100644 --- a/facade_inheritance_test.go +++ b/facade_inheritance_test.go @@ -42,7 +42,7 @@ func TestFacadeInheritancePreservesPresenceOriginsAndHistory(t *testing.T) { if !resolution.IsExplicit(configkit.Keys("entries", "child", "enabled")) { t.Error("inherited false is not explicit") } - info, found := resolution.InputInfo(configkit.Keys("entries", "child", "enabled")) + info, found := resolution.EntryInfo(configkit.Keys("entries", "child", "enabled")) if !found || len(info.Origin.Inheritance) != 1 { t.Errorf("resolver inherited origin = %#v, %v", info, found) } @@ -139,7 +139,7 @@ func TestFacadeIdentifiedInheritanceResolvesChainsAndNestedCollections(t *testin result, err := configkit.Load(filesystem.File("config/identified.yml"), configkit.Options[configuration]{ Definition: configkit.Definition[configuration]{ SequenceIdentities: registrations, - Secret: func(path configkit.Path) bool { + Sensitive: func(path configkit.Path) bool { return path.String() == "/jobs/0/credential" || path.String() == "/jobs/1/credential" || path.String() == "/jobs/2/credential" }, }, @@ -221,7 +221,7 @@ func TestFacadeIdentifiedInheritanceFailureDoesNotRevealSecretCanary(t *testing. _, err := configkit.Load(configkit.Bytes("identified-failure.yml", input), configkit.Options[configuration]{ Definition: configkit.Definition[configuration]{ SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("jobs"), "id")}, - Secret: func(path configkit.Path) bool { return path.String() == "/jobs/0/credential" }, + Sensitive: func(path configkit.Path) bool { return path.String() == "/jobs/0/credential" }, }, }) if err == nil || strings.Contains(err.Error(), canary) { @@ -468,7 +468,7 @@ func TestFacadeInheritedSecretsStayRedactedAfterLookup(t *testing.T) { Entries map[string]entry `yaml:"entries"` } result, err := configkit.Load[configuration](configkit.Bytes("secret.yml", []byte("entries: {parent: {credential: CONFIGKIT_INHERITED_TEST_SECRET, plain: visible}, child: {extends: parent}}\n")), configkit.Options[configuration]{Definition: configkit.Definition[configuration]{ - Secret: func(path configkit.Path) bool { return path.String() == "/entries/parent/credential" }, + Sensitive: func(path configkit.Path) bool { return path.String() == "/entries/parent/credential" }, }}) if err != nil || result.Config.Entries["child"].Credential.Value() != canary { t.Fatalf("inherited credential = %#v, %v", result, err) diff --git a/facade_layered_test.go b/facade_layered_test.go index 7b15e94..b3b3bd8 100644 --- a/facade_layered_test.go +++ b/facade_layered_test.go @@ -59,8 +59,8 @@ func TestFacadeLayeredMixedSourcesAndInspection(t *testing.T) { configkit.Default(resolution, configkit.Keys("derived"), &config.Derived, config.Settings["native"], "derive from native", configkit.Keys("settings", "native")) return nil }, - Validate: func(config *layeredFacadeConfig, validation *configkit.Validation) { checks.validation++ }, - Secret: func(path configkit.Path) bool { return path.String() == "/settings/secret" }, + Validate: func(config *layeredFacadeConfig, validation *configkit.Validation) { checks.validation++ }, + Sensitive: func(path configkit.Path) bool { return path.String() == "/settings/secret" }, }, }) if err != nil { diff --git a/facade_resolution_presence_test.go b/facade_resolution_presence_test.go index 1063916..11ebee2 100644 --- a/facade_resolution_presence_test.go +++ b/facade_resolution_presence_test.go @@ -31,7 +31,7 @@ func TestFacadeResolutionPresenceUsesDynamicEntryShape(t *testing.T) { Definition: configkit.Definition[configuration]{ Resolve: func(_ *configuration, resolution *configkit.Resolution) error { check := func(path configkit.Path, want bool) error { - info, found := resolution.InputInfo(path) + info, found := resolution.EntryInfo(path) explicit := resolution.IsExplicit(path) if found != want || explicit != want || (found && info.Explicit != explicit) { return errors.New("resolution presence disagrees with dynamic entry shape") diff --git a/facade_sequence_test.go b/facade_sequence_test.go index ae44ade..6c9c214 100644 --- a/facade_sequence_test.go +++ b/facade_sequence_test.go @@ -598,7 +598,7 @@ entries: Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.File(underlayPath), Imports: true}}}}, Definition: configkit.Definition[configuration]{ SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("entries"), "id")}, - Secret: func(path configkit.Path) bool { + Sensitive: func(path configkit.Path) bool { return path.String() == "/entries/0/token" || path.String() == "/entries/1/token" }, Resolve: func(value *configuration, resolution *configkit.Resolution) error { @@ -611,7 +611,7 @@ entries: if resolution.IsExplicit(configkit.Keys("entries").Index(0).Key("omitted")) { t.Error("resolver treated an omitted field as explicit") } - info, found := resolution.InputInfo(configkit.Keys("entries").Index(0).Key("count")) + info, found := resolution.EntryInfo(configkit.Keys("entries").Index(0).Key("count")) if !found || info.Origin.Layer != configkit.LayerBase { t.Errorf("resolver current-record input info = %#v, %v", info, found) } diff --git a/facade_source_version_mode_test.go b/facade_source_version_mode_test.go index 83c0b55..30e1b39 100644 --- a/facade_source_version_mode_test.go +++ b/facade_source_version_mode_test.go @@ -25,7 +25,7 @@ func TestFacadeSourceVersionEvidenceFollowsAssembledSequenceRanges(t *testing.T) definition := configkit.Definition[map[string]any]{ SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("records"), "id")}, Version: &configkit.VersionRule{Path: configkit.Keys("records").Index(0).Key("version"), Check: func(configkit.Version, *configkit.Validation) error { return nil }}, - Secret: func(path configkit.Path) bool { return sensitivePath != "" && path.String() == sensitivePath }, + Sensitive: func(path configkit.Path) bool { return sensitivePath != "" && path.String() == sensitivePath }, } first := "records: [{id: first, version: first-version}]\n" child := "records: [{id: child, version: PRIVATE-VERSION-CANARY}]\n" @@ -544,8 +544,8 @@ func TestFacadeSourceVersionEvidenceIsSanitizedAndDetached(t *testing.T) { var callbackSource configkit.SourceInfo var callbackContext configkit.ResourceContext definition := configkit.Definition[map[string]any]{ - Version: &configkit.VersionRule{Path: configkit.Keys("version"), Check: func(configkit.Version, *configkit.Validation) error { return nil }}, - Secret: func(candidate configkit.Path) bool { return candidate.String() == "/service/version" }, + Version: &configkit.VersionRule{Path: configkit.Keys("version"), Check: func(configkit.Version, *configkit.Validation) error { return nil }}, + Sensitive: func(candidate configkit.Path) bool { return candidate.String() == "/service/version" }, Resolve: func(_ *map[string]any, resolution *configkit.Resolution) error { var found bool callbackSource, found = resolution.Source("source-1") diff --git a/facade_underlay_origins_test.go b/facade_underlay_origins_test.go index a311fee..e8903c6 100644 --- a/facade_underlay_origins_test.go +++ b/facade_underlay_origins_test.go @@ -31,7 +31,7 @@ func TestUnderlaySequenceDescendantOrigins(t *testing.T) { Definition: configkit.Definition[map[string]any]{Resolve: func(_ *map[string]any, resolution *configkit.Resolution) error { for _, pointer := range paths { path, _ := configkit.ParsePath(pointer) - info, ok := resolution.InputInfo(path) + info, ok := resolution.EntryInfo(path) if !ok { t.Errorf("missing resolver input %s", pointer) } diff --git a/lifecycle.go b/lifecycle.go index 10c9dc1..dc06c63 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -7,14 +7,14 @@ import ( ) // Definition contains reusable application rules for a configuration type. -// Secret is a pure predicate that can add sensitivity to semantic paths. +// Sensitive is a pure predicate that can add sensitivity to semantic paths. // Definitions should be treated as read-only after being shared. type Definition[T any] struct { - Defaults func() T - Resolve func(*T, *Resolution) error - Validate func(*T, *Validation) - Version *VersionRule - Secret func(Path) bool + Defaults func() T + Resolve func(*T, *Resolution) error + Validate func(*T, *Validation) + Version *VersionRule + Sensitive func(Path) bool // SequenceIdentities declares the scalar fields used to combine records and // resolve record inheritance at selected sequence destinations. Registration // errors are reported before source acquisition. Before successful return, @@ -234,14 +234,15 @@ func (r *Resolution) IsExplicit(path Path) bool { return r.engineContext().IsExplicit(toEnginePath(path)) } -// InputInfo returns detached entry-state metadata for path. The entry state is -// the decoded configuration, including retained static defaults, immediately -// before resolution starts. It is fixed while resolution records mutate the -// working configuration; unknown or shape-mismatched paths return false. +// EntryInfo returns detached entry-state metadata for path. The entry state is +// the decoded configuration, including retained static defaults and CLI +// assignments, immediately before resolution starts. It is fixed while +// resolution records mutate the working configuration; unknown or +// shape-mismatched paths return false. // -// InputInfo is valid only during the resolution callback. See the configuration +// EntryInfo is valid only during the resolution callback. See the configuration // contract for source and provenance semantics. -func (r *Resolution) InputInfo(path Path) (FieldInfo, bool) { +func (r *Resolution) EntryInfo(path Path) (FieldInfo, bool) { value, ok := r.engineContext().InputInfo(toEnginePath(path)) return fromEngineFieldInfo(value), ok } @@ -368,8 +369,8 @@ func toEngineDefinition[T any](definition Definition[T]) engine.Definition[T] { } } } - if definition.Secret != nil { - result.Secret = func(path engine.Path) bool { return definition.Secret(fromEnginePath(path)) } + if definition.Sensitive != nil { + result.Secret = func(path engine.Path) bool { return definition.Sensitive(fromEnginePath(path)) } } return result } diff --git a/startup_bench_test.go b/startup_bench_test.go index 1e92c25..2d960cc 100644 --- a/startup_bench_test.go +++ b/startup_bench_test.go @@ -89,7 +89,7 @@ func BenchmarkStartupConstruction(b *testing.B) { options := configkit.LayeredOptions[startupCatalog]{Definition: configkit.Definition[startupCatalog]{ // Include a simple path-based classifier, as catalog consumers use // this callback even when the representative input has no credentials. - Secret: func(path configkit.Path) bool { + Sensitive: func(path configkit.Path) bool { return strings.HasSuffix(path.String(), "/extra_params/token") }, Resolve: func(catalog *startupCatalog, resolution *configkit.Resolution) error { @@ -101,7 +101,7 @@ func BenchmarkStartupConstruction(b *testing.B) { "timeout_seconds", "service_tier", "reasoning_effort", "api_key_env", "api_key_required", "extra_params", } { - info, exists := resolution.InputInfo(configkit.Keys("profiles", id, field)) + info, exists := resolution.EntryInfo(configkit.Keys("profiles", id, field)) fields[field] = startupPresence{explicit: exists && info.Explicit, null: exists && info.Null} } captured[id] = fields