Unify typed binding preparation

This commit is contained in:
2026-10-11 01:18:07 +00:00
parent de9bfdcc4c
commit f63b096525
5 changed files with 130 additions and 65 deletions

View File

@@ -13,24 +13,24 @@ func ValueFlag[I, V any](meta FlagMeta, defaultValue *V, parse func(string) (V,
if defaultValue != nil { if defaultValue != nil {
fallback = *defaultValue fallback = *defaultValue
} }
binding := flagBinding[I]{meta: meta, defaultText: fmt.Sprint(fallback)} binding := flagBinding[I]{identity: newBindingIdentity(), meta: meta, defaultText: fmt.Sprint(fallback)}
if parse == nil || set == nil { if parse == nil || set == nil {
return Flag[I]{binding: binding} return Flag[I]{binding: binding}
} }
binding.valid = true binding.valid = true
binding.clear = func(input *I) { set(input, Supplied[V]{}) } binding.clear = func(input *I) { set(input, Supplied[V]{}) }
binding.prepare = func(values []string) (func(*I), error) { binding.prepare = func(values []string) (preparedValue[I], error) {
value := fallback value := fallback
supplied := len(values) > 0 supplied := len(values) > 0
for _, raw := range values { for _, raw := range values {
parsed, err := parse(raw) parsed, err := parse(raw)
if err != nil { if err != nil {
return nil, err return preparedValue[I]{}, err
} }
value = parsed value = parsed
} }
result := Supplied[V]{Value: value, Set: supplied} result := Supplied[V]{Value: value, Set: supplied}
return func(input *I) { set(input, result) }, nil return preparedValue[I]{identity: binding.identity, supplied: supplied, apply: func(input *I) { set(input, result) }}, nil
} }
return Flag[I]{binding: binding} return Flag[I]{binding: binding}
} }
@@ -38,24 +38,24 @@ func ValueFlag[I, V any](meta FlagMeta, defaultValue *V, parse func(string) (V,
// BoolFlag declares a boolean flag. A bare flag is supplied by the parser as // BoolFlag declares a boolean flag. A bare flag is supplied by the parser as
// "true"; explicit values are converted with strconv.ParseBool. // "true"; explicit values are converted with strconv.ParseBool.
func BoolFlag[I any](meta FlagMeta, defaultValue bool, set func(*I, Supplied[bool])) Flag[I] { func BoolFlag[I any](meta FlagMeta, defaultValue bool, set func(*I, Supplied[bool])) Flag[I] {
binding := flagBinding[I]{meta: meta, defaultText: fmt.Sprint(defaultValue), boolean: true} binding := flagBinding[I]{identity: newBindingIdentity(), meta: meta, defaultText: fmt.Sprint(defaultValue), boolean: true}
if set == nil { if set == nil {
return Flag[I]{binding: binding} return Flag[I]{binding: binding}
} }
binding.valid = true binding.valid = true
binding.clear = func(input *I) { set(input, Supplied[bool]{}) } binding.clear = func(input *I) { set(input, Supplied[bool]{}) }
binding.prepare = func(values []string) (func(*I), error) { binding.prepare = func(values []string) (preparedValue[I], error) {
value := defaultValue value := defaultValue
supplied := len(values) > 0 supplied := len(values) > 0
for _, raw := range values { for _, raw := range values {
parsed, err := strconv.ParseBool(raw) parsed, err := strconv.ParseBool(raw)
if err != nil { if err != nil {
return nil, err return preparedValue[I]{}, err
} }
value = parsed value = parsed
} }
result := Supplied[bool]{Value: value, Set: supplied} result := Supplied[bool]{Value: value, Set: supplied}
return func(input *I) { set(input, result) }, nil return preparedValue[I]{identity: binding.identity, supplied: supplied, apply: func(input *I) { set(input, result) }}, nil
} }
return Flag[I]{binding: binding} return Flag[I]{binding: binding}
} }
@@ -64,13 +64,13 @@ func BoolFlag[I any](meta FlagMeta, defaultValue bool, set func(*I, Supplied[boo
// and are parsed in argv order, with each occurrence treated as one value. // and are parsed in argv order, with each occurrence treated as one value.
func RepeatFlag[I, V any](meta FlagMeta, defaults []V, parse func(string) (V, error), set func(*I, Supplied[[]V])) Flag[I] { func RepeatFlag[I, V any](meta FlagMeta, defaults []V, parse func(string) (V, error), set func(*I, Supplied[[]V])) Flag[I] {
fallback := append([]V(nil), defaults...) fallback := append([]V(nil), defaults...)
binding := flagBinding[I]{meta: meta, defaultText: fmt.Sprint(fallback), repeated: true} binding := flagBinding[I]{identity: newBindingIdentity(), meta: meta, defaultText: fmt.Sprint(fallback), repeated: true}
if parse == nil || set == nil { if parse == nil || set == nil {
return Flag[I]{binding: binding} return Flag[I]{binding: binding}
} }
binding.valid = true binding.valid = true
binding.clear = func(input *I) { set(input, Supplied[[]V]{}) } binding.clear = func(input *I) { set(input, Supplied[[]V]{}) }
binding.prepare = func(values []string) (func(*I), error) { binding.prepare = func(values []string) (preparedValue[I], error) {
parsedValues := append([]V(nil), fallback...) parsedValues := append([]V(nil), fallback...)
supplied := len(values) > 0 supplied := len(values) > 0
if supplied { if supplied {
@@ -78,15 +78,15 @@ func RepeatFlag[I, V any](meta FlagMeta, defaults []V, parse func(string) (V, er
for _, raw := range values { for _, raw := range values {
parsed, err := parse(raw) parsed, err := parse(raw)
if err != nil { if err != nil {
return nil, err return preparedValue[I]{}, err
} }
parsedValues = append(parsedValues, parsed) parsedValues = append(parsedValues, parsed)
} }
} }
return func(input *I) { return preparedValue[I]{identity: binding.identity, supplied: supplied, apply: func(input *I) {
result := Supplied[[]V]{Value: append([]V(nil), parsedValues...), Set: supplied} result := Supplied[[]V]{Value: append([]V(nil), parsedValues...), Set: supplied}
set(input, result) set(input, result)
}, nil }}, nil
} }
return Flag[I]{binding: binding} return Flag[I]{binding: binding}
} }
@@ -98,26 +98,26 @@ func ValueArgument[I, V any](meta ArgumentMeta, required bool, parse func(string
if required { if required {
min = 1 min = 1
} }
binding := argumentBinding[I]{meta: meta, min: min, max: 1} binding := argumentBinding[I]{identity: newBindingIdentity(), meta: meta, min: min, max: 1}
if parse == nil || set == nil { if parse == nil || set == nil {
return Argument[I]{binding: binding} return Argument[I]{binding: binding}
} }
binding.valid = true binding.valid = true
binding.prepare = func(values []string) (func(*I), error) { binding.prepare = func(values []string) (preparedValue[I], error) {
if len(values) > 1 { if len(values) > 1 {
return nil, fmt.Errorf("received %d values; maximum is 1", len(values)) return preparedValue[I]{}, fmt.Errorf("received %d values; maximum is 1", len(values))
} }
var value V var value V
supplied := len(values) == 1 supplied := len(values) == 1
if supplied { if supplied {
parsed, err := parse(values[0]) parsed, err := parse(values[0])
if err != nil { if err != nil {
return nil, err return preparedValue[I]{}, err
} }
value = parsed value = parsed
} }
result := Supplied[V]{Value: value, Set: supplied} result := Supplied[V]{Value: value, Set: supplied}
return func(input *I) { set(input, result) }, nil return preparedValue[I]{identity: binding.identity, supplied: supplied, apply: func(input *I) { set(input, result) }}, nil
} }
return Argument[I]{binding: binding} return Argument[I]{binding: binding}
} }
@@ -125,34 +125,34 @@ func ValueArgument[I, V any](meta ArgumentMeta, required bool, parse func(string
// RepeatArgument declares a positional argument that accepts a bounded number // RepeatArgument declares a positional argument that accepts a bounded number
// of values. A maximum of -1 means unbounded. // of values. A maximum of -1 means unbounded.
func RepeatArgument[I, V any](meta ArgumentMeta, min, max int, parse func(string) (V, error), set func(*I, Supplied[[]V])) Argument[I] { func RepeatArgument[I, V any](meta ArgumentMeta, min, max int, parse func(string) (V, error), set func(*I, Supplied[[]V])) Argument[I] {
binding := argumentBinding[I]{meta: meta, min: min, max: max} binding := argumentBinding[I]{identity: newBindingIdentity(), meta: meta, min: min, max: max}
if parse == nil || set == nil { if parse == nil || set == nil {
return Argument[I]{binding: binding} return Argument[I]{binding: binding}
} }
binding.valid = true binding.valid = true
binding.prepare = func(values []string) (func(*I), error) { binding.prepare = func(values []string) (preparedValue[I], error) {
if max >= 0 && len(values) > max { if max >= 0 && len(values) > max {
return nil, fmt.Errorf("received %d values; maximum is %d", len(values), max) return preparedValue[I]{}, fmt.Errorf("received %d values; maximum is %d", len(values), max)
} }
parsedValues := make([]V, 0, len(values)) parsedValues := make([]V, 0, len(values))
for _, raw := range values { for _, raw := range values {
parsed, err := parse(raw) parsed, err := parse(raw)
if err != nil { if err != nil {
return nil, err return preparedValue[I]{}, err
} }
parsedValues = append(parsedValues, parsed) parsedValues = append(parsedValues, parsed)
} }
return func(input *I) { return preparedValue[I]{identity: binding.identity, supplied: len(values) > 0, apply: func(input *I) {
result := Supplied[[]V]{Value: append([]V(nil), parsedValues...), Set: len(values) > 0} result := Supplied[[]V]{Value: append([]V(nil), parsedValues...), Set: len(values) > 0}
set(input, result) set(input, result)
}, nil }}, nil
} }
return Argument[I]{binding: binding} return Argument[I]{binding: binding}
} }
type preparedInput[I any] struct { type preparedInput[I any] struct {
newInput func() I newInput func() I
setters []func(*I) values []preparedValue[I]
} }
func (p preparedInput[I]) build() I { func (p preparedInput[I]) build() I {
@@ -160,8 +160,8 @@ func (p preparedInput[I]) build() I {
if p.newInput != nil { if p.newInput != nil {
input = p.newInput() input = p.newInput()
} }
for _, setter := range p.setters { for _, value := range p.values {
setter(&input) value.apply(&input)
} }
return input return input
} }
@@ -173,33 +173,40 @@ func prepareInput[I any](spec CommandSpec[I], flagValues [][]string, operands []
if len(flagValues) != len(spec.Flags) { if len(flagValues) != len(spec.Flags) {
return preparedInput[I]{}, fmt.Errorf("received %d flag value sets for %d bindings", len(flagValues), len(spec.Flags)) return preparedInput[I]{}, fmt.Errorf("received %d flag value sets for %d bindings", len(flagValues), len(spec.Flags))
} }
setters := make([]func(*I), 0, len(spec.Flags)+len(spec.Arguments)) preparedFlags := make([]preparedValue[I], len(spec.Flags))
missingRequired := ""
for i, flag := range spec.Flags { for i, flag := range spec.Flags {
binding := flag.binding binding := flag.binding
if enforceRequired && binding.meta.Required && len(flagValues[i]) == 0 { value, err := binding.prepare(flagValues[i])
return preparedInput[I]{}, fmt.Errorf("required flag --%s was not supplied", binding.meta.Name)
}
setter, err := binding.prepare(flagValues[i])
if err != nil { if err != nil {
return preparedInput[I]{}, fmt.Errorf("flag --%s: %w", binding.meta.Name, err) return preparedInput[I]{}, fmt.Errorf("flag --%s: %w", binding.meta.Name, err)
} }
setters = append(setters, setter) preparedFlags[i] = value
if enforceRequired && binding.meta.Required && !value.supplied && missingRequired == "" {
missingRequired = binding.meta.Name
}
}
if missingRequired != "" {
return preparedInput[I]{}, fmt.Errorf("required flag --%s was not supplied", missingRequired)
} }
argumentValues, err := allocateOperands(operands, spec.Arguments, enforceRequired) argumentValues, err := allocateOperands(operands, spec.Arguments, enforceRequired)
if err != nil { if err != nil {
return preparedInput[I]{}, err return preparedInput[I]{}, err
} }
preparedValues := append([]preparedValue[I](nil), preparedFlags...)
for i, argument := range spec.Arguments { for i, argument := range spec.Arguments {
binding := argument.binding binding := argument.binding
setter, err := binding.prepare(argumentValues[i]) value, err := binding.prepare(argumentValues[i])
if err != nil { if err != nil {
return preparedInput[I]{}, fmt.Errorf("argument %q: %w", binding.meta.Name, err) return preparedInput[I]{}, fmt.Errorf("argument %q: %w", binding.meta.Name, err)
} }
setters = append(setters, setter) preparedValues = append(preparedValues, value)
} }
return preparedInput[I]{newInput: spec.NewInput, setters: setters}, nil return preparedInput[I]{newInput: spec.NewInput, values: preparedValues}, nil
} }
func newBindingIdentity() *bindingIdentity { return &bindingIdentity{marker: 1} }
func allocateOperands[I any](operands []string, definitions []Argument[I], enforceRequired bool) ([][]string, error) { func allocateOperands[I any](operands []string, definitions []Argument[I], enforceRequired bool) ([][]string, error) {
for _, argument := range definitions { for _, argument := range definitions {
binding := argument.binding binding := argument.binding

View File

@@ -104,8 +104,7 @@ func TestBoolFlagRejectsExplicitEmptyValue(t *testing.T) {
func TestRepeatFlagUsesFreshDefaultsAndWholeOccurrenceValues(t *testing.T) { func TestRepeatFlagUsesFreshDefaultsAndWholeOccurrenceValues(t *testing.T) {
defaults := []string{"base"} defaults := []string{"base"}
var captured Supplied[[]string] flag := RepeatFlag(FlagMeta{Name: "tag"}, defaults, func(value string) (string, error) { return value, nil }, func(input *bindingInput, value Supplied[[]string]) { input.tags = value })
flag := RepeatFlag(FlagMeta{Name: "tag"}, defaults, func(value string) (string, error) { return value, nil }, func(_ *bindingInput, value Supplied[[]string]) { captured = value })
defaults[0] = "changed after declaration" defaults[0] = "changed after declaration"
applyDefault, err := flag.binding.prepare(nil) applyDefault, err := flag.binding.prepare(nil)
@@ -113,25 +112,25 @@ func TestRepeatFlagUsesFreshDefaultsAndWholeOccurrenceValues(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
first := &bindingInput{} first := &bindingInput{}
applyDefault(first) applyDefault.apply(first)
second := &bindingInput{} second := &bindingInput{}
applyDefault(second) applyDefault.apply(second)
if !reflect.DeepEqual(captured.Value, []string{"base"}) || captured.Set { if !reflect.DeepEqual(first.tags.Value, []string{"base"}) || first.tags.Set {
t.Fatalf("omitted repeat value = %#v, want copied default with Set=false", captured) t.Fatalf("omitted repeat value = %#v, want copied default with Set=false", first.tags)
} }
captured.Value[0] = "changed one input" first.tags.Value[0] = "changed one input"
applyDefault(second) if !reflect.DeepEqual(second.tags.Value, []string{"base"}) {
if !reflect.DeepEqual(captured.Value, []string{"base"}) { t.Fatalf("repeat default collection was reused: %#v", second.tags.Value)
t.Fatalf("repeat default collection was reused: %#v", captured.Value)
} }
applyValues, err := flag.binding.prepare([]string{"a,b", "c"}) applyValues, err := flag.binding.prepare([]string{"a,b", "c"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
applyValues(&bindingInput{}) third := &bindingInput{}
if !reflect.DeepEqual(captured.Value, []string{"a,b", "c"}) || !captured.Set { applyValues.apply(third)
t.Fatalf("supplied repeat values = %#v, want two ordered occurrences", captured) if !reflect.DeepEqual(third.tags.Value, []string{"a,b", "c"}) || !third.tags.Set {
t.Fatalf("supplied repeat values = %#v, want two ordered occurrences", third.tags)
} }
} }
@@ -149,6 +148,46 @@ func TestSingletonParsesEveryOccurrenceAndPreservesCallerCause(t *testing.T) {
} }
} }
func TestPreparedScalarConvertsEveryOccurrenceOnceBeforeInputCreation(t *testing.T) {
parseCalls := 0
var received Supplied[string]
flag := ValueFlag(FlagMeta{Name: "value"}, (*string)(nil), func(value string) (string, error) {
parseCalls++
return value, nil
}, func(input *bindingInput, value Supplied[string]) { input.message = value })
prepared, err := flag.binding.prepare([]string{"earlier", "last"})
if err != nil {
t.Fatal(err)
}
if parseCalls != 2 || prepared.identity != flag.binding.identity || !prepared.supplied {
t.Fatalf("prepared scalar = %#v, parse calls = %d", prepared, parseCalls)
}
copy := flag
lookalike := ValueFlag(FlagMeta{Name: "value"}, (*string)(nil), func(value string) (string, error) { return value, nil }, func(*bindingInput, Supplied[string]) {})
if copy.binding.identity != flag.binding.identity || lookalike.binding.identity == flag.binding.identity {
t.Fatal("binding identity was not preserved by copying or distinct across constructors")
}
input := &bindingInput{}
prepared.apply(input)
received = input.message
if parseCalls != 2 || received.Value != "last" || !received.Set {
t.Fatalf("applied value = %#v, parse calls = %d", received, parseCalls)
}
}
func TestInvalidBindingConstructionIsReportedAtInstallation(t *testing.T) {
badFlag := ValueFlag[struct{}, string](FlagMeta{Name: "broken"}, nil, nil, func(*struct{}, Supplied[string]) {})
badArgument := RepeatArgument[struct{}, string](ArgumentMeta{Name: "items"}, 2, 1, func(value string) (string, error) { return value, nil }, func(*struct{}, Supplied[[]string]) {})
command := NewCommand(CommandSpec[struct{}]{Name: "run", Flags: []Flag[struct{}]{badFlag}, Arguments: []Argument[struct{}]{badArgument}})
app, err := New(Identity{Name: "example"}, command)
if err == nil {
t.Fatal("installation accepted nil parser wiring and invalid argument cardinality")
}
if app != nil {
t.Fatal("failed installation returned a usable app")
}
}
func TestRepeatArgumentAllocationReservesLaterMinimums(t *testing.T) { func TestRepeatArgumentAllocationReservesLaterMinimums(t *testing.T) {
type operandsInput struct { type operandsInput struct {
prefix Supplied[[]string] prefix Supplied[[]string]

View File

@@ -58,12 +58,21 @@ type Flag[I any] struct {
binding flagBinding[I] binding flagBinding[I]
} }
type bindingIdentity struct{ marker byte }
type preparedValue[I any] struct {
identity *bindingIdentity
supplied bool
apply func(*I)
}
type flagBinding[I any] struct { type flagBinding[I any] struct {
identity *bindingIdentity
meta FlagMeta meta FlagMeta
defaultText string defaultText string
repeated bool repeated bool
boolean bool boolean bool
prepare func([]string) (func(*I), error) prepare func([]string) (preparedValue[I], error)
clear func(*I) clear func(*I)
valid bool valid bool
} }
@@ -74,11 +83,12 @@ type Argument[I any] struct {
} }
type argumentBinding[I any] struct { type argumentBinding[I any] struct {
meta ArgumentMeta identity *bindingIdentity
min int meta ArgumentMeta
max int min int
prepare func([]string) (func(*I), error) max int
valid bool prepare func([]string) (preparedValue[I], error)
valid bool
} }
// CommandSpec declares one command using typed input and handler callbacks. // CommandSpec declares one command using typed input and handler callbacks.
@@ -319,7 +329,7 @@ func validateBindings[I any](flags []Flag[I], arguments []Argument[I]) error {
} }
shortNames[binding.meta.Short] = struct{}{} shortNames[binding.meta.Short] = struct{}{}
} }
if !binding.valid || binding.prepare == nil { if binding.identity == nil || !binding.valid || binding.prepare == nil {
return fmt.Errorf("flag %q is missing parser or setter wiring", binding.meta.Name) return fmt.Errorf("flag %q is missing parser or setter wiring", binding.meta.Name)
} }
} }
@@ -332,7 +342,7 @@ func validateBindings[I any](flags []Flag[I], arguments []Argument[I]) error {
if binding.min < 0 || binding.max < -1 || (binding.max >= 0 && binding.min > binding.max) { if binding.min < 0 || binding.max < -1 || (binding.max >= 0 && binding.min > binding.max) {
return fmt.Errorf("argument %q has invalid bounds %d..%d", binding.meta.Name, binding.min, binding.max) return fmt.Errorf("argument %q has invalid bounds %d..%d", binding.meta.Name, binding.min, binding.max)
} }
if !binding.valid || binding.prepare == nil { if binding.identity == nil || !binding.valid || binding.prepare == nil {
return fmt.Errorf("argument %q is missing parser or setter wiring", binding.meta.Name) return fmt.Errorf("argument %q is missing parser or setter wiring", binding.meta.Name)
} }
} }

View File

@@ -1,6 +1,6 @@
# Implement the optional integration modules and captured registration API # Implement the optional integration modules and captured registration API
Status: implementation in progress. Stages 1 and 2 are complete; subsequent work follows the ordered roadmap. Status: implementation in progress. Stages 1 through 3 are complete; subsequent work follows the ordered roadmap.
Execute the stages in order. Each stage is sized as a separate Luna coding-agent Execute the stages in order. Each stage is sized as a separate Luna coding-agent
assignment. This document authorizes no publication or changes to sibling assignment. This document authorizes no publication or changes to sibling
repositories. repositories.
@@ -222,9 +222,14 @@ object deep copies.
combined names, short names, duplicate bindings, and help conflicts. Avoid combined names, short names, duplicate bindings, and help conflicts. Avoid
integration-specific reserved-flag lists. integration-specific reserved-flag lists.
**Completion evidence:** existing presence/repetition/help tests, adjusted to the **Completion evidence — complete:** flags and operands now prepare the same
selected constructors, plus focused converter call counts and repeated-slice identity-bearing typed application value. Binding constructors retain invalid
independence. Do not add a new grammar restriction. wiring/cardinality for installation; copies preserve identity and independent
lookalikes do not. Conversion remains before input creation, every singleton
occurrence is parsed once, and repeated core slices are copied per application.
Existing presence, repetition, help, and operand tests pass with the captured
constructors, along with focused identity, converter-count, and independence
checks. No new grammar restriction was added.
## Stage 4: Implement captured flag application ## Stage 4: Implement captured flag application

View File

@@ -9,11 +9,12 @@ type registryInput struct{ value string }
func TestCapturedDeclarationsRetainCallbacksWithoutCallingThem(t *testing.T) { func TestCapturedDeclarationsRetainCallbacksWithoutCallingThem(t *testing.T) {
var factoryCalls, prepareCalls, setterCalls, checkCalls, runCalls int var factoryCalls, prepareCalls, setterCalls, checkCalls, runCalls int
identity := newBindingIdentity()
flag := Flag[registryInput]{binding: flagBinding[registryInput]{ flag := Flag[registryInput]{binding: flagBinding[registryInput]{
meta: FlagMeta{Name: "mode"}, valid: true, identity: identity, meta: FlagMeta{Name: "mode"}, valid: true,
prepare: func([]string) (func(*registryInput), error) { prepare: func([]string) (preparedValue[registryInput], error) {
prepareCalls++ prepareCalls++
return func(*registryInput) { setterCalls++ }, nil return preparedValue[registryInput]{identity: identity, apply: func(*registryInput) { setterCalls++ }}, nil
}, },
}} }}
command := NewCommand(CommandSpec[registryInput]{ command := NewCommand(CommandSpec[registryInput]{
@@ -175,7 +176,10 @@ func TestNewRejectsEmptyIdentity(t *testing.T) {
} }
func TestNewCommandCopiesBindingMetadata(t *testing.T) { func TestNewCommandCopiesBindingMetadata(t *testing.T) {
flags := []Flag[registryInput]{{binding: flagBinding[registryInput]{meta: FlagMeta{Name: "mode"}, valid: true, prepare: func([]string) (func(*registryInput), error) { return func(*registryInput) {}, nil }}}} identity := newBindingIdentity()
flags := []Flag[registryInput]{{binding: flagBinding[registryInput]{identity: identity, meta: FlagMeta{Name: "mode"}, valid: true, prepare: func([]string) (preparedValue[registryInput], error) {
return preparedValue[registryInput]{identity: identity, apply: func(*registryInput) {}}, nil
}}}}
command := NewCommand(CommandSpec[registryInput]{Name: "run", Flags: flags}) command := NewCommand(CommandSpec[registryInput]{Name: "run", Flags: flags})
flags[0].binding.meta.Name = "changed" flags[0].binding.meta.Name = "changed"
payload := command.record.payload.(typedCommand[registryInput]) payload := command.record.payload.(typedCommand[registryInput])