hostwrite: the inert selection interface for host-specific writers (claim Plan 6 Task 2)
Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+95
−0
internal/hostwrite/write.go+45 −0internal/hostwrite/write_test.go+50 −0
diff --git a/internal/hostwrite/write.go b/internal/hostwrite/write.go| new file mode 100644 |
| index 000000000..dc9b3c097 |
| --- /dev/null |
| +++ b/internal/hostwrite/write.go |
| @@ -0,0 +1,45 @@ |
| +package hostwrite |
| + |
| +import ( |
| + "context" |
| + "errors" |
| + "strings" |
| +) |
| + |
| +var ErrUnavailable = errors.New("hostwrite: lifecycle provider unavailable") |
| + |
| +type Identity struct { |
| + Config string |
| + Release string |
| +} |
| + |
| +type Work interface { |
| + With(context.Context, Identity, func() error) error |
| +} |
| + |
| +type Selector interface { |
| + Select(context.Context, string, string) (Work, error) |
| +} |
| + |
| +type Config struct { |
| + Enabled bool |
| + Selector Selector |
| +} |
| + |
| +func Enabled(getenv func(string) string) bool { |
| + return strings.TrimSpace(getenv("CARLOS_HOST_CLAIM")) != "" |
| +} |
| + |
| +func (c Config) Select(ctx context.Context, host, action string) (Work, error) { |
| + if !c.Enabled { |
| + return legacy{}, nil |
| + } |
| + if c.Selector == nil { |
| + return nil, ErrUnavailable |
| + } |
| + return c.Selector.Select(ctx, host, action) |
| +} |
| + |
| +type legacy struct{} |
| + |
| +func (legacy) With(_ context.Context, _ Identity, fn func() error) error { return fn() } |
diff --git a/internal/hostwrite/write_test.go b/internal/hostwrite/write_test.go| new file mode 100644 |
| index 000000000..765c38f87 |
| --- /dev/null |
| +++ b/internal/hostwrite/write_test.go |
| @@ -0,0 +1,50 @@ |
| +package hostwrite |
| + |
| +import ( |
| + "context" |
| + "errors" |
| + "testing" |
| +) |
| + |
| +type rejectSelector struct{ calls *int } |
| + |
| +func (s rejectSelector) Select(context.Context, string, string) (Work, error) { |
| + *s.calls++ |
| + return nil, ErrUnavailable |
| +} |
| + |
| +func TestFlagAndDisabledParity(t *testing.T) { |
| + for _, tc := range []struct { |
| + value string |
| + want bool |
| + }{{"", false}, {" \n", false}, {"0", true}, {"1", true}} { |
| + calls := 0 |
| + got := Enabled(func(key string) string { |
| + calls++ |
| + if key != "CARLOS_HOST_CLAIM" { |
| + t.Fatal(key) |
| + } |
| + return tc.value |
| + }) |
| + if got != tc.want || calls != 1 { |
| + t.Fatal(tc, got, calls) |
| + } |
| + } |
| + calls, writes := 0, 0 |
| + work, err := (Config{Selector: rejectSelector{&calls}}).Select(context.Background(), "host", "config") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + sentinel := errors.New("legacy error") |
| + err = work.With(context.Background(), Identity{}, func() error { writes++; return sentinel }) |
| + if err != sentinel || calls != 0 || writes != 1 { |
| + t.Fatal(err, calls, writes) |
| + } |
| +} |
| + |
| +func TestEnabledMissingProviderRefuses(t *testing.T) { |
| + work, err := (Config{Enabled: true}).Select(context.Background(), "host", "config") |
| + if work != nil || !errors.Is(err, ErrUnavailable) { |
| + t.Fatal(work, err) |
| + } |
| +} |