store

package
v1.52.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package store implements per-(org, namespace) SQLite shards with optional consensus replication.

Layout on disk:

<rootDir>/<org>/<project>/<user>/<namespace>.db

The three directory levels are the Principal that owns the shard, each unset leg written as the sentinel "_" (see principal.go). The root tenant — the unscoped embedded / dev path — is therefore <rootDir>/_/_/_.

A shard is created on first use. Namespaces are not declared up front.

Schema (single table; key/value blob):

CREATE TABLE kv(
  key   TEXT PRIMARY KEY,
  value BLOB NOT NULL,
  upd   INTEGER NOT NULL
);

Encryption:

A non-nil master key opens every shard at rest encrypted, under a DEK
of its own wrapped for its Principal (see dek.go). A nil master key
opens plaintext — the zero-config dev posture.

Replication:

A Replicator may be attached via WithReplicator. Every put/del is
wrapped in a replication.Frame and Propose'd before the local apply
commits. On Accept the frame is also dispatched to peers via the
driver-internal Subscribe path.

Index

Constants

View Source
const Depth = 3

Depth is the number of legs in a Principal: org, project, user.

View Source
const MasterKeyLen = 32

MasterKeyLen is the required length of the store's root key.

View Source
const Sentinel = "_"

Sentinel is the path segment standing for an unset leg of a Principal. It is not a legal id, so a Principal with an unset leg can never be confused with one that names a tenant of that name.

Variables

View Source
var CrossNamespaceKinds = map[string]bool{
	"ns": true,
	"nx": true,
}

CrossNamespaceKinds lists the kinds whose List(prefix) operations must enumerate every shard under the org. Anything else routes to a single shard.

View Source
var ErrClosed = errors.New("store: manager closed")

ErrClosed is returned when a method runs after Close.

View Source
var ErrOccupied = errors.New("store: a shard already had a file at its destination")

ErrOccupied reports that at least one shard had a file at its destination. Nothing was overwritten and nothing was deleted; the run carried on past it, so the other tenants still moved.

View Source
var IdleEvictAfter = 10 * time.Minute

IdleEvictAfter sets how long an open shard may sit unused before the manager closes it. Mutable for tests.

View Source
var SweepEvery = time.Minute

SweepEvery is how often idle shards are evicted and resident shards sealed. Mutable for tests.

Functions

func CopyFile

func CopyFile(dst, src string) (int64, error)

CopyFile is a helper used by the migration tool. Returns bytes copied.

func IsCrossNamespacePrefix

func IsCrossNamespacePrefix(prefix string) bool

IsCrossNamespacePrefix reports whether prefix is a bare kind/ scan that must fan out across shards.

func NsFromKey

func NsFromKey(key string) (kind, ns, rest string, ok bool)

nsFromKey parses the canonical key layout to derive the namespace segment. Returns ("", false) for keys that are themselves the namespace registry (`ns/<name>`); the caller treats those specially.

Layout reference:

ns/<name>
wf/<ns>/<workflowId>/<runId>
wfh/<ns>/<workflowId>/<runId>/<eventId>
sc/<ns>/<scheduleId>
bt/<ns>/<batchId>
dp/<ns>/<deploymentName>
nx/<ns>/<endpointName>
id/<ns>/<email>
sa/<ns>/<attrName>
idem/<ns>/<workflowId>/<requestId>

func SplitPrefix

func SplitPrefix(prefix string) (kind, ns, suffix string)

SplitPrefix returns (kind, ns, suffix) for a list prefix. ns may be empty when IsCrossNamespacePrefix(prefix) is true.

func ValidName added in v1.52.4

func ValidName(name string) error

ValidName rejects anything that would escape or alias a shard directory or file, or collide with the unset-leg sentinel. Every name that becomes a path segment — a principal's legs and a namespace alike — passes through here, so there is one rule for all of them.

Types

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager owns the on-disk shard layout and the open-shard cache.

func New

func New(rootDir string, master []byte) (*Manager, error)

New opens the manager rooted at rootDir. The directory is created on demand; errors are returned only for unrecoverable IO problems.

master is the 32-byte root key every shard's DEK is wrapped under. A nil master opens shards plaintext — the zero-config dev posture; any other length is rejected here rather than at the first shard open.

func (*Manager) Close

func (m *Manager) Close() error

Close flushes and closes every open shard. Safe to call twice.

func (*Manager) Encrypted added in v1.52.4

func (m *Manager) Encrypted() bool

Encrypted reports whether shards are opened at rest encrypted.

func (*Manager) Get

func (m *Manager) Get(ctx context.Context, p Principal, ns string) (*Shard, error)

Get returns the open shard for (principal, ns), creating it on disk if needed. The returned Shard is safe for concurrent use.

func (*Manager) ListPrincipals added in v1.52.4

func (m *Manager) ListPrincipals(ctx context.Context) ([]Principal, error)

ListPrincipals enumerates every tenant that owns at least one shard. Used by the cron sweeper, which must see EVERY tenant's schedules from the root engine.

func (*Manager) ListShards

func (m *Manager) ListShards(ctx context.Context, p Principal) ([]*Shard, error)

ListShards enumerates every namespace shard the principal owns. Used by cross-namespace operations like ListNamespaces().

func (*Manager) OpenShardCount

func (m *Manager) OpenShardCount() int

OpenShardCount reports the number of resident shards (for /v1/tasks/cluster).

func (*Manager) Replicator

func (m *Manager) Replicator() replication.Replicator

Replicator returns the currently-installed driver, or nil.

func (*Manager) RootDir

func (m *Manager) RootDir() string

