carlos / platform Public

hostclaim: coverage is per region (moves rev 7.12 note 5, claim rev 6.13)

Admissions record region and predecessor_region. PublishGeneration covers
and queues park-generation only for predecessors in its own region (or
holding none); a foreign predecessor stays uncovered and the walk goes on.
CoverRegionPredecessors covers them inside the CAS that queues that
region's whole-location park. A warm admission stays in its predecessor's
region, including the first.
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
codex agent, operated by paul@keymail.dev pushed by claude agent, operated by paul@keymail.dev 47426cc18a85847db7038dc3c428ead34dbef5d3 parent 34224a2
3 files changed, +292 −56
  • internal/hostclaim/claim.go +15 −8
  • internal/hostclaim/generation.go +34 −3
  • internal/hostclaim/generation_test.go +243 −45

CI passed — run details

diff --git a/internal/hostclaim/claim.go b/internal/hostclaim/claim.go
index b20bd783f..66153a317 100644
--- a/internal/hostclaim/claim.go
+++ b/internal/hostclaim/claim.go
@@ -60,14 +60,16 @@ type Holder struct {
}
type AdmissionRecord struct {
- Session uint64 `json:"session"`
- Box string `json:"box"`
- Generation string `json:"generation"`
- Predecessor string `json:"predecessor,omitempty"`
- Mode string `json:"mode"`
- Coverage string `json:"coverage,omitempty"`
- Covered bool `json:"covered"`
- WritesLost *WritesLost `json:"writes_lost,omitempty"`
+ Region string `json:"region"`
+ PredecessorRegion string `json:"predecessor_region,omitempty"`
+ Session uint64 `json:"session"`
+ Box string `json:"box"`
+ Generation string `json:"generation"`
+ Predecessor string `json:"predecessor,omitempty"`
+ Mode string `json:"mode"`
+ Coverage string `json:"coverage,omitempty"`
+ Covered bool `json:"covered"`
+ WritesLost *WritesLost `json:"writes_lost,omitempty"`
}
type WritesLost struct {
@@ -222,6 +224,11 @@ func (c *Claim) Validate() error {
if c.Op.Kind == "" {
return fmt.Errorf("%w: holder without op", ErrMalformed)
}
+ for _, record := range h.Admissions {
+ if record.Region == "" {
+ return fmt.Errorf("%w: admission without region", ErrMalformed)
+ }
+ }
}
if len(c.RetiredIncarnations) > retiredCap {
return fmt.Errorf("%w: retired_incarnations over cap", ErrMalformed)
diff --git a/internal/hostclaim/generation.go b/internal/hostclaim/generation.go
index ac646e139..6bc1d2a4f 100644
--- a/internal/hostclaim/generation.go
+++ b/internal/hostclaim/generation.go
@@ -15,6 +15,8 @@ type Admission struct {
RemoteMaxTXID uint64
KeepStamp bool
Coverage string
+ Region string
+ PredecessorRegion string
}
// AdmitWriter is §6.4a's writer admission CAS within the current operation.
@@ -23,6 +25,9 @@ type Admission struct {
// and supplies PredecessorCertified.
func AdmitWriter(in Admission) func(*Claim) error {
return func(c *Claim) error {
+ if in.Region == "" {
+ return fmt.Errorf("%w: an admission names its region", ErrWrongState)
+ }
if c.Holder == nil || c.Op.Exec == "" || c.Op.Exec != in.Box {
return fmt.Errorf("%w: only the executor may admit a writer", ErrWrongState)
}
@@ -58,13 +63,17 @@ func AdmitWriter(in Admission) func(*Claim) error {
default:
return ErrWrongState
}
- predecessor := AdmissionRecord{Session: h.Session, Box: h.WriterBox, Generation: h.Generation}
+ predecessor := AdmissionRecord{Session: h.Session, Box: h.WriterBox, Generation: h.Generation, Region: in.PredecessorRegion}
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)
+ }
entry := AdmissionRecord{
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,
}
if in.Mode == "cold" {
entry.Generation = in.NewGeneration
@@ -117,12 +126,15 @@ func PublishGeneration(observedOpID, observedPhase string, observedSession uint6
if record.Covered {
continue
}
- record.Covered = true
predecessor := record.Predecessor
if predecessor == "" {
predecessor = RootGeneration
}
- if predecessor == activeGeneration {
+ if predecessor == activeGeneration || record.PredecessorRegion == "" {
+ record.Covered = true
+ continue
+ }
+ if record.PredecessorRegion != region {
continue
}
queued := false
@@ -138,6 +150,25 @@ func PublishGeneration(observedOpID, observedPhase string, observedSession uint6
Generation: predecessor, Retirement: newID(), Owner: "region:" + region,
})
}
+ record.Covered = true
+ }
+ return nil
+ }
+}
+
+// CoverRegionPredecessors covers predecessors retired by a region's park of
+// the whole location. Callers compose it into the CAS that queues that park
+// (a committed move's source cleanup). It queues no chores itself.
+func CoverRegionPredecessors(observedOpID, observedPhase string, region string) func(*Claim) error {
+ return func(c *Claim) error {
+ if c.Holder == nil || c.Op.ID != observedOpID || c.Op.Phase != observedPhase || region == "" {
+ return ErrWrongState
+ }
+ for i := range c.Holder.Admissions {
+ record := &c.Holder.Admissions[i]
+ if !record.Covered && record.PredecessorRegion == region {
+ record.Covered = true
+ }
}
return nil
}
diff --git a/internal/hostclaim/generation_test.go b/internal/hostclaim/generation_test.go
index 923e302bc..577116b41 100644
--- a/internal/hostclaim/generation_test.go
+++ b/internal/hostclaim/generation_test.go
@@ -33,7 +33,7 @@ func TestAdmitWriterWarmColdAdmissions(t *testing.T) {
t.Fatalf("zero %s must be omitted", name)
}
}
- o, err = Update(ctx, s, o, AdmitWriter(Admission{Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a", PredecessorCertified: true}))
+ o, err = Update(ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a", PredecessorCertified: true}))
if err != nil {
t.Fatal(err)
}
@@ -41,7 +41,7 @@ func TestAdmitWriterWarmColdAdmissions(t *testing.T) {
if o.Claim.Holder.WriterBox != "adi-knitting" || o.Claim.Holder.Generation != "gen_a" || o.Claim.Holder.Stamp != cold || o.Claim.Holder.Session != cold || cold <= stamp {
t.Fatalf("cold holder = %+v, gen = %d", o.Claim.Holder, cold)
}
- first := AdmissionRecord{Session: cold, Box: "adi-knitting", Generation: "gen_a", Mode: "cold"}
+ first := AdmissionRecord{Region: "ie", PredecessorRegion: "ie", Session: cold, Box: "adi-knitting", Generation: "gen_a", Mode: "cold"}
assertAdmissions(t, o.Claim, []AdmissionRecord{first})
o, err = Update(ctx, s, o, func(c *Claim) error {
c.Op.Exec = "boxT"
@@ -50,7 +50,7 @@ func TestAdmitWriterWarmColdAdmissions(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- o, err = Update(ctx, s, o, AdmitWriter(Admission{ObservedSession: cold, ObservedGeneration: "gen_a", Box: "boxT", Mode: "warm", PredecessorCertified: true}))
+ o, err = Update(ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", ObservedSession: cold, ObservedGeneration: "gen_a", Box: "boxT", Mode: "warm", PredecessorCertified: true}))
if err != nil {
t.Fatal(err)
}
@@ -58,16 +58,16 @@ 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{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"}
assertAdmissions(t, o.Claim, []AdmissionRecord{first, second})
- o, err = Update(ctx, s, o, AdmitWriter(Admission{ObservedSession: warm, ObservedGeneration: "gen_a", Box: "boxT", Mode: "cold", NewGeneration: "gen_b", PredecessorCertified: true}))
+ 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 {
t.Fatal(err)
}
if o.Claim.Holder.Generation != "gen_b" || o.Claim.Holder.Stamp != o.Claim.Gen || o.Claim.Holder.Session != o.Claim.Gen {
t.Fatalf("second cold holder = %+v, gen = %d", o.Claim.Holder, o.Claim.Gen)
}
- third := AdmissionRecord{Session: o.Claim.Gen, Box: "boxT", Generation: "gen_b", Mode: "cold", Predecessor: "gen_a"}
+ third := AdmissionRecord{Region: "ie", PredecessorRegion: "ie", Session: o.Claim.Gen, Box: "boxT", Generation: "gen_b", Mode: "cold", Predecessor: "gen_a"}
assertAdmissions(t, o.Claim, []AdmissionRecord{first, second, third})
stored, err := Read(ctx, s, testHost)
if err != nil {
@@ -109,7 +109,7 @@ func TestAdmitWriterPreservesOperation(t *testing.T) {
if mode == "cold" {
generation = "gen_a"
}
- next, err := Update(ctx, s, o, AdmitWriter(Admission{ObservedOpID: "m1", ObservedPhase: "probe", Box: "boxT", Mode: mode, NewGeneration: generation, PredecessorCertified: true}))
+ next, err := Update(ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", ObservedOpID: "m1", ObservedPhase: "probe", Box: "boxT", Mode: mode, NewGeneration: generation, PredecessorCertified: true}))
if err != nil {
t.Fatal(err)
}
@@ -123,7 +123,7 @@ func TestAdmitWriterPreservesOperation(t *testing.T) {
want.Holder.Generation = generation
want.Holder.Stamp = want.Gen
}
- want.Holder.Admissions = []AdmissionRecord{{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}}
if !reflect.DeepEqual(next.Claim, &want) {
t.Fatalf("admission changed fields outside holder writer fields and admissions: got %+v, want %+v", next.Claim, &want)
}
@@ -178,7 +178,7 @@ func TestAdmitWriterRefusals(t *testing.T) {
case "empty mode":
mode = ""
}
- in := Admission{ObservedOpID: o.Claim.Op.ID, ObservedPhase: o.Claim.Op.Phase, ObservedSession: session, ObservedGeneration: generation, Box: box, Mode: mode, NewGeneration: newGeneration, PredecessorCertified: true}
+ in := Admission{Region: "ie", PredecessorRegion: "ie", ObservedOpID: o.Claim.Op.ID, ObservedPhase: o.Claim.Op.Phase, ObservedSession: session, ObservedGeneration: generation, Box: box, Mode: mode, NewGeneration: newGeneration, PredecessorCertified: true}
switch name {
case "warm without certificate":
in.Mode, in.NewGeneration, in.PredecessorCertified = "warm", "", false
@@ -207,7 +207,7 @@ func TestAdmitWriterCompetingAdmissions(t *testing.T) {
ctx := context.Background()
s := release.NewDirStore(t.TempDir())
before := servingClaim(t, ctx, s)
- in := Admission{Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a"}
+ in := Admission{Region: "ie", PredecessorRegion: "ie", Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a"}
first := AdmitWriter(in)
in.NewGeneration = "gen_b"
second := AdmitWriter(in)
@@ -229,7 +229,7 @@ func TestAdmitWriterLostResponse(t *testing.T) {
ctx := context.Background()
s := release.NewDirStore(t.TempDir())
before := servingClaim(t, ctx, s)
- admit := AdmitWriter(Admission{Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a"})
+ admit := AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a"})
admitted, err := Update(ctx, s, before, admit)
if err != nil {
t.Fatal(err)
@@ -246,7 +246,7 @@ func TestAdmitWriterBoundToOperationAndPhase(t *testing.T) {
ctx := context.Background()
s := release.NewDirStore(t.TempDir())
o := servingClaim(t, ctx, s)
- stale := AdmitWriter(Admission{Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a"})
+ stale := AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a"})
var err error
if change == "delete" {
o, err = Update(ctx, s, o, BeginDelete("d1", "console", "etag", time.Now(), nil, nil))
@@ -268,7 +268,7 @@ func TestAdmitWriterBoundToOperationAndPhase(t *testing.T) {
}
assertAdmissionStored(t, ctx, s, o)
if change == "delete" {
- fresh := AdmitWriter(Admission{ObservedOpID: "d1", Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a"})
+ fresh := AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", ObservedOpID: "d1", Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a"})
if _, err := Update(ctx, s, o, fresh); !errors.Is(err, ErrWrongState) {
t.Fatalf("deleting admission = %v", err)
}
@@ -293,7 +293,7 @@ func TestAdmitWriterSurvivesOperationReplacement(t *testing.T) {
ctx := context.Background()
s := release.NewDirStore(t.TempDir())
o := servingClaim(t, ctx, s)
- o, err := Update(ctx, s, o, AdmitWriter(Admission{Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a"}))
+ o, err := Update(ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a"}))
if err != nil {
t.Fatal(err)
}
@@ -319,11 +319,11 @@ func TestAdmitWriterSurvivesOperationReplacement(t *testing.T) {
if transition == "delete" {
return
}
- o, err = Update(ctx, s, o, AdmitWriter(Admission{ObservedOpID: "m1", ObservedPhase: "probe", ObservedSession: writer.Session, ObservedGeneration: writer.Generation, Box: "boxT", Mode: "cold", NewGeneration: "gen_b", RemoteMaxTXID: 42}))
+ o, err = Update(ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", ObservedOpID: "m1", ObservedPhase: "probe", ObservedSession: writer.Session, ObservedGeneration: writer.Generation, Box: "boxT", Mode: "cold", NewGeneration: "gen_b", RemoteMaxTXID: 42}))
if err != nil {
t.Fatal(err)
}
- want := append(append([]AdmissionRecord(nil), writer.Admissions...), AdmissionRecord{
+ want := append(append([]AdmissionRecord(nil), writer.Admissions...), AdmissionRecord{Region: "ie", PredecessorRegion: "ie",
Session: o.Claim.Gen, Box: "boxT", Generation: "gen_b", Mode: "cold", Predecessor: "gen_a",
WritesLost: &WritesLost{Box: "adi-knitting", Generation: "gen_a", Session: writer.Session, RemoteMaxTXID: 42},
})
@@ -354,7 +354,7 @@ func TestAdmitWriterKeepStamp(t *testing.T) {
ctx := context.Background()
s := release.NewDirStore(t.TempDir())
o := servingClaim(t, ctx, s)
- o, err := Update(ctx, s, o, AdmitWriter(Admission{Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a", PredecessorCertified: true}))
+ o, err := Update(ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a", PredecessorCertified: true}))
if err != nil {
t.Fatal(err)
}
@@ -365,13 +365,13 @@ func TestAdmitWriterKeepStamp(t *testing.T) {
return nil
})
} else if name == "later session" {
- o, err = Update(ctx, s, o, AdmitWriter(Admission{ObservedSession: o.Claim.Holder.Session, ObservedGeneration: "gen_a", Box: "adi-knitting", Mode: "warm", PredecessorCertified: true}))
+ o, err = Update(ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", ObservedSession: o.Claim.Holder.Session, ObservedGeneration: "gen_a", Box: "adi-knitting", Mode: "warm", PredecessorCertified: true}))
}
if err != nil {
t.Fatal(err)
}
before := *o.Claim.Holder
- in := Admission{ObservedOpID: o.Claim.Op.ID, ObservedPhase: o.Claim.Op.Phase, ObservedSession: before.Session, ObservedGeneration: before.Generation, Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: "gen_b", KeepStamp: true, PredecessorCertified: true}
+ in := Admission{Region: "ie", PredecessorRegion: "ie", ObservedOpID: o.Claim.Op.ID, ObservedPhase: o.Claim.Op.Phase, ObservedSession: before.Session, ObservedGeneration: before.Generation, Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: "gen_b", KeepStamp: true, PredecessorCertified: true}
if name == "warm" {
in.Mode, in.NewGeneration = "warm", ""
}
@@ -389,7 +389,7 @@ func TestAdmitWriterKeepStamp(t *testing.T) {
if next.Claim.Holder.Stamp != before.Stamp || next.Claim.Holder.Session != next.Claim.Gen || next.Claim.Holder.Generation != "gen_b" {
t.Fatalf("landing admission holder = %+v, prior stamp = %d", next.Claim.Holder, before.Stamp)
}
- want := append(append([]AdmissionRecord(nil), before.Admissions...), AdmissionRecord{
+ want := append(append([]AdmissionRecord(nil), before.Admissions...), AdmissionRecord{Region: "ie", PredecessorRegion: "ie",
Session: next.Claim.Gen, Box: "boxT", Generation: "gen_b", Mode: "cold", Predecessor: "gen_a",
})
assertAdmissions(t, next.Claim, want)
@@ -414,15 +414,15 @@ func TestAdmitWriterCoverage(t *testing.T) {
ctx := context.Background()
s := release.NewDirStore(t.TempDir())
o := servingClaim(t, ctx, s)
- o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a", PredecessorCertified: true}))
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", Box: "adi-knitting", Mode: "cold", NewGeneration: "gen_a", PredecessorCertified: true}))
before := *o.Claim.Holder
- in := Admission{ObservedSession: before.Session, ObservedGeneration: before.Generation, Box: "adi-knitting", Mode: mode, PredecessorCertified: certified, Coverage: coverage, RemoteMaxTXID: 42}
+ in := Admission{Region: "ie", PredecessorRegion: "ie", ObservedSession: before.Session, ObservedGeneration: before.Generation, Box: "adi-knitting", Mode: mode, PredecessorCertified: certified, Coverage: coverage, RemoteMaxTXID: 42}
generation := before.Generation
if mode == "cold" {
in.NewGeneration, generation = "gen_b", "gen_b"
}
o = updateGenerationTest(t, ctx, s, o, AdmitWriter(in))
- last := AdmissionRecord{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}
if mode == "cold" && !certified && coverage == "" {
last.WritesLost = &WritesLost{Box: "adi-knitting", Generation: "gen_a", Session: before.Session, RemoteMaxTXID: 42}
}
@@ -453,15 +453,15 @@ func TestAdmitWriterCleanReturnPredecessor(t *testing.T) {
c.Op = Op{Kind: KindLanding, ID: "move", Phase: "probe", Exec: "boxT"}
return nil
})
- o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{ObservedOpID: "move", ObservedPhase: "probe", Box: "boxT", Mode: "cold", NewGeneration: "GT"}))
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", ObservedOpID: "move", ObservedPhase: "probe", Box: "boxT", 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: KindServing, Exec: "boxS"}
return nil
})
- o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{ObservedSession: target.Session, ObservedGeneration: "GS", Box: "boxS", Mode: "cold", NewGeneration: "GS2", Coverage: coverage, RemoteMaxTXID: 42}))
- last := AdmissionRecord{Session: o.Claim.Gen, Box: "boxS", Generation: "GS2", Predecessor: "GT", Mode: "cold", Coverage: coverage}
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", ObservedSession: target.Session, ObservedGeneration: "GS", Box: "boxS", Mode: "cold", NewGeneration: "GS2", Coverage: coverage, RemoteMaxTXID: 42}))
+ last := AdmissionRecord{Region: "ie", PredecessorRegion: "ie", Session: o.Claim.Gen, Box: "boxS", Generation: "GS2", Predecessor: "GT", Mode: "cold", Coverage: coverage}
if coverage == "" {
last.WritesLost = &WritesLost{Box: "boxT", Generation: "GT", Session: target.Session, RemoteMaxTXID: 42}
}
@@ -500,8 +500,8 @@ func TestAdoptAdmitPublishRootGeneration(t *testing.T) {
return nil
})
}
- o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: "G1", RemoteMaxTXID: 42}))
- assertAdmissions(t, o.Claim, []AdmissionRecord{{
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: "G1", RemoteMaxTXID: 42}))
+ assertAdmissions(t, o.Claim, []AdmissionRecord{{Region: "ie", PredecessorRegion: "ie",
Session: o.Claim.Gen, Box: o.Claim.Op.Exec, Generation: "G1", Mode: "cold",
WritesLost: &WritesLost{Box: "boxS", Session: 0, Generation: "", RemoteMaxTXID: 42},
}})
@@ -539,7 +539,7 @@ func TestPublishGenerationChain(t *testing.T) {
})
}
for _, generation := range []string{"G1", "G2"} {
- o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{ObservedOpID: o.Claim.Op.ID, ObservedPhase: o.Claim.Op.Phase,
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", ObservedOpID: o.Claim.Op.ID, ObservedPhase: o.Claim.Op.Phase,
ObservedSession: o.Claim.Holder.Session, ObservedGeneration: o.Claim.Holder.Generation,
Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: generation}))
}
@@ -582,7 +582,7 @@ func TestPublishGenerationRefusals(t *testing.T) {
ctx := context.Background()
s := release.NewDirStore(t.TempDir())
o := servingClaim(t, ctx, s)
- o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Box: "adi-knitting", Mode: "cold", NewGeneration: "G1"}))
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", Box: "adi-knitting", Mode: "cold", NewGeneration: "G1"}))
o = updateGenerationTest(t, ctx, s, o, func(c *Claim) error {
c.Op = Op{Kind: KindLanding, ID: "move", Phase: "probe", Exec: "boxT"}
if name == "no holder" {
@@ -626,12 +626,12 @@ func TestPublishGenerationCoverageBoundary(t *testing.T) {
o = updateGenerationTest(t, ctx, s, o, func(c *Claim) error {
c.Holder.Generation = "G2"
c.Holder.Admissions = []AdmissionRecord{
- {Generation: "old", Predecessor: "already-covered", Covered: true},
- {Generation: "G1", Predecessor: ""},
- {Generation: "G1", Predecessor: RootGeneration},
- {Generation: "G2", Predecessor: "G1"},
- {Generation: "G2", Predecessor: "G2", Mode: "warm"},
- {Generation: "later", Predecessor: "G2"},
+ {Region: "ie", PredecessorRegion: "ie", Generation: "old", Predecessor: "already-covered", Covered: true},
+ {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"},
}
c.Obligations = []Obligation{
{ID: "existing", Op: "old-op", Kind: "park-generation", Region: "ie", Generation: "G1", Retirement: "ret1", Owner: "region:ie"},
@@ -660,7 +660,7 @@ func TestPublishGenerationActiveRoot(t *testing.T) {
ctx := context.Background()
s := release.NewDirStore(t.TempDir())
o := servingClaim(t, ctx, s)
- o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Box: "adi-knitting", Mode: "warm", PredecessorCertified: true}))
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", Box: "adi-knitting", Mode: "warm", PredecessorCertified: true}))
o = updateGenerationTest(t, ctx, s, o, PublishGeneration("", "", o.Claim.Holder.Session, "", "ie", func() string {
t.Fatal("active root must not be retired")
return ""
@@ -677,7 +677,7 @@ func TestAdmitWriterTrimsCoveredPrefix(t *testing.T) {
s := release.NewDirStore(t.TempDir())
o := servingClaim(t, ctx, s)
for i := 1; i <= 10; i++ {
- o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{ObservedSession: o.Claim.Holder.Session, ObservedGeneration: o.Claim.Holder.Generation,
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", ObservedSession: o.Claim.Holder.Session, ObservedGeneration: o.Claim.Holder.Generation,
Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: fmt.Sprintf("G%d", i)}))
}
if len(o.Claim.Holder.Admissions) != 10 {
@@ -695,7 +695,7 @@ func TestAdmitWriterTrimsCoveredPrefix(t *testing.T) {
})
}
before := o.Claim.Holder.Admissions
- o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{ObservedSession: o.Claim.Holder.Session, ObservedGeneration: "G10",
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", ObservedSession: o.Claim.Holder.Session, ObservedGeneration: "G10",
Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: "G11", PredecessorCertified: true}))
drop := 3
if name == "uncovered first" {
@@ -703,7 +703,7 @@ func TestAdmitWriterTrimsCoveredPrefix(t *testing.T) {
} else if name == "uncovered middle" {
drop = 1
}
- want := append(append([]AdmissionRecord(nil), before[drop:]...), AdmissionRecord{Session: o.Claim.Gen, Box: o.Claim.Op.Exec, Generation: "G11", Predecessor: "G10", Mode: "cold"})
+ want := append(append([]AdmissionRecord(nil), before[drop:]...), AdmissionRecord{Region: "ie", PredecessorRegion: "ie", Session: o.Claim.Gen, Box: o.Claim.Op.Exec, Generation: "G11", Predecessor: "G10", Mode: "cold"})
assertAdmissions(t, o.Claim, want)
})
}
@@ -713,8 +713,8 @@ func TestAdmissionRecordJSON(t *testing.T) {
ctx := context.Background()
s := release.NewDirStore(t.TempDir())
o := servingClaim(t, ctx, s)
- o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Box: "adi-knitting", Mode: "cold", NewGeneration: "G1", RemoteMaxTXID: 42}))
- o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{ObservedSession: o.Claim.Holder.Session, ObservedGeneration: "G1", Box: "adi-knitting", Mode: "cold", NewGeneration: "G2", Coverage: "sealed", PredecessorCertified: true}))
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", Box: "adi-knitting", Mode: "cold", NewGeneration: "G1", RemoteMaxTXID: 42}))
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", ObservedSession: o.Claim.Holder.Session, ObservedGeneration: "G1", Box: "adi-knitting", Mode: "cold", NewGeneration: "G2", Coverage: "sealed", PredecessorCertified: true}))
o = updateGenerationTest(t, ctx, s, o, PublishGeneration("", "", o.Claim.Holder.Session, "G2", "ie", counter()))
raw, err := json.Marshal(o.Claim.Holder)
if err != nil {
@@ -729,9 +729,9 @@ func TestAdmissionRecordJSON(t *testing.T) {
t.Fatal(err)
}
want := []map[string]any{
- {"session": float64(o.Claim.Holder.Admissions[0].Session), "box": "adi-knitting", "generation": "G1", "mode": "cold", "covered": true,
+ {"region": "ie", "predecessor_region": "ie", "session": float64(o.Claim.Holder.Admissions[0].Session), "box": "adi-knitting", "generation": "G1", "mode": "cold", "covered": true,
"writes_lost": map[string]any{"box": "", "generation": "", "session": float64(0), "remote_max_txid": float64(42)}},
- {"session": float64(o.Claim.Holder.Session), "box": "adi-knitting", "generation": "G2", "predecessor": "G1", "mode": "cold", "coverage": "sealed", "covered": true},
+ {"region": "ie", "predecessor_region": "ie", "session": float64(o.Claim.Holder.Session), "box": "adi-knitting", "generation": "G2", "predecessor": "G1", "mode": "cold", "coverage": "sealed", "covered": true},
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("admissions JSON = %s, want %+v", holder["admissions"], want)
@@ -747,8 +747,206 @@ func TestAdmitWriterIgnoresLegacyWriterJournal(t *testing.T) {
return nil
})
before := o.Claim.Op
- o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Box: "adi-knitting", Mode: "cold", NewGeneration: "G1"}))
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", PredecessorRegion: "ie", Box: "adi-knitting", Mode: "cold", NewGeneration: "G1"}))
if !reflect.DeepEqual(o.Claim.Op, before) || len(o.Claim.Holder.Admissions) != 1 {
t.Fatalf("legacy journal affected admission: %+v", o.Claim)
}
}
+
+func TestPublishGenerationForeignRegion(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, AdmitWriter(Admission{Region: "uk", ObservedSession: o.Claim.Holder.Session, ObservedGeneration: "G1", Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: "G2"}))
+ newID := func() string { t.Fatal("foreign-region publication allocated an ID"); return "" }
+ publish := PublishGeneration("", "", o.Claim.Holder.Session, "G2", "uk", newID)
+ o = updateGenerationTest(t, ctx, s, o, publish)
+ for _, record := range o.Claim.Holder.Admissions {
+ if record.Covered || record.PredecessorRegion != "ie" {
+ t.Fatalf("foreign predecessor = %+v", record)
+ }
+ }
+ if len(o.Claim.Obligations) != 0 {
+ t.Fatalf("foreign publication queued chores: %+v", o.Claim.Obligations)
+ }
+ o = updateGenerationTest(t, ctx, s, o, CoverRegionPredecessors("", "", "ie"))
+ for _, record := range o.Claim.Holder.Admissions {
+ if !record.Covered {
+ t.Fatalf("source cleanup left predecessor uncovered: %+v", record)
+ }
+ }
+ before := *o.Claim
+ o = updateGenerationTest(t, ctx, s, o, publish)
+ before.Gen++
+ if len(o.Claim.Obligations) != 0 || !reflect.DeepEqual(o.Claim, &before) {
+ t.Fatalf("publication after source coverage changed claim: %+v", o.Claim)
+ }
+}
+
+func TestPublishGenerationHoldsNone(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", Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: "G1"}))
+ o = updateGenerationTest(t, ctx, s, o, PublishGeneration("", "", o.Claim.Holder.Session, "G1", "ie", func() string {
+ t.Fatal("predecessor with no objects allocated an ID")
+ return ""
+ }))
+ if !o.Claim.Holder.Admissions[0].Covered || len(o.Claim.Obligations) != 0 {
+ t.Fatalf("empty predecessor publication = %+v", o.Claim)
+ }
+ raw, err := json.Marshal(o.Claim.Holder.Admissions[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ var fields map[string]json.RawMessage
+ if err := json.Unmarshal(raw, &fields); err != nil {
+ t.Fatal(err)
+ }
+ if string(fields["region"]) != `"ie"` || fields["predecessor_region"] != nil {
+ t.Fatalf("region JSON = %s", raw)
+ }
+}
+
+func TestAdmitWriterRegionRules(t *testing.T) {
+ for _, name := range []string{"empty region", "warm other region", "warm same region", "recorded predecessor region", "first predecessor region", "first warm other region"} {
+ t.Run(name, func(t *testing.T) {
+ ctx := context.Background()
+ s := release.NewDirStore(t.TempDir())
+ o := servingClaim(t, ctx, s)
+ if name != "first predecessor region" && name != "empty region" && name != "first warm other region" {
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "ie", Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: "G1"}))
+ }
+ in := Admission{Region: "uk", PredecessorRegion: "fr", ObservedSession: o.Claim.Holder.Session, ObservedGeneration: o.Claim.Holder.Generation,
+ Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: "G2", PredecessorCertified: true}
+ switch name {
+ case "empty region":
+ in.Region = ""
+ case "warm other region", "warm same region", "first warm other region":
+ in.Mode, in.NewGeneration = "warm", ""
+ if name == "warm same region" {
+ in.Region = "ie"
+ }
+ }
+ next, err := Update(ctx, s, o, AdmitWriter(in))
+ 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)
+ }
+ assertAdmissionStored(t, ctx, s, o)
+ return
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ assertAdmissionStored(t, ctx, s, next)
+ record := next.Claim.Holder.Admissions[len(next.Claim.Holder.Admissions)-1]
+ wantPredecessorRegion := "ie"
+ if name == "first predecessor region" {
+ wantPredecessorRegion = "fr"
+ }
+ if record.Region != in.Region || record.PredecessorRegion != wantPredecessorRegion {
+ t.Fatalf("admission regions = %+v, want %q / %q", record, in.Region, wantPredecessorRegion)
+ }
+ })
+ }
+}
+
+func TestAdmitWriterNeverTrimsForeignUncovered(t *testing.T) {
+ ctx := context.Background()
+ s := release.NewDirStore(t.TempDir())
+ o := servingClaim(t, ctx, s)
+ newID := counter()
+ for i := 1; i <= 12; i++ {
+ o = updateGenerationTest(t, ctx, s, o, AdmitWriter(Admission{Region: "uk", PredecessorRegion: "ie",
+ ObservedSession: o.Claim.Holder.Session, ObservedGeneration: o.Claim.Holder.Generation,
+ Box: o.Claim.Op.Exec, Mode: "cold", NewGeneration: fmt.Sprintf("G%d", i)}))
+ o = updateGenerationTest(t, ctx, s, o, PublishGeneration("", "", o.Claim.Holder.Session, o.Claim.Holder.Generation, "uk", newID))
+ }
+ if len(o.Claim.Holder.Admissions) != 12 || o.Claim.Holder.Admissions[0].Generation != "G1" || o.Claim.Holder.Admissions[0].Covered {
+ t.Fatalf("foreign uncovered head was trimmed or covered: %+v", o.Claim.Holder.Admissions)
+ }
+ for _, record := range o.Claim.Holder.Admissions[1:] {
+ if !record.Covered {
+ t.Fatalf("publication stopped at foreign predecessor: %+v", record)
+ }
+ }
+}
+
+func TestCoverRegionPredecessors(t *testing.T) {
+ ctx := context.Background()
+ s := release.NewDirStore(t.TempDir())
+ o := servingClaim(t, ctx, s)
+ o = updateGenerationTest(t, ctx, s, o, func(c *Claim) error {
+ c.Op = Op{Kind: KindLanding, ID: "move", Phase: "cleanup", Exec: c.Op.Exec}
+ c.Holder.Admissions = []AdmissionRecord{
+ {Region: "ie", PredecessorRegion: "ie", Covered: true},
+ {Region: "uk", PredecessorRegion: "uk"},
+ {Region: "uk", PredecessorRegion: "ie"},
+ {Region: "ie"},
+ {Region: "ie", PredecessorRegion: "ie"},
+ }
+ c.Obligations = []Obligation{{ID: "source", Op: "move", Kind: "park-replica", Region: "ie", Retirement: "ret", Owner: "region:ie"}}
+ return nil
+ })
+ want := *o.Claim
+ holder := *want.Holder
+ want.Holder = &holder
+ holder.Admissions = append([]AdmissionRecord(nil), holder.Admissions...)
+ holder.Admissions[2].Covered, holder.Admissions[4].Covered = true, true
+ for i := 0; i < 2; i++ {
+ o = updateGenerationTest(t, ctx, s, o, CoverRegionPredecessors("move", "cleanup", "ie"))
+ want.Gen++
+ if !reflect.DeepEqual(o.Claim, &want) {
+ t.Fatalf("region coverage = %+v, want %+v", o.Claim, &want)
+ }
+ }
+}
+
+func TestCoverRegionPredecessorsRefusals(t *testing.T) {
+ for _, name := range []string{"stale op", "stale phase", "nil holder", "empty region"} {
+ t.Run(name, func(t *testing.T) {
+ ctx := context.Background()
+ s := release.NewDirStore(t.TempDir())
+ o := servingClaim(t, ctx, s)
+ o = updateGenerationTest(t, ctx, s, o, func(c *Claim) error {
+ c.Op = Op{Kind: KindLanding, ID: "move", Phase: "cleanup", Exec: c.Op.Exec}
+ c.Holder.Admissions = []AdmissionRecord{{Region: "uk", PredecessorRegion: "ie"}}
+ if name == "nil holder" {
+ c.Holder, c.Op = nil, Op{}
+ }
+ return nil
+ })
+ op, phase, region := o.Claim.Op.ID, o.Claim.Op.Phase, "ie"
+ switch name {
+ case "stale op":
+ op = ""
+ case "stale phase":
+ phase = ""
+ case "empty region":
+ region = ""
+ }
+ if _, err := Update(ctx, s, o, CoverRegionPredecessors(op, phase, region)); !errors.Is(err, ErrWrongState) {
+ t.Fatalf("region coverage = %v, want ErrWrongState", err)
+ }
+ assertAdmissionStored(t, ctx, s, o)
+ })
+ }
+}
+
+func TestValidateAdmissionRegion(t *testing.T) {
+ ctx := context.Background()
+ s := release.NewDirStore(t.TempDir())
+ o := servingClaim(t, ctx, s)
+ for _, records := range [][]AdmissionRecord{
+ {{Region: ""}},
+ {{Region: "ie"}, {Region: ""}},
+ {{Region: "", Covered: true}, {Region: "ie"}},
+ } {
+ o.Claim.Holder.Admissions = records
+ if err := o.Claim.Validate(); !errors.Is(err, ErrMalformed) {
+ t.Fatalf("admissions %+v: Validate = %v, want ErrMalformed", records, err)
+ }
+ }
+}