carlos / platform Public

hostbox: selected work under the real host guard (claim Plan 6 Task 3)

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 8e5ebc6ce45551781358295a372ae2ab90dbbd2c parent 5d64522
2 files changed, +207 −0
  • internal/hostbox/work.go +91 −0
  • internal/hostbox/work_test.go +116 −0
diff --git a/internal/hostbox/work.go b/internal/hostbox/work.go
new file mode 100644
index 000000000..66ac634c2
--- /dev/null
+++ b/internal/hostbox/work.go
@@ -0,0 +1,91 @@
+package hostbox
+
+import (
+ "context"
+ "errors"
+ "time"
+
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostguard"
+ "github.com/carlosframework/platform/internal/hostwrite"
+)
+
+var ErrChanged = errors.New("hostbox: selected authority changed")
+var ErrUnsupported = errors.New("hostbox: unresolved lifecycle contract")
+
+type Selection struct {
+ Host, Action, Box, Region string
+ Captured hostguard.Captured
+ Kind, Op, Phase, Generation string
+ Session uint64
+}
+
+type WorkSource struct {
+ Guard hostguard.Guard
+ Box, Region string
+ Now func() time.Time
+ // Policy checks the action's role/phase and the selected config/release.
+ Policy func(context.Context, *hostclaim.Claim, Selection, hostwrite.Identity) error
+ // Local checks quarantine, execution deadlines and failed cleanup under flock.
+ Local func(context.Context, *hostclaim.Claim, Selection) error
+}
+
+type selectedWork struct {
+ source WorkSource
+ selection Selection
+}
+
+func (s WorkSource) Select(ctx context.Context, host, action string) (hostwrite.Work, error) {
+ if ctx == nil || s.Box == "" || s.Region == "" || action == "" || s.Guard.Claims == nil || s.Policy == nil || s.Local == nil || s.Now == nil {
+ return nil, ErrUnsupported
+ }
+ captured, err := hostguard.Capture(s.Guard.DataDir, host)
+ if err != nil {
+ return nil, err
+ }
+ obs, err := s.Guard.Claims.Read(ctx, host)
+ if err != nil {
+ return nil, err
+ }
+ if obs.Absent || obs.Host != host || obs.Claim == nil || obs.Claim.Holder == nil || obs.Claim.Host != host {
+ return nil, ErrChanged
+ }
+ c := obs.Claim
+ if c.Holder.Incarnation != captured.Incarnation || c.Holder.Stamp != captured.Stamp || captured.Stamp == 0 {
+ return nil, ErrChanged
+ }
+ w := selectedWork{source: s, selection: Selection{Host: host, Action: action, Box: s.Box, Region: s.Region, Captured: captured, Kind: c.Op.Kind, Op: c.Op.ID, Phase: c.Op.Phase, Generation: c.Holder.Generation, Session: c.Holder.Session}}
+ return w, nil
+}
+
+func (w selectedWork) With(ctx context.Context, identity hostwrite.Identity, fn func() error) error {
+ s, selected := w.source, w.selection
+ return s.Guard.With(ctx, selected.Host, selected.Captured, func(c *hostclaim.Claim) error {
+ if c.Op.Kind != selected.Kind || c.Op.ID != selected.Op || c.Op.Phase != selected.Phase || c.Holder.Stamp != selected.Captured.Stamp || c.Holder.Session != selected.Session || c.Holder.Generation != selected.Generation {
+ return ErrChanged
+ }
+ switch selected.Action {
+ case "install", "restore", "spawn", "seed", "writer":
+ if until, ok := c.QuietAfter[s.Region]; ok && !s.Now().After(until) {
+ return ErrChanged
+ }
+ }
+ if err := s.Local(ctx, c, selected); err != nil {
+ return err
+ }
+ return s.Policy(ctx, c, selected, identity)
+ }, fn)
+}
+
+// Serving permits ordinary host writes only. Operations need their own policy.
+func Serving(c *hostclaim.Claim, selected Selection) error {
+ if c.Holder == nil || c.Op.Kind != hostclaim.KindServing || c.Op.ID != "" || c.Op.Phase != "" || c.Op.Exec != selected.Box {
+ return ErrChanged
+ }
+ for _, box := range c.Lost {
+ if box == selected.Box {
+ return ErrChanged
+ }
+ }
+ return nil
+}
diff --git a/internal/hostbox/work_test.go b/internal/hostbox/work_test.go
new file mode 100644
index 000000000..2d013ef9b
--- /dev/null
+++ b/internal/hostbox/work_test.go
@@ -0,0 +1,116 @@
+//go:build unix
+
+package hostbox
+
+import (
+ "context"
+ "errors"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostguard"
+ "github.com/carlosframework/platform/internal/hostwrite"
+ "github.com/carlosframework/platform/internal/release"
+)
+
+func workFixture(t *testing.T) (WorkSource, release.Store, hostclaim.Observed) {
+ t.Helper()
+ ctx := context.Background()
+ dir := t.TempDir()
+ locks := filepath.Join(dir, "locks")
+ for _, p := range []string{locks, filepath.Join(dir, "hostguard")} {
+ if err := os.Mkdir(p, 0770); err != nil {
+ t.Fatal(err)
+ }
+ }
+ store := release.NewDirStore(t.TempDir())
+ obs, err := hostclaim.Read(ctx, store, "shop.example")
+ if err != nil {
+ t.Fatal(err)
+ }
+ obs, err = hostclaim.Update(ctx, store, obs, hostclaim.Adopt(hostclaim.NewHolder{Family: "instance", Key: "apps/acct/app/instances/shop.example.json", Account: "acct", App: "app", Host: "shop.example", Region: "ie"}, "box", nil, nil, "adopt", func() string { return "id" }))
+ if err != nil {
+ t.Fatal(err)
+ }
+ source := WorkSource{Guard: hostguard.Guard{DataDir: dir, LockDir: locks, Claims: hostguard.StoreReader{Store: store}}, Box: "box", Region: "ie", Now: func() time.Time { return time.Unix(100, 0) }}
+ source.Policy = func(_ context.Context, c *hostclaim.Claim, s Selection, _ hostwrite.Identity) error {
+ return Serving(c, s)
+ }
+ source.Local = func(context.Context, *hostclaim.Claim, Selection) error { return nil }
+ if err := source.Guard.Activate(ctx, "shop.example", "box", nil); err != nil {
+ t.Fatal(err)
+ }
+ return source, store, obs
+}
+
+func TestSelectedWorkRejectsChangesAndNeverRecaptures(t *testing.T) {
+ for _, mode := range []string{"phase", "executor", "round-trip", "lease", "held"} {
+ t.Run(mode, func(t *testing.T) {
+ source, store, obs := workFixture(t)
+ localLost, heldChanged := false, false
+ source.Local = func(context.Context, *hostclaim.Claim, Selection) error {
+ if localLost {
+ return ErrUnsupported
+ }
+ return nil
+ }
+ source.Policy = func(_ context.Context, c *hostclaim.Claim, s Selection, i hostwrite.Identity) error {
+ if heldChanged || i.Config != "selected" {
+ return ErrChanged
+ }
+ return Serving(c, s)
+ }
+ ctx := context.Background()
+ work, err := source.Select(ctx, "shop.example", "config")
+ if err != nil {
+ t.Fatal(err)
+ }
+ writes := 0
+ if err := work.With(ctx, hostwrite.Identity{Config: "selected"}, func() error { writes++; return nil }); err != nil {
+ t.Fatal(err)
+ }
+ switch mode {
+ case "phase":
+ _, err = hostclaim.Update(ctx, store, obs, hostclaim.BeginDelete("delete", "test", "etag", time.Unix(1, 0), []string{"ie"}, []string{"box"}))
+ case "executor":
+ _, err = hostclaim.Update(ctx, store, obs, func(c *hostclaim.Claim) error { c.Op.Exec = "other"; c.Op.Participants = []string{"other"}; return nil })
+ case "round-trip":
+ _, err = hostclaim.Update(ctx, store, obs, hostclaim.AdmitWriter(hostclaim.Admission{Box: "box", Mode: "cold", NewGeneration: "next", Region: "ie", PredecessorRegion: "ie"}))
+ if err == nil {
+ err = source.Guard.Activate(ctx, "shop.example", "box", nil)
+ }
+ case "lease":
+ localLost = true
+ case "held":
+ heldChanged = true
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := work.With(ctx, hostwrite.Identity{Config: "selected"}, func() error { writes++; return nil }); err == nil {
+ t.Fatal("stale write admitted")
+ }
+ if writes != 1 {
+ t.Fatal(writes)
+ }
+ })
+ }
+}
+
+func TestSelectedWorkNeedsLocalRecordAndProviders(t *testing.T) {
+ source, _, _ := workFixture(t)
+ source.Local = nil
+ if _, err := source.Select(context.Background(), "shop.example", "env"); !errors.Is(err, ErrUnsupported) {
+ t.Fatal(err)
+ }
+ source.Local = func(context.Context, *hostclaim.Claim, Selection) error { return nil }
+ if err := os.Remove(filepath.Join(source.Guard.DataDir, "hostguard", "shop.example.json")); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := source.Select(context.Background(), "shop.example", "env"); !errors.Is(err, hostguard.ErrNoRecord) {
+ t.Fatal(err)
+ }
+}