state

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func EscapeSegment

func EscapeSegment(s string) string

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 IsExists

func IsExists(err error) bool

IsExists reports whether err is an already-exists error from any Store.

func IsMalformed

func IsMalformed(err error) bool

IsMalformed reports whether err is a decode failure from any Store.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is a not-found error from any Store.

func Key

func Key(segments ...string) string

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

func NewestKey(ctx context.Context, store Store, prefix string) (string, error)

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

func UnescapeSegment(s string) (string, error)

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

func NewDocuments[T any](store Store, prefix string) *Documents[T]

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]) Create

func (d *Documents[T]) Create(ctx context.Context, v T, id ...string) error

Create encodes and stores a document that must not already exist.

func (*Documents[T]) CreateKey

func (d *Documents[T]) CreateKey(ctx context.Context, key string, v T) error

CreateKey stores a document at an exact key, refusing to replace one.

func (*Documents[T]) Delete

func (d *Documents[T]) Delete(ctx context.Context, id ...string) error

Delete removes a document.

func (*Documents[T]) DeleteKey

func (d *Documents[T]) DeleteKey(ctx context.Context, key string) error

DeleteKey removes the document at an exact key.

func (*Documents[T]) Get

func (d *Documents[T]) Get(ctx context.Context, id ...string) (T, error)

Get decodes the document at the given id path.

func (*Documents[T]) GetKey

func (d *Documents[T]) GetKey(ctx context.Context, key string) (T, error)

GetKey decodes the document stored at an exact key (as returned by Keys).

func (*Documents[T]) IDs

func (d *Documents[T]) IDs(ctx context.Context) ([]string, error)

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.

func (*Documents[T]) Key

func (d *Documents[T]) Key(id ...string) string

Key returns the full key a document id maps to.

func (*Documents[T]) Keys

func (d *Documents[T]) Keys(ctx context.Context, id ...string) ([]string, error)

Keys lists the document keys below an id path, in lexical order.

func (*Documents[T]) Replace

func (d *Documents[T]) Replace(ctx context.Context, v T, id ...string) error

Replace encodes and stores a document, discarding any previous value. Only re-derivable records may use it (see Store.Replace).

func (*Documents[T]) ReplaceKey

func (d *Documents[T]) ReplaceKey(ctx context.Context, key string, v T) error

ReplaceKey stores a document at an exact key, discarding any previous value.

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) Create

func (m *MemoryStore) Create(_ context.Context, key string, value []byte) error

Create stores a copy of value, refusing to replace an existing one.

func (*MemoryStore) Delete

func (m *MemoryStore) Delete(_ context.Context, key string) error

Delete removes a key.

func (*MemoryStore) Get

func (m *MemoryStore) Get(_ context.Context, key string) ([]byte, error)

Get returns a copy of the stored value.

func (*MemoryStore) List

func (m *MemoryStore) List(_ context.Context, prefix string) ([]string, error)

List returns the keys under prefix, matched on whole segments.

func (*MemoryStore) Replace

func (m *MemoryStore) Replace(_ context.Context, key string, value []byte) error

Replace stores a copy of value, discarding any previous one.

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

func NewVersions[T any](store Store, prefix string) *Versions[T]

NewVersions returns an append-only collection rooted at prefix.

func (*Versions[T]) Append

func (v *Versions[T]) Append(ctx context.Context, at time.Time, value T, id ...string) error

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

func (v *Versions[T]) DeleteAll(ctx context.Context, id ...string) (bool, error)

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

func (v *Versions[T]) IDs(ctx context.Context) ([]string, error)

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

func (v *Versions[T]) Keys(ctx context.Context, id ...string) ([]string, error)

Keys returns the version keys stored for a record, oldest first. It reads no documents: the names carry the timestamps.

func (*Versions[T]) Latest

func (v *Versions[T]) Latest(ctx context.Context) (values []T, unreadable []string, err error)

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

func (v *Versions[T]) Load(ctx context.Context, id ...string) (T, Span, error)

Load returns the newest version of a record together with the span of versions stored for it, in one listing.

func (*Versions[T]) Newest

func (v *Versions[T]) Newest(ctx context.Context, id ...string) (T, error)

Newest returns the newest version of a record, falling back to the legacy single-document layout, or NotFoundError.

func (*Versions[T]) WithLegacy

func (v *Versions[T]) WithLegacy(prefix string) *Versions[T]

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.

Jump to

Keyboard shortcuts

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