Documentation
¶
Overview ¶
Package state is the service's durable document store: opaque values filed under slash-separated keys, plus a typed JSON view over them.
Why this exists (Phase 6): scheduling only means something if it survives a restart. Job history, schedules, key envelopes and target records must all outlive the process, and the deployment target is a single instance, so the store is deliberately tiny — get, create, replace, delete, list-by-prefix. Create and Replace are separate on purpose: see Store.
What it deliberately does NOT offer:
- Transactions or compare-and-set. The production backend is OpenCloud's own storage, reached over CS3, which provides neither. Mutual exclusion between concurrent runs is therefore process-local (see internal/jobs); the durable lease and instance records are checked too, but a read-then-write is not atomic, so they catch mistakes rather than prevent races. This is sound for the locked single-instance deployment and unsound for a scaled-out one; anything that changes that assumption needs a backend with real transactions.
- Queries. Callers list a prefix and filter in memory. Family scale.
Everything written here is either non-secret metadata or already-wrapped ciphertext (SRW/TW envelopes). No plaintext key material is ever handed to a Store.
Index ¶
- func EscapeSegment(s string) string
- func IsExists(err error) bool
- func IsMalformed(err error) bool
- func IsNotFound(err error) bool
- func Key(segments ...string) string
- func NewestKey(ctx context.Context, store Store, prefix string) (string, error)
- func UnescapeSegment(s string) (string, error)
- type Documents
- func (d *Documents[T]) All(ctx context.Context, id ...string) (values []T, unreadable []string, err error)
- func (d *Documents[T]) Create(ctx context.Context, v T, id ...string) error
- func (d *Documents[T]) CreateKey(ctx context.Context, key string, v T) error
- func (d *Documents[T]) Delete(ctx context.Context, id ...string) error
- func (d *Documents[T]) DeleteKey(ctx context.Context, key string) error
- func (d *Documents[T]) Get(ctx context.Context, id ...string) (T, error)
- func (d *Documents[T]) GetKey(ctx context.Context, key string) (T, error)
- func (d *Documents[T]) IDs(ctx context.Context) ([]string, error)
- func (d *Documents[T]) Key(id ...string) string
- func (d *Documents[T]) Keys(ctx context.Context, id ...string) ([]string, error)
- func (d *Documents[T]) Replace(ctx context.Context, v T, id ...string) error
- func (d *Documents[T]) ReplaceKey(ctx context.Context, key string, v T) error
- type ExistsError
- type MalformedError
- type MemoryStore
- func (m *MemoryStore) Create(_ context.Context, key string, value []byte) error
- func (m *MemoryStore) Delete(_ context.Context, key string) error
- func (m *MemoryStore) Get(_ context.Context, key string) ([]byte, error)
- func (m *MemoryStore) List(_ context.Context, prefix string) ([]string, error)
- func (m *MemoryStore) Replace(_ context.Context, key string, value []byte) error
- type NotFoundError
- type Span
- type Store
- type Versions
- func (v *Versions[T]) Append(ctx context.Context, at time.Time, value T, id ...string) error
- func (v *Versions[T]) DeleteAll(ctx context.Context, id ...string) (bool, error)
- func (v *Versions[T]) IDs(ctx context.Context) ([]string, error)
- func (v *Versions[T]) Keys(ctx context.Context, id ...string) ([]string, error)
- func (v *Versions[T]) Latest(ctx context.Context) (values []T, unreadable []string, err error)
- func (v *Versions[T]) Load(ctx context.Context, id ...string) (T, Span, error)
- func (v *Versions[T]) Newest(ctx context.Context, id ...string) (T, error)
- func (v *Versions[T]) WithLegacy(prefix string) *Versions[T]
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func EscapeSegment ¶
EscapeSegment percent-encodes everything outside [A-Za-z0-9._-].
It is deliberately stricter than URL escaping: the CS3 backend turns segments into file names, and "." / ".." or a stray separator must not be able to walk out of the state tree.
func IsMalformed ¶
IsMalformed reports whether err is a decode failure from any Store.
func IsNotFound ¶
IsNotFound reports whether err is a not-found error from any Store.
func Key ¶
Key builds a key from raw segments, escaping each one. Segments come from untrusted-ish sources — OpenCloud space ids contain "$" and "!", and could in principle contain "/" — so every segment is percent-escaped down to a safe alphabet. The escaping is reversible (see UnescapeSegment), which is what lets a listing be turned back into ids.
func NewestKey ¶
NewestKey returns the lexically last key under prefix, or NotFoundError when the prefix holds nothing. It is the primitive Versions is built on, exported because "read the newest record under this prefix" is the shape every append-only store needs.
func UnescapeSegment ¶
UnescapeSegment reverses EscapeSegment. It accepts only what EscapeSegment writes, so every record has exactly one key: lowercase hex ("%2e"), an escape of a safe byte ("%61" for "a"), or an unsafe byte left raw are all refused, as is a malformed escape.
Types ¶
type Documents ¶
type Documents[T any] struct { // contains filtered or unexported fields }
Documents is a typed JSON collection stored under a fixed key prefix. It is the only encoder/decoder in the codebase's persistence path, so every store built on it (jobs, schedules, key envelopes, targets) serializes identically.
func NewDocuments ¶
NewDocuments returns a typed view over store rooted at prefix.
func (*Documents[T]) All ¶
func (d *Documents[T]) All(ctx context.Context, id ...string) (values []T, unreadable []string, err error)
All decodes every document below an id path, in key order. A document that fails to decode is left out and reported by key in unreadable: one corrupt record must not make a Space's entire history unreadable, and it must not vanish without trace either.
func (*Documents[T]) CreateKey ¶
CreateKey stores a document at an exact key, refusing to replace one.
func (*Documents[T]) GetKey ¶
GetKey decodes the document stored at an exact key (as returned by Keys).
func (*Documents[T]) IDs ¶
IDs returns the distinct first-level ids the collection holds, unescaped and in order. A document filed under further segments reports the first one, which is the id its owner indexes by. It reads no documents — the ids are in the keys — and exists for the operations that must visit every record rather than one.
type ExistsError ¶
type ExistsError struct{ Key string }
ExistsError is returned when a create would overwrite an existing value.
func (ExistsError) Error ¶
func (e ExistsError) Error() string
type MalformedError ¶
type MalformedError struct{ Key string }
MalformedError is returned when a stored document cannot be decoded.
It names the key and never the content: a document may hold wrapped key material, and an error is the one place bytes escape by accident. It is a distinct type because "the record is corrupt" and "the store is unreachable" call for different reactions — one is reported and skipped, the other is retried.
func (MalformedError) Error ¶
func (e MalformedError) Error() string
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is a concurrency-safe in-memory Store.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore returns an empty MemoryStore.
func (*MemoryStore) Delete ¶
func (m *MemoryStore) Delete(_ context.Context, key string) error
Delete removes a key.
type NotFoundError ¶
type NotFoundError struct{ Key string }
NotFoundError is returned when a key holds no value.
func (NotFoundError) Error ¶
func (e NotFoundError) Error() string
type Span ¶
type Span struct {
// Oldest is the write time of the oldest version still stored.
Oldest time.Time
// Newest is the write time of the newest version.
Newest time.Time
}
Span is when a record's stored versions were written. Both fields are the zero value for a record that has no versions — one read from the legacy layout, which carries no version stamps.
type Store ¶
type Store interface {
// Get returns the value stored under key, or NotFoundError.
Get(ctx context.Context, key string) ([]byte, error)
// Create stores value under key. A key that already holds a value is
// ExistsError: creating never destroys anything, which is what lets
// append-only records (see Versions) survive a crash mid-write.
Create(ctx context.Context, key string, value []byte) error
// Replace stores value under key, discarding any previous value. On a
// backend without transactions this is destructive and not atomic — the old
// value can be gone before the new one is durable — so it is reserved for
// records the service can re-derive after a restart (leases, job records).
Replace(ctx context.Context, key string, value []byte) error
// Delete removes key. Deleting a missing key returns NotFoundError.
Delete(ctx context.Context, key string) error
// List returns the keys under prefix, in lexical order. The prefix is
// matched on whole segments: "a/b" matches "a/b/c" but never "a/bc".
List(ctx context.Context, prefix string) ([]string, error)
}
Store is the durable key/value boundary. Implementations must be safe for concurrent use.
Keys are slash-separated paths of escaped segments (see Key). A Store may map them onto directories and files, so callers must not invent key syntax of their own — build every key with Key.
type Versions ¶
type Versions[T any] struct { // contains filtered or unexported fields }
Versions is an append-only, typed collection of documents filed under "<prefix>/<id...>/<nanos>". Writes create a new version; reads take the newest.
func NewVersions ¶
NewVersions returns an append-only collection rooted at prefix.
func (*Versions[T]) Append ¶
Append stores a new version of a record. at is the version's timestamp; it is moved forward when it would collide with, or sort before, the newest existing version, because a clock that jumps backwards must not be able to make a new record look older than the one it supersedes.
func (*Versions[T]) DeleteAll ¶
DeleteAll removes every version of a record, and its legacy document. It is the only destructive operation in this file and exists for records a user or admin may genuinely retire (a backup target, a Space's configuration). It reports whether anything was removed.
func (*Versions[T]) IDs ¶
IDs returns the first-level record ids the collection holds, unescaped and in order, including ids that exist only in the legacy layout. A record filed under further segments (a key envelope is "<space>/<kind>/<version>") reports its first segment, which is the id its owner indexes by.
It reads no documents — the keys carry everything — and exists for the operations that must visit every record rather than one: rotating a wrapping key across all Spaces, most of all.
func (*Versions[T]) Keys ¶
Keys returns the version keys stored for a record, oldest first. It reads no documents: the names carry the timestamps.
func (*Versions[T]) Latest ¶
Latest returns the newest version of every record in the collection, in id order, including records that exist only in the legacy layout.
A record that cannot be decoded is left out of the values and reported by key in unreadable. Both halves matter: one corrupt document must not make the whole collection unreadable, and a record that silently disappears from a listing is a Space that silently stops being backed up. For the same reason a read that fails for any reason other than "not found" or "malformed" fails the whole listing rather than dropping the record (skipUnreadable).
func (*Versions[T]) Load ¶
Load returns the newest version of a record together with the span of versions stored for it, in one listing.
func (*Versions[T]) Newest ¶
Newest returns the newest version of a record, falling back to the legacy single-document layout, or NotFoundError.
func (*Versions[T]) WithLegacy ¶
WithLegacy declares the single-document prefix this collection replaces, so records written before versioning existed are still readable. It returns the receiver for chaining.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package statetest holds the behavioural contract every state.Store must satisfy, so a second implementation cannot quietly differ from the one the rest of the test suite exercises.
|
Package statetest holds the behavioural contract every state.Store must satisfy, so a second implementation cannot quietly differ from the one the rest of the test suite exercises. |