Update the simple configuration consumer

This commit is contained in:
2026-11-07 04:54:44 +00:00
parent 0b70f37267
commit 42af1da142
5 changed files with 84 additions and 3 deletions

View File

@@ -1,6 +1,6 @@
# Shared reporting and PromptKit v0.20.0 implementation plan # Shared reporting and PromptKit v0.20.0 implementation plan
Status: implementation in progress; Stages 1–20 complete. Status: implementation in progress; Stages 1–21 complete.
## Scope, authority, and execution ## Scope, authority, and execution
@@ -756,6 +756,11 @@ snapshots and upstream validation-rule tests.
## Stage 21: Simple consumer and core/configuration API exercise ## Stage 21: Simple consumer and core/configuration API exercise
**Complete.** The simple consumer opts into native overrides and demonstrates
the new inspection and failure workflows. The core consumer's structured report
and template remain exercised. Core example and ckconfig workspace tests passed
with Go 1.27.0.
Update the maintained [simple ckconfig consumer](../../integrations/ckconfig/examples/simple) Update the maintained [simple ckconfig consumer](../../integrations/ckconfig/examples/simple)
to install an explicitly configured FromConfigKit scope with Overrides enabled, to install an explicitly configured FromConfigKit scope with Overrides enabled,
replacing AppCommand convenience wiring there. Append the selected native CLI replacing AppCommand convenience wiring there. Append the selected native CLI

View File

@@ -11,7 +11,12 @@ Run the maintained example with:
go run . config show go run . config show
go run . config show --config fixtures/before.yml --verbose go run . config show --config fixtures/before.yml --verbose
go run . config show --config fixtures/before.yml --quiet go run . config show --config fixtures/before.yml --quiet
go run . config explain --list --under /schema
go run . config show --path /greeting --set '/greeting="Hi"'
go run . config diff --config fixtures/before.yml --against-config fixtures/after.yml go run . config diff --config fixtures/before.yml --against-config fixtures/after.yml
go run . config diff --set '/greeting="Hi"' --details --check
go run . config validate --config fixtures/invalid.yml --details --severity error
go run . config sources --config fixtures/invalid.yml --source-id source-1
go run . greet go run . greet
go run . greet --count=0 --tag=first --tag second go run . greet --count=0 --tag=first --tag second
``` ```
@@ -25,3 +30,8 @@ command loads the two local fixtures and reports `changes.greeting: Goodbye`.
The before fixture uses schema version 1, which ConfigKit reports as a provider The before fixture uses schema version 1, which ConfigKit reports as a provider
warning in verbose mode. Ordinary mode hides the warning while preserving the warning in verbose mode. Ordinary mode hides the warning while preserving the
effective YAML report; quiet mode suppresses that report. effective YAML report; quiet mode suppresses that report.
The invalid fixture demonstrates a failed-load report and captured source
inventory; `source-1` is the ID from that fixture's acquisition. The scope opts
in to transient overrides and passes them into its normal ConfigKit load. The
diff example compares an overridden left side with ordinary configuration on
the right and returns status 3 because the values differ.

View File

@@ -0,0 +1,3 @@
schema:
version: 2
greeting: [invalid]

View File

@@ -49,10 +49,15 @@ func loadAppConfiguration(_ context.Context, selection ckconfig.Selection) (*con
options.Overlay = selection.Overlay.Value options.Overlay = selection.Overlay.Value
options.Filesystem = &configkit.FilesystemContext{Root: ".", BaseDir: "."} options.Filesystem = &configkit.FilesystemContext{Root: ".", BaseDir: "."}
} }
options.Overrides = append(options.Overrides, selection.Overrides...)
return configkit.Load(source, options) return configkit.Load(source, options)
} }
func newApp() (*commandkit.App, error) { func newApp() (*commandkit.App, error) {
return newAppWithLoader(loadAppConfiguration)
}
func newAppWithLoader(load func(context.Context, ckconfig.Selection) (*configkit.Result[appConfiguration], error)) (*commandkit.App, error) {
countDefault := 1 countDefault := 1
tagDefaults := []string{"example"} tagDefaults := []string{"example"}
greet := commandkit.NewCommand(commandkit.CommandSpec[greetInput]{ greet := commandkit.NewCommand(commandkit.CommandSpec[greetInput]{
@@ -68,7 +73,7 @@ func newApp() (*commandkit.App, error) {
}, },
Run: func(invocation commandkit.Invocation, input greetInput) commandkit.Outcome { Run: func(invocation commandkit.Invocation, input greetInput) commandkit.Outcome {
reporter := invocation.Reporter() reporter := invocation.Reporter()
configuration, err := loadAppConfiguration(invocation.Context, ckconfig.Selection{}) configuration, err := load(invocation.Context, ckconfig.Selection{})
if err != nil { if err != nil {
return commandkit.Failure("Could not load application configuration.").WithCause(err) return commandkit.Failure("Could not load application configuration.").WithCause(err)
} }
@@ -87,9 +92,13 @@ func newApp() (*commandkit.App, error) {
return commandkit.Success() return commandkit.Success()
}, },
}) })
scope := ckconfig.FromConfigKit("app", "Application configuration.", func(ctx context.Context, selection ckconfig.Selection, _, _ struct{}) (*configkit.Result[appConfiguration], error) {
return load(ctx, selection)
})
scope.Features.Overrides = true
return commandkit.New( return commandkit.New(
commandkit.Identity{Name: "simple-example", Version: "1.0"}, commandkit.Identity{Name: "simple-example", Version: "1.0"},
ckconfig.AppCommand(loadAppConfiguration), ckconfig.Command(ckconfig.Suite[struct{}, struct{}]{Scopes: []ckconfig.Scope[struct{}, struct{}]{scope}}),
greet, greet,
) )
} }

