Apply deployment permissions to checkpoints
This commit is contained in:
@@ -179,6 +179,8 @@ passes. This stage is small enough for one implementation prompt.
|
||||
|
||||
## Stage 3: Convert Checkpoints And Prove Assembled Cache Behavior
|
||||
|
||||
**Completed:** 2026-09-11
|
||||
|
||||
### Goal
|
||||
|
||||
Move checkpoints to the new shared writer, remove stale private-cache
|
||||
|
||||
@@ -57,7 +57,6 @@ func TestRunStateSurfaceMatrix(t *testing.T) {
|
||||
}
|
||||
}
|
||||
assertAnyFile(t, roots.checkpoints)
|
||||
assertRestrictedTree(t, roots.checkpoints)
|
||||
if debug {
|
||||
bundle := onlyChildDir(t, roots.debug)
|
||||
assertFile(t, filepath.Join(bundle, "summary", "invocation.json"))
|
||||
|
||||
61
internal/cli/state_permissions_unix_test.go
Normal file
61
internal/cli/state_permissions_unix_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRunCreatesReusableGroupAccessibleCacheState(t *testing.T) {
|
||||
previousUmask := syscall.Umask(0o007)
|
||||
defer syscall.Umask(previousUmask)
|
||||
|
||||
roots := newStateTestRoots(t)
|
||||
harness := newStateTestHarness()
|
||||
result := runStateTest(t, roots, harness.options(), false, false, "auto")
|
||||
if result.code != 0 {
|
||||
t.Fatalf("initial run code=%d stderr=%q", result.code, result.stderr)
|
||||
}
|
||||
assertGroupAccessibleCacheTree(t, roots.plans)
|
||||
assertGroupAccessibleCacheTree(t, roots.checkpoints)
|
||||
|
||||
extractCalls := harness.extractCalls
|
||||
result = runStateTest(t, roots, harness.options(), false, true, "auto")
|
||||
if result.code != 0 {
|
||||
t.Fatalf("resume run code=%d stderr=%q", result.code, result.stderr)
|
||||
}
|
||||
if harness.extractCalls != extractCalls {
|
||||
t.Fatalf("extract calls after reuse = %d, want %d", harness.extractCalls, extractCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func assertGroupAccessibleCacheTree(t *testing.T, root string) {
|
||||
t.Helper()
|
||||
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
want := os.FileMode(0o660)
|
||||
if info.IsDir() {
|
||||
want = 0o770
|
||||
}
|
||||
if got := info.Mode().Perm(); got != want {
|
||||
return &cachePermissionError{path: path, got: got, want: want}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type cachePermissionError struct {
|
||||
path string
|
||||
got, want os.FileMode
|
||||
}
|
||||
|
||||
func (err *cachePermissionError) Error() string {
|
||||
return err.path + " mode mismatch"
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||
)
|
||||
|
||||
func TestFilesystemCheckpointPermissionsFollowUmask(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
umask int
|
||||
dirMode os.FileMode
|
||||
fileMode os.FileMode
|
||||
}{
|
||||
{name: "private", umask: 0o077, dirMode: 0o700, fileMode: 0o600},
|
||||
{name: "group", umask: 0o007, dirMode: 0o770, fileMode: 0o660},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
previousUmask := syscall.Umask(test.umask)
|
||||
defer syscall.Umask(previousUmask)
|
||||
|
||||
root := filepath.Join(t.TempDir(), "checkpoints")
|
||||
identity, err := NewIdentity(representativeIdentityInput())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
recorder, err := NewFilesystemRecorder(root, identity)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := recorder.SourceSucceeded("source-module", sourceDocumentForPermissions()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
relative, err := identity.RelativePath()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
identityRoot := filepath.Join(root, filepath.FromSlash(relative))
|
||||
sourceRoot := filepath.Join(identityRoot, "source")
|
||||
for path, want := range map[string]os.FileMode{
|
||||
root: test.dirMode,
|
||||
identityRoot: test.dirMode,
|
||||
sourceRoot: test.dirMode,
|
||||
filepath.Join(sourceRoot, "manifest.json"): test.fileMode,
|
||||
filepath.Join(sourceRoot, "source-document.json"): test.fileMode,
|
||||
} {
|
||||
assertCheckpointPermissions(t, path, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilesystemCheckpointLeavesExistingRootUntouched(t *testing.T) {
|
||||
previousUmask := syscall.Umask(0o077)
|
||||
defer syscall.Umask(previousUmask)
|
||||
|
||||
root := filepath.Join(t.TempDir(), "checkpoints")
|
||||
syscall.Umask(0)
|
||||
if err := os.Mkdir(root, 0o751); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
syscall.Umask(0o077)
|
||||
identity, err := NewIdentity(representativeIdentityInput())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
recorder, err := NewFilesystemRecorder(root, identity)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := recorder.SourceSucceeded("source-module", sourceDocumentForPermissions()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertCheckpointPermissions(t, root, 0o751)
|
||||
}
|
||||
|
||||
func sourceDocumentForPermissions() *source.SourceDocument {
|
||||
document := checkpointDocument()
|
||||
return &document
|
||||
}
|
||||
|
||||
func assertCheckpointPermissions(t *testing.T, path string, want os.FileMode) {
|
||||
t.Helper()
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := info.Mode().Perm(); got != want {
|
||||
t.Fatalf("%s mode = %#o, want %#o", path, got, want)
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
@@ -106,27 +105,6 @@ func TestFilesystemCheckpointRoundTripsAllStages(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Run("restrictive permissions", func(t *testing.T) {
|
||||
root := filepath.Join(fixture.root, mustRelativePath(t, fixture.identity))
|
||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
want := os.FileMode(0o600)
|
||||
if info.IsDir() {
|
||||
want = 0o700
|
||||
}
|
||||
if got := info.Mode().Perm(); got != want {
|
||||
t.Errorf("%s permissions = %o, want %o", path, got, want)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilesystemCheckpointRejectsMissingAndCorruptState(t *testing.T) {
|
||||
|
||||
@@ -217,7 +217,7 @@ func (r *FilesystemRecorder) writeJSON(name string, payload any) error {
|
||||
if r == nil || strings.TrimSpace(r.root) == "" {
|
||||
return nil
|
||||
}
|
||||
return fileio.WriteJSON(r.root, name, payload, 0o700, 0o600)
|
||||
return fileio.WriteJSONWithProcessPermissions(r.root, name, payload)
|
||||
}
|
||||
|
||||
func (r *FilesystemRecorder) timestamp() time.Time {
|
||||
|
||||
Reference in New Issue
Block a user