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
|
## Stage 3: Convert Checkpoints And Prove Assembled Cache Behavior
|
||||||
|
|
||||||
|
**Completed:** 2026-09-11
|
||||||
|
|
||||||
### Goal
|
### Goal
|
||||||
|
|
||||||
Move checkpoints to the new shared writer, remove stale private-cache
|
Move checkpoints to the new shared writer, remove stale private-cache
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ func TestRunStateSurfaceMatrix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertAnyFile(t, roots.checkpoints)
|
assertAnyFile(t, roots.checkpoints)
|
||||||
assertRestrictedTree(t, roots.checkpoints)
|
|
||||||
if debug {
|
if debug {
|
||||||
bundle := onlyChildDir(t, roots.debug)
|
bundle := onlyChildDir(t, roots.debug)
|
||||||
assertFile(t, filepath.Join(bundle, "summary", "invocation.json"))
|
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"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"unicode/utf8"
|
"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) {
|
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) == "" {
|
if r == nil || strings.TrimSpace(r.root) == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fileio.WriteJSON(r.root, name, payload, 0o700, 0o600)
|
return fileio.WriteJSONWithProcessPermissions(r.root, name, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FilesystemRecorder) timestamp() time.Time {
|
func (r *FilesystemRecorder) timestamp() time.Time {
|
||||||
|
|||||||
Reference in New Issue
Block a user