carlos / platform Public

stopevidence, hostclaim: tests follow main's FDHolders lstat rule and the enrollment parameter

The two KNOWN GAP skips come off now that main lstats the entry after an
ENOENT stat (9cf9f18f); the Reconstruct case creates the database so
identities are compared; the closed-between-reads case makes the entry
vanish too. Main's C7 contract test passes the source enrollment through
BeginMove's new parameter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 45a3af27c4e9b8a36ba4e405304521e959878eec parent 4d94f7f
3 files changed, +20 −24
  • internal/hostclaim/relocating_test.go +1 −5
  • internal/stopevidence/fdholders_alias_linux_test.go +11 −13
  • internal/stopevidence/reconstruct_linux_test.go +8 −6
diff --git a/internal/hostclaim/relocating_test.go b/internal/hostclaim/relocating_test.go
index d7a13ab81..0ee31ca2a 100644
--- a/internal/hostclaim/relocating_test.go
+++ b/internal/hostclaim/relocating_test.go
@@ -267,11 +267,7 @@ func TestC7ConvertsAClaimBeginMoveProduced(t *testing.T) {
o, err = Update(ctx, s, o, func(c *Claim) error {
c.Op = Op{Kind: KindServing, Exec: "adi-knitting"}
c.Obligations = nil
- if err := BeginMove("move-1", "member", "adi-knitting", "bri-knitting", "uk", time.Now())(c); err != nil {
- return err
- }
- c.Op.Intent["source_enrollment"] = ref.Enrollment
- return nil
+ return BeginMove("move-1", "member", "adi-knitting", ref.Enrollment, "bri-knitting", "uk", time.Now())(c)
})
if err != nil {
t.Fatal(err)
diff --git a/internal/stopevidence/fdholders_alias_linux_test.go b/internal/stopevidence/fdholders_alias_linux_test.go
index 9ee3a2b44..6c9971286 100644
--- a/internal/stopevidence/fdholders_alias_linux_test.go
+++ b/internal/stopevidence/fdholders_alias_linux_test.go
@@ -223,12 +223,20 @@ func TestFDIdentityFailsClosed(t *testing.T) {
}
}, true},
{"fd closed between the reads", func(o *fdOps) {
+ // The descriptor is gone: both the stat through the link and the
+ // entry itself answer ENOENT.
o.stat = func(p string) (os.FileInfo, error) {
if p == fdLink {
return nil, syscall.ENOENT
}
return real.stat(p)
}
+ o.lstat = func(p string) (os.FileInfo, error) {
+ if p == fdLink {
+ return nil, syscall.ENOENT
+ }
+ return real.lstat(p)
+ }
}, false},
{"fd link refused", func(o *fdOps) {
o.readlink = func(string) (string, error) { return "", syscall.EACCES }
@@ -278,16 +286,9 @@ func TestFDIdentityFailsClosed(t *testing.T) {
}
}
-// The stricter reading fix round 1 had: a descriptor whose stat says
-// ENOENT while its /proc entry is still there is unidentified, so the scan
-// is incomplete. Main's FDHolders reads stat ENOENT as "the descriptor
-// closed" without re-checking the entry. On a real /proc the stat follows
-// the kernel's magic link to the open file and does not answer ENOENT for
-// a descriptor that is still open, so this is a gap only where a stat can
-// say ENOENT for a live entry (not seen on Linux). Reported in
-// task-3-report.md ("Dedupe after main merge"), not patched here: main's
-// helper belongs to the claim code. The test skips while the gap is
-// present, and passes, unchanged, the day main closes it.
+// A descriptor whose stat through /proc answers ENOENT while its entry
+// remains is unreadable, not closed: main's FDHolders lstats the entry after
+// such a stat (9cf9f18f) and fails the scan with ErrIncomplete.
func TestFDHoldersStatGoneWhileTheEntryRemains(t *testing.T) {
root, fdLink, _, files := aliasFaultFixture(t)
ops := realFDOps()
@@ -299,9 +300,6 @@ func TestFDHoldersStatGoneWhileTheEntryRemains(t *testing.T) {
return real(p)
}
got, err := fdHolders(context.Background(), root, files, ops)
- if err == nil && len(got) == 0 {
- t.Skip("KNOWN GAP (main FDHolders): stat ENOENT on a descriptor whose /proc entry remains is read as closed; see task-3-report.md")
- }
if !errors.Is(err, ErrIncomplete) || got != nil {
t.Fatalf("%v %v, want ErrIncomplete", got, err)
}
diff --git a/internal/stopevidence/reconstruct_linux_test.go b/internal/stopevidence/reconstruct_linux_test.go
index c15e5aa6d..f533a71af 100644
--- a/internal/stopevidence/reconstruct_linux_test.go
+++ b/internal/stopevidence/reconstruct_linux_test.go
@@ -370,16 +370,18 @@ func TestReconstructDatabaseHolders(t *testing.T) {
// A descriptor whose file cannot be identified (its stat says ENOENT while
// its entry remains: here a dangling fixture link) should not pass step 3.
-// Main's FDHolders reads that as a closed descriptor; this is the gap
-// TestFDHoldersStatGoneWhileTheEntryRemains documents, seen through
-// Reconstruct. It skips while the gap is present.
+// Main's FDHolders lstats the entry after an ENOENT stat (9cf9f18f), so an
+// entry that remains but cannot be followed is ErrIncomplete, seen here
+// through Reconstruct.
func TestReconstructUnidentifiedDescriptorIsNotAPass(t *testing.T) {
f := newBoxFixture(t)
+ // The database exists, so FDHolders compares identities; a descriptor
+ // whose target cannot be followed is then unreadable, not closed.
+ if err := os.WriteFile(f.db, nil, 0o600); err != nil {
+ t.Fatal(err)
+ }
f.holdDB(t, 5150, filepath.Join(filepath.Dir(f.db), "vanished.db"))
rep, err := reconstruct(context.Background(), f.inputs(), opener(&fakeTree{}, nil))
- if err == nil && rep.Steps[moves.StepFDs].OK {
- t.Skip("KNOWN GAP (main FDHolders): stat ENOENT on a descriptor whose /proc entry remains is read as closed; see task-3-report.md")
- }
if !errors.Is(err, ErrIncomplete) {
t.Fatalf("unidentifiable descriptor: %+v %v", rep.Steps, err)
}