carlos / platform Public

hostclaim: cold recovery installs a new generation and stamp (§6.4a, R7-1)

Co-Authored-By: Codex (gpt-6-astra) <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 c677ae68f60ca907ff72d62f71df8f0cec015d1d parent faa5573
2 files changed, +54 −0
  • internal/hostclaim/generation.go +24 −0
  • internal/hostclaim/generation_test.go +30 −0
diff --git a/internal/hostclaim/generation.go b/internal/hostclaim/generation.go
new file mode 100644
index 000000000..0a82cd8c1
--- /dev/null
+++ b/internal/hostclaim/generation.go
@@ -0,0 +1,24 @@
+package hostclaim
+
+import "fmt"
+
+// NewGeneration is §6.4a's cold recovery: an installing CAS that gives a
+// writer whose predecessor may still have uploads in flight a fresh
+// generation, and with it a fresh stamp (§2, R6-4). It is never a
+// same-generation restart.
+func NewGeneration(observedGeneration, newGeneration, execBox string) func(*Claim) error {
+ return func(c *Claim) error {
+ if c.Holder == nil || c.Op.Kind != KindServing || c.Op.Exec == "" || c.Op.Exec != execBox {
+ return fmt.Errorf("%w: only the serving executor may start a generation", ErrWrongState)
+ }
+ if c.Holder.Generation != observedGeneration {
+ return fmt.Errorf("%w: generation moved since it was observed", ErrWrongState)
+ }
+ if newGeneration == "" || newGeneration == c.Holder.Generation {
+ return fmt.Errorf("%w: a cold recovery needs a new generation", ErrWrongState)
+ }
+ c.Holder.Generation = newGeneration
+ c.Holder.Stamp = c.Gen + 1 // the resulting gen
+ return nil
+ }
+}
diff --git a/internal/hostclaim/generation_test.go b/internal/hostclaim/generation_test.go
new file mode 100644
index 000000000..9b3a320b3
--- /dev/null
+++ b/internal/hostclaim/generation_test.go
@@ -0,0 +1,30 @@
+package hostclaim
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/release"
+)
+
+func TestNewGenerationInstallsOnce(t *testing.T) {
+ ctx := context.Background()
+ s := release.NewDirStore(t.TempDir())
+ o := servingClaim(t, ctx, s)
+ before := o.Claim.Holder.Stamp
+ o2, err := Update(ctx, s, o, NewGeneration("", "gen_a", "adi-knitting"))
+ if err != nil || o2.Claim.Holder.Generation != "gen_a" || o2.Claim.Holder.Stamp != o2.Claim.Gen || o2.Claim.Holder.Stamp <= before {
+ t.Fatalf("new generation = %+v, %v", o2.Claim.Holder, err)
+ }
+ // A second recovery that observed the old generation must not install.
+ if _, err := Update(ctx, s, o2, NewGeneration("", "gen_b", "adi-knitting")); !errors.Is(err, ErrWrongState) {
+ t.Fatalf("stale recovery = %v, want ErrWrongState", err)
+ }
+ if _, err := Update(ctx, s, o2, NewGeneration("gen_a", "gen_c", "some-other-box")); !errors.Is(err, ErrWrongState) {
+ t.Fatalf("recovery by a non-executor = %v, want ErrWrongState", err)
+ }
+ if _, err := Update(ctx, s, o2, NewGeneration("gen_a", "gen_a", "adi-knitting")); !errors.Is(err, ErrWrongState) {
+ t.Fatalf("same-generation restart = %v, want ErrWrongState", err)
+ }
+}