Clarify resolution metadata and sensitivity names

This commit is contained in:
2026-10-27 15:00:04 +00:00
parent 556b10b2cd
commit 7ee9e92ef5
18 changed files with 47 additions and 46 deletions

View File

@@ -1,6 +1,6 @@
# Pre-v1 Public API Cleanup Implementation Plan # 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`. Baseline: `07e4ae5d9236935389e81b207d4a8f00543fafce`.
Audience: a Luna coding agent executing the stages sequentially. 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 ## 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 1. In [lifecycle.go](../../lifecycle.go), rename root `Resolution.InputInfo` to
`EntryInfo` and `Definition.Secret` to `Sensitive`, without old aliases. `EntryInfo` and `Definition.Secret` to `Sensitive`, without old aliases.

View File

@@ -167,7 +167,7 @@ func LoadNotariusPaths(path, invocationDir string, overrides ...configkit.Overri
resolveNotariusReference(configuration, resolution, invocationDir) resolveNotariusReference(configuration, resolution, invocationDir)
return nil 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) { if configuration.Reference == "" || filepath.IsAbs(configuration.Reference) {
return return
} }
info, found := resolution.InputInfo(path) info, found := resolution.EntryInfo(path)
if !found { if !found {
resolution.Warning(path, "relative reference has no entry provenance") resolution.Warning(path, "relative reference has no entry provenance")
return return

View File

@@ -86,7 +86,7 @@ func resolveCollectionResources(configuration *CollectionBundleConfig, resolutio
continue continue
} }
contentPath := configkit.Keys("collections", collectionID, "prompts", promptID, "content_file") 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 { if !found || len(info.Origin.Locations) != 1 {
return fmt.Errorf("prompt resource has no single declaring source") return fmt.Errorf("prompt resource has no single declaring source")
} }

View File

@@ -47,7 +47,7 @@ func LoadVersionedService(mode configkit.SourceVersionMode, overrides ...configk
return nil return nil
}, },
}, },
Secret: func(path configkit.Path) bool { Sensitive: func(path configkit.Path) bool {
return path.String() == "/service/version" return path.String() == "/service/version"
}, },
} }

View File

