moves copy: keep the files root's archive errors untyped, and MV-N42's escaping (Astra review)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+14
−3
internal/moves/archive/archive.go+5 −2internal/provision/provision.go+9 −1
CI passed — run details
diff --git a/internal/moves/archive/archive.go b/internal/moves/archive/archive.go| index cecd1306b..648743084 100644 |
| --- a/internal/moves/archive/archive.go |
| +++ b/internal/moves/archive/archive.go |
| @@ -282,7 +282,9 @@ func openFilesRoot(filesDir string) (*os.Root, error) { |
| return nil, err |
| } |
| if !info.IsDir() { |
| - return nil, specialFileError(filesDir) |
| + // The files root itself is not a directory: untyped, so the caller |
| + // reports it as the files failing to archive, never as a <path>. |
| + return nil, fmt.Errorf("%w: %s", ErrSpecialFile, filesDir) |
| } |
| root, err := os.OpenRoot(filesDir) |
| if err != nil { |
| @@ -295,7 +297,8 @@ func openFilesRoot(filesDir string) (*os.Root, error) { |
| } |
| if !os.SameFile(info, got) { |
| root.Close() |
| - return nil, changedError(filesDir) |
| + // The root itself, not a file in it: untyped, as above. |
| + return nil, fmt.Errorf("%w: %s", ErrChanged, filesDir) |
| } |
| return root, nil |
| } |
diff --git a/internal/provision/provision.go b/internal/provision/provision.go| index 03e3fcee6..b8f125f71 100644 |
| --- a/internal/provision/provision.go |
| +++ b/internal/provision/provision.go |
| @@ -1350,7 +1350,7 @@ func MoveGuardedAbort(g MoveGuard) func(*Record) error { |
| case MoveAborting: |
| return fmt.Errorf("%w: %s", ErrMoveConflict, refusedAlreadyAborting) |
| default: |
| - return fmt.Errorf("%w: %s", ErrMoveConflict, fmt.Sprintf("a move in state \"%s\" cannot be aborted", rec.Move.State)) |
| + return fmt.Errorf("%w: %s", ErrMoveConflict, fmt.Sprintf("a move in state \"%s\" cannot be aborted", quotedInner(rec.Move.State))) |
| } |
| rec.Move.State = MoveAborting |
| return nil |
| @@ -2746,3 +2746,11 @@ func PutManifestIfMatch(ctx context.Context, s release.Store, acct, app string, |
| }) |
| return |
| } |
| + |
| +// quotedInner is strconv.Quote without the surrounding quotes: the approved |
| +// sentence (MV-N42) carries its own quotes, and the state inside them keeps |
| +// %q's escaping of quotes, backslashes and control characters. |
| +func quotedInner(s string) string { |
| + q := strconv.Quote(s) |
| + return q[1 : len(q)-1] |
| +} |