diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 9bc6466..d962763 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # Audit remediation implementation plan -Status: in progress; Stages 1–10 complete. +Status: in progress; Stages 1–11 complete. Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint, intervening changes recorded decisions and retired the completed older @@ -380,6 +380,12 @@ Complete when exported guidance accurately states the already selected rules. ## Stage 11 — Export the shared ConfigKit diagnostic adapter +**Complete.** `ckconfig.AdaptDiagnostics` captures the existing diagnostic +presentation for ordinary and comparison callers. Focused ConfigKit adapter and +comparison tests passed offline in the aligned Go 1.27.0 workspace with +`-mod=readonly`, including public helper snapshot and writer-failure coverage. +F21 remains open until ckprompt adopts it in Stage 12. + Findings: F21, ckconfig portion. Source: ckconfig `configkit_adapter.go`. 1. Expose the existing configKitDiagnosticsWithHeading responsibility as: diff --git a/integrations/ckconfig/configkit_adapter.go b/integrations/ckconfig/configkit_adapter.go index f229aa8..fd5b470 100644 --- a/integrations/ckconfig/configkit_adapter.go +++ b/integrations/ckconfig/configkit_adapter.go @@ -127,14 +127,17 @@ func loadComparisonSide[I, S, T any]( } func configKitDiagnostics(scope string, sources []configkit.SourceInfo, records []configkit.Diagnostic) ([]commandkit.Diagnostic, commandkit.Explanation) { - return configKitDiagnosticsWithHeading("Configuration scope "+strconv.Quote(scope)+":\n", sources, records) + return AdaptDiagnostics("Configuration scope "+strconv.Quote(scope)+":\n", sources, records) } func configKitComparisonDiagnostics(scope, side string, sources []configkit.SourceInfo, records []configkit.Diagnostic) ([]commandkit.Diagnostic, commandkit.Explanation) { - return configKitDiagnosticsWithHeading("Configuration scope "+strconv.Quote(scope)+" "+side+":\n", sources, records) + return AdaptDiagnostics("Configuration scope "+strconv.Quote(scope)+" "+side+":\n", sources, records) } -func configKitDiagnosticsWithHeading(heading string, sources []configkit.SourceInfo, records []configkit.Diagnostic) ([]commandkit.Diagnostic, commandkit.Explanation) { +// AdaptDiagnostics captures ConfigKit diagnostics as CLI presentation evidence. +// It does not print, acquire configuration, determine outcomes, or schedule messages. +// The heading is used as supplied when the captured evidence is rendered. +func AdaptDiagnostics(heading string, sources []configkit.SourceInfo, records []configkit.Diagnostic) ([]commandkit.Diagnostic, commandkit.Explanation) { if len(records) == 0 { return nil, commandkit.Explanation{} } diff --git a/integrations/ckconfig/contracts_adapter_test.go b/integrations/ckconfig/contracts_adapter_test.go index ff0f735..0c0e322 100644 --- a/integrations/ckconfig/contracts_adapter_test.go +++ b/integrations/ckconfig/contracts_adapter_test.go @@ -235,20 +235,23 @@ func TestFromConfigKitRetainsResultEvidenceWithApplicationFailure(t *testing.T) } func TestConfigKitDiagnosticsMapSeverityAndPropagateWriterFailures(t *testing.T) { - warning, noExplanation := configKitDiagnostics("app", nil, []configkit.Diagnostic{{Severity: configkit.SeverityWarning, Message: "safe warning"}}) + warning, noExplanation := AdaptDiagnostics("scope:\n", nil, []configkit.Diagnostic{{Severity: configkit.SeverityWarning, Message: "safe warning"}}) if len(warning) != 1 || warning[0].Severity != commandkit.SeverityWarning { t.Fatalf("warning diagnostics = %#v", warning) } if noExplanation.Message != "" || len(noExplanation.Details) != 0 { t.Fatalf("warning-only explanation = %#v", noExplanation) } - withWarning, withExplanation := configKitDiagnostics("app", nil, []configkit.Diagnostic{ + withWarning, withExplanation := AdaptDiagnostics("scope:\n", nil, []configkit.Diagnostic{ {Severity: configkit.SeverityWarning, Message: "safe warning"}, {Severity: configkit.SeverityError, Message: "safe error"}, }) if len(withWarning) != 1 || withWarning[0].Severity != commandkit.SeverityWarning || len(withExplanation.Details) != 1 { t.Fatalf("mixed diagnostics = %#v, explanation = %#v", withWarning, withExplanation) } + if empty, explanation := AdaptDiagnostics("unused", nil, nil); len(empty) != 0 || len(explanation.Details) != 0 { + t.Fatalf("empty evidence = %#v, %#v", empty, explanation) + } if err := withWarning[0].Write(shortWriter{}); !errors.Is(err, io.ErrShortWrite) { t.Fatalf("short writer error = %v, want %v", err, io.ErrShortWrite) @@ -280,10 +283,11 @@ func TestConfigKitTerminalContextRetainsRelatedLocationsAndSnapshots(t *testing. Primary: primary, Related: related, }} - _, explanation := configKitDiagnostics("app", sources, records) + _, explanation := AdaptDiagnostics("Configuration scope \"app\":\n", sources, records) primary.SourceID = "mutated-primary" related[0].SourceID = "mutated-related" sources[0].Path = "mutated.yml" + records[0].Message = "mutated message" if len(explanation.Details) != 1 { t.Fatalf("explanation = %#v", explanation) @@ -297,7 +301,7 @@ func TestConfigKitTerminalContextRetainsRelatedLocationsAndSnapshots(t *testing. t.Errorf("terminal context %q omits %q", output.String(), fragment) } } - if strings.Contains(output.String(), "mutated-") || strings.Contains(output.String(), "mutated.yml") { + if strings.Contains(output.String(), "mutated-") || strings.Contains(output.String(), "mutated.yml") || strings.Contains(output.String(), "mutated message") { t.Fatalf("terminal renderer retained caller mutations: %q", output.String()) } }