View File

@@ -3,12 +3,14 @@ package main
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"io" "io"
"strings" "strings"
"testing" "testing"
"go.maximumdirect.net/ckconfig" "go.maximumdirect.net/ckconfig"
commandkit "go.maximumdirect.net/commandkit" commandkit "go.maximumdirect.net/commandkit"
configkit "go.maximumdirect.net/configkit"
"go.yaml.in/yaml/v4" "go.yaml.in/yaml/v4"
) )
@@ -147,3 +149,55 @@ func decodeReport(data []byte) (*yaml.Node, error) {
} }
return document.Content[0], nil return document.Content[0], nil
} }
func TestSimpleInspectionAndIndependentOverrides(t *testing.T) {
outcome, output, _ := executeSimple(t, "config", "explain", "--list", "--under=/schema", "--format=json")
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output, `"path": "/schema/version"`) {
t.Fatalf("discovery = %#v %q", outcome, output)
}
outcome, output, _ = executeSimple(t, "config", "show", "--path=/greeting", "--set=/greeting=\"Hi\"")
if outcome.Kind != commandkit.OutcomeSuccess || output != "Hi\n" {
t.Fatalf("focused override = %#v %q", outcome, output)
}
outcome, output, _ = executeSimple(t, "config", "diff", "--set=/greeting=\"Hi\"", "--details", "--check", "--format=json")
if outcome.Kind != commandkit.OutcomeDomain || outcome.Status != 3 || !strings.Contains(output, `"before"`) || !strings.Contains(output, `"after"`) {
t.Fatalf("independent diff = %#v %q", outcome, output)
}
outcome, output, _ = executeSimple(t, "config", "diff", "--set=/greeting=\"Hi\"", "--against-set=/greeting=\"Hi\"", "--details", "--check", "--format=json")
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output, `"differences": []`) {
t.Fatalf("equal overrides = %#v %q", outcome, output)
}
}
func TestSimpleFailedLoadReportsEvidence(t *testing.T) {
_, failure := loadAppConfiguration(context.Background(), ckconfig.Selection{Config: commandkit.Supplied[string]{Value: "fixtures/invalid.yml", Set: true}})
var native *configkit.LoadError
if !errors.As(failure, &native) || len(native.Sources()) == 0 {
t.Fatalf("fixture failure = %v", failure)
}
outcome, output, _ := executeSimple(t, "config", "validate", "--config=fixtures/invalid.yml", "--details", "--severity=error", "--format=json")
if outcome.Kind != commandkit.OutcomeOperational || !strings.Contains(output, `"state": "present"`) || !strings.Contains(output, `"severity": "error"`) {
t.Fatalf("detailed failure = %#v %q", outcome, output)
}
outcome, output, _ = executeSimple(t, "config", "sources", "--config=fixtures/invalid.yml", "--source-id="+native.Sources()[0].ID, "--format=json")
if outcome.Kind != commandkit.OutcomeOperational || !strings.Contains(output, `"id":`) || !strings.Contains(output, "invalid.yml") {
t.Fatalf("source failure = %#v %q", outcome, output)
}
}
func TestSimpleMetadataCommandsDoNotLoadConfiguration(t *testing.T) {
loads := 0
app, err := newAppWithLoader(func(context.Context, ckconfig.Selection) (*configkit.Result[appConfiguration], error) {
loads++
return nil, errors.New("unexpected load")
})
if err != nil {
t.Fatal(err)
}
for _, args := range [][]string{{"version"}, {"config", "scopes"}, {"config", "explain"}, {"config", "show", "--help"}} {
result := app.Execute(context.Background(), args, commandkit.Streams{Out: io.Discard, Err: io.Discard})
if result.Kind != commandkit.OutcomeSuccess || loads != 0 {
t.Fatalf("%v: %#v loads=%d", args, result, loads)
}
}
}