@@ -203,7 +203,7 @@ func TestFacadeLoadBridgesLifecycleCallbacks(t *testing.T) {
if !resolution.IsExplicit(configkit.Keys("input")) { if !resolution.IsExplicit(configkit.Keys("input")) {
resolution.Error(configkit.Keys("input"), "input is required") 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 { if !ok || !input.Explicit {
resolution.Error(configkit.Keys("input"), "missing input provenance") resolution.Error(configkit.Keys("input"), "missing input provenance")
} }

View File

@@ -24,7 +24,7 @@ func TestFacadeInactiveContextsPreserveClosedFailure(t *testing.T) {
call func() call func()
}{ }{
{"IsExplicit", func() { state.resolution.IsExplicit(path) }}, {"IsExplicit", func() { state.resolution.IsExplicit(path) }},
{"InputInfo", func() { state.resolution.InputInfo(path) }}, {"EntryInfo", func() { state.resolution.EntryInfo(path) }},
{"Source", func() { state.resolution.Source("source") }}, {"Source", func() { state.resolution.Source("source") }},
{"SourceContext", func() { state.resolution.SourceContext("source") }}, {"SourceContext", func() { state.resolution.SourceContext("source") }},
{"Record", func() { state.resolution.Record(path, configkit.ChangeNormalization, "reason") }}, {"Record", func() { state.resolution.Record(path, configkit.ChangeNormalization, "reason") }},

View File

@@ -352,7 +352,7 @@ func TestFacadeMountedMainAndUnderlayInspectionResourcesAndDiff(t *testing.T) {
Resolve: func(configuration *map[string]any, resolution *configkit.Resolution) error { Resolve: func(configuration *map[string]any, resolution *configkit.Resolution) error {
callbacks.resolve++ callbacks.resolve++
resourcePath := configkit.Keys("catalog", "entries", "item", "resource") resourcePath := configkit.Keys("catalog", "entries", "item", "resource")
input, ok := resolution.InputInfo(resourcePath) input, ok := resolution.EntryInfo(resourcePath)
if !ok || len(input.Origin.Locations) != 1 { if !ok || len(input.Origin.Locations) != 1 {
return fmt.Errorf("mounted resource input has no single source origin") 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) resolution.Record(configkit.Keys("catalog", "entries", "item", "resource_text"), configkit.ChangeNormalization, "read source-relative summary", resourcePath)
return nil return nil
}, },
Validate: func(*map[string]any, *configkit.Validation) { callbacks.validate++ }, Validate: func(*map[string]any, *configkit.Validation) { callbacks.validate++ },
Secret: func(candidate configkit.Path) bool { return candidate.String() == tokenPath.String() }, Sensitive: func(candidate configkit.Path) bool { return candidate.String() == tokenPath.String() },
}, },
}) })
if err != nil { if err != nil {

View File

@@ -29,7 +29,7 @@ func TestFacadeDiffKeepsPublicSequenceChangesWithHistoricalDescendants(t *testin
t.Helper() t.Helper()
result, err := configkit.Load(configkit.Bytes("historical-sequence.yml", []byte("items: [{secret: disposable-canary}, "+public+"]\n")), result, err := configkit.Load(configkit.Bytes("historical-sequence.yml", []byte("items: [{secret: disposable-canary}, "+public+"]\n")),
configkit.Options[configuration]{Definition: configkit.Definition[configuration]{ 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 { Resolve: func(current *configuration, resolution *configkit.Resolution) error {
current.Items[0] = "public scalar" current.Items[0] = "public scalar"
resolution.Record(configkit.Keys("items"), configkit.ChangeNormalization, "normalize first item") 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"` Dynamic map[string]any `yaml:"dynamic"`
} }
options := configkit.Options[configuration]{Definition: configkit.Definition[configuration]{ 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 { Resolve: func(current *configuration, resolution *configkit.Resolution) error {
delete(current.Dynamic, "entry") delete(current.Dynamic, "entry")
resolution.Record(configkit.Keys("dynamic", "entry"), configkit.ChangeNormalization, "remove 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) { func TestFacadeDiffCombinesSecretSensitivityAndLoadErrors(t *testing.T) {
t.Setenv("FACADE_DIFF_SECRET", "credential-one") 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" return path.String() == "/secret"
}}} }}}
left, err := configkit.Load[facadeDiffSecretConfig](configkit.Bytes("left.yml", []byte("secret: FACADE_DIFF_SECRET\n")), secretOptions) left, err := configkit.Load[facadeDiffSecretConfig](configkit.Bytes("left.yml", []byte("secret: FACADE_DIFF_SECRET\n")), secretOptions)

View File

@@ -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]{ 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"})}}}}, 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" { if err != nil || result.Config.Secret != "new-secret-canary" {
t.Fatalf("secret load = %#v, %v", result, err) t.Fatalf("secret load = %#v, %v", result, err)

View File

@@ -42,7 +42,7 @@ entries:
Definition: Definition[map[string]any]{ Definition: Definition[map[string]any]{
Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }}, Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }},
SequenceIdentities: []SequenceIdentity{SequenceIdentityAt(Keys("services", "records"), "id")}, SequenceIdentities: []SequenceIdentity{SequenceIdentityAt(Keys("services", "records"), "id")},
Secret: func(candidate Path) bool { Sensitive: func(candidate Path) bool {
return candidate.String() == "/services/records/0/version" return candidate.String() == "/services/records/0/version"
}, },
}, },
@@ -107,7 +107,7 @@ entries:
Definition: Definition[map[string]any]{ Definition: Definition[map[string]any]{
Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }}, Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }},
SequenceIdentities: []SequenceIdentity{SequenceIdentityAt(Keys("services", "records"), "version")}, SequenceIdentities: []SequenceIdentity{SequenceIdentityAt(Keys("services", "records"), "version")},
Secret: func(candidate Path) bool { Sensitive: func(candidate Path) bool {
return candidate.String() == "/services/records/0/version" return candidate.String() == "/services/records/0/version"
}, },
}, },

View File

