Replace generic resolution recording with intent methods
This commit is contained in:
@@ -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
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user