carlos / platform Public

moves: end-to-end loss admissions through the real writer (Task 4 loss-boundary rule, platform-1e item 4)

Declared (two target sessions and the cut) and fenced: 0 in the
validated evidence and in the persisted WritesLost/AdditionalWritesLost
for every target generation the restore omits, every tuple kept, the
cut at seal N; a replay over the stale or the admitted observation
declares nothing again; an omitted or duplicated tuple is refused with
the claim unmoved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev f8997c3c0077e58b73c9fde40a303f99612299b9 parent 92079fa
1 file changed, +112 −0
  • internal/host/movesloss_test.go +112 −0
diff --git a/internal/host/movesloss_test.go b/internal/host/movesloss_test.go
index f6867a9fd..10a9186ab 100644
--- a/internal/host/movesloss_test.go
+++ b/internal/host/movesloss_test.go
@@ -848,3 +848,115 @@ func TestLostTargetGenerationOutsideTheRestoreDeclaresAllItsWrites(t *testing.T)
t.Fatalf("declared %+v / %+v", a.WritesLost, a.AdditionalWritesLost)
}
}
+
+// Item 4 of platform-1e's review: the loss admission end to end, through
+// the real writer, the moves validator, the moves loss owner and the
+// claim's AdmitWriterWithLoss or AdmitWriterWithFencedTargetLoss. Every
+// target tuple the restore omits is 0 in the validated evidence and in the
+// persisted entry, every tuple is kept, the cut session is the seal's N; a
+// replay declares nothing again, and an omitted or duplicated tuple is
+// refused with the claim unmoved.
+func TestLossAdmissionEndToEndThroughTheWriter(t *testing.T) {
+ ctx := context.Background()
+ twoSessions := func(c *hostclaim.Claim) {
+ c.Holder.Session = 25
+ c.Holder.Admissions = append(c.Holder.Admissions, hostclaim.AdmissionRecord{Session: 25, Box: "uk-box", Generation: "t2", Predecessor: poOp, Region: "uk", PredecessorRegion: "uk", Mode: "cold"})
+ }
+ type setup struct {
+ w hostbox.Writer
+ // want are the validated maxima by generation; primary and more the
+ // persisted entries.
+ want map[string]uint64
+ primary hostclaim.WritesLost
+ more []hostclaim.WritesLost
+ }
+ paths := map[string]func(f *rcFixture) setup{
+ "declared": func(f *rcFixture) setup {
+ pub := f.declare(false, []hostclaim.WriteSource{rcTargetSource("t2", 25), rcTargetSource(poOp, 20), rcCutSource()}, twoSessions)
+ return setup{w: f.lossWriter(pub), want: map[string]uint64{"t2": 0, poOp: 0, "src": rcSealN},
+ primary: hostclaim.WritesLost{Box: "uk-box", Generation: "t2", Session: 25, RemoteMaxTXID: 0},
+ more: []hostclaim.WritesLost{{Box: "uk-box", Generation: poOp, Session: 20, RemoteMaxTXID: 0}, {Box: "ie-box", Generation: "src", Session: rcS, RemoteMaxTXID: rcSealN}}}
+ },
+ "fenced": func(f *rcFixture) setup {
+ f.fence()
+ return setup{w: f.lossWriter(nil), want: map[string]uint64{poOp: 0}, primary: hostclaim.WritesLost{Box: "uk-box", Generation: poOp, Session: 20, RemoteMaxTXID: 0}}
+ },
+ }
+ // recording wraps the writer's loss sources: it keeps what the moves
+ // validator answered and lets a case alter what the writer receives.
+ recording := func(w *hostbox.Writer, alter func([]hostclaim.ValidatedWriteSource) []hostclaim.ValidatedWriteSource) *[]hostclaim.ValidatedWriteSource {
+ var got []hostclaim.ValidatedWriteSource
+ inner := w.Loss.Sources
+ w.Loss.Sources = func(ctx context.Context, o hostclaim.Observed, l hostclaim.LossRecord, in hostclaim.Admission, p *genselect.Plan) ([]hostclaim.ValidatedWriteSource, error) {
+ v, err := inner(ctx, o, l, in, p)
+ got = append([]hostclaim.ValidatedWriteSource(nil), v...)
+ if err == nil && alter != nil {
+ v = alter(v)
+ }
+ return v, err
+ }
+ return &got
+ }
+ for name, build := range paths {
+ t.Run(name+": admitted, zero in the evidence and the entry", func(t *testing.T) {
+ f := newRCFixture(t, rcClean)
+ st := build(f)
+ validated := recording(&st.w, nil)
+ o := f.read()
+ in := f.selectAdmission(o)
+ admitted, err := st.w.Admit(ctx, o, in.Admission, in.Baseline)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(*validated) != len(st.want) {
+ t.Fatalf("validated %+v", *validated)
+ }
+ for _, v := range *validated {
+ if want, ok := st.want[v.Generation]; !ok || v.RemoteMaxTXID != want {
+ t.Fatalf("validated %s at %d, want %d", v.Generation, v.RemoteMaxTXID, want)
+ }
+ }
+ a := admitted.Claim.Holder.Admissions[len(admitted.Claim.Holder.Admissions)-1]
+ if a.WritesLost == nil || *a.WritesLost != st.primary || len(a.AdditionalWritesLost) != len(st.more) || (len(st.more) != 0 && !reflect.DeepEqual(a.AdditionalWritesLost, st.more)) {
+ t.Fatalf("persisted %+v / %+v", a.WritesLost, a.AdditionalWritesLost)
+ }
+ // Replay: the same admission again, over the observation it was
+ // made on and over the claim it made, declares nothing again.
+ after := f.read()
+ if _, err := st.w.Admit(ctx, o, in.Admission, in.Baseline); err == nil {
+ t.Fatal("a replay over the stale observation was admitted")
+ }
+ if _, err := st.w.Admit(ctx, after, in.Admission, in.Baseline); err == nil {
+ t.Fatal("a replay over the admitted claim was admitted")
+ }
+ again := f.read()
+ declared := 0
+ for _, e := range again.Claim.Holder.Admissions {
+ if e.WritesLost != nil {
+ declared++
+ }
+ }
+ if again.ETag != after.ETag || declared != 1 {
+ t.Fatalf("the replay moved the claim: %d declaring admissions", declared)
+ }
+ })
+ for refusal, alter := range map[string]func([]hostclaim.ValidatedWriteSource) []hostclaim.ValidatedWriteSource{
+ "omission": func(v []hostclaim.ValidatedWriteSource) []hostclaim.ValidatedWriteSource { return v[:len(v)-1] },
+ "duplication": func(v []hostclaim.ValidatedWriteSource) []hostclaim.ValidatedWriteSource { return append(v, v[0]) },
+ } {
+ t.Run(name+": "+refusal+" refused", func(t *testing.T) {
+ f := newRCFixture(t, rcClean)
+ st := build(f)
+ recording(&st.w, alter)
+ o := f.read()
+ in := f.selectAdmission(o)
+ if _, err := st.w.Admit(ctx, o, in.Admission, in.Baseline); err == nil {
+ t.Fatalf("an admission with an %s was accepted", refusal)
+ }
+ if after := f.read(); after.ETag != o.ETag {
+ t.Fatal("the claim moved")
+ }
+ })
+ }
+ }
+}