// Package ledger — the open-ledger stream primitive: append-only, // hash-chained public JSON entries in the deployment bucket, keyed by // (account, app) exactly as the fleet page groups routes. The chain // hashes STORED entry bytes verbatim: the writer marshals once and the // hash commits to exactly those bytes, so verification needs no // canonicalization pass in any language. // Spec: docs/superpowers/specs/2026-08-03-open-ledgers-design.md §2. package ledger import ( "crypto/sha256" "encoding/hex" "encoding/json" "regexp" "strings" "github.com/carlosframework/platform/internal/release" ) const ( Schema = 1 MaxPayload = 64 << 10 MaxBlob = 4 << 20 ) type Entry struct { Schema int `json:"schema"` Ledger string `json:"ledger"` Writer string `json:"writer"` TS string `json:"ts"` Payload json.RawMessage `json:"payload"` PrevKey string `json:"prev_key"` PrevSHA256 string `json:"prev_sha256"` } type Head struct { Schema int `json:"schema"` UpdatedAt string `json:"updated_at"` HeadKey string `json:"head_key"` HeadSHA256 string `json:"head_sha256"` Count int `json:"count"` } type Manifest struct { Schema int `json:"schema"` Public bool `json:"public"` Ledgers []string `json:"ledgers"` Title string `json:"title"` } var nameRe = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,63}$`) func ValidName(s string) bool { return nameRe.MatchString(s) } func Prefix(sqid, app, name string) string { return release.LedgerAppPrefix(sqid, app) + name + "/" } func HeadKey(sqid, app, name string) string { return Prefix(sqid, app, name) + "HEAD.json" } func ManifestKey(sqid, app string) string { return release.LedgerAppPrefix(sqid, app) + "ledgers.json" } func BlobKey(sqid, app, name, sha string) string { return Prefix(sqid, app, name) + "blobs/" + sha } // tsCompact strips the separators RFC3339 puts in a UTC timestamp so entry // keys sort chronologically as plain strings. func tsCompact(ts string) string { return strings.NewReplacer("-", "", ":", "").Replace(ts) } func EntryKey(sqid, app, name, ts, sha string) string { return Prefix(sqid, app, name) + "entries/" + tsCompact(ts) + "-" + sha[:12] + ".json" } func SHA256Hex(b []byte) string { sum := sha256.Sum256(b) return hex.EncodeToString(sum[:]) }