@@ -42,7 +42,7 @@ func TestFacadeInheritancePreservesPresenceOriginsAndHistory(t *testing.T) {
if !resolution.IsExplicit(configkit.Keys("entries", "child", "enabled")) { if !resolution.IsExplicit(configkit.Keys("entries", "child", "enabled")) {
t.Error("inherited false is not explicit") 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 { if !found || len(info.Origin.Inheritance) != 1 {
t.Errorf("resolver inherited origin = %#v, %v", info, found) 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]{ result, err := configkit.Load(filesystem.File("config/identified.yml"), configkit.Options[configuration]{
Definition: configkit.Definition[configuration]{ Definition: configkit.Definition[configuration]{
SequenceIdentities: registrations, 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" 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]{ _, err := configkit.Load(configkit.Bytes("identified-failure.yml", input), configkit.Options[configuration]{
Definition: configkit.Definition[configuration]{ Definition: configkit.Definition[configuration]{
SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("jobs"), "id")}, 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) { if err == nil || strings.Contains(err.Error(), canary) {
@@ -468,7 +468,7 @@ func TestFacadeInheritedSecretsStayRedactedAfterLookup(t *testing.T) {
Entries map[string]entry `yaml:"entries"` 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]{ 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 { if err != nil || result.Config.Entries["child"].Credential.Value() != canary {
t.Fatalf("inherited credential = %#v, %v", result, err) t.Fatalf("inherited credential = %#v, %v", result, err)

View File

@@ -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")) configkit.Default(resolution, configkit.Keys("derived"), &config.Derived, config.Settings["native"], "derive from native", configkit.Keys("settings", "native"))
return nil return nil
}, },
Validate: func(config *layeredFacadeConfig, validation *configkit.Validation) { checks.validation++ }, Validate: func(config *layeredFacadeConfig, validation *configkit.Validation) { checks.validation++ },
Secret: func(path configkit.Path) bool { return path.String() == "/settings/secret" }, Sensitive: func(path configkit.Path) bool { return path.String() == "/settings/secret" },
}, },
}) })
if err != nil { if err != nil {

View File

@@ -31,7 +31,7 @@ func TestFacadeResolutionPresenceUsesDynamicEntryShape(t *testing.T) {
Definition: configkit.Definition[configuration]{ Definition: configkit.Definition[configuration]{
Resolve: func(_ *configuration, resolution *configkit.Resolution) error { Resolve: func(_ *configuration, resolution *configkit.Resolution) error {
check := func(path configkit.Path, want bool) error { check := func(path configkit.Path, want bool) error {
info, found := resolution.InputInfo(path) info, found := resolution.EntryInfo(path)
explicit := resolution.IsExplicit(path) explicit := resolution.IsExplicit(path)
if found != want || explicit != want || (found && info.Explicit != explicit) { if found != want || explicit != want || (found && info.Explicit != explicit) {
return errors.New("resolution presence disagrees with dynamic entry shape") return errors.New("resolution presence disagrees with dynamic entry shape")

View File

@@ -598,7 +598,7 @@ entries:
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.File(underlayPath), Imports: true}}}}, Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.File(underlayPath), Imports: true}}}},
Definition: configkit.Definition[configuration]{ Definition: configkit.Definition[configuration]{
SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("entries"), "id")}, 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" return path.String() == "/entries/0/token" || path.String() == "/entries/1/token"
}, },
Resolve: func(value *configuration, resolution *configkit.Resolution) error { Resolve: func(value *configuration, resolution *configkit.Resolution) error {
@@ -611,7 +611,7 @@ entries:
if resolution.IsExplicit(configkit.Keys("entries").Index(0).Key("omitted")) { if resolution.IsExplicit(configkit.Keys("entries").Index(0).Key("omitted")) {
t.Error("resolver treated an omitted field as explicit") 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 { if !found || info.Origin.Layer != configkit.LayerBase {
t.Errorf("resolver current-record input info = %#v, %v", info, found) t.Errorf("resolver current-record input info = %#v, %v", info, found)
} }

View File

@@ -25,7 +25,7 @@ func TestFacadeSourceVersionEvidenceFollowsAssembledSequenceRanges(t *testing.T)
definition := configkit.Definition[map[string]any]{ definition := configkit.Definition[map[string]any]{
SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("records"), "id")}, 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 }}, 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" first := "records: [{id: first, version: first-version}]\n"
child := "records: [{id: child, version: PRIVATE-VERSION-CANARY}]\n" child := "records: [{id: child, version: PRIVATE-VERSION-CANARY}]\n"
@@ -544,8 +544,8 @@ func TestFacadeSourceVersionEvidenceIsSanitizedAndDetached(t *testing.T) {
var callbackSource configkit.SourceInfo var callbackSource configkit.SourceInfo
var callbackContext configkit.ResourceContext var callbackContext configkit.ResourceContext
definition := configkit.Definition[map[string]any]{ definition := configkit.Definition[map[string]any]{
Version: &configkit.VersionRule{Path: configkit.Keys("version"), Check: func(configkit.Version, *configkit.Validation) error { return nil }}, 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" }, Sensitive: func(candidate configkit.Path) bool { return candidate.String() == "/service/version" },
Resolve: func(_ *map[string]any, resolution *configkit.Resolution) error { Resolve: func(_ *map[string]any, resolution *configkit.Resolution) error {
var found bool var found bool
callbackSource, found = resolution.Source("source-1") callbackSource, found = resolution.Source("source-1")

View File

@@ -31,7 +31,7 @@ func TestUnderlaySequenceDescendantOrigins(t *testing.T) {
Definition: configkit.Definition[map[string]any]{Resolve: func(_ *map[string]any, resolution *configkit.Resolution) error { Definition: configkit.Definition[map[string]any]{Resolve: func(_ *map[string]any, resolution *configkit.Resolution) error {
for _, pointer := range paths { for _, pointer := range paths {
path, _ := configkit.ParsePath(pointer) path, _ := configkit.ParsePath(pointer)
info, ok := resolution.InputInfo(path) info, ok := resolution.EntryInfo(path)
if !ok { if !ok {
t.Errorf("missing resolver input %s", pointer) t.Errorf("missing resolver input %s", pointer)
} }

View File

@@ -7,14 +7,14 @@ import (
) )
// Definition contains reusable application rules for a configuration type. // 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. // Definitions should be treated as read-only after being shared.
type Definition[T any] struct { type Definition[T any] struct {
Defaults func() T Defaults func() T
Resolve func(*T, *Resolution) error Resolve func(*T, *Resolution) error
Validate func(*T, *Validation) Validate func(*T, *Validation)
Version *VersionRule Version *VersionRule
Secret func(Path) bool Sensitive func(Path) bool
// SequenceIdentities declares the scalar fields used to combine records and // SequenceIdentities declares the scalar fields used to combine records and
// resolve record inheritance at selected sequence destinations. Registration // resolve record inheritance at selected sequence destinations. Registration
// errors are reported before source acquisition. Before successful return, // 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)) return r.engineContext().IsExplicit(toEnginePath(path))
} }
// InputInfo returns detached entry-state metadata for path. The entry state is // EntryInfo returns detached entry-state metadata for path. The entry state is
// the decoded configuration, including retained static defaults, immediately // the decoded configuration, including retained static defaults and CLI
// before resolution starts. It is fixed while resolution records mutate the // assignments, immediately before resolution starts. It is fixed while
// working configuration; unknown or shape-mismatched paths return false. // 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. // 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)) value, ok := r.engineContext().InputInfo(toEnginePath(path))
return fromEngineFieldInfo(value), ok return fromEngineFieldInfo(value), ok
} }
@@ -368,8 +369,8 @@ func toEngineDefinition[T any](definition Definition[T]) engine.Definition[T] {
} }
} }
} }
if definition.Secret != nil { if definition.Sensitive != nil {
result.Secret = func(path engine.Path) bool { return definition.Secret(fromEnginePath(path)) } result.Secret = func(path engine.Path) bool { return definition.Sensitive(fromEnginePath(path)) }
} }
return result return result
} }

View File

@@ -89,7 +89,7 @@ func BenchmarkStartupConstruction(b *testing.B) {
options := configkit.LayeredOptions[startupCatalog]{Definition: configkit.Definition[startupCatalog]{ options := configkit.LayeredOptions[startupCatalog]{Definition: configkit.Definition[startupCatalog]{
// Include a simple path-based classifier, as catalog consumers use // Include a simple path-based classifier, as catalog consumers use
// this callback even when the representative input has no credentials. // 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") return strings.HasSuffix(path.String(), "/extra_params/token")
}, },
Resolve: func(catalog *startupCatalog, resolution *configkit.Resolution) error { 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", "timeout_seconds", "service_tier", "reasoning_effort", "api_key_env",
"api_key_required", "extra_params", "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} fields[field] = startupPresence{explicit: exists && info.Explicit, null: exists && info.Null}
} }
captured[id] = fields captured[id] = fields