Documentation
¶
Index ¶
- Constants
- Variables
- func ContentHash(meta *v1alpha1.ObjectMeta, spec json.RawMessage) (string, error)
- func DefaultTag() string
- func NewOSSMigrator(ctx context.Context, dsn string) (*migrate.Migrate, error)
- func NewStores(pool *pgxpool.Pool, schemas *pkgdb.SchemaRegistry, opts ...StoreOption) map[string]*Store
- func SpecHash(raw json.RawMessage) string
- type ControlPlaneEvent
- type ControlPlaneEventStore
- func (s *ControlPlaneEventStore) CurrentRevision(ctx context.Context) (int64, error)
- func (s *ControlPlaneEventStore) ListAfter(ctx context.Context, afterRevision int64, limit int) ([]ControlPlaneEvent, error)
- func (s *ControlPlaneEventStore) OldestRevision(ctx context.Context) (revision int64, ok bool, err error)
- func (s *ControlPlaneEventStore) PruneBefore(ctx context.Context, before time.Time, keepAfterRevision int64, limit int) (int64, error)
- type FindReferrersOpts
- type ListOpts
- type PatchOpts
- type ResourceKey
- type Store
- func (s *Store) ApplyPatch(ctx context.Context, namespace, name, tag string, patch PatchOpts) error
- func (s *Store) Behavior() StoreBehavior
- func (s *Store) Delete(ctx context.Context, namespace, name, tag string) error
- func (s *Store) DeleteAllTags(ctx context.Context, namespace, name string) error
- func (s *Store) DeleteByRef(ctx context.Context, namespace, name, tag string) error
- func (s *Store) FindReferrers(ctx context.Context, pathJSON json.RawMessage, opts FindReferrersOpts) ([]*v1alpha1.RawObject, error)
- func (s *Store) Get(ctx context.Context, namespace, name, tag string) (*v1alpha1.RawObject, error)
- func (s *Store) GetByRef(ctx context.Context, namespace, name, tag string) (*v1alpha1.RawObject, error)
- func (s *Store) GetLatest(ctx context.Context, namespace, name string) (*v1alpha1.RawObject, error)
- func (s *Store) GetLatestIncludingTerminating(ctx context.Context, namespace, name string) (*v1alpha1.RawObject, error)
- func (s *Store) List(ctx context.Context, opts ListOpts) ([]*v1alpha1.RawObject, string, error)
- func (s *Store) ListTags(ctx context.Context, namespace, name string) ([]*v1alpha1.RawObject, error)
- func (s *Store) PatchAnnotations(ctx context.Context, namespace, name, tag string, ...) error
- func (s *Store) PatchFinalizers(ctx context.Context, namespace, name, tag string, ...) error
- func (s *Store) PatchStatus(ctx context.Context, namespace, name, tag string, ...) error
- func (s *Store) PurgeFinalized(ctx context.Context) (int64, error)
- func (s *Store) Upsert(ctx context.Context, obj v1alpha1.Object, opts ...UpsertOpts) (UpsertResult, error)
- type StoreBehavior
- type StoreOption
- type UpsertOpts
- type UpsertOutcome
- type UpsertResult
Constants ¶
const ControlPlaneNotifyChannel = "v1alpha1_control_plane_changed"
ControlPlaneNotifyChannel is the single coarse wakeup channel for controllers. The payload is only a hint; controllers must replay control_plane_events and re-read canonical source rows.
const DefaultTagValue = "latest"
const MigrationsDir = "migrations"
MigrationsDir is the directory inside MigrationFiles holding NNN_name.up.sql / NNN_name.down.sql pairs. Exported so the CLI and the orchestrator can pass it alongside MigrationFiles.
Variables ¶
var ErrInvalidCursor = errors.New("v1alpha1 store: invalid cursor")
ErrInvalidCursor reports that a list pagination cursor could not be parsed.
var ErrInvalidExtraWhere = errors.New("v1alpha1 store: ExtraWhere / ExtraArgs placeholder mismatch")
ErrInvalidExtraWhere reports that ListOpts.ExtraWhere references more placeholders than ExtraArgs has bind values (or vice versa), which would either be a silent misuse or a runtime pgx error.
var ErrTerminating = errors.New("v1alpha1 store: object is terminating")
ErrTerminating reports that an Upsert targeted a row whose deletion_timestamp is set — the row is mid-teardown and cannot be mutated until its finalizers drain and the GC pass hard-deletes it. Matches Kubernetes semantics: `kubectl apply` against a terminating object returns 409 AlreadyExists ("object is being deleted; delete and recreate").
var MigrationFiles fs.FS = v1alpha1MigrationFiles
MigrationFiles is the embedded FS containing every OSS migration. Exported so callers (the CLI, the orchestrator, downstream tooling) can compute pending-migration counts and pass the embed to `golang-migrate`'s iofs source without piercing migrate.Migrate's internals.
Functions ¶
func ContentHash ¶
func ContentHash(meta *v1alpha1.ObjectMeta, spec json.RawMessage) (string, error)
ContentHash returns the canonical digest used for same-tag replacement detection. It deliberately includes only user-authored declarative state: spec plus labels/annotations.
func DefaultTag ¶
func DefaultTag() string
DefaultTag returns the tag assigned when metadata.tag is omitted.
func NewOSSMigrator ¶
NewOSSMigrator constructs a `*migrate.Migrate` against `database.OSSSchema` for the OSS migration set. The caller owns `mg.Close()`. ctx is accepted for API symmetry with the surrounding startup path.
func NewStores ¶
func NewStores(pool *pgxpool.Pool, schemas *pkgdb.SchemaRegistry, opts ...StoreOption) map[string]*Store
NewStores builds one *Store per OSS built-in v1alpha1 Kind, bound to its canonical table. The returned map is keyed by Kind name (e.g. "Agent", "MCPServer") and is the single input the router/apply layers take. They never look up tables by string literal themselves.
Kinds whose descriptors use KindStorageMutableObject are bound through NewMutableObjectStore. Every other built-in kind uses NewStore (tagged-artifact behavior). Extension kinds are intentionally not built here; the composition root wires them from V1Alpha1StoreTables after this function returns.
The variadic opts are applied to every Store produced. Downstream callers pass WithAuditor(...) here to plumb a single audit sink across all kinds in one call.
func SpecHash ¶
func SpecHash(raw json.RawMessage) string
SpecHash returns a deterministic SHA-256 hex digest of a JSON spec. Field order and whitespace do not affect the result; only the content (keys + values) does. Empty/null specs hash to a stable sentinel.
Types ¶
type ControlPlaneEvent ¶
type ControlPlaneEvent struct {
Revision int64
Key ResourceKey
UID string
Generation int64
Operation string
CommittedAt time.Time
}
ControlPlaneEvent records that a canonical v1alpha1 source row changed after a monotonic revision. It intentionally carries identity only, not object payload or derived desired state.
type ControlPlaneEventStore ¶
type ControlPlaneEventStore struct {
// contains filtered or unexported fields
}
ControlPlaneEventStore reads and prunes the durable invalidation cursor used by controllers.
func NewControlPlaneEventStore ¶
func NewControlPlaneEventStore(pool *pgxpool.Pool, schema pkgdb.Schema) *ControlPlaneEventStore
NewControlPlaneEventStore constructs a control-plane event reader.
func (*ControlPlaneEventStore) CurrentRevision ¶
func (s *ControlPlaneEventStore) CurrentRevision(ctx context.Context) (int64, error)
CurrentRevision returns the current high-water revision, or 0 when the event table is empty.
func (*ControlPlaneEventStore) ListAfter ¶
func (s *ControlPlaneEventStore) ListAfter(ctx context.Context, afterRevision int64, limit int) ([]ControlPlaneEvent, error)
ListAfter returns events with revision > afterRevision, ordered by revision.
func (*ControlPlaneEventStore) OldestRevision ¶
func (s *ControlPlaneEventStore) OldestRevision(ctx context.Context) (revision int64, ok bool, err error)
OldestRevision returns the oldest retained event revision. ok=false means the table is empty.
func (*ControlPlaneEventStore) PruneBefore ¶
func (s *ControlPlaneEventStore) PruneBefore(ctx context.Context, before time.Time, keepAfterRevision int64, limit int) (int64, error)
PruneBefore deletes retained events in bounded batches. At least one of before or keepAfterRevision must be set. Controllers must use gap detection before relying on pruning in production.
type FindReferrersOpts ¶
type FindReferrersOpts struct {
// Namespace, when non-empty, restricts results to a single namespace.
Namespace string
// LatestOnly, when true, restricts to the literal "latest" tag per
// (namespace, name), or the private latest row for mutable-object stores.
LatestOnly bool
// IncludeTerminating, when true, keeps rows whose deletion_timestamp
// is set. Default (false) excludes them.
IncludeTerminating bool
}
FindReferrersOpts controls the FindReferrers scan.
type ListOpts ¶
type ListOpts struct {
// Namespace narrows results to a specific namespace. Empty means "across
// all namespaces".
Namespace string
// LabelSelector narrows results to rows whose labels JSONB contains
// this subset (uses `@>` with a GIN index).
LabelSelector map[string]string
// Limit caps the number of rows returned. Zero means default (50).
Limit int
// Cursor is an opaque pagination token. Empty starts from the beginning.
Cursor string
// Tag restricts the result set to a single tag value on tagged-artifact
// stores. Empty means "no tag filter" — every tag of every name is
// returned. Ignored on mutable-object stores (they have no tag column).
// Mutually exclusive with LatestOnly (validated at the caller level;
// the store treats LatestOnly as the literal `Tag = "latest"` filter
// when both are set, but new callers should pick one).
Tag string
// LatestOnly restricts to the literal "latest" tag per (namespace, name),
// or the private latest row for mutable-object stores. Equivalent to
// `Tag = "latest"` on tagged stores; kept as a separate field because
// it also covers the mutable-object latest-row case (where there's no
// user-facing tag column).
LatestOnly bool
// IncludeTerminating includes rows with deletion_timestamp set. Default
// false — callers asking for "alive" rows shouldn't see terminating ones.
IncludeTerminating bool
// ExtraWhere appends a caller-supplied parameterized SQL predicate to
// the WHERE clause. It's the RBAC / tenancy / extension-filter seam:
// the generic Store stays kind-agnostic while a wrapper injects
// authz-derived constraints like `namespace = ANY($1)`.
//
// Rules:
// - Placeholders are numbered from `$1` relative to ExtraArgs (so
// the fragment reads naturally on its own). The Store rebases them
// to continue after its own internal $N before executing.
// - The placeholder count in the fragment MUST equal len(ExtraArgs).
// List returns ErrInvalidExtraWhere when they disagree.
// - NEVER interpolate untrusted input into ExtraWhere with
// fmt.Sprintf/string concatenation — always use placeholders with
// ExtraArgs. Doing otherwise is a SQL injection; this is the
// authz surface.
// - The fragment is appended with a leading AND, so a single
// standalone predicate like "deleted_by IS NULL" is fine; don't
// prefix with "AND " yourself.
ExtraWhere string
// ExtraArgs are the bind parameters for ExtraWhere. Number of entries
// MUST equal the distinct placeholder count in ExtraWhere.
ExtraArgs []any
}
ListOpts controls paginated list queries.
type PatchOpts ¶
type PatchOpts struct {
Status func(current json.RawMessage) (json.RawMessage, error)
Annotations func(map[string]string) map[string]string
Finalizers func([]string) []string
}
PatchOpts bundles optional column mutations applied atomically by ApplyPatch. Nil mutators skip the corresponding column entirely; the row's other fields are never touched.
type ResourceKey ¶
ResourceKey identifies a source row in the v1alpha1 control plane.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the single generic persistence layer for every v1alpha1 kind. One Store instance is bound to one table; callers construct one per kind (v1alpha1.agents, v1alpha1.mcp_servers, etc.).
Store has two private behaviors, picked at construction time:
TaggedArtifactStore (the default; produced by NewStore). Storage key is (namespace, name, tag). Users may supply the tag declaratively; missing tags are filled with the literal "latest". Re-applying the same tag replaces the prior row atomically when the content changes. Used for agents, mcp_servers, skills, and prompts.
MutableObjectStore (produced by NewMutableObjectStore). Storage key is (namespace, name). Used for Runtime/Deployment and additional downstream mutable control-plane/config kinds.
PatchStatus is disjoint from Upsert: it touches only status and updated_at, never spec. Reconcilers use PatchStatus exclusively; apply handlers use Upsert exclusively.
Delete hard-deletes tagged-artifact rows and mutable rows without finalizers. Mutable rows with finalizers are marked terminating via deletion_timestamp; exact Get can still load them, while GetLatest/List hide them unless the caller explicitly includes terminating rows. PurgeFinalized removes terminating mutable rows after finalizers are empty.
func NewMutableObjectStore ¶
func NewMutableObjectStore(pool *pgxpool.Pool, schema pkgdb.Schema, table string, opts ...StoreOption) *Store
NewMutableObjectStore constructs a mutable-object Store for tables keyed by namespace/name in schema.
func NewStore ¶
NewStore constructs a tagged-artifact Store bound to a single table (e.g. "agents") in schema. The table must exist; NewStore does not validate it. Queries qualify the table with schema explicitly, so the Store does not depend on the connection's search_path.
For mutable object tables, use NewMutableObjectStore.
func (*Store) ApplyPatch ¶
ApplyPatch atomically applies PatchOpts to one row. Tagged-artifact stores require tag=metadata.tag; mutable-object stores ignore tag and use namespace/name. Columns whose mutator is nil are left untouched. Returns pkgdb.ErrNotFound if the row doesn't exist.
Finalizers patching is supported only on the deployments table; the tagged-artifact tables don't carry a finalizers column. Calling PatchFinalizers on a tagged-artifact Store returns an error to surface the misconfiguration loudly rather than silently no-op.
func (*Store) Behavior ¶
func (s *Store) Behavior() StoreBehavior
Behavior reports which private persistence behavior this Store uses. Generic controller/read-model code uses it to interpret ResourceRef tag semantics without maintaining its own per-kind switch.
func (*Store) Delete ¶
Delete removes a single row. Mutable-object stores may use soft-delete plus finalizer drain. Tagged-artifact rows have no finalizers and are hard-deleted immediately so name/tag can be reapplied without waiting for GC. Returns pkgdb.ErrNotFound if the row doesn't exist.
func (*Store) DeleteAllTags ¶
DeleteAllTags hard-deletes every tag row for (namespace, name) on a tagged-artifact table. This is the contract of the batch DELETE endpoint when metadata.tag is omitted; callers delete a single tag by including metadata.tag. Returns pkgdb.ErrNotFound when no row exists for (namespace, name).
Calling on a mutable-object Store is a programming error; the per-kind Store hands mutable objects to the single-row Delete path instead.
func (*Store) DeleteByRef ¶
DeleteByRef applies the public reference/delete shape shared by v1alpha1 resources. For tagged artifacts, blank tag deletes every tag for (namespace, name), while a non-empty tag deletes that exact tag. Mutable objects delete by namespace/name and reject explicit tag pins.
func (*Store) FindReferrers ¶
func (s *Store) FindReferrers(ctx context.Context, pathJSON json.RawMessage, opts FindReferrersOpts) ([]*v1alpha1.RawObject, error)
FindReferrers returns rows from this Store's table whose spec JSONB matches pathJSON (via the `@>` containment operator).
func (*Store) Get ¶
Get returns a single row, including terminating rows. For tagged-artifact stores, tag is metadata.tag. Mutable-object stores ignore tag and load by namespace/name. Returns pkgdb.ErrNotFound if missing.
func (*Store) GetByRef ¶
func (s *Store) GetByRef(ctx context.Context, namespace, name, tag string) (*v1alpha1.RawObject, error)
GetByRef resolves the public reference shape shared by v1alpha1 resources. Blank tag means the current live row: literal "latest" for tagged artifacts, namespace/name for mutable objects. Non-empty tag selects a tagged artifact row and is invalid for mutable-object stores.
func (*Store) GetLatest ¶
GetLatest returns the literal "latest" live tag for (namespace, name) on tagged-artifact tables, or the current live row for mutable-object stores. Returns pkgdb.ErrNotFound if no live row exists. Terminating rows are excluded.
func (*Store) GetLatestIncludingTerminating ¶
func (s *Store) GetLatestIncludingTerminating(ctx context.Context, namespace, name string) (*v1alpha1.RawObject, error)
GetLatestIncludingTerminating is GetLatest without the `deletion_timestamp IS NULL` filter, so soft-deleted rows are still returned. Used by resource-handler GET / DELETE paths when the kind opts into IncludeTerminatingByDefault; without this view those handlers contradict LIST, which surfaces the terminating row. Returns pkgdb.ErrNotFound only when no row exists at all.
func (*Store) List ¶
List returns rows filtered by opts, ordered by stable resource key (namespace, name, tag) with updated_at as a stable tiebreaker. Pagination cursor is returned when more rows are available; pass it back via ListOpts.Cursor to continue. Terminating rows are excluded unless IncludeTerminating is true.
func (*Store) ListTags ¶
func (s *Store) ListTags(ctx context.Context, namespace, name string) ([]*v1alpha1.RawObject, error)
ListTags returns every non-deleted tag row for (namespace, name), ordered by most recently applied first. Tagged-artifact mode only — mutable-object stores do not model "list every tag of a logical resource" and report an error.
Returns an empty slice (no error) when no rows exist for the tag: list semantics differ from the single-row Get path. The HTTP layer surfaces empty results as 200 with `{"items": []}`.
func (*Store) PatchAnnotations ¶
func (s *Store) PatchAnnotations(ctx context.Context, namespace, name, tag string, mutate func(map[string]string) map[string]string) error
PatchAnnotations is a thin wrapper over ApplyPatch for the single- column annotations case.
func (*Store) PatchFinalizers ¶
func (s *Store) PatchFinalizers(ctx context.Context, namespace, name, tag string, mutate func([]string) []string) error
PatchFinalizers is a thin wrapper over ApplyPatch for the single- column finalizers case. Only valid for the deployments table.
func (*Store) PatchStatus ¶
func (s *Store) PatchStatus(ctx context.Context, namespace, name, tag string, mutate func(current json.RawMessage) (json.RawMessage, error)) error
PatchStatus is a thin wrapper over ApplyPatch for the single-column status case.
func (*Store) PurgeFinalized ¶
PurgeFinalized hard-deletes terminating rows. For deployments this requires finalizers to be empty; for tagged-artifact tables there is no finalizers column, so any row past deletion_timestamp is purged. Returns the number of rows purged.
func (*Store) Upsert ¶
func (s *Store) Upsert(ctx context.Context, obj v1alpha1.Object, opts ...UpsertOpts) (UpsertResult, error)
Upsert applies obj into the Store. Behaviour depends on the table's persistence behavior:
- Tagged-artifact tables (agents, mcp_servers, etc.) follow declarative tag semantics:
- missing metadata.tag → default to the literal "latest" tag
- new (namespace, name, tag) → insert the row
- same tag and same canonical content hash → no-op
- same tag and different content hash → replace the row in place
- Mutable-object tables follow Kubernetes-like update-in-place semantics behind namespace/name key.
Status is never touched by Upsert — use PatchStatus for that.
type StoreBehavior ¶
type StoreBehavior string
StoreBehavior names the private persistence behavior used below the single public v1alpha1 API shape.
const ( // TaggedArtifactStore keys immutable-ish registry artifacts by // namespace/name/tag. TaggedArtifactStore StoreBehavior = "TaggedArtifactStore" // MutableObjectStore keys normal Kubernetes-like objects by namespace/name // in the public API and storage. MutableObjectStore StoreBehavior = "MutableObjectStore" )
type StoreOption ¶
type StoreOption func(*Store)
StoreOption configures an optional Store behaviour at construction time. Options compose; later options override earlier ones for the same field.
func WithAuditor ¶
func WithAuditor(a types.Auditor) StoreOption
WithAuditor plugs a types.Auditor into the Store so every state change the Store considers significant fires the matching audit event after the underlying transaction commits. Default is types.NoopAuditor.
func WithKind ¶
func WithKind(kind string) StoreOption
WithKind tags a Store with the canonical v1alpha1 Kind name (e.g. v1alpha1.KindAgent) so audit events can name the kind without the caller having to set obj.TypeMeta. NewStores sets this for every kind; ad-hoc constructors leave it empty unless the caller passes WithKind explicitly. When unset, the Store falls back to the Kind carried on the inbound object (if any).
type UpsertOpts ¶
type UpsertOpts struct {
// InitialFinalizers is applied only on the create path for mutable-object
// stores. Updates preserve existing finalizers.
InitialFinalizers []string
}
UpsertOpts customizes create-time behavior for Store.Upsert.
type UpsertOutcome ¶
type UpsertOutcome int
UpsertOutcome categorises what an Upsert call did.
const ( // UpsertCreated reports that a new tag row was inserted. UpsertCreated UpsertOutcome = iota // UpsertNoOp reports that the incoming content matched the existing row // for the tag. No row was written. UpsertNoOp // UpsertReplaced reports that an existing tag row was atomically replaced // with new content. UpsertReplaced )
type UpsertResult ¶
type UpsertResult struct {
// Tag is the content tag after the call for tagged-artifact tables.
Tag string
// UID is the server-managed row identity after the write.
UID string
// Generation is the server-managed row generation after the call.
Generation int64
// Outcome categorises what the call did. See UpsertOutcome constants.
Outcome UpsertOutcome
}
UpsertResult is the outcome of Upsert.