diff --git a/docs/roadmap/audit/catalog.md b/docs/roadmap/audit/catalog.md index d7d6918..325f351 100644 --- a/docs/roadmap/audit/catalog.md +++ b/docs/roadmap/audit/catalog.md @@ -1,6 +1,6 @@ # Consolidated audit catalog -Status: **4 distinct open findings: 4 P3**, consolidated from 30 +Status: **3 distinct open findings: 3 P3**, consolidated from 30 numbered entries in seven audit documents. Reviewed audit state: `db732e3a1de55c844f77870e1c6db683c2685d5d`. @@ -507,6 +507,9 @@ construction becomes linear. ## F23 — P3: Concise diff rendering validates already-valid native paths +**Complete.** The private decoder accepts ConfigKit's native path and performs +single-pass token decoding. Focused diff, token and snapshot tests pass. + ckconfig's private pointer decoder validates arbitrary strings even though its sole production caller supplies the canonical string of an immutable ConfigKit `Path`. Tests sustain malformed-input branches that this boundary cannot reach. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 0d4a661..b9840e4 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # Audit remediation implementation plan -Status: in progress; Stages 1–20 complete. +Status: in progress; Stages 1–21 complete. Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint, intervening changes recorded decisions and retired the completed older @@ -662,6 +662,11 @@ record these outcomes separately. ## Stage 21 — Decode native diff paths without revalidation +**Complete.** Concise diff adaptation decodes tokens from native ConfigKit paths +without revalidating arbitrary strings. Focused diff, pointer and snapshot tests +pass offline in the aligned Go 1.27.0 workspace with `-mod=readonly`, including +root, empty, Unicode and escaped keys. + Findings: F23. Source: ckconfig `diff_report.go` and diff report tests. 1. Change the private token helper to accept configkit.Path. Split its canonical diff --git a/integrations/ckconfig/diff_report.go b/integrations/ckconfig/diff_report.go index da4d1c8..fa368d7 100644 --- a/integrations/ckconfig/diff_report.go +++ b/integrations/ckconfig/diff_report.go @@ -8,39 +8,17 @@ import ( configkit "go.maximumdirect.net/configkit" ) -func decodeJSONPointer(pointer string) ([]string, error) { +func decodeJSONPointer(path configkit.Path) []string { + pointer := path.String() if pointer == "" { - return nil, nil + return nil } - if pointer[0] != '/' { - return nil, fmt.Errorf("decode JSON pointer %q: path must start with /", pointer) - } - parts := strings.Split(pointer[1:], "/") + decoder := strings.NewReplacer("~1", "/", "~0", "~") for index, part := range parts { - var decoded strings.Builder - decoded.Grow(len(part)) - for offset := 0; offset < len(part); offset++ { - if part[offset] != '~' { - decoded.WriteByte(part[offset]) - continue - } - if offset+1 >= len(part) { - return nil, fmt.Errorf("decode JSON pointer %q: incomplete escape in token %d", pointer, index) - } - offset++ - switch part[offset] { - case '0': - decoded.WriteByte('~') - case '1': - decoded.WriteByte('/') - default: - return nil, fmt.Errorf("decode JSON pointer %q: invalid escape in token %d", pointer, index) - } - } - parts[index] = decoded.String() + parts[index] = decoder.Replace(part) } - return parts, nil + return parts } func placeReportValue(root any, path []string, value any) (any, error) { @@ -135,10 +113,7 @@ func renderDiffReport(differences []configkit.Difference) (any, error) { if !difference.Right.Present { continue } - tokens, err := decodeJSONPointer(path) - if err != nil { - return nil, fmt.Errorf("difference at %q: %w", path, err) - } + tokens := decodeJSONPointer(difference.Path) value, err := commandkit.DecodeYAMLReport(strings.NewReader(difference.Right.YAML)) if err != nil { return nil, fmt.Errorf("difference at %q: %w", path, err) diff --git a/integrations/ckconfig/diff_report_test.go b/integrations/ckconfig/diff_report_test.go index c6bf562..2698586 100644 --- a/integrations/ckconfig/diff_report_test.go +++ b/integrations/ckconfig/diff_report_test.go @@ -3,6 +3,8 @@ package ckconfig import ( "reflect" "testing" + + configkit "go.maximumdirect.net/configkit" ) func TestDecodeJSONPointerPreservesSemanticTokens(t *testing.T) { @@ -16,21 +18,18 @@ func TestDecodeJSONPointerPreservesSemanticTokens(t *testing.T) { {pointer: "/0/../a.b/50%", want: []string{"0", "..", "a.b", "50%"}}, {pointer: "/a~1b/c~0d", want: []string{"a/b", "c~d"}}, {pointer: "/~01", want: []string{"~1"}}, + {pointer: "/café", want: []string{"café"}}, } for _, test := range tests { - got, err := decodeJSONPointer(test.pointer) + path, err := configkit.ParsePath(test.pointer) if err != nil { - t.Fatalf("decode %q: %v", test.pointer, err) + t.Fatal(err) } + got := decodeJSONPointer(path) if !reflect.DeepEqual(got, test.want) { t.Errorf("decode %q = %#v, want %#v", test.pointer, got, test.want) } } - for _, pointer := range []string{"missing-leading-slash", "/bad~", "/bad~2escape"} { - if _, err := decodeJSONPointer(pointer); err == nil { - t.Errorf("decode %q unexpectedly succeeded", pointer) - } - } } func TestPlaceReportValue(t *testing.T) {