Clarify configuration evidence providers
This commit is contained in:
@@ -253,6 +253,9 @@ owns operator syntax and report details. `Scope.Features` declares optional mode
|
|||||||
`View.Metadata` and `View.Findings` carry native path metadata and diagnostic
|
`View.Metadata` and `View.Findings` carry native path metadata and diagnostic
|
||||||
records when available. `Findings.Messages` contains presentations associated
|
records when available. `Findings.Messages` contains presentations associated
|
||||||
with those records, while `Acquired.Diagnostics` carries independent messages.
|
with those records, while `Acquired.Diagnostics` carries independent messages.
|
||||||
|
Custom explanation providers implement `Explain`; CommandKit renders the
|
||||||
|
acquired evidence. See `EffectiveWriter` and `ExplanationReader` in the
|
||||||
|
[ckconfig GoDoc](../../integrations/ckconfig/contracts.go) for the provider boundary.
|
||||||
Set `Scope.Features.Overrides` only when the loader passes
|
Set `Scope.Features.Overrides` only when the loader passes
|
||||||
`Selection.Overrides` to ConfigKit as part of its normal load. The integration
|
`Selection.Overrides` to ConfigKit as part of its normal load. The integration
|
||||||
parses CLI records but cannot add them to an already-loaded result. The
|
parses CLI records but cannot add them to an already-loaded result. The
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Consolidated audit catalog
|
# Consolidated audit catalog
|
||||||
|
|
||||||
Status: **18 distinct open findings: 18 P3**, consolidated from 30
|
Status: **16 distinct open findings: 16 P3**, consolidated from 30
|
||||||
numbered entries in seven audit documents. Reviewed audit state:
|
numbered entries in seven audit documents. Reviewed audit state:
|
||||||
`db732e3a1de55c844f77870e1c6db683c2685d5d`.
|
`db732e3a1de55c844f77870e1c6db683c2685d5d`.
|
||||||
|
|
||||||
@@ -191,6 +191,9 @@ consumer without changing core's optional-binding semantics.
|
|||||||
|
|
||||||
## F09 — P3: The configuration provider contract omits its required YAML representation
|
## F09 — P3: The configuration provider contract omits its required YAML representation
|
||||||
|
|
||||||
|
**Complete.** EffectiveWriter, View and DecodeYAMLReport GoDoc describe the
|
||||||
|
actual single-document conversion boundary; focused conversion and show tests pass.
|
||||||
|
|
||||||
`EffectiveWriter` GoDoc promises a native representation but omits that `config
|
`EffectiveWriter` GoDoc promises a native representation but omits that `config
|
||||||
show` converts one supported YAML document through `DecodeYAMLReport`. Custom
|
show` converts one supported YAML document through `DecodeYAMLReport`. Custom
|
||||||
providers can supply output that appears compatible with the description yet
|
providers can supply output that appears compatible with the description yet
|
||||||
@@ -227,6 +230,10 @@ of the documentation correction remains pending.
|
|||||||
|
|
||||||
## F11 — P3: Explanation providers must implement an unused text writer
|
## F11 — P3: Explanation providers must implement an unused text writer
|
||||||
|
|
||||||
|
**Complete.** The interface now requires only Explain; focused tests exercise an
|
||||||
|
Explain-only provider for focused show and explanations across formats, including
|
||||||
|
delivery failures. Obsolete writer stubs and the interface-writer assertion were removed.
|
||||||
|
|
||||||
`ExplanationReader` still requires `WriteExplanation` even though production
|
`ExplanationReader` still requires `WriteExplanation` even though production
|
||||||
show/explain handlers acquire evidence with `Explain` and core renders it. This
|
show/explain handlers acquire evidence with `Explain` and core renders it. This
|
||||||
burdens custom providers and suggests a presentation responsibility that
|
burdens custom providers and suggests a presentation responsibility that
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Audit remediation implementation plan
|
# Audit remediation implementation plan
|
||||||
|
|
||||||
Status: in progress; Stages 1–8 complete.
|
Status: in progress; Stages 1–9 complete.
|
||||||
|
|
||||||
Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint,
|
Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint,
|
||||||
intervening changes recorded decisions and retired the completed older
|
intervening changes recorded decisions and retired the completed older
|
||||||
@@ -327,6 +327,12 @@ Complete when both paths use one implementation of the delicate detail policy.
|
|||||||
|
|
||||||
## Stage 9 — Clarify configuration evidence and narrow explanation providers
|
## Stage 9 — Clarify configuration evidence and narrow explanation providers
|
||||||
|
|
||||||
|
**Complete.** Provider and YAML conversion GoDoc now describe the current
|
||||||
|
handoff; `ExplanationReader` only acquires evidence. Focused core YAML conversion
|
||||||
|
and ckconfig show, explanation, adapter and delivery tests passed in the offline
|
||||||
|
aligned Go 1.27.0 workspace with `-mod=readonly`, including a custom Explain-only
|
||||||
|
provider for path show and all explanation formats.
|
||||||
|
|
||||||
Findings: F09 and F11. Sources: ckconfig `contracts.go`, `show.go`, `explain.go`,
|
Findings: F09 and F11. Sources: ckconfig `contracts.go`, `show.go`, `explain.go`,
|
||||||
`contracts_adapter_test.go`; core `report_value.go` conversion GoDoc.
|
`contracts_adapter_test.go`; core `report_value.go` conversion GoDoc.
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ type ComparisonRequest[I any] struct {
|
|||||||
Right ComparisonSide[I]
|
Right ComparisonSide[I]
|
||||||
}
|
}
|
||||||
|
|
||||||
// EffectiveWriter writes a configuration's native effective representation.
|
// EffectiveWriter writes one safe YAML document for conversion by
|
||||||
|
// commandkit.DecodeYAMLReport. Its formatting, comments, and framing are not
|
||||||
|
// passed through to the resulting report.
|
||||||
type EffectiveWriter interface {
|
type EffectiveWriter interface {
|
||||||
WriteEffective(io.Writer) error
|
WriteEffective(io.Writer) error
|
||||||
}
|
}
|
||||||
@@ -73,14 +75,12 @@ type SourceReader interface {
|
|||||||
Sources() []configkit.SourceInfo
|
Sources() []configkit.SourceInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExplanationReader reads and writes a configuration's native path explanation.
|
// ExplanationReader acquires a configuration's native path explanation.
|
||||||
type ExplanationReader interface {
|
type ExplanationReader interface {
|
||||||
Explain(configkit.Path) (configkit.Explanation, bool)
|
Explain(configkit.Path) (configkit.Explanation, bool)
|
||||||
WriteExplanation(io.Writer, configkit.Path) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// View exposes native configuration facets without reconstructing their
|
// View exposes native configuration evidence used to build reports.
|
||||||
// reports.
|
|
||||||
type View struct {
|
type View struct {
|
||||||
Effective EffectiveWriter
|
Effective EffectiveWriter
|
||||||
Sources SourceReader
|
Sources SourceReader
|
||||||
|
|||||||
@@ -117,13 +117,6 @@ func TestFromConfigKitPassesInvocationValuesAndNativeFacets(t *testing.T) {
|
|||||||
if _, ok := acquired.Value.Explanation.Explain(path); !ok {
|
if _, ok := acquired.Value.Explanation.Explain(path); !ok {
|
||||||
t.Fatal("native explanation facet did not resolve /name")
|
t.Fatal("native explanation facet did not resolve /name")
|
||||||
}
|
}
|
||||||
var explanation bytes.Buffer
|
|
||||||
if err := acquired.Value.Explanation.WriteExplanation(&explanation, path); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if explanation.Len() == 0 {
|
|
||||||
t.Fatal("native explanation writer produced no output")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromConfigKitRetainsLoadErrorAndDiagnostics(t *testing.T) {
|
func TestFromConfigKitRetainsLoadErrorAndDiagnostics(t *testing.T) {
|
||||||
|
|||||||
@@ -491,16 +491,21 @@ func TestExplainUsesAcquiredEvidenceAcrossFormats(t *testing.T) {
|
|||||||
t.Fatal("missing fixture explanation")
|
t.Fatal("missing fixture explanation")
|
||||||
}
|
}
|
||||||
metadata := result.Metadata()
|
metadata := result.Metadata()
|
||||||
reader := &failingNativeExplanation{value: explanation, err: errors.New("provider writer must not run")}
|
reader := &nativeExplanation{value: explanation, found: true}
|
||||||
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{Name: "tenant", Operations: []Operation{Explain}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{Name: "tenant", Operations: []Operation{Explain, Show}, Features: ScopeFeatures{PathShow: true}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
||||||
return commandkit.Acquired[View]{Value: View{Explanation: reader, Metadata: &metadata, Sources: result}}, nil
|
return commandkit.Acquired[View]{Value: View{Explanation: reader, Metadata: &metadata, Sources: result}}, nil
|
||||||
}}}}))
|
}}}}))
|
||||||
|
var focused bytes.Buffer
|
||||||
|
show := app.Execute(context.Background(), []string{"config", "show", "--scope=tenant", "--path=/token", "--format=json"}, commandkit.Streams{Out: &focused, Err: io.Discard})
|
||||||
|
if show.Kind != commandkit.OutcomeSuccess || strings.TrimSpace(focused.String()) != `"after"` {
|
||||||
|
t.Fatalf("focused show = %#v %q", show, focused.String())
|
||||||
|
}
|
||||||
for _, format := range []string{"text", "yaml", "json"} {
|
for _, format := range []string{"text", "yaml", "json"} {
|
||||||
t.Run(format, func(t *testing.T) {
|
t.Run(format, func(t *testing.T) {
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
outcome := app.Execute(context.Background(), []string{"config", "explain", "--scope=tenant", "--path=/token", "--format=" + format}, commandkit.Streams{Out: &output, Err: io.Discard})
|
outcome := app.Execute(context.Background(), []string{"config", "explain", "--scope=tenant", "--path=/token", "--format=" + format}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||||
if outcome.Status != 0 || reader.calls != 0 {
|
if outcome.Status != 0 {
|
||||||
t.Fatalf("outcome=%#v writer calls=%d output=%s", outcome, reader.calls, output.String())
|
t.Fatalf("outcome=%#v output=%s", outcome, output.String())
|
||||||
}
|
}
|
||||||
for _, evidence := range []string{"tenant", "/token", "before", "after", "original source", "--fixture-set"} {
|
for _, evidence := range []string{"tenant", "/token", "before", "after", "original source", "--fixture-set"} {
|
||||||
if !strings.Contains(output.String(), evidence) {
|
if !strings.Contains(output.String(), evidence) {
|
||||||
@@ -586,35 +591,11 @@ type nativeExplanation struct {
|
|||||||
found bool
|
found bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type failingNativeExplanation struct {
|
|
||||||
err error
|
|
||||||
value configkit.Explanation
|
|
||||||
calls int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (reader *failingNativeExplanation) Explain(configkit.Path) (configkit.Explanation, bool) {
|
|
||||||
return reader.value, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (reader *failingNativeExplanation) WriteExplanation(writer io.Writer, _ configkit.Path) error {
|
|
||||||
reader.calls++
|
|
||||||
if _, err := io.WriteString(writer, "partial native explanation\n"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return reader.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (reader *nativeExplanation) Explain(path configkit.Path) (configkit.Explanation, bool) {
|
func (reader *nativeExplanation) Explain(path configkit.Path) (configkit.Explanation, bool) {
|
||||||
reader.path = path
|
reader.path = path
|
||||||
return reader.value, reader.found
|
return reader.value, reader.found
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reader *nativeExplanation) WriteExplanation(writer io.Writer, path configkit.Path) error {
|
|
||||||
reader.path = path
|
|
||||||
_, err := fmt.Fprintf(writer, "Explanation for %q\n", path.String())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type failingOutput struct{ err error }
|
type failingOutput struct{ err error }
|
||||||
|
|
||||||
func (writer failingOutput) Write(value []byte) (int, error) {
|
func (writer failingOutput) Write(value []byte) (int, error) {
|
||||||
|
|||||||
@@ -32,9 +32,10 @@ func normalizeReport(value any) (any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DecodeYAMLReport decodes one YAML document from safe inspection output into
|
// DecodeYAMLReport decodes one YAML document from safe inspection output into
|
||||||
// JSON-compatible values. Unsupported YAML meanings return an error; the result
|
// JSON-compatible values. Multiple documents, unsupported tags and non-string
|
||||||
// is not a lossless representation of the input document. Scalar types follow
|
// mapping keys return an error. The result does not preserve formatting, comments
|
||||||
// the resolved YAML tags; numeric-looking strings remain strings.
|
// or framing. Scalar types follow resolved YAML tags; numeric-looking strings
|
||||||
|
// remain strings.
|
||||||
func DecodeYAMLReport(reader io.Reader) (any, error) {
|
func DecodeYAMLReport(reader io.Reader) (any, error) {
|
||||||
decoder := yaml.NewDecoder(reader)
|
decoder := yaml.NewDecoder(reader)
|
||||||
var doc yaml.Node
|
var doc yaml.Node
|
||||||
|
|||||||
Reference in New Issue
Block a user