Replace generic resolution recording with intent methods
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Pre-v1 Public API Cleanup Implementation Plan
|
||||
|
||||
Status: in progress; Stages 1–5 are complete and Stages 6–11 are pending.
|
||||
Status: in progress; Stages 1–6 are complete and Stages 7–11 are pending.
|
||||
Baseline: `07e4ae5d9236935389e81b207d4a8f00543fafce`.
|
||||
Audience: a Luna coding agent executing the stages sequentially.
|
||||
|
||||
@@ -254,7 +254,7 @@ bootstrap behavior remain unchanged.
|
||||
|
||||
## Stage 6 — Replace Kind-Selecting Recording With Intent Methods
|
||||
|
||||
Status: pending. Implements D6 and N4.
|
||||
Status: complete. Implements D6 and N4.
|
||||
|
||||
1. Replace root and engine `Resolution.Record` with:
|
||||
|
||||
|
||||
@@ -104,9 +104,9 @@ func resolveCollectionResources(configuration *CollectionBundleConfig, resolutio
|
||||
}
|
||||
prompt.ResourceText = string(data)
|
||||
collection.Prompts[promptID] = prompt
|
||||
resolution.Record(
|
||||
resolution.RecordNormalization(
|
||||
configkit.Keys("collections", collectionID, "prompts", promptID, "resource_text"),
|
||||
configkit.ChangeNormalization, "read source-relative prompt content", contentPath,
|
||||
"read source-relative prompt content", contentPath,
|
||||
)
|
||||
}
|
||||
configuration.Collections[collectionID] = collection
|
||||
|
||||
@@ -101,8 +101,8 @@ func resolveRollout(configuration *RolloutConfig, resolution *configkit.Resoluti
|
||||
url := fmt.Sprintf("https://%s:%d", endpoint.Host, endpoint.Port)
|
||||
if endpoint.URL != url {
|
||||
endpoint.URL = url
|
||||
resolution.Record(configkit.Keys("service", "endpoint", "url"),
|
||||
configkit.ChangeNormalization, "derived endpoint URL",
|
||||
resolution.RecordNormalization(configkit.Keys("service", "endpoint", "url"),
|
||||
"derived endpoint URL",
|
||||
configkit.Keys("service", "endpoint", "host"),
|
||||
configkit.Keys("service", "endpoint", "port"))
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func LoadSequenceCatalog(mainPath, overlayName, runLabel string) (*configkit.Res
|
||||
},
|
||||
Resolve: func(configuration *SequenceCatalogConfig, resolution *configkit.Resolution) error {
|
||||
configuration.ResolvedLabel = "for:" + configuration.RunLabel
|
||||
resolution.Record(configkit.Keys("resolved_label"), configkit.ChangeNormalization,
|
||||
resolution.RecordNormalization(configkit.Keys("resolved_label"),
|
||||
"derive the resolved label from the selected run label", configkit.Keys("run_label"))
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -245,6 +245,8 @@ func TestFacadeLoadBridgesLifecycleCallbacks(t *testing.T) {
|
||||
}
|
||||
for _, context := range []func(){
|
||||
func() { retainedResolution.IsExplicit(configkit.Keys("input")) },
|
||||
func() { retainedResolution.RecordDerivedDefault(configkit.Keys("input"), "too late") },
|
||||
func() { retainedResolution.RecordNormalization(configkit.Keys("input"), "too late") },
|
||||
func() { retainedValidation.Warning(configkit.Keys("input"), "too late") },
|
||||
} {
|
||||
func() {
|
||||
|
||||
@@ -27,7 +27,8 @@ func TestFacadeInactiveContextsPreserveClosedFailure(t *testing.T) {
|
||||
{"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") }},
|
||||
{"RecordDerivedDefault", func() { state.resolution.RecordDerivedDefault(path, "reason") }},
|
||||
{"RecordNormalization", func() { state.resolution.RecordNormalization(path, "reason") }},
|
||||
{"Resolution.Error", func() { state.resolution.Error(path, "message") }},
|
||||
{"Resolution.Warning", func() { state.resolution.Warning(path, "message") }},
|
||||
{"Default", func() { configkit.Default(state.resolution, path, &target, 1, "reason") }},
|
||||
|
||||
@@ -370,7 +370,7 @@ func TestFacadeMountedMainAndUnderlayInspectionResourcesAndDiff(t *testing.T) {
|
||||
}
|
||||
entry := facadeDestinationMap(t, facadeDestinationMap(t, facadeDestinationMap(t, *configuration, "catalog"), "entries"), "item")
|
||||
entry["resource_text"] = string(data)
|
||||
resolution.Record(configkit.Keys("catalog", "entries", "item", "resource_text"), configkit.ChangeNormalization, "read source-relative summary", resourcePath)
|
||||
resolution.RecordNormalization(configkit.Keys("catalog", "entries", "item", "resource_text"), "read source-relative summary", resourcePath)
|
||||
return nil
|
||||
},
|
||||
Validate: func(*map[string]any, *configkit.Validation) { callbacks.validate++ },
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestFacadeDiffKeepsPublicSequenceChangesWithHistoricalDescendants(t *testin
|
||||
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")
|
||||
resolution.RecordNormalization(configkit.Keys("items"), "normalize first item")
|
||||
return nil
|
||||
},
|
||||
}})
|
||||
@@ -74,7 +74,7 @@ func TestFacadeDiffKeepsPublicScalarAcrossHistoricalAbsence(t *testing.T) {
|
||||
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")
|
||||
resolution.RecordNormalization(configkit.Keys("dynamic", "entry"), "remove entry")
|
||||
return nil
|
||||
},
|
||||
}}
|
||||
@@ -221,7 +221,7 @@ func TestFacadeDiffIgnoresSourceAndResolutionHistory(t *testing.T) {
|
||||
resolve := func(value string) configkit.Options[config] {
|
||||
return configkit.Options[config]{Definition: configkit.Definition[config]{Resolve: func(current *config, resolution *configkit.Resolution) error {
|
||||
current.Count = len(value) + 8
|
||||
resolution.Record(configkit.Keys("count"), configkit.ChangeNormalization, "set effective count")
|
||||
resolution.RecordNormalization(configkit.Keys("count"), "set effective count")
|
||||
return nil
|
||||
}}}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ func TestFacadeSearchRunsCallbacksOnceAndKeepsInspectionWithSelectedFile(t *test
|
||||
Resolve: func(configuration *facadeDiscoveryLifecycleConfig, resolution *Resolution) error {
|
||||
resolveCalls++
|
||||
configuration.Resolved = "from:" + configuration.Value
|
||||
resolution.Record(Keys("resolved"), ChangeNormalization, "derive from selected value", Keys("value"))
|
||||
resolution.RecordNormalization(Keys("resolved"), "derive from selected value", Keys("value"))
|
||||
return nil
|
||||
},
|
||||
Validate: func(_ *facadeDiscoveryLifecycleConfig, _ *Validation) { validateCalls++ },
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestFinalSequenceIdentityTreatsLateExtendsFieldsAsPayload(t *testing.T) {
|
||||
if phase == "resolution" {
|
||||
definition.Resolve = func(value *map[string]any, resolution *configkit.Resolution) error {
|
||||
(*value)["items"] = items
|
||||
resolution.Record(configkit.Keys("items"), configkit.ChangeNormalization, "supply application payload")
|
||||
resolution.RecordNormalization(configkit.Keys("items"), "supply application payload")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func TestFinalSequenceIdentityValidatesLateValues(t *testing.T) {
|
||||
case "resolution":
|
||||
options.Definition.Resolve = func(value *map[string]any, resolution *configkit.Resolution) error {
|
||||
(*value)["items"] = items
|
||||
resolution.Record(configkit.Keys("items"), configkit.ChangeNormalization, "create effective records")
|
||||
resolution.RecordNormalization(configkit.Keys("items"), "create effective records")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func TestFinalSequenceIdentityMatchesLateDynamicNamespaces(t *testing.T) {
|
||||
case "resolution":
|
||||
options.Definition.Resolve = func(value *map[string]any, resolution *configkit.Resolution) error {
|
||||
(*value)["collections"] = map[string]any{name: map[string]any{"items": items}}
|
||||
resolution.Record(configkit.Keys("collections"), configkit.ChangeNormalization, "create namespaced records")
|
||||
resolution.RecordNormalization(configkit.Keys("collections"), "create namespaced records")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func TestPublicHistoryUsesResolutionShapeAttribution(t *testing.T) {
|
||||
Definition: configkit.Definition[map[string]any]{
|
||||
Resolve: func(configuration *map[string]any, resolution *configkit.Resolution) error {
|
||||
(*configuration)["shape"] = []any{"replacement"}
|
||||
resolution.Record(configkit.Keys("shape"), configkit.ChangeNormalization, "replace numeric mapping key")
|
||||
resolution.RecordNormalization(configkit.Keys("shape"), "replace numeric mapping key")
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
||||
43
facade_resolution_recording_test.go
Normal file
43
facade_resolution_recording_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package configkit
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFacadeResolutionRecordingIntents(t *testing.T) {
|
||||
type configuration struct {
|
||||
Derived string `yaml:"derived"`
|
||||
Normalized string `yaml:"normalized"`
|
||||
}
|
||||
|
||||
result, err := Load(Bytes("recording.yml", []byte("derived: explicit\nnormalized: before\n")), Options[configuration]{
|
||||
Definition: Definition[configuration]{Resolve: func(value *configuration, resolution *Resolution) error {
|
||||
value.Derived = "changed explicit input"
|
||||
resolution.RecordDerivedDefault(Keys("derived"), "derive explicit input")
|
||||
value.Normalized = "after"
|
||||
resolution.RecordNormalization(Keys("normalized"), "normalize configured input")
|
||||
return nil
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
path Path
|
||||
kind ChangeKind
|
||||
}{
|
||||
{path: Keys("derived"), kind: ChangeDerivedDefault},
|
||||
{path: Keys("normalized"), kind: ChangeNormalization},
|
||||
} {
|
||||
info, ok := result.Metadata().Lookup(test.path)
|
||||
if !ok || info.Origin.Operation != test.kind {
|
||||
t.Errorf("metadata %s = %#v, %v; want operation %q", test.path, info, ok, test.kind)
|
||||
}
|
||||
history := result.Metadata().History(test.path)
|
||||
if len(history) == 0 || history[len(history)-1].Origin.Operation != test.kind {
|
||||
t.Errorf("history %s = %#v; want final operation %q", test.path, history, test.kind)
|
||||
}
|
||||
}
|
||||
if !result.Metadata().IsExplicit(Keys("derived")) {
|
||||
t.Fatal("recording a derived default suppressed explicit-input provenance")
|
||||
}
|
||||
}
|
||||
@@ -807,7 +807,7 @@ func TestFacadeSourceVersionLifecycleKeepsConsumedHeadersOutOfPayloadHistory(t *
|
||||
},
|
||||
Resolve: func(current *configuration, resolution *configkit.Resolution) error {
|
||||
current.Service.Resolved = "resolved once"
|
||||
resolution.Record(configkit.Keys("service", "resolved"), configkit.ChangeNormalization, "derive resolved value")
|
||||
resolution.RecordNormalization(configkit.Keys("service", "resolved"), "derive resolved value")
|
||||
return nil
|
||||
},
|
||||
Validate: func(current *configuration, validation *configkit.Validation) {
|
||||
|
||||
@@ -20,9 +20,9 @@ func TestCallbackAssignmentsAreDetachedAcrossLoads(t *testing.T) {
|
||||
definition := Definition[cfg]{Resolve: func(c *cfg, r *Resolution) error {
|
||||
Normalize(r, Keys("map"), &c.Map, shared, "populate")
|
||||
c.Pointer = external
|
||||
r.Record(Keys("pointer"), ChangeNormalization, "populate")
|
||||
r.RecordNormalization(Keys("pointer"), "populate")
|
||||
c.Secret = SecretEnv("CONFIGKIT_CALLBACK_SECRET")
|
||||
r.Record(Keys("secret"), ChangeNormalization, "reference")
|
||||
r.RecordNormalization(Keys("secret"), "reference")
|
||||
return nil
|
||||
}}
|
||||
if validatorAssignment {
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestRelativeImportsFeedFinalInheritanceAndInspectionLifecycle(t *testing.T)
|
||||
child := config.Catalog.Entries["child"]
|
||||
child.Derived = child.Label + ":" + child.Dynamic + ":" + fmt.Sprint(child.Height)
|
||||
config.Catalog.Entries["child"] = child
|
||||
resolution.Record(Keys("catalog", "entries", "child", "derived"), ChangeNormalization, "derive final child summary", Keys("catalog", "entries", "child", "label"), Keys("catalog", "entries", "child", "dynamic"), Keys("catalog", "entries", "child", "height"))
|
||||
resolution.RecordNormalization(Keys("catalog", "entries", "child", "derived"), "derive final child summary", Keys("catalog", "entries", "child", "label"), Keys("catalog", "entries", "child", "dynamic"), Keys("catalog", "entries", "child", "height"))
|
||||
return nil
|
||||
},
|
||||
Validate: func(config *configuration, validation *Validation) {
|
||||
|
||||
@@ -287,7 +287,7 @@ func TestLayeredDestinationLifecycleAndSensitiveInspectionUseFinalShape(t *testi
|
||||
Resolve: func(configuration *destinationLifecycleConfig, resolution *Resolution) error {
|
||||
observed.resolve++
|
||||
configuration.Tenant.Derived = configuration.Tenant.Dynamic
|
||||
resolution.Record(Keys("tenant", "derived"), ChangeNormalization, "derive from mounted input", Keys("tenant", "dynamic"))
|
||||
resolution.RecordNormalization(Keys("tenant", "derived"), "derive from mounted input", Keys("tenant", "dynamic"))
|
||||
return nil
|
||||
},
|
||||
Validate: func(configuration *destinationLifecycleConfig, _ *Validation) {
|
||||
@@ -425,7 +425,7 @@ func TestLayeredDestinationDiffUsesBilateralSensitivity(t *testing.T) {
|
||||
definition: Definition[config]{
|
||||
Resolve: func(configuration *config, resolution *Resolution) error {
|
||||
configuration.Tenant.Derived = configuration.Tenant.Dynamic
|
||||
resolution.Record(Keys("tenant", "derived"), ChangeNormalization, "derive from dynamic input", Keys("tenant", "dynamic"))
|
||||
resolution.RecordNormalization(Keys("tenant", "derived"), "derive from dynamic input", Keys("tenant", "dynamic"))
|
||||
return nil
|
||||
},
|
||||
Secret: func(path Path) bool { return classify && path.String() == "/tenant/dynamic" },
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestDiffUsesFinalValuesAndDependencyDerivedSensitivity(t *testing.T) {
|
||||
Resolve: func(configuration *diffIntegrationConfig, resolution *Resolution) error {
|
||||
resolveCalls++
|
||||
configuration.Derived = configuration.Dynamic["visible"].(string)
|
||||
resolution.Record(Keys("derived"), ChangeNormalization, "derive from effective values", Keys("credential"))
|
||||
resolution.RecordNormalization(Keys("derived"), "derive from effective values", Keys("credential"))
|
||||
return nil
|
||||
},
|
||||
}}
|
||||
@@ -203,7 +203,7 @@ func TestDiffProtectsHistoricalSensitiveRemovalAndReplacement(t *testing.T) {
|
||||
t.Run("removed historical value", func(t *testing.T) {
|
||||
left := load("dynamic: {token: removed-canary}\n", func(configuration *configuration, resolution *Resolution) error {
|
||||
delete(configuration.Dynamic, "token")
|
||||
resolution.Record(secretPath, ChangeNormalization, "remove token")
|
||||
resolution.RecordNormalization(secretPath, "remove token")
|
||||
return nil
|
||||
})
|
||||
right := load("dynamic: {token: current-canary}\n", nil)
|
||||
@@ -228,7 +228,7 @@ func TestDiffProtectsHistoricalSensitiveRemovalAndReplacement(t *testing.T) {
|
||||
t.Run("replaced historical value", func(t *testing.T) {
|
||||
left := load("dynamic: {token: original-canary}\n", func(configuration *configuration, resolution *Resolution) error {
|
||||
configuration.Dynamic["token"] = "replacement-canary"
|
||||
resolution.Record(secretPath, ChangeNormalization, "replace token")
|
||||
resolution.RecordNormalization(secretPath, "replace token")
|
||||
return nil
|
||||
})
|
||||
right := load("dynamic: {token: other-canary}\n", nil)
|
||||
|
||||
@@ -525,10 +525,10 @@ func TestExplanationReplaysResolutionAdditionsAndOverlappingIntent(t *testing.T)
|
||||
result, err := testLoad(Bytes("base", []byte("m: {old: 1}\n")), Options[cfg]{Definition: Definition[cfg]{Resolve: func(c *cfg, r *Resolution) error {
|
||||
c.M["new"] = map[string]any{"child": int64(7)}
|
||||
delete(c.M, "old")
|
||||
r.Record(Keys("m", "new"), ChangeDerivedDefault, "add nested", Keys("m", "old"))
|
||||
r.Record(Keys("m", "old"), ChangeNormalization, "remove old")
|
||||
r.RecordDerivedDefault(Keys("m", "new"), "add nested", Keys("m", "old"))
|
||||
r.RecordNormalization(Keys("m", "old"), "remove old")
|
||||
if overlap {
|
||||
r.Record(Keys("m"), ChangeNormalization, "update container")
|
||||
r.RecordNormalization(Keys("m"), "update container")
|
||||
}
|
||||
return nil
|
||||
}}})
|
||||
@@ -587,7 +587,7 @@ func TestExplanationReplayDescriptorsRemainDetachedAcrossQueries(t *testing.T) {
|
||||
result, err := testLoad(Bytes("resolution-explanation.yml", []byte("count: 1\n")), Options[config]{
|
||||
Definition: Definition[config]{Resolve: func(value *config, resolution *Resolution) error {
|
||||
value.Count = 2
|
||||
resolution.Record(Keys("count"), ChangeNormalization, "normalized", Keys("count"))
|
||||
resolution.RecordNormalization(Keys("count"), "normalized", Keys("count"))
|
||||
return nil
|
||||
}},
|
||||
})
|
||||
@@ -635,7 +635,7 @@ func TestConcurrentReadOnlyInspectionQueries(t *testing.T) {
|
||||
result, err := testLoad(Bytes("concurrent-inspection.yml", []byte("group: {value: input}\n")), Options[config]{
|
||||
Definition: Definition[config]{Resolve: func(value *config, resolution *Resolution) error {
|
||||
value.Group.Value = "resolved"
|
||||
resolution.Record(Keys("group", "value"), ChangeNormalization, "normalized", Keys("group", "value"))
|
||||
resolution.RecordNormalization(Keys("group", "value"), "normalized", Keys("group", "value"))
|
||||
return nil
|
||||
}},
|
||||
})
|
||||
|
||||
@@ -149,8 +149,8 @@ func TestLayeredHistoryBudgetRejectsBeforeSecretsAndValidation(t *testing.T) {
|
||||
definition: Definition[historyBudgetLayeredConfig]{
|
||||
Resolve: func(configuration *historyBudgetLayeredConfig, resolution *Resolution) error {
|
||||
configuration.Child.Value += "-resolved"
|
||||
resolution.Record(Keys("child", "value"), ChangeNormalization, "normalize child")
|
||||
resolution.Record(Keys("child"), ChangeDerivedDefault, "record enclosing child state")
|
||||
resolution.RecordNormalization(Keys("child", "value"), "normalize child")
|
||||
resolution.RecordDerivedDefault(Keys("child"), "record enclosing child state")
|
||||
return nil
|
||||
},
|
||||
Validate: func(*historyBudgetLayeredConfig, *Validation) { counts.validations++ },
|
||||
|
||||
@@ -290,7 +290,7 @@ func TestResolutionInputKeepsDetachedEntryMetadata(t *testing.T) {
|
||||
configurationAtEntry := decoded.value.Interface().(configuration)
|
||||
resolved := runResolution(&configurationAtEntry, func(value *configuration, resolution *Resolution) error {
|
||||
value.Items[0] = "normalized"
|
||||
resolution.Record(Keys("items"), ChangeNormalization, "normalized item")
|
||||
resolution.RecordNormalization(Keys("items"), "normalized item")
|
||||
return nil
|
||||
}, schema, decoded, document, input)
|
||||
if len(resolved.diagnostics) != 0 {
|
||||
|
||||
@@ -85,7 +85,7 @@ func TestPostDecodeOverrideContextOrdersDecodeCLIAndResolutionEvidence(t *testin
|
||||
|
||||
resolvedContext, resolveDiagnostics := resolveLoadInput(schema, Definition[config]{Resolve: func(value *config, resolution *Resolution) error {
|
||||
value.Count = 6
|
||||
resolution.Record(Keys("count"), ChangeNormalization, "normalize count")
|
||||
resolution.RecordNormalization(Keys("count"), "normalize count")
|
||||
return nil
|
||||
}}, input, preparePostDecodeOverrideContext(input, decoded, assigned))
|
||||
if len(resolveDiagnostics) != 0 {
|
||||
@@ -130,7 +130,7 @@ func TestPostDecodeOverrideContextOrdersDecodeCLIAndResolutionEvidence(t *testin
|
||||
Default(resolution, Keys("count"), &value.Count, 99, "fill missing count")
|
||||
Default(resolution, Keys("derived"), &value.Derived, 42, "derive default")
|
||||
value.APIKey = SecretEnv("RESOLVED_SECRET")
|
||||
resolution.Record(Keys("api_key"), ChangeNormalization, "select effective secret reference")
|
||||
resolution.RecordNormalization(Keys("api_key"), "select effective secret reference")
|
||||
return nil
|
||||
}
|
||||
resolved := runResolution(&configuration, resolver, schema, prepared.decoded, prepared.referenceInput, prepared.resolution)
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestCLIHistoryFollowsInheritanceAndPrecedesResolution(t *testing.T) {
|
||||
} else {
|
||||
(*value)["child"].(map[string]any)["value"] = "resolved"
|
||||
}
|
||||
resolution.Record(recorded, ChangeNormalization, "normalize after CLI", parent)
|
||||
resolution.RecordNormalization(recorded, "normalize after CLI", parent)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ empty: {}
|
||||
Normalize(resolution, Keys("values"), &configuration.Values, []int{5},
|
||||
"bounded values", Keys("shared", "imported"))
|
||||
delete(configuration.Items, "remove")
|
||||
resolution.Record(Keys("items"), ChangeNormalization, "removed disabled items")
|
||||
resolution.RecordNormalization(Keys("items"), "removed disabled items")
|
||||
Default(resolution, Keys("derived"), &configuration.Derived, 7,
|
||||
"derived from equal", Keys("shared", "equal"), Keys("shared", "equal"))
|
||||
resolution.Warning(Keys("values"), "values were bounded", Keys("shared", "imported"))
|
||||
@@ -434,7 +434,7 @@ func TestResolutionInputRemainsFixedAcrossRecordedMutations(t *testing.T) {
|
||||
Normalize(resolution, Keys("existing"), &value.Existing, "normalized", "normalized existing value")
|
||||
value.Values["destination"] = value.Values["original"]
|
||||
delete(value.Values, "original")
|
||||
resolution.Record(Keys("values"), ChangeNormalization, "moved configured value")
|
||||
resolution.RecordNormalization(Keys("values"), "moved configured value")
|
||||
againOriginal, againOriginalFound := resolution.InputInfo(Keys("values", "original"))
|
||||
againDestination, againDestinationFound := resolution.InputInfo(Keys("values", "destination"))
|
||||
againExisting, againExistingFound := resolution.InputInfo(Keys("existing"))
|
||||
|
||||
@@ -129,7 +129,6 @@ const (
|
||||
CodeResolveWarning Code = "resolve.warning"
|
||||
CodeResolveHookFailed Code = "resolve.hook_failed"
|
||||
CodeResolveInvalidPath Code = "resolve.invalid_path"
|
||||
CodeResolveInvalidKind Code = "resolve.invalid_kind"
|
||||
CodeResolveInvalidRecord Code = "resolve.invalid_record"
|
||||
CodeResolveInvalidTarget Code = "resolve.invalid_target"
|
||||
CodeResolveInvalidValue Code = "resolve.invalid_value"
|
||||
|
||||
@@ -112,11 +112,21 @@ func (r *Resolution) SourceContext(id string) (ResourceContext, bool) {
|
||||
return r.activeState().input.sourceContext(id)
|
||||
}
|
||||
|
||||
// Record declares the intent for a resolver mutation. A container path covers
|
||||
// mutations to its descendants.
|
||||
func (r *Resolution) Record(path Path, kind ChangeKind, reason string, dependencies ...Path) {
|
||||
// RecordDerivedDefault declares a derived-default intent for a resolver
|
||||
// mutation already performed by application code. A container path covers
|
||||
// mutations to its descendants. This does not assign a value or suppress work
|
||||
// for explicit input.
|
||||
func (r *Resolution) RecordDerivedDefault(path Path, reason string, dependencies ...Path) {
|
||||
state := r.activeState()
|
||||
state.addRecord(path, kind, reason, dependencies, nil)
|
||||
state.addRecord(path, ChangeDerivedDefault, reason, dependencies, nil)
|
||||
}
|
||||
|
||||
// RecordNormalization declares a normalization intent for a resolver mutation
|
||||
// already performed by application code. A container path covers mutations to
|
||||
// its descendants. This does not assign a value.
|
||||
func (r *Resolution) RecordNormalization(path Path, reason string, dependencies ...Path) {
|
||||
state := r.activeState()
|
||||
state.addRecord(path, ChangeNormalization, reason, dependencies, nil)
|
||||
}
|
||||
|
||||
// Error reports a safe, value-free resolution error at path.
|
||||
@@ -219,10 +229,6 @@ func (state *resolutionState) addRecord(path Path, kind ChangeKind, reason strin
|
||||
if !state.validPath(path) {
|
||||
return
|
||||
}
|
||||
if kind != ChangeDerivedDefault && kind != ChangeNormalization {
|
||||
state.addDiagnostic(CodeResolveInvalidKind, path, "resolution record has an invalid change kind")
|
||||
return
|
||||
}
|
||||
if reason == "" {
|
||||
state.addDiagnostic(CodeResolveInvalidRecord, path, "resolution record requires a safe reason")
|
||||
return
|
||||
|
||||
@@ -167,7 +167,7 @@ func TestResolutionContainerRecordCoversSubtreeRemovalAndSnapshotsAreDetached(t
|
||||
result, err := testLoad(Bytes("remove.yml", []byte("items:\n keep: {value: 'yes'}\n remove: {value: 'no'}\n")), Options[config]{Definition: Definition[config]{
|
||||
Resolve: func(configuration *config, resolution *Resolution) error {
|
||||
delete(configuration.Items, "remove")
|
||||
resolution.Record(Keys("items"), ChangeNormalization, "removed disabled entries")
|
||||
resolution.RecordNormalization(Keys("items"), "removed disabled entries")
|
||||
return nil
|
||||
},
|
||||
}})
|
||||
@@ -259,12 +259,8 @@ func TestResolutionRejectsUnrecordedAndMismatchedMutations(t *testing.T) {
|
||||
Normalize(resolution, Keys("value"), &unrelated, 2, "changed value")
|
||||
return nil
|
||||
}, code: CodeResolveInvalidTarget},
|
||||
{name: "invalid record kind", resolve: func(_ *config, resolution *Resolution) error {
|
||||
resolution.Record(Keys("value"), ChangeKind("other"), "invalid kind")
|
||||
return nil
|
||||
}, code: CodeResolveInvalidKind},
|
||||
{name: "invalid path", resolve: func(_ *config, resolution *Resolution) error {
|
||||
resolution.Record(Keys("unknown"), ChangeNormalization, "unknown field")
|
||||
resolution.RecordNormalization(Keys("unknown"), "unknown field")
|
||||
return nil
|
||||
}, code: CodeResolveInvalidPath},
|
||||
}
|
||||
@@ -361,7 +357,7 @@ func TestResolutionPresenceUsesEntryShapeAcrossDynamicChanges(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
resolution.Record(Keys("dynamic"), ChangeNormalization, "change dynamic shape")
|
||||
resolution.RecordNormalization(Keys("dynamic"), "change dynamic shape")
|
||||
child := ""
|
||||
if test.keyPresent {
|
||||
configuration.Dynamic = []any{}
|
||||
@@ -466,7 +462,7 @@ func TestResolutionMetadataExplicitnessUsesEntryShape(t *testing.T) {
|
||||
callbackErr = err
|
||||
return err
|
||||
}
|
||||
resolution.Record(Keys("shape"), ChangeNormalization, "normalize shape")
|
||||
resolution.RecordNormalization(Keys("shape"), "normalize shape")
|
||||
test.mutate(configuration)
|
||||
if err := checkEntry(); err != nil {
|
||||
callbackErr = err
|
||||
@@ -605,6 +601,8 @@ func TestResolutionEntryQueriesRequireAnActiveCallback(t *testing.T) {
|
||||
}()
|
||||
call()
|
||||
}
|
||||
requireClosed(t, func() { (*Resolution)(nil).RecordDerivedDefault(Keys("value"), "late") })
|
||||
requireClosed(t, func() { (*Resolution)(nil).RecordNormalization(Keys("value"), "late") })
|
||||
requireClosed(t, func() { (*Resolution)(nil).InputInfo(Keys("value")) })
|
||||
requireClosed(t, func() { (&Resolution{}).Source("source-1") })
|
||||
|
||||
@@ -617,6 +615,8 @@ func TestResolutionEntryQueriesRequireAnActiveCallback(t *testing.T) {
|
||||
}
|
||||
requireClosed(t, func() { successful.InputInfo(Keys("value")) })
|
||||
requireClosed(t, func() { successful.Source("source-1") })
|
||||
requireClosed(t, func() { successful.RecordDerivedDefault(Keys("value"), "late") })
|
||||
requireClosed(t, func() { successful.RecordNormalization(Keys("value"), "late") })
|
||||
|
||||
var failed *Resolution
|
||||
if _, err := testLoad(NoSource(), Options[config]{Definition: Definition[config]{Resolve: func(_ *config, resolution *Resolution) error {
|
||||
@@ -627,6 +627,8 @@ func TestResolutionEntryQueriesRequireAnActiveCallback(t *testing.T) {
|
||||
}
|
||||
requireClosed(t, func() { failed.InputInfo(Keys("value")) })
|
||||
requireClosed(t, func() { failed.Source("source-1") })
|
||||
requireClosed(t, func() { failed.RecordDerivedDefault(Keys("value"), "late") })
|
||||
requireClosed(t, func() { failed.RecordNormalization(Keys("value"), "late") })
|
||||
}
|
||||
|
||||
func TestValidationContextPanicsAfterVersionCallback(t *testing.T) {
|
||||
|
||||
@@ -436,7 +436,7 @@ func TestRegisteredSequencePolicyAcrossLoadingLifecycle(t *testing.T) {
|
||||
resolved := definition
|
||||
resolved.Resolve = func(value *config, resolution *Resolution) error {
|
||||
value.Items[1].ID = value.Items[0].ID
|
||||
resolution.Record(Keys("items"), ChangeNormalization, "registered identities collide")
|
||||
resolution.RecordNormalization(Keys("items"), "registered identities collide")
|
||||
return nil
|
||||
}
|
||||
result, err = testLoad[config](Bytes("main", []byte("items: [{id: first}, {id: second}]\n")), Options[config]{Definition: resolved})
|
||||
|
||||
@@ -1252,7 +1252,7 @@ func TestSourceVersionEvidencePropagatesThroughRecordedResolutionDependency(t *t
|
||||
},
|
||||
Resolve: func(value *configuration, resolution *Resolution) error {
|
||||
value.Derived = value.Credentials.Password
|
||||
resolution.Record(Keys("derived"), ChangeNormalization, "derive from credentials", Keys("credentials"))
|
||||
resolution.RecordNormalization(Keys("derived"), "derive from credentials", Keys("credentials"))
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
||||
17
lifecycle.go
17
lifecycle.go
@@ -259,10 +259,19 @@ func (r *Resolution) Source(id string) (SourceInfo, bool) {
|
||||
return fromEngineSourceInfo(value), ok
|
||||
}
|
||||
|
||||
// Record declares the intent for a resolver mutation. A container path covers
|
||||
// mutations to its descendants.
|
||||
func (r *Resolution) Record(path Path, kind ChangeKind, reason string, dependencies ...Path) {
|
||||
r.engineContext().Record(toEnginePath(path), engine.ChangeKind(kind), reason, toEnginePaths(dependencies)...)
|
||||
// RecordDerivedDefault records a derived-default intent for a resolver mutation
|
||||
// already performed by application code. A container path covers mutations to
|
||||
// its descendants. This does not assign a value or suppress work for explicit
|
||||
// input.
|
||||
func (r *Resolution) RecordDerivedDefault(path Path, reason string, dependencies ...Path) {
|
||||
r.engineContext().RecordDerivedDefault(toEnginePath(path), reason, toEnginePaths(dependencies)...)
|
||||
}
|
||||
|
||||
// RecordNormalization records a normalization intent for a resolver mutation
|
||||
// already performed by application code. A container path covers mutations to
|
||||
// its descendants. This does not assign a value.
|
||||
func (r *Resolution) RecordNormalization(path Path, reason string, dependencies ...Path) {
|
||||
r.engineContext().RecordNormalization(toEnginePath(path), reason, toEnginePaths(dependencies)...)
|
||||
}
|
||||
|
||||
// Error reports a safe, value-free resolution error at path.
|
||||
|
||||
@@ -130,7 +130,6 @@ const (
|
||||
CodeResolveWarning Code = "resolve.warning"
|
||||
CodeResolveHookFailed Code = "resolve.hook_failed"
|
||||
CodeResolveInvalidPath Code = "resolve.invalid_path"
|
||||
CodeResolveInvalidKind Code = "resolve.invalid_kind"
|
||||
CodeResolveInvalidRecord Code = "resolve.invalid_record"
|
||||
CodeResolveInvalidTarget Code = "resolve.invalid_target"
|
||||
CodeResolveInvalidValue Code = "resolve.invalid_value"
|
||||
|
||||
Reference in New Issue
Block a user