carlos / platform Public

ledger: content-addressed blobs and the per-app opt-in manifest

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell fe7472eb0b08bab91ae6991e5a2a7212d5296ea2 parent 9a5fd0d
4 files changed, +150 −0
  • internal/ledger/blob.go +28 −0
  • internal/ledger/blob_test.go +37 −0
  • internal/ledger/manifest.go +50 −0
  • internal/ledger/manifest_test.go +35 −0
diff --git a/internal/ledger/blob.go b/internal/ledger/blob.go
new file mode 100644
index 000000000..746bc127f
--- /dev/null
+++ b/internal/ledger/blob.go
@@ -0,0 +1,28 @@
+package ledger
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ "github.com/carlosframework/platform/internal/release"
+)
+
+func PutBlob(ctx context.Context, st release.Store, sqid, app, name string, body []byte) (string, error) {
+ if !ValidName(name) {
+ return "", fmt.Errorf("ledger: invalid ledger name %q", name)
+ }
+ if len(body) > MaxBlob {
+ return "", fmt.Errorf("ledger: blob %d bytes exceeds cap %d", len(body), MaxBlob)
+ }
+ sha := SHA256Hex(body)
+ if _, err := st.Put(ctx, BlobKey(sqid, app, name, sha), body, ""); err != nil {
+ // Content-addressed: an existing object with this key IS this
+ // content. A create-only conflict means it's already there.
+ if errors.Is(err, release.ErrCASConflict) {
+ return sha, nil
+ }
+ return "", err
+ }
+ return sha, nil
+}
diff --git a/internal/ledger/blob_test.go b/internal/ledger/blob_test.go
new file mode 100644
index 000000000..5c1818b8c
--- /dev/null
+++ b/internal/ledger/blob_test.go
@@ -0,0 +1,37 @@
+package ledger
+
+import (
+ "context"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/release"
+)
+
+func TestPutBlobIdempotent(t *testing.T) {
+ st := release.NewDirStore(t.TempDir())
+ ctx := context.Background()
+ body := []byte("redacted corpus bytes")
+ sha1, err := PutBlob(ctx, st, "bab", "platform", "prompts", body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ sha2, err := PutBlob(ctx, st, "bab", "platform", "prompts", body)
+ if err != nil {
+ t.Fatalf("re-upload of identical content errored: %v", err)
+ }
+ if sha1 != sha2 || sha1 != SHA256Hex(body) {
+ t.Fatalf("sha mismatch: %s %s", sha1, sha2)
+ }
+ got, _, err := st.Get(ctx, BlobKey("bab", "platform", "prompts", sha1))
+ if err != nil || string(got) != string(body) {
+ t.Fatalf("stored blob: %q %v", got, err)
+ }
+}
+
+func TestPutBlobRejectsOversize(t *testing.T) {
+ st := release.NewDirStore(t.TempDir())
+ if _, err := PutBlob(context.Background(), st, "bab", "platform", "prompts",
+ make([]byte, MaxBlob+1)); err == nil {
+ t.Fatal("oversized blob accepted")
+ }
+}
diff --git a/internal/ledger/manifest.go b/internal/ledger/manifest.go
new file mode 100644
index 000000000..b9ea88e78
--- /dev/null
+++ b/internal/ledger/manifest.go
@@ -0,0 +1,50 @@
+package ledger
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "os"
+
+ "github.com/carlosframework/platform/internal/release"
+)
+
+func ReadManifest(ctx context.Context, st release.Store, sqid, app string) (Manifest, error) {
+ b, _, err := st.Get(ctx, ManifestKey(sqid, app))
+ if err != nil {
+ return Manifest{}, err
+ }
+ var m Manifest
+ if err := json.Unmarshal(b, &m); err != nil {
+ return Manifest{}, err
+ }
+ return m, nil
+}
+
+// WriteManifest is read-etag-then-Put with one retry on a lost race —
+// the manifest is small owner-intent state where last-writer-wins is
+// correct (the putOverwrite pattern, internal/status/fleet.go).
+func WriteManifest(ctx context.Context, st release.Store, sqid, app string, m Manifest) error {
+ m.Schema = Schema
+ body, err := json.Marshal(m)
+ if err != nil {
+ return err
+ }
+ key := ManifestKey(sqid, app)
+ for attempt := 0; ; attempt++ {
+ _, etag, err := st.Get(ctx, key)
+ ifMatch := etag
+ if errors.Is(err, os.ErrNotExist) {
+ ifMatch = ""
+ } else if err != nil {
+ return err
+ }
+ _, err = st.Put(ctx, key, body, ifMatch)
+ if err == nil {
+ return nil
+ }
+ if !errors.Is(err, release.ErrCASConflict) || attempt >= 1 {
+ return err
+ }
+ }
+}
diff --git a/internal/ledger/manifest_test.go b/internal/ledger/manifest_test.go
new file mode 100644
index 000000000..58b7156f0
--- /dev/null
+++ b/internal/ledger/manifest_test.go
@@ -0,0 +1,35 @@
+package ledger
+
+import (
+ "context"
+ "errors"
+ "os"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/release"
+)
+
+func TestManifestRoundTrip(t *testing.T) {
+ st := release.NewDirStore(t.TempDir())
+ ctx := context.Background()
+ if _, err := ReadManifest(ctx, st, "bab", "platform"); !errors.Is(err, os.ErrNotExist) {
+ t.Fatalf("missing manifest: %v, want os.ErrNotExist", err)
+ }
+ m := Manifest{Schema: Schema, Public: true, Ledgers: []string{"carbon"}, Title: "CARLOS"}
+ if err := WriteManifest(ctx, st, "bab", "platform", m); err != nil {
+ t.Fatal(err)
+ }
+ got, err := ReadManifest(ctx, st, "bab", "platform")
+ if err != nil || !got.Public || got.Title != "CARLOS" || len(got.Ledgers) != 1 {
+ t.Fatalf("%+v %v", got, err)
+ }
+ // Overwrite flips public off — WriteManifest must replace, not create-only.
+ m.Public = false
+ if err := WriteManifest(ctx, st, "bab", "platform", m); err != nil {
+ t.Fatal(err)
+ }
+ got, _ = ReadManifest(ctx, st, "bab", "platform")
+ if got.Public {
+ t.Fatal("manifest overwrite did not take")
+ }
+}