Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // NewReplicator binds the DB at key to its store slot. NewReplicator = replica.NewReplicator // WithEncryption seals every pushed snapshot with the org's per-org key. WithEncryption = replica.WithEncryption // Owner returns the single writer for orgID (HRW), fail-closed on empty. Owner = ha.Owner // IsOwner reports whether selfID owns the writer for orgID. IsOwner = ha.IsOwner // Replicas returns the owner then ordered failover successors. Replicas = ha.Replicas // DBPath is the canonical object-store location of an org's SQLite DB. DBPath = replica.DBPath // NewFencedStore wraps a ConditionalStore as the round-fenced per-org ship path. NewFencedStore = replica.NewFencedStore // StaticFencer is the single-process Fencer (round always 1) for dev and tests. StaticFencer = ha.StaticFencer // ErrStaleRound is the deposed-writer rejection: a ship below the recorded round. ErrStaleRound = replica.ErrStaleRound )
var ErrNoMembership = errors.New("org: empty membership — no safe writer")
ErrNoMembership is returned by a Fencer when the live replica set is empty: no safe owner can be named, so the caller fails CLOSED (does not write).
var ErrNotOwner = errors.New("org: not the elected writer for this org")
ErrNotOwner is returned when this replica is not the HRW-elected writer for the org. The caller does not write; it forwards to, or steps aside for, the owner.
Functions ¶
func ConstantTimeEqual ¶ added in v1.786.12
ConstantTimeEqual compares two secrets without leaking timing — exported for callers verifying wrapped-key material.
Types ¶
type CASFencer ¶ added in v1.786.165
type CASFencer struct {
// contains filtered or unexported fields
}
CASFencer implements ha.Fencer by claiming/renewing a per-org writer lease in a linearizable object store. Acquire returns the caller's monotone lease round; FencedStore then stamps every ship with it.
func NewCASFencer ¶ added in v1.786.165
func NewCASFencer(store replica.ConditionalStore, view ownerView) *CASFencer
NewCASFencer builds a CASFencer over an atomic-CAS store and a membership view. The SAME store instance also backs the per-org replica.FencedStore, so the lease and the data ships share one linearizable register.
type Cipher ¶ added in v1.786.12
type Cipher struct {
// contains filtered or unexported fields
}
Cipher seals each org's SQLite snapshot at rest with a distinct 256-bit key, so the object in SeaweedFS is ciphertext and orgs are cryptographically isolated — a leaked file is useless without the KMS master key.
Envelope model: the KMS master (KMSMasterKeyRef, 32 bytes) never leaves the process. A per-org key is DERIVED from it via HKDF(master, label, orgID); it is held only in-memory, never stored, never on the wire. Rotating the master re-keys every org. AES-256-GCM provides confidentiality + integrity; the orgID is bound in as AAD so a sealed blob cannot be replayed under a different org.
The GCM nonce is DERIVED from (key, plaintext) — HMAC(key,"nonce"||plaintext), so identical plaintext seals to identical ciphertext (content-addressable, keeps the Replicator's version-skip working) while distinct plaintexts get distinct nonces (GCM safety: the same (key,nonce) never covers two different messages).
func NewCipher ¶ added in v1.786.12
NewCipher builds a Cipher from the KMS master key. master must be exactly 32 bytes (derive it from KMSMasterKeyRef, e.g. SHA-256 of the raw secret).
type ConditionalStore ¶ added in v1.786.165
type ConditionalStore = replica.ConditionalStore
type Durability ¶ added in v1.801.191
type Durability struct {
// contains filtered or unexported fields
}
Durability is the per-deployment, org-agnostic durable-store factory: the shared election+fence over ONE object store, plus the optional at-rest envelope. It holds no per-org state — For() mints a Durable per org DB. nil ⇒ durability disabled (local-only single-node/dev), the caller's default open path unchanged.
func NewDurability ¶ added in v1.801.191
func NewDurability(cond replica.ConditionalStore, view ownerView, cipher *Cipher) *Durability
NewDurability builds the factory over an atomic-CAS object store (the SeaweedFS S3 If-Match ConditionalStore), the live membership view (election input), and an optional per-org envelope Cipher (nil ⇒ the durable object is stored in the clear; pure-Go dev only). The SAME cond backs both the lease and the data ships, so they share one linearizable register.
func (*Durability) For ¶ added in v1.801.191
func (dy *Durability) For(orgID, dbKey, dbPath string) *Durable
For mints the Durable binding for one org DB: orgID is the org SLUG (the HRW election key AND the cipher AAD — the caller passes the SAME slug the on-disk path and the shard router hash use). dbKey is the durable object location (replica.DBPath). dbPath is the local SQLite file.
type Durable ¶ added in v1.801.191
type Durable struct {
// contains filtered or unexported fields
}
Durable binds one org's local SQLite file to its fenced durable object slot, gated by the org's single-writer lease. It never opens or closes the local handle (the store owns that) — Bind lends it the handle so Sync can checkpoint on the same single connection the store writes through.
func (*Durable) Bind ¶ added in v1.801.191
Bind lends Durable the store's live handle so Sync checkpoints on the SAME single connection the store writes through (serializing snapshot against writes without a second handle). Call after the store opens the local file.
func (*Durable) Close ¶ added in v1.801.191
Close best-effort ships any final state under the caller's (time-bounded) ctx — ship-before-ack already covers every acknowledged write, so this is belt-and-suspenders — and releases. It does NOT close the live *sql.DB; that is the store handle the caller owns.
func (*Durable) Hydrate ¶ added in v1.801.191
Hydrate acquires the writer lease and restores the latest durable snapshot into the local file, BEFORE the store opens its handle. As the elected owner it CarryForward-seals the durable object to the lease round while hydrating (safe takeover: no acknowledged write lost). A non-owner refreshes read-only. Any other condition (store unreachable, empty membership, lost race) degrades to read-only on the local file and is returned for the caller to log — it NEVER makes the store unopenable, so a store is always available for reads; writes fail closed until a later open re-acquires.
RECOVERY from a degraded open (Red M3): a pod that could not acquire at open stays read-only for that store's cached lifetime — it does NOT re-acquire in place, because a takeover CarryForward restores the durable snapshot OVER the local file, which is unsafe under the live handle the store already handed out (stale reads). Recovery is therefore a FRESH open: a pod restart (a readiness/liveness probe can gate on the degraded log) or the shard router routing the org to a healthy owner. An in-process quiesce-close-reopen on the cached entry is the future enhancement; until then the safe, simple recovery is re-open.
func (*Durable) Owned ¶ added in v1.801.191
Owned reports whether this replica currently holds the writer lease. A store may gate a write on it, but the authoritative gate is Sync's fenced ship.
func (*Durable) Sync ¶ added in v1.801.191
Sync snapshots the local file and ships it to the durable object fenced at the lease round — the ship-before-ack step. acked is true only if the fenced store admitted our round. A ship rejected as ErrStaleRound returns (false, nil): this replica was deposed, so it drops ownership and does NOT acknowledge — the caller retries on the new owner. A non-owner returns (false, ErrNotOwner). Call AFTER the write transaction commits and OUTSIDE any open transaction (Sync takes the sole connection to checkpoint).
type FencedStore ¶ added in v1.786.165
type FencedStore = replica.FencedStore
FencedStore admits a per-org-DB ship only at a round >= the recorded one, fencing a deposed writer. ConditionalStore is its atomic-CAS backing seam.
type Lease ¶ added in v1.786.165
Lease binds an elected owner to the monotone round it writes at (the fencing value). Round is that monotone epoch. Fencer issues a Lease from a linearizable round source (CASFencer here; the Lux BFT round later).
type Membership ¶
type Membership struct {
// contains filtered or unexported fields
}
Membership caches the latest replica snapshot and refreshes it from a Source on an interval. Reads are lock-free via an atomic snapshot pointer, so the per-request AmOwner hot-path never blocks on the refresher.
func NewMembership ¶
func NewMembership(selfID string, src Source, interval time.Duration) *Membership
NewMembership builds a Membership over src. selfID is this replica's stable id (see Member.ID). interval<=0 defaults to 5s.
func (*Membership) AmOwner ¶
func (m *Membership) AmOwner(orgID string) bool
AmOwner reports whether THIS replica owns orgID's writer right now — the per-write hot-path check. Lock-free.
func (*Membership) Members ¶
func (m *Membership) Members() []Member
Members returns the current membership snapshot (never nil; may be empty).
func (*Membership) OwnerOf ¶
func (m *Membership) OwnerOf(orgID string) (Member, bool)
OwnerOf resolves the writer-owner replica for orgID under the live set.
func (*Membership) Start ¶
func (m *Membership) Start(ctx context.Context) error
Start does an initial synchronous refresh (so Members() is populated before the first request) then refreshes on the interval until Stop or ctx cancel. Returns the initial refresh error, if any; callers may serve with a stale or self-only set regardless.
type Replicator ¶
type Replicator = replica.Replicator
Replicator binds one per-org SQLite to its object-store slot.
type S3ConditionalStore ¶ added in v1.801.191
S3ConditionalStore is a replica.ConditionalStore over the SeaweedFS S3 gateway's native optimistic-locking extension (SetMatchETag / SetMatchETagExcept). The server — not this process — evaluates the precondition atomically, which is the property the fence's correctness depends on.
func NewS3ConditionalStore ¶ added in v1.801.191
func NewS3ConditionalStore(client *s3.Client, bucket string) *S3ConditionalStore
NewS3ConditionalStore builds a ConditionalStore over an already-constructed S3 client and bucket. The caller owns the client lifecycle (credentials, TLS, endpoint) — this type adds only the conditional-write semantics.
func (*S3ConditionalStore) Get ¶ added in v1.801.191
Get returns the object bytes and its ETag from ONE GET response (obj.Stat reads the header of the same request io.ReadAll drains), so the version is guaranteed consistent with the bytes. Absent key maps to replica.ErrNotFound.
func (*S3ConditionalStore) PutIfVersion ¶ added in v1.801.191
func (s *S3ConditionalStore) PutIfVersion(ctx context.Context, key string, data []byte, expectVersion string) (string, error)
PutIfVersion writes data at key iff the store's current ETag still equals expectVersion (SetMatchETag), or iff the object does not exist when expectVersion is "" (SetMatchETagExcept "*"). A failed precondition is mapped to replica.ErrConflict.
type Source ¶
Source yields the current live replica set. It is the ONE thing in the horizontally-scaled cloud that needs a peer view — everything else is a pure function of (org, members). Any discovery mechanism plugs in as a Source without touching the ownership core: a static list (dev), a K8s Endpoints poll (prod), or a zapd gossip view.
func StaticSource ¶
StaticSource yields a fixed set — single-node / local dev. With no explicit members it reads CLOUD_REPLICAS ("id@addr,id2@addr2", or bare "id" with addr==id).