Documentation
¶
Overview ¶
Package resource is the agent's unified foundation: everything in the system is a Resource, a typed, versioned, schema-validated record on one event-sourced store. One model, one envelope, one scope/label system, one provenance path.
The shape is deliberately Kubernetes-aligned (APIVersion + Kind + Spec/Status + Labels/Selectors), because that declarative model is proven and instantly familiar, but these are Flynn resources, not Kubernetes objects: they carry our own API groups (e.g. core.ionagent.io/v1alpha1), live in this package, and are never the upstream k8s types (which belong to the Kubernetes integration). The alignment keeps a future where resources export as CRDs / GitOps-manage open, without depending on a cluster.
Every kind is data, including Kind itself (a Kind is a Resource of kind "Kind"), so the agent can author and validate new kinds at runtime. Typed domains layer on top as thin facades; specialized indexes (full-text, vector, ordering) are projections over the resource event log, not parallel stores.
Index ¶
- Constants
- Variables
- func DecodeSpec[T any](r Resource) (T, error)
- func DecodeStatus[T any](r Resource) (T, error)
- func EncodeStatus[T any](s T) (json.RawMessage, error)
- func Hash(r Resource) (string, error)
- func Hashes(r Resource) (content, spec string, err error)
- func MarshalSnapshot(resources []Resource, lastSeq int64) ([]byte, error)
- func MergeEvent(r Resource) (spine.AppendInput, error)
- func OwnerGone(ctx context.Context, store Store, r Resource) (bool, error)
- func RegisterCoreKinds(reg *Registry) error
- func SpecHash(r Resource) (string, error)
- func ValidateForMerge(r Resource) error
- type AnyScopeGetter
- type Condition
- type Envelope
- type Key
- type KeyLister
- type Kind
- type MergeOutcome
- type MergeResult
- type Op
- type Option
- type OwnerReference
- type Registry
- type RegistryOption
- type Requirement
- type Resource
- func DecodeResource(payload map[string]any) (Resource, error)
- func KindResource(k Kind, scope Scope) (Resource, error)
- func Resolve(incoming, current Resource) (winner Resource, takeIncoming bool)
- func UnmarshalSnapshot(payload []byte) ([]Resource, int64, error)
- func Update(ctx context.Context, store Store, kind string, scope Scope, name string, ...) (Resource, error)
- func UpdateByID(ctx context.Context, store Store, id string, mutate func(*Resource) error) (Resource, error)
- type SchemaCompiler
- type Scope
- type Selector
- type Stamper
- type Store
- type Validator
Constants ¶
const ( EvPut = "resource.put" EvDeleted = "resource.deleted" // EvMerged records a resource applied by cross-instance Merge: its payload is // the winning post-image verbatim (the remote envelope preserved, not // restamped), so every replica that folds the stream converges identically. It // projects exactly like EvPut, but is a distinct type so the log stays auditable // about which records arrived by local write versus fleet sync. EvMerged = "resource.merged" )
Resource event types: the vocabulary of the resource stream. Each event is the canonical post-image of the affected resource, so replaying in order reproduces identical state. Exported so durable backends project the same vocabulary.
const CoreGroupVersion = "core.ionagent.io/v1alpha1"
CoreGroupVersion is the API group/version for the foundation's own built-in kinds. Domains define their own groups (e.g. skill.ionagent.io/v1); the `.ionagent.io` suffix marks every kind unmistakably as ours, never a Kubernetes built-in.
const KindKind = "Kind"
KindKind is the name of the kind that describes kinds. A Kind is itself stored as a Resource of this kind, which is what makes the type system data the agent can author and validate at runtime (meta-circular self-extension).
const ResourceStream = "resources"
ResourceStream is the spine stream every resource mutation is recorded on. One ordered stream means a Replay folds the whole foundation back into existence.
Variables ¶
var ErrConflict = errors.New("resource: version conflict")
ErrConflict is returned when optimistic concurrency fails: the caller passed a non-zero SyncVersion that no longer matches the stored record. Re-read and retry.
var ErrInvalid = errors.New("resource: invalid")
ErrInvalid is returned when a resource fails admission (missing required field, unregistered kind, or a Spec that does not satisfy its kind's JSON schema).
var ErrNotFound = errors.New("resource: not found")
ErrNotFound is returned when a requested resource does not exist.
var ErrSkipUpdate = errors.New("resource: skip update")
ErrSkipUpdate is returned by an Update mutate function to end the retry loop without writing: the state the mutation observed makes the write unnecessary (a park already cleared, a limit already exceeded). Update returns the resource as read and a nil error.
Functions ¶
func DecodeSpec ¶
DecodeSpec reads a resource's typed spec. An empty spec decodes to the zero value, so a kind whose spec is optional reads cleanly without a nil check.
func DecodeStatus ¶
DecodeStatus reads a resource's typed status. An empty status decodes to the zero value: a freshly created resource has no status yet.
func EncodeStatus ¶
func EncodeStatus[T any](s T) (json.RawMessage, error)
EncodeStatus marshals a typed status for writing back onto a resource.
func Hash ¶
Hash returns the stable content hash of r: a hex SHA-256 over its canonical content (identity, scope, labels and annotations, spec, status, valid-time, and the tombstone flag), excluding the volatile envelope fields (versions, clocks, timestamps, and the hash itself). Equal content yields an equal hash on any machine, so resource history forms a Merkle DAG: dedup, provenance ("which version produced this"), tamper-evidence, and efficient diff-based sync.
Spec is canonicalized (re-encoded with sorted keys) so two semantically equal specs that differ only in key order or whitespace hash the same. Status is folded in as a fixed-size digest of its bytes rather than canonicalized: status is self-produced by one owning controller through a stable Go encoder (so byte identity already equals semantic identity), and Merge decides conflicts by envelope while carrying the origin's hash verbatim, so it never re-hashes a remote status. Digesting it keeps a write allocation-flat no matter how large the status grows (a goal embeds its whole transcript in status), instead of materializing the entire status as a generic tree on every write.
func Hashes ¶
Hashes returns Hash and SpecHash together, canonicalizing the spec once. The Stamper stamps both onto every write, so readers (the reconciler's no-op check above all) compare stored fields instead of re-canonicalizing per tick.
func MarshalSnapshot ¶
MarshalSnapshot serializes a projection (all its records, live and tombstoned, and the Seq it is current as of) into the snapshot payload Replay restores from. A backend builds the record set from its own storage and calls this, so every backend snapshots in one identical format. Records are sorted by id so the payload is deterministic.
func MergeEvent ¶
func MergeEvent(r Resource) (spine.AppendInput, error)
MergeEvent builds the spine event recording that r was applied by cross-instance merge. Its payload is the winning post-image verbatim, so folding the stream reproduces the merged state on any backend, and it preserves r's provenance (origin instance and writer actor) rather than restamping it locally.
func OwnerGone ¶
OwnerGone reports whether r's controller owner no longer exists or is itself terminating, which makes r an orphan a garbage collector should reap so an owner's deletion cascades to the subtree it created. A resource with no controller owner is a root and is never orphaned. The owner is resolved by its stable id, so a rename never breaks the link. It is the reusable predicate a kind's reconciler calls to garbage-collect owned resources.
func RegisterCoreKinds ¶
RegisterCoreKinds registers the foundation's built-in kinds, starting with Kind itself (kind == "Kind"), so a Kind can be stored and validated as a Resource. This bootstraps meta-circularity: the type system is data on the same store.
func SpecHash ¶
SpecHash returns a stable hash of a resource's desired state alone (its kind and canonical spec), excluding status and all envelope metadata. It is the agent's equivalent of Kubernetes' metadata.generation: a controller records the SpecHash it last acted on in status, and a reconcile is a no-op while the stored spec hash still matches, so writing status (which changes the full content hash) never re-triggers work. Equal spec yields an equal hash on any machine.
func ValidateForMerge ¶
ValidateForMerge checks that a replicated record carries the full envelope Merge relies on. A record produced by a real write always has these; rejecting a record without them guards against feeding a half-built local resource into the merge path (where no Stamper assigns identity). Backends call it before resolving.
Types ¶
type AnyScopeGetter ¶
type AnyScopeGetter interface {
GetAnyScope(ctx context.Context, kind, name string) (Resource, bool, error)
}
AnyScopeGetter is an optional Store capability: a keyed lookup of the first live resource of a kind with a name in any scope, in the same scope-then-name order ListAll surfaces. A caller resolving a scope-independent name (the control plane's cross-scope get, when the global scope missed) reads through it when the backend offers it, so the lookup seeks to the name instead of listing and scanning the whole kind. found is false with a nil error when no scope has the name. A backend that cannot answer this from an index simply does not implement it; the caller falls back to ListAll and a name scan.
type Condition ¶
type Condition struct {
Type string `json:"type"`
Status string `json:"status"` // "True" | "False" | "Unknown"
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
LastTransitionTime time.Time `json:"lastTransitionTime"`
}
Condition is one standard status condition, the Kubernetes-shaped signal a controller writes to say where a resource stands (Ready, Stalled, ...). It is shared so every kind reports conditions in one shape and a generic reader (a CLI listing, a wait-for-ready helper) can interpret any kind's status.
type Envelope ¶
type Envelope struct {
// The shared sync envelope: SyncVersion (optimistic concurrency),
// OriginInstanceID (creator provenance), UpdatedHLC + LastWriterID (the
// last-writer-wins merge key), and Deleted (the syncing tombstone). The
// fields and the stamping rules over them are defined once in the envelope
// package and shared with state records, so fleet merge is one discipline,
// not two. Embedding inlines the fields, so the wire format and hashes are
// unchanged.
envelope.Envelope
// WriterActor records who authored the last write: a human, the agent, or the
// runtime (system). It is the provenance signal cross-instance Merge uses for
// precedence, so a person's correction outranks a later automated write and is
// never silently overwritten by the fleet. It is metadata, not content, so it is
// excluded from the content hash (identical content authored by different actors
// shares a hash). The zero value is treated as the agent.
WriterActor spine.ActorType
// Finalizers are keys that must be cleared before the resource is actually
// removed. While any remain, a Delete does not tombstone the record: it sets
// DeletionTimestamp and the resource stays live (still returned by reads) so
// each owner can run its cleanup and then remove its own key. When the last
// finalizer is removed from a resource that has a DeletionTimestamp, the delete
// completes and the record tombstones. This is how external state (a worktree, a
// child run) is cleaned up reliably, even across a crash: a controller re-reads
// the pending deletion every reconcile until cleanup is done.
Finalizers []string
// DeletionTimestamp is set when a delete is requested on a resource that still
// has finalizers: the resource is terminating but not yet gone. Nil means the
// resource is not being deleted. It is system-assigned, never set or cleared by
// a Put (only Delete sets it; resurrecting a tombstone clears it), so a caller
// cannot fake or cancel a deletion by writing the field.
DeletionTimestamp *time.Time
// OwnerReferences link this resource to the resources that own it (its parent
// run or goal). The controller owner (Controller=true) drives its lifecycle: a
// garbage collector reaps the resource once that owner is gone or terminating,
// so deleting an owner cascades to the subtree it created. They are metadata,
// excluded from the content hash. A resource with none is a root, owned by
// nothing, which is the single-run (n=1) case.
OwnerReferences []OwnerReference
// Version is the content revision (incremented on every content change),
// distinct from SyncVersion (the sync/concurrency token).
Version int64
// ContentHash is a stable hash of the resource's canonical content (see
// Hash). Equal content yields an equal hash across machines, which makes
// history a Merkle DAG: dedup, "which version produced this", tamper-evidence,
// and efficient diff-based sync.
ContentHash string
// SpecHash is a stable hash of the resource's desired state alone (see
// SpecHash), stamped at write time so a controller's no-op check ("has the
// spec I acted on changed?") reads a field instead of re-canonicalizing the
// spec every reconcile tick.
SpecHash string
// ValidFrom and ValidTo are the resource's valid-time: when it became and
// ceased to be true in the world, distinct from event-time (when we recorded
// it, carried by UpdatedHLC and the event log). Nil ValidFrom means "valid
// since creation"; nil ValidTo means "still valid". Reserved from day one so a
// second time axis never requires a schema migration; the query surface that
// uses them can follow.
ValidFrom *time.Time
ValidTo *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
Envelope is the universal metadata carried by every resource: sync/concurrency, content provenance, and bitemporal time. Designing the whole envelope in from the first write keeps replay, optimistic concurrency, fleet merge, verifiable history, and as-of queries reachable without ever migrating it in later.
type Key ¶
Key uniquely identifies a resource by its logical coordinates (Kind, Scope, Name), the address an upsert or fetch targets. ID addresses the same record by its stable id. Key is comparable.
type KeyLister ¶
KeyLister is an optional Store capability: a key-only listing of every live resource of a kind, ordered by scope then name. Callers that need addresses rather than records (the reconcile resync, which only enqueues keys) read through it when the backend offers it, so a periodic sweep does not copy every record of the kind. Both bundled backends implement it.
type Kind ¶
type Kind struct {
APIVersion string // group/version of resources of this kind, e.g. "skill.ionagent.io/v1"
Name string // e.g. "Skill"
Schema json.RawMessage // JSON Schema for Spec; nil = unconstrained
Singular string // optional display form
Plural string // optional display form
}
Kind describes a resource kind: the API group/version its resources carry, the kind name, and the JSON Schema their Spec must satisfy (admission). A nil Schema means specs are unconstrained.
type MergeOutcome ¶
type MergeOutcome int
MergeOutcome reports what a Merge did to the local record.
const ( // MergeApplied means the remote record won and replaced (or first-inserted) the // local one. MergeApplied MergeOutcome = iota // MergeIgnored means the local record won and the remote was discarded as older // or lower-precedence. The local state is unchanged. MergeIgnored // MergeUnchanged means the remote was already present (same write), so the apply // was an idempotent no-op. MergeUnchanged )
func (MergeOutcome) String ¶
func (o MergeOutcome) String() string
type MergeResult ¶
type MergeResult struct {
Outcome MergeOutcome
Resource Resource
}
MergeResult is the outcome of applying a replicated resource: what happened and the resource that now holds the record's slot (the winner, or the unchanged local record).
type Op ¶
type Op int
Op is a label-selector match operator.
const ( // OpExists matches when the key is present (any value). OpExists Op = iota // OpNotExists matches when the key is absent. OpNotExists // OpEquals matches when the key's value equals the requirement value. OpEquals // OpNotEquals matches when the key is absent or its value differs. OpNotEquals // OpIn matches when the key's value is one of the requirement values. OpIn // OpNotIn matches when the key is absent or its value is none of the values. OpNotIn )
type Option ¶
type Option func(*memStore)
Option configures the in-memory Store.
func WithClock ¶
WithClock sets the time source (default clock.System), so tests and replay can supply a clock.Manual.
func WithEventLog ¶
WithEventLog backs the store with a specific spine.Log instead of a private in-memory one (inject a shared log to observe, audit, or Replay the stream).
func WithIDGenerator ¶
WithIDGenerator sets the source of resource IDs (default: a generator on the store's clock with crypto/rand entropy). Supply a seeded generator for deterministic replay.
func WithInstanceID ¶
WithInstanceID sets the origin/last-writer instance stamped onto records this store creates (default "local").
func WithSnapshotCodec ¶
func WithSnapshotCodec(c spine.SnapshotCodec) Option
WithSnapshotCodec makes the store's snapshots verified: Snapshot seals the projection payload through the codec before saving it, and Replay opens (verifies) a stored snapshot through it before restoring - one that fails to open is skipped and the stream is folded from the start instead. With a codec set, an unsigned or tampered snapshot is never restored.
func WithSnapshotEvery ¶
WithSnapshotEvery makes the store checkpoint itself automatically: after every k successful mutations it writes a snapshot, so a later Replay folds at most k events past the last checkpoint instead of the whole stream. The snapshot is written after the mutation completes, outside any lock, and best effort: a snapshot failure never fails the write (a missing snapshot is only slower, never wrong). Zero or negative disables automatic snapshots (the default).
type OwnerReference ¶
type OwnerReference struct {
// APIVersion and Kind identify the owner's kind.
APIVersion string
Kind string
// Name and ID address the owner. ID is the stable, unambiguous handle the
// collector resolves the owner by; Name is the logical handle for selectors and
// debugging.
Name string
ID string
// Controller marks the one owner that manages this resource's lifecycle. A
// resource has at most one controller owner; when that owner is gone or
// terminating, the resource is garbage-collected.
Controller bool
}
OwnerReference records that a resource is owned by another resource: the parent run or goal that created it. It is the graph edge the garbage collector follows to reap a resource once its owner is gone, so deleting an owner cascades to the subtree it created. Owner references are envelope metadata, excluded from the content hash.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds the registered kinds and validates resources against them. It is safe for concurrent use, and is the admission control point: a resource of an unregistered kind, or one whose Spec fails its kind's schema, is rejected before it is ever stored.
func NewRegistry ¶
func NewRegistry(opts ...RegistryOption) *Registry
NewRegistry returns an empty registry using the built-in schema compiler unless one is injected.
func (*Registry) Kinds ¶
Kinds returns every registered kind, ordered by group/version then name, for introspection ("what can this agent represent?").
type RegistryOption ¶
type RegistryOption func(*Registry)
RegistryOption configures a Registry.
func WithSchemaCompiler ¶
func WithSchemaCompiler(c SchemaCompiler) RegistryOption
WithSchemaCompiler overrides the schema compiler (default: the built-in, dependency-free subset compiler). A host can inject a full JSON Schema engine.
type Requirement ¶
Requirement is one label constraint. Values is empty for existence operators, a single element for (in)equality, and a set for In/NotIn.
type Resource ¶
type Resource struct {
APIVersion string
Kind string
ID string
Name string
// GenerateName requests a server-assigned Name for a kind that has no natural
// one. When Name is empty and GenerateName is set, a create assigns
// Name = GenerateName + ID (e.g. "mem-01J..."); it is a create-only directive,
// consumed and cleared on write, never stored. Setting Name takes precedence
// and GenerateName is ignored. Mirrors Kubernetes metadata.generateName, but
// uses our globally unique, sortable ID as the suffix instead of a random one,
// so there is never a name collision to retry.
GenerateName string
Scope Scope
Labels map[string]string
Annotations map[string]string
// Spec is the desired state (validated against the kind's JSON schema). It is
// raw JSON so it embeds readably in events and hashes canonically; nil when
// unset.
Spec json.RawMessage
// Status is the observed state (set by controllers/reconcilers, not admitted
// against the spec schema). Raw JSON; nil when unset.
Status json.RawMessage
Envelope
}
Resource is the universal record for every data-defined kind in the system.
Identity: ID is a stable, globally unique id assigned on creation. Name is the logical name, unique within (Kind, Scope), the handle callers use to upsert and fetch (a Skill's slug, an Agent's name). APIVersion is the kind's group/version, e.g. "skill.ionagent.io/v1".
Kinds without a natural name (a memory fact, a turn, a run) set GenerateName instead of Name: the store assigns Name = GenerateName + ID on create, so every such record gets a unique, sortable name from the one deterministic ID source rather than each facade minting its own. See GenerateName.
func DecodeResource ¶
DecodeResource reconstructs a Resource from an event payload. Durable backends use it to project the same records the in-memory core does.
func KindResource ¶
KindResource renders a Kind as a Resource of kind "Kind", so kind definitions are stored and synced through the same foundation as everything else. Optional fields are omitted when empty so the spec satisfies the Kind schema.
func Resolve ¶
Resolve decides a cross-instance conflict between an incoming replicated record and the current local one for the same ID, returning the winner and whether it is the incoming record. It is the pure heart of Merge, deterministic and commutative so every instance converges on the same result no matter the order records arrive. The rules, in order:
- Idempotence: the same write (equal UpdatedHLC and LastWriterID) is a no-op, so re-delivering a record never changes anything.
- Provenance precedence: a human-authored write outranks an automated one regardless of clocks, because human intent should not be silently overwritten by a later agent or system write.
- Last-writer-wins by UpdatedHLC, with LastWriterID as the deterministic tiebreak when two writes share an exact HLC.
Tombstones carry the same envelope as live writes, so a delete and a write are ordered by the very same key: a higher-HLC delete removes, and a higher-HLC write after a delete intentionally resurrects.
func UnmarshalSnapshot ¶
UnmarshalSnapshot decodes a snapshot payload back into the records and the Seq it is current as of - the inverse of MarshalSnapshot, so any backend restores from the one shared format.
func Update ¶
func Update(ctx context.Context, store Store, kind string, scope Scope, name string, mutate func(*Resource) error) (Resource, error)
Update applies mutate to the resource addressed by (kind, scope, name) under optimistic concurrency: read, mutate, Put, and on ErrConflict re-read and reapply against the fresh version. It is the one conflict-retry policy for read-modify-write updates, so every caller converges under contention the same way instead of hand-rolling its own bound and yield. mutate sees a fresh copy each attempt and must be safe to run more than once. A read error, a mutate error other than ErrSkipUpdate, and a non-conflict write error end the loop and are returned as-is (a caller maps ErrNotFound to its own semantics). An exhausted retry budget returns an error matching errors.Is ErrConflict.
func UpdateByID ¶
func UpdateByID(ctx context.Context, store Store, id string, mutate func(*Resource) error) (Resource, error)
UpdateByID is Update addressed by the resource's stable id.
func (Resource) Controller ¶
func (r Resource) Controller() (OwnerReference, bool)
Controller returns the resource's controller owner reference (the owner that manages its lifecycle) and whether one is set. At most one owner reference is the controller.
func (Resource) ValidAt ¶
ValidAt reports whether the resource is true in the world at valid-time t.
The valid interval is half-open, [from, to): the instant a fact ceases to be true is excluded, so adjacent intervals tile without overlap. A nil ValidFrom defaults to the resource's creation time (valid-time tracks event-time when the writer did not say otherwise); a nil ValidTo means the fact is still valid, with no upper bound.
type SchemaCompiler ¶
SchemaCompiler compiles a JSON Schema document (raw JSON) into a reusable Validator. It is a port: the foundation ships a small, zero-dependency compiler (newBuiltinCompiler) that supports the structural subset of JSON Schema our specs use; a host that needs full JSON Schema 2020-12 compliance can inject an adapter wrapping a complete engine, without the core depending on it.
func NewSchemaCompiler ¶
func NewSchemaCompiler() SchemaCompiler
NewSchemaCompiler returns the built-in, dependency-free schema compiler, so a caller outside this package can validate an instance against a JSON Schema with the same engine the resource store uses. The semantics match registered-kind validation exactly.
type Scope ¶
Scope locates a resource on the instance/project/workspace axis (the namespace), so resources can be partitioned and resolved most-specific-first and shared selectively across a fleet. The zero Scope is the global (instance) scope. Scope is comparable.
type Selector ¶
type Selector []Requirement
Selector is a conjunction of requirements: a resource matches when its labels satisfy every requirement. The empty Selector matches everything. This is the universal declarative query plane over all kinds (the Kubernetes label-selector model).
func Everything ¶
func Everything() Selector
Everything is the selector that matches every resource.
func ParseSelector ¶
ParseSelector parses a Kubernetes-style label selector: comma-separated requirements, each one of `key`, `!key`, `key=value`, `key==value`, `key!=value`, `key in (a, b)`, or `key notin (a, b)`. An empty string is Everything.
type Stamper ¶
type Stamper struct {
// contains filtered or unexported fields
}
Stamper computes the canonical post-image of a resource mutation: it runs admission (the kind must be registered and the Spec must satisfy its schema), assigns identity, the sync envelope, content and sync versions, the content hash, and timestamps, enforces optimistic concurrency, and builds the spine event to append. Every backend routes writes through one Stamper, so the rules live in exactly one place and backends cannot drift. It does no persistence: the caller supplies the existing record (looked up under its own lock/transaction) and persists the returned resource and event.
func NewStamper ¶
func NewStamper(instanceID string, clk clock.Clock, hc *hlc.Clock, gen *ids.Generator, reg *Registry) *Stamper
NewStamper builds a Stamper from a backend's instance identity, deterministic primitives, and the kind registry used for admission.
func (*Stamper) Delete ¶
Delete requests deletion of the given live resource. If it has no finalizers it tombstones immediately (EvDeleted). If it has finalizers it is marked terminating instead: DeletionTimestamp is set and the record stays live (EvPut) so its owners can run cleanup and remove their finalizer keys; the deletion completes later, in Put, when the last finalizer is removed.
func (*Stamper) Put ¶
Put stamps a create or update of r keyed by (Kind, Scope, Name). existing is the stored record for that key (tombstones included, so a put over a tombstone resurrects it) or nil. It admits the spec, enforces opt-in CAS, and recomputes the content hash, returning the canonical resource and the event to append.
type Store ¶
type Store interface {
// Put creates or updates the resource addressed by (Kind, Scope, Name). It
// validates Spec against the kind's schema (admission), assigns identity,
// envelope, content hash, and timestamps, and records the mutation on the
// event log. Optimistic concurrency is opt-in via SyncVersion.
Put(ctx context.Context, r Resource) (Resource, error)
// Get returns the live resource for (kind, scope, name), or ErrNotFound.
Get(ctx context.Context, kind string, scope Scope, name string) (Resource, error)
// GetByID returns the live resource by its stable id, or ErrNotFound.
GetByID(ctx context.Context, id string) (Resource, error)
// List returns the live resources of a kind in a scope whose labels satisfy
// the selector (nil selector matches all), ordered by name.
List(ctx context.Context, kind string, scope Scope, sel Selector) ([]Resource, error)
// ListAll returns the live resources of a kind across every scope whose labels
// satisfy the selector (nil selector matches all), ordered by scope then name.
// It is the cross-namespace query: typed facades that resolve by a
// scope-independent handle (a skill slug, say) and selector-driven views over a
// whole kind read through it, the way Kubernetes lists a kind across all
// namespaces.
ListAll(ctx context.Context, kind string, sel Selector) ([]Resource, error)
// Delete requests deletion of the resource addressed by (kind, scope, name), or
// returns ErrNotFound. With no finalizers it tombstones immediately; with
// finalizers it marks the resource terminating (sets DeletionTimestamp, keeps it
// live) and the deletion completes via Put when the last finalizer is removed.
// Deleting an already-terminating resource is an idempotent no-op.
Delete(ctx context.Context, kind string, scope Scope, name string) error
// Merge applies a resource replicated from another instance, converging the two
// without losing a write. Distinct from Put (the local-write command): Merge
// trusts the remote envelope (ID, origin, HLC, versions, provenance) and never
// restamps it, so all replicas reach byte-identical state regardless of the
// order replicas arrive. See Resolve for the conflict rules; the result reports
// whether the remote was applied, ignored as stale, or already present.
//
// Identity is the global ID: a record is merged against the local record with
// the same ID. The same (Kind, Scope, Name) created independently on two
// instances has two different IDs and so stays two distinct records; resolving
// such a name collision is a higher-level concern, not part of the apply path.
Merge(ctx context.Context, remote Resource) (MergeResult, error)
// Snapshot checkpoints the current projection onto the event log, so a later
// rebuild resumes from the snapshot and folds only the events after it instead
// of replaying the whole stream. A snapshot is a derived cache: it never changes
// what a read returns, only how fast a rebuild is.
Snapshot(ctx context.Context) error
// Close releases backend resources.
Close() error
}
Store is the generic, event-sourced port every kind is read and written through: the single interface a backend implements to persist the entire foundation. Adding a domain is registering a Kind, never editing this interface or forking a backend. Backends (in-memory, SQLite, a host) are interchangeable, held to one contract by resourcetest.RunSuite.
func NewMemory ¶
NewMemory returns an in-memory Store admitting against reg. Every mutation is recorded on a spine.Log and projected, so the store's state is always a fold of its log (see Replay). Safe for concurrent use; the zero-setup default backend.
func Replay ¶
Replay reconstructs an in-memory Store by folding a log's resource stream: the running proof that the resource layer is a projection of the spine. It resumes from the log's latest resource snapshot when one exists and folds only the events after it, so a long-lived stream rebuilds in bounded work instead of from the start. The result is identical either way: a snapshot is a cache, not a source of truth.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package resourcetest is the conformance suite for resource.Store.
|
Package resourcetest is the conformance suite for resource.Store. |