carlos / platform Public

release: the platform's own channel pointer, root-key-only

control/system/<component>/<channel>.json names a version and its
per-platform artifacts. History carries whole releases so a rollback
restores artifacts, and a rollback is a new promote (new PromotedAt).
A grant on the wire is refused before the signature is read, like the
production flag.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QCUCpfxbCyEAVQ2m51dH5
Paul Campbell pushed by paul@keymail.dev c632c3890a5f30ce39dca7ef3f02c725251a20c1 parent fc904cf
2 files changed, +501 −0
  • internal/release/system.go +291 −0
  • internal/release/system_test.go +210 −0
diff --git a/internal/release/system.go b/internal/release/system.go
new file mode 100644
index 000000000..d4a8529e7
--- /dev/null
+++ b/internal/release/system.go
@@ -0,0 +1,291 @@
+package release
+
+import (
+ "context"
+ "crypto/ed25519"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "regexp"
+ "time"
+)
+
+// SystemPointer is the platform's own channel pointer: what a ChannelPointer
+// is for an app, for the thing that does the adopting. It lives under
+// control/, not apps/, because the substrate is not an app (the
+// console-as-an-app spec's dividing line), and it is ROOT-KEY-ONLY: a scoped
+// grant's rungs name app channels, and a key that can roll the platform is a
+// bigger key than any app promoter should hold. Spec §3.
+type SystemPointer struct {
+ Schema int `json:"schema"`
+ Component string `json:"component"`
+ Channel string `json:"channel"`
+ Current SystemRelease `json:"current"`
+ History []SystemRelease `json:"history,omitempty"`
+ Spread string `json:"spread"`
+ PromotedAt time.Time `json:"promoted_at"` // of THIS write; the box's replay guard reads only this
+ Audit []Promotion `json:"audit,omitempty"`
+}
+
+// SystemRelease is everything a box needs to install one version. It travels
+// whole into History so a rollback re-installs what was there, not a version
+// string with someone else's artifacts.
+type SystemRelease struct {
+ Version string `json:"version"`
+ Artifacts map[string]SystemArtifact `json:"artifacts"` // keyed by "<goos>-<goarch>"
+ Source string `json:"source,omitempty"`
+ ReleasedAt time.Time `json:"released_at"` // audit only; never compared
+}
+
+// SystemArtifact is one mirrored binary. Its sha in the signed pointer IS
+// the artifact's signature: boxes need no .sig object.
+type SystemArtifact struct {
+ Key string `json:"key"`
+ SHA256 string `json:"sha256"`
+}
+
+// SystemComponentRouter is the one component today: the substrate binary
+// (edge + activator + adoption, one process). A split adds a name here.
+const SystemComponentRouter = "router"
+
+// ErrNoSystemHistory is RollbackSystem's answer when the pointer has never
+// moved: there is nothing older to go back to.
+var ErrNoSystemHistory = errors.New("system pointer: nothing to roll back to")
+
+const (
+ systemSpreadMax = 24 * time.Hour
+ systemSpreadDefault = 5 * time.Minute
+)
+
+var (
+ systemComponentRe = regexp.MustCompile(`^[a-z][a-z0-9-]{0,31}$`)
+ systemPlatformRe = regexp.MustCompile(`^[a-z0-9]+-[a-z0-9]+$`)
+ systemSHA256Re = regexp.MustCompile(`^[0-9a-f]{64}$`)
+)
+
+// SystemPointerKey is control/system/<component>/<channel>.json.
+func SystemPointerKey(component, channel string) string {
+ return "control/system/" + component + "/" + channel + ".json"
+}
+
+// SystemArtifactKey is bootstrap/router/<version>/carlos-<platform>: under
+// bootstrap/ because the store door already means "the platform's own
+// binary" by that name, per-version because canary and stable boxes must be
+// able to run different binaries at once.
+func SystemArtifactKey(version, platform string) string {
+ return "bootstrap/router/" + version + "/carlos-" + platform
+}
+
+// SpreadDuration is Spread parsed, or the default when it is unparseable —
+// validation refuses the unparseable case on the way in, so this only
+// defaults for a hand-edited or pre-field pointer.
+func (p *SystemPointer) SpreadDuration() time.Duration {
+ d, err := time.ParseDuration(p.Spread)
+ if err != nil || d < 0 || d > systemSpreadMax {
+ return systemSpreadDefault
+ }
+ return d
+}
+
+func validateSystemRelease(r SystemRelease) error {
+ if !ValidVersion(r.Version) || r.Version == "dev" {
+ return fmt.Errorf("version %q is not a valid platform version", r.Version)
+ }
+ if len(r.Artifacts) == 0 {
+ return fmt.Errorf("version %s names no artifacts", r.Version)
+ }
+ for plat, a := range r.Artifacts {
+ if !systemPlatformRe.MatchString(plat) {
+ return fmt.Errorf("platform %q is not <goos>-<goarch>", plat)
+ }
+ if a.Key != SystemArtifactKey(r.Version, plat) {
+ return fmt.Errorf("artifact key %q is not %s", a.Key, SystemArtifactKey(r.Version, plat))
+ }
+ if !systemSHA256Re.MatchString(a.SHA256) {
+ return fmt.Errorf("artifact %s: sha256 %q is not 64 hex characters", a.Key, a.SHA256)
+ }
+ }
+ return nil
+}
+
+func validateSystemPointer(p *SystemPointer, wantComponent, wantChannel string) error {
+ if p.Schema != 1 {
+ return fmt.Errorf("system pointer schema %d unsupported", p.Schema)
+ }
+ if p.Component != wantComponent || p.Channel != wantChannel {
+ return fmt.Errorf("system pointer is for %s/%s, not %s/%s", p.Component, p.Channel, wantComponent, wantChannel)
+ }
+ if !systemComponentRe.MatchString(p.Component) || !ValidChannel(p.Channel) {
+ return fmt.Errorf("system pointer %s/%s: bad component or channel", p.Component, p.Channel)
+ }
+ if err := validateSystemRelease(p.Current); err != nil {
+ return fmt.Errorf("system pointer %s/%s: %w", p.Component, p.Channel, err)
+ }
+ if d, err := time.ParseDuration(p.Spread); err != nil || d < 0 || d > systemSpreadMax {
+ return fmt.Errorf("system pointer %s/%s: spread %q must be a duration between 0 and %s", p.Component, p.Channel, p.Spread, systemSpreadMax)
+ }
+ if p.PromotedAt.IsZero() {
+ return fmt.Errorf("system pointer %s/%s: promoted_at unset", p.Component, p.Channel)
+ }
+ if len(p.History) > historyMax {
+ return fmt.Errorf("system pointer %s/%s: history longer than %d", p.Component, p.Channel, historyMax)
+ }
+ for _, h := range p.History {
+ if err := validateSystemRelease(h); err != nil {
+ return fmt.Errorf("system pointer %s/%s: history: %w", p.Component, p.Channel, err)
+ }
+ }
+ if len(p.Audit) > auditMax {
+ return fmt.Errorf("system pointer %s/%s: audit longer than %d", p.Component, p.Channel, auditMax)
+ }
+ return nil
+}
+
+// signSystemPointer mirrors signPointer with two differences: the signer
+// must be the root key, and the wrapper never carries a grant.
+func signSystemPointer(sg Signer, p *SystemPointer) ([]byte, error) {
+ if err := sg.usable(); err != nil {
+ return nil, err
+ }
+ if sg.scope != nil {
+ return nil, errors.New("system pointer: signed by a scoped key — the platform takes the root key only")
+ }
+ p.Schema = 1
+ p.PromotedAt = time.Now().UTC().Truncate(time.Second)
+ if err := validateSystemPointer(p, p.Component, p.Channel); err != nil {
+ return nil, err
+ }
+ payload, err := json.MarshalIndent(p, "", " ")
+ if err != nil {
+ return nil, err
+ }
+ b64 := base64.StdEncoding.EncodeToString(payload)
+ sig := ed25519.Sign(sg.key, []byte(b64))
+ return json.MarshalIndent(signedWrapper{Payload: b64, Sig: base64.StdEncoding.EncodeToString(sig)}, "", " ")
+}
+
+// verifySystemPointer is the single parse site for system pointers. A grant
+// on the wire is refused before the signature is looked at (the production
+// flag's rule, production.go).
+func verifySystemPointer(pub ed25519.PublicKey, wrapper []byte, wantComponent, wantChannel string) (*SystemPointer, error) {
+ var sw signedWrapper
+ if err := json.Unmarshal(wrapper, &sw); err != nil {
+ return nil, fmt.Errorf("system pointer %s/%s: %w", wantComponent, wantChannel, err)
+ }
+ if sw.Grant != nil {
+ return nil, fmt.Errorf("system pointer %s/%s: signed by a scoped key — the platform takes the root key only", wantComponent, wantChannel)
+ }
+ sig, err := base64.StdEncoding.DecodeString(sw.Sig)
+ if err != nil {
+ return nil, fmt.Errorf("system pointer %s/%s: bad signature encoding", wantComponent, wantChannel)
+ }
+ if len(pub) != ed25519.PublicKeySize || !ed25519.Verify(pub, []byte(sw.Payload), sig) {
+ return nil, fmt.Errorf("system pointer %s/%s: signature does not verify", wantComponent, wantChannel)
+ }
+ payload, err := base64.StdEncoding.DecodeString(sw.Payload)
+ if err != nil {
+ return nil, fmt.Errorf("system pointer %s/%s: bad payload encoding", wantComponent, wantChannel)
+ }
+ var p SystemPointer
+ if err := json.Unmarshal(payload, &p); err != nil {
+ return nil, fmt.Errorf("system pointer %s/%s: %w", wantComponent, wantChannel, err)
+ }
+ if err := validateSystemPointer(&p, wantComponent, wantChannel); err != nil {
+ return nil, err
+ }
+ return &p, nil
+}
+
+// GetSystemPointer reads and verifies one channel's pointer. A missing key
+// is os.ErrNotExist, which is the normal state of every deployment before
+// its first `carlos system update`.
+func GetSystemPointer(ctx context.Context, s Store, pub ed25519.PublicKey, component, channel string) (*SystemPointer, string, error) {
+ body, etag, err := s.Get(ctx, SystemPointerKey(component, channel))
+ if err != nil {
+ return nil, "", err
+ }
+ p, err := verifySystemPointer(pub, body, component, channel)
+ if err != nil {
+ return nil, "", err
+ }
+ return p, etag, nil
+}
+
+// SystemPromoteOptions: who did it, and how long boxes spread their starts.
+type SystemPromoteOptions struct {
+ Actor string
+ Spread time.Duration // 0 = hotfix, every box at once; unset in the CLI means the default
+}
+
+// PromoteSystem writes the pointer for one channel, CAS-guarded. The
+// outgoing release goes whole into History unless it is the same version
+// (a re-promote of the running version is a no-op for boxes and must not
+// push that version into its own history).
+func PromoteSystem(ctx context.Context, s Store, sg Signer, component, channel string, rel SystemRelease, o SystemPromoteOptions) (*SystemPointer, error) {
+ key := SystemPointerKey(component, channel)
+ var old *SystemPointer
+ body, etag, err := s.Get(ctx, key)
+ switch {
+ case err == nil:
+ if old, err = verifySystemPointer(sg.verifyPub(), body, component, channel); err != nil {
+ return nil, err
+ }
+ case errors.Is(err, os.ErrNotExist):
+ etag = ""
+ default:
+ return nil, err
+ }
+ next := &SystemPointer{Component: component, Channel: channel, Current: rel, Spread: o.Spread.String()}
+ if rel.ReleasedAt.IsZero() {
+ next.Current.ReleasedAt = time.Now().UTC().Truncate(time.Second)
+ }
+ from := ""
+ if old != nil {
+ next.History, next.Audit = old.History, old.Audit
+ from = old.Current.Version
+ if old.Current.Version != rel.Version {
+ next.History = prependCapped(old.Current, old.History, historyMax)
+ }
+ }
+ next.Audit = prependCapped(Promotion{Version: rel.Version, From: from, To: channel, Actor: o.Actor, At: time.Now().UTC()}, next.Audit, auditMax)
+ wrapper, err := signSystemPointer(sg, next)
+ if err != nil {
+ return nil, err
+ }
+ if _, err := s.Put(ctx, key, wrapper, etag); err != nil {
+ return nil, err
+ }
+ return next, nil
+}
+
+// RollbackSystem swaps Current with History[0] and writes a NEW pointer —
+// new PromotedAt — so every box's replay guard sees a promote, not a replay.
+func RollbackSystem(ctx context.Context, s Store, sg Signer, component, channel, actor string) (*SystemPointer, error) {
+ key := SystemPointerKey(component, channel)
+ body, etag, err := s.Get(ctx, key)
+ if err != nil {
+ return nil, err
+ }
+ old, err := verifySystemPointer(sg.verifyPub(), body, component, channel)
+ if err != nil {
+ return nil, err
+ }
+ if len(old.History) == 0 {
+ return nil, ErrNoSystemHistory
+ }
+ next := &SystemPointer{
+ Component: component, Channel: channel,
+ Current: old.History[0], History: old.History[1:], Spread: old.Spread,
+ Audit: prependCapped(Promotion{Version: old.History[0].Version, From: old.Current.Version, To: channel, Actor: actor, At: time.Now().UTC()}, old.Audit, auditMax),
+ }
+ wrapper, err := signSystemPointer(sg, next)
+ if err != nil {
+ return nil, err
+ }
+ if _, err := s.Put(ctx, key, wrapper, etag); err != nil {
+ return nil, err
+ }
+ return next, nil
+}
diff --git a/internal/release/system_test.go b/internal/release/system_test.go
new file mode 100644
index 000000000..dcb2a9b2b
--- /dev/null
+++ b/internal/release/system_test.go
@@ -0,0 +1,210 @@
+package release
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "os"
+ "strings"
+ "testing"
+ "time"
+)
+
+func testSystemRelease(v string) SystemRelease {
+ return SystemRelease{
+ Version: v,
+ Artifacts: map[string]SystemArtifact{
+ "linux-arm64": {Key: SystemArtifactKey(v, "linux-arm64"), SHA256: strings.Repeat("ab", 32)},
+ },
+ Source: "release:" + v,
+ ReleasedAt: time.Date(2026, 9, 6, 8, 0, 0, 0, time.UTC),
+ }
+}
+
+func TestSystemPointerRoundTrip(t *testing.T) {
+ priv, pub := testKeys(t)
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ if _, _, err := GetSystemPointer(ctx, s, pub, SystemComponentRouter, "stable"); !errors.Is(err, os.ErrNotExist) {
+ t.Fatalf("missing pointer: err = %v, want ErrNotExist", err)
+ }
+ p, err := PromoteSystem(ctx, s, RootSigner(priv), SystemComponentRouter, "stable", testSystemRelease("v0.18.0"),
+ SystemPromoteOptions{Actor: "paul", Spread: 5 * time.Minute})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p.Current.Version != "v0.18.0" || p.Spread != "5m0s" || p.Schema != 1 || p.PromotedAt.IsZero() {
+ t.Fatalf("promoted pointer = %+v", p)
+ }
+ got, etag, err := GetSystemPointer(ctx, s, pub, SystemComponentRouter, "stable")
+ if err != nil || etag == "" {
+ t.Fatalf("get: %v etag=%q", err, etag)
+ }
+ if got.Current.Artifacts["linux-arm64"].SHA256 != strings.Repeat("ab", 32) {
+ t.Fatalf("artifact lost: %+v", got.Current)
+ }
+ if got.SpreadDuration() != 5*time.Minute {
+ t.Fatalf("spread = %v", got.SpreadDuration())
+ }
+ if len(got.Audit) != 1 || got.Audit[0].Actor != "paul" || got.Audit[0].Version != "v0.18.0" {
+ t.Fatalf("audit = %+v", got.Audit)
+ }
+}
+
+func TestSystemPromoteKeepsWholeReleaseInHistoryAndRollbackRestoresIt(t *testing.T) {
+ priv, pub := testKeys(t)
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ sg := RootSigner(priv)
+ if _, err := PromoteSystem(ctx, s, sg, "router", "canary", testSystemRelease("v0.18.0"), SystemPromoteOptions{}); err != nil {
+ t.Fatal(err)
+ }
+ first, _, _ := GetSystemPointer(ctx, s, pub, "router", "canary")
+ time.Sleep(1100 * time.Millisecond) // PromotedAt is second-truncated; the rollback must be NEWER
+ if _, err := PromoteSystem(ctx, s, sg, "router", "canary", testSystemRelease("v0.19.0"), SystemPromoteOptions{}); err != nil {
+ t.Fatal(err)
+ }
+ p, _, _ := GetSystemPointer(ctx, s, pub, "router", "canary")
+ if len(p.History) != 1 || p.History[0].Version != "v0.18.0" || p.History[0].Artifacts["linux-arm64"].Key == "" {
+ t.Fatalf("history must carry the whole release: %+v", p.History)
+ }
+ time.Sleep(1100 * time.Millisecond)
+ rb, err := RollbackSystem(ctx, s, sg, "router", "canary", "paul")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if rb.Current.Version != "v0.18.0" || rb.Current.Artifacts["linux-arm64"].Key != SystemArtifactKey("v0.18.0", "linux-arm64") {
+ t.Fatalf("rollback current = %+v", rb.Current)
+ }
+ if !rb.PromotedAt.After(first.PromotedAt) || !rb.PromotedAt.After(p.PromotedAt) {
+ t.Fatalf("a rollback is a NEW promote: promoted_at %v must be newer than %v and %v", rb.PromotedAt, first.PromotedAt, p.PromotedAt)
+ }
+ if len(rb.History) != 0 {
+ t.Fatalf("history after rollback = %+v", rb.History)
+ }
+ if _, err := RollbackSystem(ctx, s, sg, "router", "canary", "paul"); !errors.Is(err, ErrNoSystemHistory) {
+ t.Fatalf("second rollback err = %v", err)
+ }
+}
+
+func TestSystemPromoteSameVersionDoesNotGrowHistory(t *testing.T) {
+ priv, pub := testKeys(t)
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ sg := RootSigner(priv)
+ for i := 0; i < 2; i++ {
+ if _, err := PromoteSystem(ctx, s, sg, "router", "stable", testSystemRelease("v1.0.0"), SystemPromoteOptions{}); err != nil {
+ t.Fatal(err)
+ }
+ }
+ p, _, _ := GetSystemPointer(ctx, s, pub, "router", "stable")
+ if len(p.History) != 0 {
+ t.Fatalf("re-promoting the running version must not push it into history: %+v", p.History)
+ }
+}
+
+func TestSystemPointerRefusesScopedSigner(t *testing.T) {
+ _, _, sg := scopedFixture(t, []string{RungCanary, RungEdge}, 30*24*time.Hour)
+ s := NewDirStore(t.TempDir())
+ _, err := PromoteSystem(context.Background(), s, sg, "router", "canary", testSystemRelease("v1.0.0"), SystemPromoteOptions{})
+ if err == nil || !strings.Contains(err.Error(), "root key") {
+ t.Fatalf("scoped signer signed a system pointer: err = %v", err)
+ }
+}
+
+func TestSystemPointerRefusesAGrantOnTheWire(t *testing.T) {
+ // A wrapper carrying a grant is refused BEFORE the signature is checked,
+ // like the production flag (production.go) and the pipeline.
+ priv, pub := testKeys(t)
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ if _, err := PromoteSystem(ctx, s, RootSigner(priv), "router", "stable", testSystemRelease("v1.0.0"), SystemPromoteOptions{}); err != nil {
+ t.Fatal(err)
+ }
+ body, etag, _ := s.Get(ctx, SystemPointerKey("router", "stable"))
+ var sw signedWrapper
+ if err := json.Unmarshal(body, &sw); err != nil {
+ t.Fatal(err)
+ }
+ sw.Grant = &signedGrant{Payload: "x", Sig: "y"}
+ tampered, _ := json.Marshal(sw)
+ if _, err := s.Put(ctx, SystemPointerKey("router", "stable"), tampered, etag); err != nil {
+ t.Fatal(err)
+ }
+ if _, _, err := GetSystemPointer(ctx, s, pub, "router", "stable"); err == nil || !strings.Contains(err.Error(), "root key") {
+ t.Fatalf("grant-bearing wrapper accepted: err = %v", err)
+ }
+}
+
+func TestSystemPointerTamperAndCrossChannelRefused(t *testing.T) {
+ priv, pub := testKeys(t)
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ if _, err := PromoteSystem(ctx, s, RootSigner(priv), "router", "canary", testSystemRelease("v1.0.0"), SystemPromoteOptions{}); err != nil {
+ t.Fatal(err)
+ }
+ body, _, _ := s.Get(ctx, SystemPointerKey("router", "canary"))
+ // Cross-channel replay: the canary wrapper planted at the stable key.
+ if _, err := s.Put(ctx, SystemPointerKey("router", "stable"), body, ""); err != nil {
+ t.Fatal(err)
+ }
+ if _, _, err := GetSystemPointer(ctx, s, pub, "router", "stable"); err == nil {
+ t.Fatal("a canary pointer verified as stable")
+ }
+ // Tampered signature.
+ var sw signedWrapper
+ _ = json.Unmarshal(body, &sw)
+ sw.Sig = "AA" + sw.Sig[2:]
+ bad, _ := json.Marshal(sw)
+ _, etag, _ := s.Get(ctx, SystemPointerKey("router", "canary"))
+ if _, err := s.Put(ctx, SystemPointerKey("router", "canary"), bad, etag); err != nil {
+ t.Fatal(err)
+ }
+ if _, _, err := GetSystemPointer(ctx, s, pub, "router", "canary"); err == nil {
+ t.Fatal("tampered signature verified")
+ }
+}
+
+func TestSystemPointerValidation(t *testing.T) {
+ priv, _ := testKeys(t)
+ sg := RootSigner(priv)
+ ctx := context.Background()
+ cases := map[string]SystemRelease{
+ "dev version": {Version: "dev", Artifacts: testSystemRelease("x").Artifacts},
+ "no artifacts": {Version: "v1.0.0"},
+ "bad platform": {Version: "v1.0.0", Artifacts: map[string]SystemArtifact{"linux_arm64": {Key: "bootstrap/router/v1.0.0/carlos-linux_arm64", SHA256: strings.Repeat("a", 64)}}},
+ "key not derived": {Version: "v1.0.0", Artifacts: map[string]SystemArtifact{"linux-arm64": {Key: "bootstrap/carlos", SHA256: strings.Repeat("a", 64)}}},
+ "short sha": {Version: "v1.0.0", Artifacts: map[string]SystemArtifact{"linux-arm64": {Key: SystemArtifactKey("v1.0.0", "linux-arm64"), SHA256: "abc"}}},
+ "version traversal": {Version: "../x", Artifacts: testSystemRelease("../x").Artifacts},
+ }
+ for name, rel := range cases {
+ s := NewDirStore(t.TempDir())
+ if _, err := PromoteSystem(ctx, s, sg, "router", "stable", rel, SystemPromoteOptions{}); err == nil {
+ t.Errorf("%s: accepted", name)
+ }
+ }
+ s := NewDirStore(t.TempDir())
+ if _, err := PromoteSystem(ctx, s, sg, "Router", "stable", testSystemRelease("v1.0.0"), SystemPromoteOptions{}); err == nil {
+ t.Error("bad component accepted")
+ }
+ if _, err := PromoteSystem(ctx, s, sg, "router", "stable", testSystemRelease("v1.0.0"), SystemPromoteOptions{Spread: 48 * time.Hour}); err == nil {
+ t.Error("48h spread accepted")
+ }
+}
+
+func TestSystemPromoteCASConflict(t *testing.T) {
+ priv, _ := testKeys(t)
+ ctx := context.Background()
+ inner := NewDirStore(t.TempDir())
+ // A store whose Put always says "someone else moved it".
+ s := conflictingStore{Store: inner}
+ if _, err := PromoteSystem(ctx, s, RootSigner(priv), "router", "stable", testSystemRelease("v1.0.0"), SystemPromoteOptions{}); !errors.Is(err, ErrCASConflict) {
+ t.Fatalf("err = %v, want ErrCASConflict", err)
+ }
+}
+
+type conflictingStore struct{ Store }
+
+func (c conflictingStore) Put(ctx context.Context, key string, body []byte, ifMatch string) (string, error) {
+ return "", ErrCASConflict
+}