hostclaim: publication walks every entry, warm entries born covered, warm-region check restored (claim rev 6.22, joint round 13)
Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 files changed,
+136
−53
internal/hostclaim/generation.go+10 −5internal/hostclaim/generation_test.go+21 −13internal/hostclaim/rev713_test.go+105 −35
CI passed — run details
diff --git a/internal/hostclaim/generation.go b/internal/hostclaim/generation.go| index 99abc974b..5491b62f0 100644 |
| --- a/internal/hostclaim/generation.go |
| +++ b/internal/hostclaim/generation.go |
| @@ -68,6 +68,9 @@ func AdmitWriter(in Admission) func(*Claim) error { |
| if len(h.Admissions) > 0 { |
| predecessor = h.Admissions[len(h.Admissions)-1] |
| } |
| + if in.Mode == "warm" && in.Region != predecessor.Region { |
| + return fmt.Errorf("%w: a warm admission stays in its predecessor's region", ErrWrongState) |
| + } |
| type writerSession struct { |
| box string |
| session uint64 |
| @@ -87,6 +90,7 @@ func AdmitWriter(in Admission) func(*Claim) error { |
| Session: c.Gen + 1, Box: in.Box, Generation: h.Generation, |
| Predecessor: predecessor.Generation, Mode: in.Mode, Coverage: in.Coverage, |
| Region: in.Region, PredecessorRegion: predecessor.Region, |
| + Covered: in.Mode == "warm", |
| AdditionalWritesLost: append([]WritesLost(nil), in.AdditionalWritesLost...), |
| } |
| for _, ob := range c.Obligations { |
| @@ -124,13 +128,14 @@ func PublishGeneration(observedOpID, observedPhase string, observedSession uint6 |
| return ErrWrongState |
| } |
| h := c.Holder |
| - last := -1 |
| - for i, record := range h.Admissions { |
| + admitted := false |
| + for _, record := range h.Admissions { |
| if record.Generation == publishedGeneration { |
| - last = i |
| + admitted = true |
| + break |
| } |
| } |
| - if last == -1 { |
| + if !admitted { |
| return ErrWrongState |
| } |
| activeGeneration := publishedGeneration |
| @@ -141,7 +146,7 @@ func PublishGeneration(observedOpID, observedPhase string, observedSession uint6 |
| if op == "" { |
| op = fmt.Sprintf("session:%d", h.Session) |
| } |
| - for i := 0; i <= last; i++ { |
| + for i := range h.Admissions { |
| record := &h.Admissions[i] |
| if record.Covered { |
| continue |
diff --git a/internal/hostclaim/generation_test.go b/internal/hostclaim/generation_test.go| index 51a7d7448..ae486d4a7 100644 |
| --- a/internal/hostclaim/generation_test.go |
| +++ b/internal/hostclaim/generation_test.go |
| @@ -58,7 +58,7 @@ func TestAdmitWriterWarmColdAdmissions(t *testing.T) { |
| if o.Claim.Holder.WriterBox != "boxT" || o.Claim.Holder.Session != warm || warm <= cold || o.Claim.Holder.Generation != "gen_a" || o.Claim.Holder.Stamp != cold { |
| t.Fatalf("warm holder = %+v, gen = %d", o.Claim.Holder, warm) |
| } |
| - second := AdmissionRecord{Region: "ie", PredecessorRegion: "ie", Session: warm, Box: "boxT", Generation: "gen_a", Mode: "warm", Predecessor: "gen_a"} |
| + second := AdmissionRecord{Region: "ie", PredecessorRegion: "ie", Session: warm, Box: "boxT", Generation: "gen_a", Mode: "warm", Predecessor: "gen_a", Covered: true} |
| assertAdmissions(t, o.Claim, []AdmissionRecord{first, second}) |
| o, err = Update(ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", ObservedSession: warm, ObservedGeneration: "gen_a", Box: "boxT", Mode: "cold", NewGeneration: "gen_b", PredecessorCertified: true})) |
| if err != nil { |
| @@ -123,7 +123,7 @@ func TestAdmitWriterPreservesOperation(t *testing.T) { |
| want.Holder.Generation = generation |
| want.Holder.Stamp = want.Gen |
| } |
| - want.Holder.Admissions = []AdmissionRecord{{Region: "ie", PredecessorRegion: "ie", Session: want.Gen, Box: "boxT", Generation: generation, Mode: mode}} |
| + want.Holder.Admissions = []AdmissionRecord{{Region: "ie", PredecessorRegion: "ie", Session: want.Gen, Box: "boxT", Generation: generation, Mode: mode, Covered: mode == "warm"}} |
| if !reflect.DeepEqual(next.Claim, &want) { |
| t.Fatalf("admission changed fields outside holder writer fields and admissions: got %+v, want %+v", next.Claim, &want) |
| } |
| @@ -422,7 +422,7 @@ func TestAdmitWriterCoverage(t *testing.T) { |
| in.NewGeneration, generation = "gen_b", "gen_b" |
| } |
| o = updateGenerationTest(t, ctx, s, o, AdmitWriter(in)) |
| - last := AdmissionRecord{Region: "ie", PredecessorRegion: "ie", Session: o.Claim.Gen, Box: "adi-knitting", Generation: generation, Predecessor: "gen_a", Mode: mode, Coverage: coverage} |
| + last := AdmissionRecord{Region: "ie", PredecessorRegion: "ie", Session: o.Claim.Gen, Box: "adi-knitting", Generation: generation, Predecessor: "gen_a", Mode: mode, Coverage: coverage, Covered: mode == "warm"} |
| if mode == "cold" && !certified && coverage == "" { |
| last.WritesLost = &WritesLost{Box: "adi-knitting", Generation: "gen_a", Session: before.Session, RemoteMaxTXID: 42} |
| } |
| @@ -619,7 +619,7 @@ func TestPublishGenerationRefusals(t *testing.T) { |
| } |
| } |
| -func TestPublishGenerationCoverageBoundary(t *testing.T) { |
| +func TestPublishGenerationAllAdmissionsInOrder(t *testing.T) { |
| ctx := context.Background() |
| s := release.NewDirStore(t.TempDir()) |
| o := servingClaim(t, ctx, s) |
| @@ -630,8 +630,14 @@ func TestPublishGenerationCoverageBoundary(t *testing.T) { |
| {Region: "ie", PredecessorRegion: "ie", Generation: "G1", Predecessor: ""}, |
| {Region: "ie", PredecessorRegion: "ie", Generation: "G1", Predecessor: RootGeneration}, |
| {Region: "ie", PredecessorRegion: "ie", Generation: "G2", Predecessor: "G1"}, |
| - {Region: "ie", PredecessorRegion: "ie", Generation: "G2", Predecessor: "G2", Mode: "warm"}, |
| - {Region: "ie", PredecessorRegion: "ie", Generation: "later", Predecessor: "G2"}, |
| + {Region: "ie", PredecessorRegion: "ie", Generation: "G2", Predecessor: "G2", Mode: "warm", Covered: true}, |
| + {Region: "ie", PredecessorRegion: "ie", Generation: "later", Predecessor: "G2", Mode: "cold"}, |
| + {Region: "ie", PredecessorRegion: "ie", Generation: "after-last-match", Predecessor: "later", Mode: "cold"}, |
| + {Region: "ie", PredecessorRegion: "uk", Generation: "foreign", Predecessor: "foreign-predecessor", Mode: "cold"}, |
| + {Region: "ie", Generation: "empty", Predecessor: "no-objects", Mode: "cold"}, |
| + {Region: "ie", PredecessorRegion: "ie", Generation: "duplicate", Predecessor: "later", Mode: "cold"}, |
| + {Region: "ie", PredecessorRegion: "ie", Generation: "warm-only", Predecessor: "warm-only", Mode: "warm", Covered: true}, |
| + {Region: "ie", PredecessorRegion: "ie", Generation: "last", Predecessor: "final-predecessor", Mode: "cold"}, |
| } |
| c.Obligations = []Obligation{ |
| {ID: "existing", Op: "old-op", Kind: "park-generation", Region: "ie", Generation: "G1", Retirement: "ret1", Owner: "region:ie"}, |
| @@ -643,14 +649,16 @@ func TestPublishGenerationCoverageBoundary(t *testing.T) { |
| calls := 0 |
| newID := func() string { calls++; return fmt.Sprintf("id%d", calls) } |
| o = updateGenerationTest(t, ctx, s, o, PublishGeneration("", "", 0, "G2", "ie", newID)) |
| - want := append(append([]Obligation(nil), before.Claim.Obligations...), Obligation{ |
| - ID: "id1", Op: "session:0", Kind: "park-generation", Region: "ie", Generation: RootGeneration, Retirement: "id2", Owner: "region:ie", |
| - }) |
| - if calls != 2 || !reflect.DeepEqual(o.Claim.Obligations, want) { |
| + want := append(append([]Obligation(nil), before.Claim.Obligations...), |
| + Obligation{ID: "id1", Op: "session:0", Kind: "park-generation", Region: "ie", Generation: RootGeneration, Retirement: "id2", Owner: "region:ie"}, |
| + Obligation{ID: "id3", Op: "session:0", Kind: "park-generation", Region: "ie", Generation: "later", Retirement: "id4", Owner: "region:ie"}, |
| + Obligation{ID: "id5", Op: "session:0", Kind: "park-generation", Region: "ie", Generation: "final-predecessor", Retirement: "id6", Owner: "region:ie"}, |
| + ) |
| + if calls != 6 || !reflect.DeepEqual(o.Claim.Obligations, want) { |
| t.Fatalf("deduplicated chores = %+v, want %+v; calls %d", o.Claim.Obligations, want, calls) |
| } |
| for i, record := range o.Claim.Holder.Admissions { |
| - if record.Covered != (i < 4) { |
| + if record.Covered != (i != 5 && i != 7) { |
| t.Fatalf("record %d covered = %t", i, record.Covered) |
| } |
| } |
| @@ -665,7 +673,7 @@ func TestPublishGenerationActiveRoot(t *testing.T) { |
| t.Fatal("active root must not be retired") |
| return "" |
| })) |
| - if o.Claim.Holder.Admissions[0].Covered || len(o.Claim.Obligations) != 0 { |
| + if !o.Claim.Holder.Admissions[0].Covered || len(o.Claim.Obligations) != 0 { |
| t.Fatalf("active root publication = %+v", o.Claim) |
| } |
| } |
| @@ -830,7 +838,7 @@ func TestAdmitWriterRegionRules(t *testing.T) { |
| } |
| } |
| next, err := Update(ctx, s, o, AdmitWriter(in)) |
| - if name == "empty region" { |
| + if name == "empty region" || name == "warm other region" || name == "first warm other region" { |
| if !errors.Is(err, ErrWrongState) { |
| t.Fatalf("admission = %v, want ErrWrongState", err) |
| } |
diff --git a/internal/hostclaim/rev713_test.go b/internal/hostclaim/rev713_test.go| index 0a2359bc3..b14aa245f 100644 |
| --- a/internal/hostclaim/rev713_test.go |
| +++ b/internal/hostclaim/rev713_test.go |
| @@ -11,40 +11,45 @@ import ( |
| "github.com/carlosframework/platform/internal/release" |
| ) |
| -func TestAdmitWriterWarmReturnRegion(t *testing.T) { |
| - for _, region := range []string{"ie", ""} { |
| - t.Run("region="+region, func(t *testing.T) { |
| - ctx := context.Background() |
| - s := release.NewDirStore(t.TempDir()) |
| - o := servingClaim(t, ctx, s) |
| - o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "uk", Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: "GT"})) |
| - target := o.Claim.Holder.Admissions[0] |
| - o = updateGenerationTest(t, ctx, s, o, func(c *Claim) error { |
| - c.Holder.Generation = "GS" |
| - c.Op = Op{Kind: KindReverting, ID: "move", Phase: "return", Exec: "boxS"} |
| - return nil |
| - }) |
| - next, err := Update(ctx, s, o, AdmitWriter(Admission{ |
| - ObservedOpID: "move", ObservedPhase: "return", ObservedSession: target.Session, ObservedGeneration: "GS", |
| - Box: "boxS", Mode: "warm", Region: region, PredecessorRegion: "ignored", |
| - PredecessorCertified: true, Coverage: "clean-return", |
| - })) |
| - if region == "" { |
| - if !errors.Is(err, ErrWrongState) { |
| - t.Fatalf("empty region admission = %v", err) |
| +func TestAdmitWriterReturnRegion(t *testing.T) { |
| + for _, mode := range []string{"warm", "cold"} { |
| + for _, region := range []string{"ie", ""} { |
| + t.Run(mode+"/region="+region, func(t *testing.T) { |
| + ctx := context.Background() |
| + s := release.NewDirStore(t.TempDir()) |
| + o := servingClaim(t, ctx, s) |
| + o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "uk", Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: "GT"})) |
| + target := o.Claim.Holder.Admissions[0] |
| + o = updateGenerationTest(t, ctx, s, o, func(c *Claim) error { |
| + c.Op = Op{Kind: KindReverting, ID: "move", Phase: "return", Exec: "boxS"} |
| + return nil |
| + }) |
| + in := Admission{ |
| + ObservedOpID: "move", ObservedPhase: "return", ObservedSession: target.Session, ObservedGeneration: "GT", |
| + Box: "boxS", Mode: mode, Region: region, PredecessorRegion: "ignored", |
| + PredecessorCertified: true, Coverage: "clean-return", |
| } |
| - assertAdmissionStored(t, ctx, s, o) |
| - return |
| - } |
| - if err != nil { |
| - t.Fatal(err) |
| - } |
| - last := next.Claim.Holder.Admissions[1] |
| - if last.Region != "ie" || last.PredecessorRegion != "uk" || last.Predecessor != "GT" || last.Generation != "GS" || last.Covered { |
| - t.Fatalf("warm return = %+v", last) |
| - } |
| - assertAdmissionStored(t, ctx, s, next) |
| - }) |
| + if mode == "cold" { |
| + in.NewGeneration = "GS2" |
| + } |
| + next, err := Update(ctx, s, o, AdmitWriter(in)) |
| + if mode == "warm" || region == "" { |
| + if !errors.Is(err, ErrWrongState) { |
| + t.Fatalf("return admission = %v, want ErrWrongState", err) |
| + } |
| + assertAdmissionStored(t, ctx, s, o) |
| + return |
| + } |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + last := next.Claim.Holder.Admissions[1] |
| + if last.Region != "ie" || last.PredecessorRegion != "uk" || last.Predecessor != "GT" || last.Generation != "GS2" || last.Mode != "cold" || last.Covered { |
| + t.Fatalf("cold return = %+v", last) |
| + } |
| + assertAdmissionStored(t, ctx, s, next) |
| + }) |
| + } |
| } |
| } |
| @@ -130,8 +135,8 @@ func TestPublishGenerationServingCyclesBoundAdmissions(t *testing.T) { |
| })) |
| o = updateGenerationTest(t, ctx, s, o, PublishGeneration("", "", o.Claim.Holder.Session, generation, "ie", newID)) |
| records := o.Claim.Holder.Admissions |
| - if records[len(records)-1].Covered || len(o.Claim.Obligations) != 0 { |
| - t.Fatalf("cycle %d covered or retired live predecessor", cycle) |
| + if !records[len(records)-1].Covered || len(o.Claim.Obligations) != 0 { |
| + t.Fatalf("cycle %d left warm entry uncovered or retired live predecessor", cycle) |
| } |
| if len(records) > 8 { |
| t.Fatalf("cycle %d admissions grew to %d", cycle, len(records)) |
| @@ -364,6 +369,7 @@ func TestAdmitWriterAdditionalWritesLostValidation(t *testing.T) { |
| loss.Generation = RootGeneration |
| case "warm": |
| in.Mode, in.NewGeneration, in.PredecessorCertified = "warm", "", true |
| + in.Region = "uk" |
| case "sealed": |
| in.Coverage = "sealed" |
| } |
| @@ -400,3 +406,67 @@ func TestAdmitWriterAdditionalWritesLostValidation(t *testing.T) { |
| }) |
| } |
| } |
| + |
| +func TestAdmitWriterWarmBornCovered(t *testing.T) { |
| + for _, generation := range []string{"", "G1"} { |
| + t.Run("generation="+generation, func(t *testing.T) { |
| + ctx := context.Background() |
| + s := release.NewDirStore(t.TempDir()) |
| + o := servingClaim(t, ctx, s) |
| + if generation != "" { |
| + o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{ |
| + Region: "ie", PredecessorRegion: "ie", Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: generation, |
| + })) |
| + } |
| + o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{ |
| + Region: "ie", PredecessorRegion: "ie", ObservedSession: o.Claim.Holder.Session, ObservedGeneration: generation, |
| + Box: o.Claim.Op.Exec, Mode: "warm", PredecessorCertified: true, |
| + })) |
| + records := o.Claim.Holder.Admissions |
| + last := records[len(records)-1] |
| + if !last.Covered || last.Mode != "warm" || last.Generation != generation || last.Predecessor != generation || last.WritesLost != nil || len(o.Claim.Obligations) != 0 { |
| + t.Fatalf("warm birth = %+v; chores = %+v", last, o.Claim.Obligations) |
| + } |
| + if len(records) > 1 && records[0].Covered { |
| + t.Fatal("warm birth covered an earlier cold entry") |
| + } |
| + assertAdmissionStored(t, ctx, s, o) |
| + }) |
| + } |
| +} |
| + |
| +func TestCompleteObligationDoesNotCoverAdmissions(t *testing.T) { |
| + for _, kind := range []string{"park-replica", "park-generation"} { |
| + t.Run(kind, func(t *testing.T) { |
| + ctx := context.Background() |
| + s := release.NewDirStore(t.TempDir()) |
| + o := servingClaim(t, ctx, s) |
| + o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{ |
| + Region: "ie", PredecessorRegion: "ie", Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: "G1", |
| + })) |
| + o = updateGenerationTest(t, ctx, s, o, func(c *Claim) error { |
| + ob := Obligation{ID: "park", Op: "cleanup", Kind: kind, Region: "ie", Owner: "region:ie", Retirement: "ret"} |
| + if kind == "park-generation" { |
| + ob.Generation = RootGeneration |
| + } |
| + c.Obligations = append(c.Obligations, ob) |
| + return nil |
| + }) |
| + before := o.Claim.Holder.Admissions |
| + if before[0].Covered { |
| + t.Fatal("queueing a park alone covered its predecessor") |
| + } |
| + next, err := Update(ctx, s, o, CompleteObligation("cleanup", "park")) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + stored, err := Read(ctx, s, testHost) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if stored.ETag != next.ETag || len(stored.Claim.Obligations) != 0 || !reflect.DeepEqual(stored.Claim.Holder.Admissions, before) { |
| + t.Fatalf("acknowledgement changed admissions: %+v", stored.Claim) |
| + } |
| + }) |
| + } |
| +} |