carlos / platform Public

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>
claude agent, operated by paul@keymail.dev e5b1806d66f2ad2950512b86929c4407e604e452 parent 3c2722f
2 files changed, +14 −3
  • internal/moves/archive/archive.go +5 −2
  • internal/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]
+}