From 2584bc8be839c82993e5239d1338752e782a7b2d Mon Sep 17 00:00:00 2001 From: lczyk Date: Sat, 10 Oct 2026 03:15:04 +0100 Subject: [PATCH 1/2] fix: script writes to mutable paths with until set to mutate A path listed with both "mutable: true" and "until: mutate" is accepted by the release validation, but writing it from a mutation script failed the whole cut with "cannot mutate path in report: ... not previously added". Such paths are kept out of the manifest report when they are created, so the write callback has to skip the report as well. --- internal/slicer/slicer.go | 9 ++++++++- internal/slicer/slicer_test.go | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/internal/slicer/slicer.go b/internal/slicer/slicer.go index ce791162..41b150ef 100644 --- a/internal/slicer/slicer.go +++ b/internal/slicer/slicer.go @@ -327,7 +327,14 @@ func Run(options *RunOptions) error { RootDir: targetDir, CheckWrite: checker.checkMutable, CheckRead: checker.checkKnown, - OnWrite: report.Mutate, + OnWrite: func(entry *fsutil.Entry) error { + // Paths with "until: mutate" are not in the report. + relPath := filepath.Clean("/" + strings.TrimPrefix(entry.Path, targetDir)) + if knownPaths[relPath].until == setup.UntilMutate { + return nil + } + return report.Mutate(entry) + }, } for _, slice := range options.Selection.Slices { opts := scripts.RunOptions{ diff --git a/internal/slicer/slicer_test.go b/internal/slicer/slicer_test.go index d6ef9ca0..4d057dea 100644 --- a/internal/slicer/slicer_test.go +++ b/internal/slicer/slicer_test.go @@ -478,6 +478,24 @@ var slicerTests = []slicerTest{{ manifestPaths: map[string]string{ "/foo/text-file-2": "file 0644 d98cf53e 5b41362b {test-package_myslice}", }, +}, { + summary: "Script: write to a mutable path with 'until' set to mutate", + slices: []setup.SliceKey{{"test-package", "myslice"}}, + release: map[string]string{ + "slices/mydir/test-package.yaml": ` + package: test-package + slices: + myslice: + contents: + /dir/text-file: {text: data1, mutable: true, until: mutate} + mutate: | + content.write("/dir/text-file", "data2") + `, + }, + filesystem: map[string]string{ + "/dir/": "dir 0755", + }, + manifestPaths: map[string]string{}, }, { summary: "Script: use 'until' to remove wildcard after mutate", slices: []setup.SliceKey{{"test-package", "myslice"}}, From d8b1f639f2756ba31aeb1639ffd03e0c2223116e Mon Sep 17 00:00:00 2001 From: lczyk Date: Sat, 10 Oct 2026 11:53:21 +0100 Subject: [PATCH 2/2] fix: reject mutable paths with until set to mutate A path listed with both "mutable: true" and "until: mutate" passed release validation but failed the whole cut when a mutation script wrote it, with "cannot mutate path in report: ... not previously added", since such paths are kept out of the manifest report. The combination is now rejected when the release is read instead of being special-cased in the slicer, which keeps the manifest rules simple. No release branch uses it. --- internal/setup/setup_test.go | 12 ++++++++++++ internal/setup/yaml.go | 3 +++ internal/slicer/slicer.go | 9 +-------- internal/slicer/slicer_test.go | 18 ------------------ 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/internal/setup/setup_test.go b/internal/setup/setup_test.go index ec61490b..2bda79c5 100644 --- a/internal/setup/setup_test.go +++ b/internal/setup/setup_test.go @@ -878,6 +878,18 @@ var setupTests = []setupTest{{ `, }, relerror: `slice mypkg_myslice mutable is not a regular file: /path`, +}, { + summary: "Mutable path cannot have 'until' set to mutate", + input: map[string]string{ + "slices/mydir/mypkg.yaml": ` + package: mypkg + slices: + myslice: + contents: + /path: {text: data, mutable: true, until: mutate} + `, + }, + relerror: `slice mypkg_myslice cannot have both 'mutable' and 'until: mutate' for path /path`, }, { summary: "Until checks its value for validity", input: map[string]string{ diff --git a/internal/setup/yaml.go b/internal/setup/yaml.go index 1760f1b4..2ebbf352 100644 --- a/internal/setup/yaml.go +++ b/internal/setup/yaml.go @@ -666,6 +666,9 @@ func parsePackage(release *Release, pkgName, pkgPath string, data []byte) (*Pack if mutable && kinds[0] != TextPath && (kinds[0] != CopyPath || isDir) { return nil, fmt.Errorf("slice %s_%s mutable is not a regular file: %s", pkg.Name, sliceName, contPath) } + if mutable && until == UntilMutate { + return nil, fmt.Errorf("slice %s_%s cannot have both 'mutable' and 'until: mutate' for path %s", pkg.Name, sliceName, contPath) + } slice.Contents[contPath] = PathInfo{ Kind: kinds[0], Info: info, diff --git a/internal/slicer/slicer.go b/internal/slicer/slicer.go index 41b150ef..ce791162 100644 --- a/internal/slicer/slicer.go +++ b/internal/slicer/slicer.go @@ -327,14 +327,7 @@ func Run(options *RunOptions) error { RootDir: targetDir, CheckWrite: checker.checkMutable, CheckRead: checker.checkKnown, - OnWrite: func(entry *fsutil.Entry) error { - // Paths with "until: mutate" are not in the report. - relPath := filepath.Clean("/" + strings.TrimPrefix(entry.Path, targetDir)) - if knownPaths[relPath].until == setup.UntilMutate { - return nil - } - return report.Mutate(entry) - }, + OnWrite: report.Mutate, } for _, slice := range options.Selection.Slices { opts := scripts.RunOptions{ diff --git a/internal/slicer/slicer_test.go b/internal/slicer/slicer_test.go index 4d057dea..d6ef9ca0 100644 --- a/internal/slicer/slicer_test.go +++ b/internal/slicer/slicer_test.go @@ -478,24 +478,6 @@ var slicerTests = []slicerTest{{ manifestPaths: map[string]string{ "/foo/text-file-2": "file 0644 d98cf53e 5b41362b {test-package_myslice}", }, -}, { - summary: "Script: write to a mutable path with 'until' set to mutate", - slices: []setup.SliceKey{{"test-package", "myslice"}}, - release: map[string]string{ - "slices/mydir/test-package.yaml": ` - package: test-package - slices: - myslice: - contents: - /dir/text-file: {text: data1, mutable: true, until: mutate} - mutate: | - content.write("/dir/text-file", "data2") - `, - }, - filesystem: map[string]string{ - "/dir/": "dir 0755", - }, - manifestPaths: map[string]string{}, }, { summary: "Script: use 'until' to remove wildcard after mutate", slices: []setup.SliceKey{{"test-package", "myslice"}},