RootDir returns the on-disk root.

func (*Manager) ShardPath

func (m *Manager) ShardPath(p Principal, ns string) string

ShardPath returns the on-disk file for (principal, ns).

func (*Manager) WithReplicator

func (m *Manager) WithReplicator(r replication.Replicator)

WithReplicator installs r as the consensus driver for every shard opened from now on, and re-installs it on already-open shards.

type Move added in v1.52.4

type Move struct {
	From string `json:"from"`
	To   string `json:"to"`
	// Skipped is set when the destination was already occupied. Both files
	// survive untouched and Upgrade returns ErrOccupied, because only an
	// operator can say which one is authoritative: the destination can only
	// have got there by this binary booting before the move ran, which
	// creates an EMPTY shard where the full one belongs — and anything
	// started after that boot is in the empty one.
	Skipped bool `json:"skipped,omitempty"`
}

Move is one file's relocation.

func Upgrade added in v1.52.4

func Upgrade(root string, master []byte) ([]Move, error)

Upgrade moves every shard under root into the principal-addressed layout and reports what it moved. Idempotent: a moved shard leaves nothing at its old path, and a directory already in this layout holds no shard file at its top level, so a re-run costs a scan and nothing else.

master must be nil. A store written by the older binary is PLAINTEXT — encryption arrived with this layout — and moving a file does not encrypt it. Boot once with the key unset and the shards read; boot with the key set and each one gets a fresh DEK sidecar and is then opened as SQLCipher ciphertext, which it is not. Move first, encrypt second.

type Principal added in v1.52.4

type Principal struct {
	Org     string
	Project string
	User    string
}

Principal is the tenant that owns a shard: the org, optionally narrowed to a project and to a user. It is the SAME value in both places tenancy is decided, so the two can never disagree:

  • the directory the shard's file lives in (String), and
  • the key-encryption key that file's DEK is wrapped under (KEK).

Legs narrow left to right and each unset leg is written as Sentinel, so the encoding is fixed-width and injective: acme/_/z (an org's user, no project) and acme/z/_ (an org's project) are distinct paths and derive distinct keys. The zero Principal is the root — the unscoped embedded / dev tenant.

The org is what a tenant IS; project and user narrow it and are each independently optional, because IAM mints identities that carry a user without a project. Nothing may be set without an org.

func Org added in v1.52.4

func Org(org string) Principal

Org returns the principal naming an org.

func ParsePrincipal added in v1.52.4

func ParsePrincipal(s string) (Principal, error)

ParsePrincipal is the inverse of String.

func (Principal) KEK added in v1.52.4

func (p Principal) KEK(master []byte) ([]byte, error)

KEK derives the key-encryption key for p from the master key by walking the principal chain: master → org → project → user, skipping any leg p leaves unset. Each narrowing step derives from the key it narrows, so a user's key is cryptographically contained in its org's — reaching a user's data requires the whole chain, not just the master. Each step carries its own domain-separation tag, so narrowing to a project named "z" and to a user named "z" derive different keys.

The KEK never encrypts a page. It wraps that shard's own DEK (see dek.go), which is what makes master-key rotation O(1) per shard.

func (Principal) Root added in v1.52.4

func (p Principal) Root() bool

Root reports whether p names no tenant at all.

func (Principal) String added in v1.52.4

func (p Principal) String() string

String is the canonical encoding: the three legs joined by "/", each unset leg written as Sentinel. It is BOTH the shard's directory (relative to the store root) and the routing key carried on a replication frame — one encoding, so a frame always lands in the directory its principal names.

func (Principal) Valid added in v1.52.4

func (p Principal) Valid() error

Valid reports whether p is well formed: every set leg is a usable path segment, and nothing is set without an org to narrow.

type Shard

type Shard struct {
	// contains filtered or unexported fields
}

Shard owns one SQLite file. WAL mode + foreign keys + 5s busy timeout. One writer; readers share the cache. Replicator hooks fire from put/del before local commit so the cluster sees the mutation first.

func (*Shard) Checkpoint

func (s *Shard) Checkpoint() error

Checkpoint makes the on-disk file fully self-contained: it truncates the WAL and, for an encrypted shard the pure-Go codec envelope backs, seals the committed pages back into the ciphertext file. The seal is a successful no-op for a shard that persists per commit (the live codec and plaintext paths), so callers never ask which one they hold. Used by the migration tool before a copy and by the manager's sweep, which is what bounds an envelope-backed shard's exposure to an unclean exit.

func (*Shard) Close

func (s *Shard) Close() error

Close flushes WAL and releases the connection. Idempotent.

func (*Shard) Del

func (s *Shard) Del(ctx context.Context, key string) error

Del removes key. No-op if missing.

func (*Shard) Get

func (s *Shard) Get(ctx context.Context, key string) ([]byte, bool, error)

Get reads key.

func (*Shard) List

func (s *Shard) List(ctx context.Context, prefix string, fn func(key string, value []byte) error) error

List walks every kv row whose key starts with prefix in lexicographic order.

func (*Shard) Namespace

func (s *Shard) Namespace() string

Namespace returns the shard's namespace.

func (*Shard) Path

func (s *Shard) Path() string

Path returns the underlying file path.

func (*Shard) Principal added in v1.52.4

func (s *Shard) Principal() Principal

Principal returns the tenant that owns the shard.

func (*Shard) Put

func (s *Shard) Put(ctx context.Context, key string, value []byte) error

Put writes value at key. If a Replicator is installed it runs Propose first; on Accept the local commit happens. On Reject the transaction is dropped and an error is returned.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL