Documentation
¶
Overview ¶
Package store is the central, in-memory state container for the controller pipeline. It tracks three orthogonal kinds of information for each Kubernetes-style resource (keyed by manifest.NamedResource):
- The parsed manifest object itself.
- The processing status (Pending / Ready / Failed) with an optional error message.
- The artifact produced by a controller (e.g. a checked-out git working tree, a rendered manifest, a downloaded chart).
Three event types are dispatched as state evolves: ObjectAdded, StatusUpdated, ArtifactUpdated. Callers subscribe via AddListener (sync, returns an unsubscribe function) or block on the high-level Watch* helpers, which return a single value once the awaited state is reached.
Store is safe for concurrent use. Listeners run inline on the goroutine that triggered the event — they MUST NOT block on Store operations, or they'll deadlock. The Watch* helpers handle this correctly by sending to a buffered channel from inside the listener.
Index ¶
- Constants
- func Get[T manifest.BaseManifest](s *Store, id manifest.NamedResource) (T, bool)
- func GetByName[T manifest.BaseManifest](s *Store, kind, namespace, name string) (T, bool)
- func IsSkipped(info StatusInfo) bool
- func IsSuspended(info StatusInfo) bool
- func IsUnchanged(info StatusInfo) bool
- func ListAs[T manifest.BaseManifest](s *Store, kind string) []T
- func SupportsStatus(kind string) bool
- type Artifact
- type Condition
- type EventKind
- type HelmReleaseArtifact
- type KustomizationArtifact
- type Listener
- type RenderedArtifact
- type ResourceSetArtifact
- type SourceArtifact
- type Status
- type StatusInfo
- type Store
- func (s *Store) AddListener(event EventKind, fn Listener, flush bool) Unsubscribe
- func (s *Store) AddObject(obj manifest.BaseManifest)
- func (s *Store) AddObjects(objs []manifest.BaseManifest)
- func (s *Store) AddRendered(obj manifest.BaseManifest)
- func (s *Store) AddWarning(w manifest.Warning)
- func (s *Store) BlockedBy(id manifest.NamedResource) []manifest.NamedResource
- func (s *Store) DeleteObject(id manifest.NamedResource) bool
- func (s *Store) FailedResources() map[manifest.NamedResource]StatusInfo
- func (s *Store) GetArtifact(id manifest.NamedResource) Artifact
- func (s *Store) GetByName(kind, namespace, name string) manifest.BaseManifest
- func (s *Store) GetConditions(id manifest.NamedResource) []Condition
- func (s *Store) GetObject(id manifest.NamedResource) manifest.BaseManifest
- func (s *Store) GetStatus(id manifest.NamedResource) (StatusInfo, bool)
- func (s *Store) ListObjects(kind string) []manifest.BaseManifest
- func (s *Store) OnArtifact(fn func(manifest.NamedResource, Artifact), replay bool) Unsubscribe
- func (s *Store) OnObject(fn func(manifest.NamedResource, manifest.BaseManifest), replay bool) Unsubscribe
- func (s *Store) OnStatus(fn func(manifest.NamedResource, StatusInfo), replay bool) Unsubscribe
- func (s *Store) Refire(id manifest.NamedResource)
- func (s *Store) SetArtifact(id manifest.NamedResource, artifact Artifact)
- func (s *Store) SetBlocked(id manifest.NamedResource, deps []manifest.NamedResource)
- func (s *Store) SetCondition(id manifest.NamedResource, cond Condition)
- func (s *Store) Snapshot(id manifest.NamedResource) (manifest.BaseManifest, []Condition)
- func (s *Store) UpdateStatus(id manifest.NamedResource, status Status, message string)
- func (s *Store) Warnings() []manifest.Warning
- type Unsubscribe
Constants ¶
const ( SkippedPrefix = "skipped:" MsgUnchanged = "unchanged" MsgSuspended = "suspended" // MsgRefetching is the Pending-message Refire writes before // re-dispatching EventObjectAdded. Surfaced as a distinct sentinel // (vs. the source controller's own "fetching" mid-reconcile) so log // scraping can distinguish a Refire-triggered re-pull from a // first-time fetch. MsgRefetching = "re-fetching" )
Canonical StatusReady message vocabulary. flate overloads StatusReady with several "ready but…" sub-states encoded in the message text — depwait treats them uniformly as ready (so deps unblock), while consumers branch on the message to decide UI / propagation.
Three sub-states exist today:
- SkippedPrefix — a resource soft-skipped by --allow-missing- secrets (or by a consumer propagating a source-skip). Detect via IsSkipped. Message format: "skipped: <reason>".
- MsgUnchanged — the change-filter excluded this resource because its sources weren't in the diff. Detect via IsUnchanged. Used by `flate test` for SKIPPED outcomes.
- MsgSuspended — spec.suspend was honored. Detect via IsSuspended.
All three are kept in one place so the literal-vs-helper drift (testrunner's `info.Message == "unchanged"` direct compare, etc.) has exactly one source of truth and a future rephrasing in controllers/base only requires updating the constant here.
const ( ConditionReady = "Ready" ConditionHealthy = "Healthy" )
Condition type identifiers used by flate. These mirror Flux's conventions so a future watch-mode could publish to a real cluster without translating.
const ( ReasonSucceeded = "Succeeded" ReasonFailed = "Failed" ReasonReconciling = "Reconciling" )
Condition reasons attached to the Ready condition.
Variables ¶
This section is empty.
Functions ¶
func Get ¶
func Get[T manifest.BaseManifest](s *Store, id manifest.NamedResource) (T, bool)
Get fetches the object at id and asserts it to T in one step. Returns (zero, false) when the object is absent or stored under a different type. Eliminates the two-line GetObject + type-assert pattern that otherwise repeats across controllers, discovery, and the orchestrator:
// before ks, ok := s.GetObject(id).(*manifest.Kustomization) // after ks, ok := store.Get[*manifest.Kustomization](s, id)
The constraint on T is manifest.BaseManifest — any stored manifest type — so Get and GetByName share one uniform, type-safe lookup.
func GetByName ¶
func GetByName[T manifest.BaseManifest](s *Store, kind, namespace, name string) (T, bool)
GetByName is the (kind, namespace, name) sibling of Get — performs the same store lookup as Store.GetByName and asserts the result to T. Returns (zero, false) on miss or type mismatch.
Useful when the caller has the human-readable triple (e.g. from a SecretRef or valuesFrom) rather than a NamedResource.
func IsSkipped ¶
func IsSkipped(info StatusInfo) bool
IsSkipped reports whether info represents a soft-skip — i.e. a StatusReady whose message starts with SkippedPrefix.
func IsSuspended ¶
func IsSuspended(info StatusInfo) bool
IsSuspended reports whether info represents a spec.suspend honor (StatusReady + MsgSuspended).
func IsUnchanged ¶
func IsUnchanged(info StatusInfo) bool
IsUnchanged reports whether info represents a change-filter exclusion (StatusReady + MsgUnchanged).
func ListAs ¶
func ListAs[T manifest.BaseManifest](s *Store, kind string) []T
ListAs lists every stored object of kind and returns those that type-assert to T. Mirrors ListObjects(kind) but returns the concrete-typed slice the caller almost always needs:
// before
for _, obj := range s.ListObjects(manifest.KindKustomization) {
ks, ok := obj.(*manifest.Kustomization)
if !ok { continue }
// use ks
}
// after
for _, ks := range store.ListAs[*manifest.Kustomization](s, manifest.KindKustomization) {
// use ks
}
The store invariant guarantees that ListObjects(K) returns only objects whose Named().Kind == K, so the inner type-assert is a defensive net rather than a real filter — but keeping it cheap means a future invariant relaxation degrades safely rather than panicking.
func SupportsStatus ¶
SupportsStatus reports whether the given kind participates in the status pipeline. Kinds outside this set (ConfigMap, Secret, ...) are considered "ready" simply by existing.
Types ¶
type Artifact ¶
type Artifact interface {
// contains filtered or unexported methods
}
Artifact is a marker interface implemented by every artifact type. Controllers type-assert to the concrete type they expect.
type Condition ¶
Condition is an alias for k8s metav1.Condition. flate stores per-resource conditions so SOPS-encrypted-secret detection, health-check rollups, and Flux's dependsOn ReadyExpr CEL evaluation can interoperate with the rest of the K8s ecosystem.
type EventKind ¶
type EventKind int
EventKind enumerates the three observable changes the Store dispatches.
const ( // EventObjectAdded fires when a new manifest is added (or when a // listener is registered with flush=true, to replay existing state). EventObjectAdded EventKind = iota + 1 // EventStatusUpdated fires when status transitions. EventStatusUpdated // EventArtifactUpdated fires when an artifact is stored. EventArtifactUpdated )
type HelmReleaseArtifact ¶
HelmReleaseArtifact is the rendered output of a HelmRelease template.
Fingerprint is a stable hash of the inputs that determine the rendered output (chart identity, expanded values, install/upgrade flags). The HR controller compares it on every reconcile and skips the helm render — which is by far the hot path — when a re-AddObject event arrives with the same effective spec. Typical trigger: the parent Kustomization's render re-emits the HR with `kustomize.toolkit.fluxcd.io/{name,namespace}` ownership labels stamped on metadata, which fails AddObject's reflect-DeepEqual gate even though the rendered output would be byte-identical.
func (*HelmReleaseArtifact) RenderedFingerprint ¶ added in v0.3.4
func (a *HelmReleaseArtifact) RenderedFingerprint() string
RenderedFingerprint implements RenderedArtifact.
func (*HelmReleaseArtifact) RenderedManifests ¶
func (a *HelmReleaseArtifact) RenderedManifests() []map[string]any
RenderedManifests implements RenderedArtifact.
type KustomizationArtifact ¶
KustomizationArtifact is the rendered output of a Kustomization build.
Fingerprint mirrors HelmReleaseArtifact: a stable hash of the inputs that determine the rendered output (path, inline contents, spec, expanded substitutions, resolved source root). The KS controller compares it on every reconcile and skips re-running kustomize when a re-AddObject event arrives with the same effective spec — the same wasted-work pattern HR had before PR #219, just for KS.
func (*KustomizationArtifact) RenderedFingerprint ¶ added in v0.3.4
func (a *KustomizationArtifact) RenderedFingerprint() string
RenderedFingerprint implements RenderedArtifact.
func (*KustomizationArtifact) RenderedManifests ¶
func (a *KustomizationArtifact) RenderedManifests() []map[string]any
RenderedManifests implements RenderedArtifact.
type Listener ¶
type Listener func(id manifest.NamedResource, payload any)
Listener receives store events. The payload type depends on EventKind:
- EventObjectAdded → manifest.BaseManifest
- EventStatusUpdated → StatusInfo
- EventArtifactUpdated → Artifact
Listeners run synchronously on the goroutine that triggered the event, so they MUST NOT call back into the same Store with a blocking call.
type RenderedArtifact ¶
type RenderedArtifact interface {
Artifact
RenderedManifests() []map[string]any
// RenderedFingerprint is the stable hash of the inputs that produced
// RenderedManifests (named distinctly from the Fingerprint field). The
// controllers' shared dedup short-circuit (base.Controller.FingerprintDedup)
// compares it to skip a re-render whose inputs are byte-identical.
RenderedFingerprint() string
}
RenderedArtifact is satisfied by artifacts that carry a rendered manifest set — KustomizationArtifact and HelmReleaseArtifact. CLI emitters use it to collect rendered output without caring which controller produced it.
type ResourceSetArtifact ¶ added in v0.3.4
ResourceSetArtifact is the rendered output of a ResourceSet expansion.
Fingerprint mirrors KustomizationArtifact: a stable hash of the inputs that determine the rendered output (the RS spec plus the resolved inputsFrom provider set). The RS controller compares it on every reconcile and skips re-running resourceset.Render when a re-dispatch (a parent KS re-emit, or a drain-rerun whose inputs converged) arrives with the same effective inputs — the same wasted-work short-circuit KS and HR carry.
func (*ResourceSetArtifact) RenderedFingerprint ¶ added in v0.3.4
func (a *ResourceSetArtifact) RenderedFingerprint() string
RenderedFingerprint implements RenderedArtifact.
func (*ResourceSetArtifact) RenderedManifests ¶ added in v0.3.4
func (a *ResourceSetArtifact) RenderedManifests() []map[string]any
RenderedManifests implements RenderedArtifact.
type SourceArtifact ¶
type SourceArtifact struct {
Kind string
URL string
LocalPath string
Revision string
Digest string
Size int64
Metadata map[string]string
}
SourceArtifact is the unified working-tree artifact produced by source fetchers (GitRepository, OCIRepository, Bucket, ExternalArtifact, …). Kind identifies which CR kind produced it so consumers that care (e.g. the helm-controller's local-git registration) can filter without the previous per-kind type union.
Mirrors Flux's meta.Artifact contract: URL is the upstream address, Revision is the human-readable identifier (branch:main, tag:v1.2.3, commit sha), Digest is the content-addressed verification value, Size is the artifact size in bytes when known, and Metadata holds kind-specific annotations (OCI image annotations, bucket ETag…).
type Status ¶
type Status string
Status is the processing state of a resource as projected from its Ready condition. Kept as a high-level rollup for callers (depwait, `test` reporting) that don't need the full condition slice.
type StatusInfo ¶
StatusInfo bundles a status with an optional descriptive message. Derived from the Ready condition; see Store.GetStatus.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the central in-memory state container.
Sharded layout ¶
The Store's hot state is sharded across shardCount shards keyed by the FNV-1a hash of id.Kind. Reads and writes on different Kinds no longer serialize on a single global lock — the profile evidence from `buroa/k8s-gitops` had `store.SetCondition` + `UpdateStatus` contributing ~17% of total mutex delay on a warm 1.1s run, almost all of it cross-Kind contention (KS writers blocking HR writers blocking source-controller readers).
Cross-shard operations (the ListObjects(""), FailedResources, AddListener(replay=true) iterate-everything paths) acquire every shard in ascending index order via lockAll/unlockAll. Holding all shards in canonical order is mandatory: any code path that locks two or more shards MUST do so via lockAll / rLockAll — taking them out-of-order or interleaving with shardFor's lock will deadlock.
Immutability contract ¶
Objects passed to AddObject (and returned by GetObject / GetByName / ListObjects) are treated as IMMUTABLE after insertion. The store returns shared pointers rather than defensive copies for performance: rendering pipelines read millions of fields per reconcile, and cloning the full manifest tree on every read would dominate CPU.
Callers that need to "modify" a stored object must:
- Shallow-copy the struct (most manifest types are flat enough that *clone := *orig works).
- Mutate the copy's fields.
- Re-AddObject the modified copy. AddObject's reflect.DeepEqual dedup avoids spurious events, and the second-pass dispatch reaches downstream controllers.
Mutating an object after AddObject is a bug — concurrent readers will see torn state and AddObject's dedup will compare against a moving target. The loader (pkg/loader/inherit.go) and orchestrator (pkg/orchestrator/orchestrator.go) follow the clone-then-AddObject pattern; any new mutation site should too.
func (*Store) AddListener ¶
func (s *Store) AddListener(event EventKind, fn Listener, flush bool) Unsubscribe
AddListener registers a callback for the given event kind. When flush==true, the listener is immediately invoked with every matching object already in the store before the call returns. Replay order is unspecified (Go-map iteration); listeners that need a deterministic order must sort what they receive. Listener panics during replay are recovered, same as live dispatch. The returned Unsubscribe removes the listener.
Lock strategy (sharded):
- flush=false: holds every shard's RLock during set.add so a concurrent writer can't snapshot listeners (fireUnderLock) and dispatch before fn is registered. We need RLocks on ALL shards because a writer on any shard runs fireUnderLock under its own shard's write lock — without holding every shard's RLock here, a concurrent writer on shard X could snapshot listeners (not yet including fn), release shard X's lock, and dispatch while fn is being added. The cost (one rLockAll per AddListener) is paid once at controller wiring time, not on the dispatch hot path.
- flush=true: holds every shard's write lock across (register + snapshot) so the pair is atomic with respect to writers. Without the write locks, a concurrent AddObject on some shard could update its map, snapshot listeners (already including fn via set.add), and dispatch — while this goroutine replays the same object from the post-update map snapshot, double-firing fn. Exactly-one delivery is the invariant; the canonical-order lockAll prevents deadlock between two simultaneous flush=true calls.
func (*Store) AddObject ¶
func (s *Store) AddObject(obj manifest.BaseManifest)
AddObject inserts a manifest. Re-adding an equal object is a no-op. Re-adding a different object overwrites the existing entry AND still dispatches an ObjectAdded event (so newer values propagate).
The equal-prev fast path reads under RLock and runs reflect.DeepEqual outside the write lock. Reflection on a typed CR is the slowest operation in this method; keeping it off the write path means re-emits (a common pattern when a parent KS re-renders unchanged children) don't block concurrent readers.
Dedup is cheapest-first, mirroring SetArtifact: a pointer-identity check (prev == obj) ahead of reflect.DeepEqual. Every BaseManifest implementer is a pointer receiver, so the interface holds a pointer and == is a safe, allocation-free pointer compare that never panics. A parent KS that re-emits the SAME child pointer on an unchanged re-render (the dominant re-emit shape) then skips reflection entirely — reflect.DeepEqual is ~70% of this method's cost on a typed CR with a populated Data map, and stored objects are immutable by contract so same-pointer implies content-equal.
func (*Store) AddObjects ¶
func (s *Store) AddObjects(objs []manifest.BaseManifest)
AddObjects inserts multiple manifests, then dispatches their events after every changed object is visible in the store. Use this when a render emits a coherent sibling set whose listeners need the whole set before reacting.
Sharded layout: each input object routes to its Kind's shard. We group writes by shard so each shard locks at most once, then drop every lock before dispatching — the "writes visible before any listener fires" guarantee holds within each shard, and listeners running outside any shard lock can see writes that landed in other shards before this call returned.
func (*Store) AddRendered ¶
func (s *Store) AddRendered(obj manifest.BaseManifest)
AddRendered records a manifest produced by helm/kustomize rendering. Compared to AddObject it skips the reflect.DeepEqual dedup check — rendered docs change on every render and the dedup would never hit. Listener dispatch is unconditional: the listener-contract gap that previously existed (silent miss for any future kind with listeners, e.g. watching rendered Secret docs for valuesFrom invalidation) is closed by routing every write through fireUnderLock. The empty-set fast path in listenerSet.snapshot keeps the common "no listeners for this kind" case at one mutex pair, no allocations.
func (*Store) AddWarning ¶ added in v0.4.6
AddWarning records a render advisory for Result.Warnings. Identical warnings (same resource, category, message, and detail) collapse into one entry whose Count is incremented — so a producer that fires the same advisory N times (e.g. across retries) surfaces once with "(×N)". Producers leave Count unset (the zero value normalizes to 1); it exists for the collector's tally. Concurrency-safe via a dedicated mutex, off the shard locks' hot path.
func (*Store) BlockedBy ¶ added in v0.4.3
func (s *Store) BlockedBy(id manifest.NamedResource) []manifest.NamedResource
BlockedBy returns the immediate dependencies that blocked id (set via SetBlocked), or nil when id failed on its own — i.e. a primary failure.
func (*Store) DeleteObject ¶
func (s *Store) DeleteObject(id manifest.NamedResource) bool
DeleteObject removes the object stored under id. Returns whether anything was removed. Status and artifact entries (if any) are also dropped so a re-add under a different id starts clean.
func (*Store) FailedResources ¶
func (s *Store) FailedResources() map[manifest.NamedResource]StatusInfo
FailedResources returns every (id, info) currently in Failed state for an object that is also present in the store. The "also present" gate filters phantom entries: DeleteObject wipes the conditions map, but a SetCondition that lands AFTER the delete (e.g. a slow reconcile goroutine writing back its terminal status) would otherwise resurrect a failure entry for an id that no longer exists. Conditioning on s.objects ensures FailedResources never reports a failure for a resource the orchestrator has explicitly removed.
Iterating conditions rather than objects is faster when most objects don't have conditions yet (common during bootstrap) — avoids the secondary map lookup for every un-reconciled object.
Cross-shard read: walks every shard's conditions/objects. Each shard is RLocked independently in canonical (ascending) order via rLockAll because conditions and objects for a single id always share the same shard, so per-shard reads suffice to detect the phantom — no global lock needed beyond the canonical-order ordering itself, which prevents lockAll-vs-rLockAll deadlocks.
func (*Store) GetArtifact ¶
func (s *Store) GetArtifact(id manifest.NamedResource) Artifact
GetArtifact returns the artifact for id, or nil if none was set.
func (*Store) GetByName ¶
func (s *Store) GetByName(kind, namespace, name string) manifest.BaseManifest
GetByName returns the object matching (kind, namespace, name), or nil when none is present. Hot-path callers (valuesFrom expansion, source resolution) should prefer this over filtering ListObjects.
func (*Store) GetConditions ¶
func (s *Store) GetConditions(id manifest.NamedResource) []Condition
GetConditions returns a copy of id's condition list. Empty for unknown ids.
func (*Store) GetObject ¶
func (s *Store) GetObject(id manifest.NamedResource) manifest.BaseManifest
GetObject returns the manifest for id, or nil if not present.
func (*Store) GetStatus ¶
func (s *Store) GetStatus(id manifest.NamedResource) (StatusInfo, bool)
GetStatus returns the Ready-derived StatusInfo for id and whether a Ready condition was present.
func (*Store) ListObjects ¶
func (s *Store) ListObjects(kind string) []manifest.BaseManifest
ListObjects returns every stored manifest, optionally filtered by kind, sorted deterministically by (Kind, Namespace, Name). An empty kind matches all objects. The byName index is hit directly when kind is set — O(K) instead of O(N) — which matters on the orchestrator's per-pass list calls when one kind dominates the store (HelmReleases are typically the bulk).
Sharded layout: when kind is set, the lookup routes to exactly one shard. When kind is empty, ALL shards are read-locked in canonical (ascending) order to capture a consistent global snapshot; the caller pays an N-shard read-lock cost only when truly iterating everything.
Deterministic order is a correctness requirement: ownership tie-breaking in change/ownership.go (and any future tie-break) must produce the same winner across runs. Go map iteration is randomized, so without sorting the same file can be attributed to different KS owners on different runs.
func (*Store) OnArtifact ¶
func (s *Store) OnArtifact(fn func(manifest.NamedResource, Artifact), replay bool) Unsubscribe
OnArtifact registers fn for every EventArtifactUpdated with the typed Artifact payload.
func (*Store) OnObject ¶
func (s *Store) OnObject(fn func(manifest.NamedResource, manifest.BaseManifest), replay bool) Unsubscribe
OnObject registers fn for every EventObjectAdded with a typed payload. When replay is true, fn fires synchronously for every object already in the store before returning — useful when wiring a UI mid-render. Listeners MUST NOT block the dispatching goroutine.
func (*Store) OnStatus ¶
func (s *Store) OnStatus(fn func(manifest.NamedResource, StatusInfo), replay bool) Unsubscribe
OnStatus registers fn for every EventStatusUpdated with the typed StatusInfo payload. Same blocking / replay semantics as OnObject.
func (*Store) Refire ¶
func (s *Store) Refire(id manifest.NamedResource)
Refire resets the resource's Ready condition to Pending and then dispatches EventObjectAdded for the existing object at id. Used to wake up listeners that short-circuited the first time — e.g. source controllers that PreGate-skipped a source whose consumer joined the change-filter keep set only at runtime (issue #260).
The status reset is load-bearing, not cosmetic. Without it, a consumer's depwait may read the stale Ready/"unchanged" status the initial PreGate skip wrote, return immediately, and race ahead of the queued re-reconcile — producing an "artifact not found" failure while the actual fetch is still in flight. UpdateStatus completes before EventObjectAdded fires, so any depwait that reads status between the two events still sees Pending.
No-op when id is not in the store. The object existence check, the status reset, and the event capture all run under one sh.mu acquisition so a concurrent DeleteObject can't slip in and leave the listener dispatching against a stale object or the status map resurrected with a phantom Pending entry. Single shard — both objects and conditions for id live in the same shard since both are keyed by id.Kind.
func (*Store) SetArtifact ¶
func (s *Store) SetArtifact(id manifest.NamedResource, artifact Artifact)
SetArtifact stores an artifact for id and dispatches an ArtifactUpdated event. Re-setting with a content-equal value is a no-op.
Equality cascade, cheapest-first:
- Pointer identity (prev == artifact): trivially equal, no-op. Source-controller refresh loops cache their own SourceArtifact and re-publish the same pointer on every tick; the short- circuit avoids reflection entirely for that case (~3× faster than the legacy reflect.DeepEqual-on-aliased-pointers fast path, which still walks struct headers).
- reflect.DeepEqual fallback for distinct-pointer dedup. Phase 1 evaluated hashing here (FNV64a via both json.Marshal and a hand-rolled walker) and benched it against DeepEqual on the realistic KS / HR re-emit shape (20-200 docs, fresh maps every reconcile, no aliased sub-pointers). Result: DeepEqual was ≥2× faster than either hash variant because the FNV walker pays full per-leaf write cost on every call, while DeepEqual short-circuits on the first leaf mismatch and is hand-tuned by the runtime for nested map / slice shapes. The plan acknowledged this outcome ("if JSON encode is slower than DeepEqual on the artifact size you have, this is a wash. Bench it."). The pointer-identity short-circuit is the residual win.
func (*Store) SetBlocked ¶ added in v0.4.3
func (s *Store) SetBlocked(id manifest.NamedResource, deps []manifest.NamedResource)
SetBlocked records that id failed only because the given dependencies failed or were missing — the structured form of a DependencyFailedError, captured at the failure sink. The failure report uses it to group derived failures under their root cause instead of reprinting nested "dependencies failed: …" chains. A defensive copy is stored so the caller's slice can't mutate store state.
func (*Store) SetCondition ¶
func (s *Store) SetCondition(id manifest.NamedResource, cond Condition)
SetCondition upserts cond into the resource's condition list keyed by cond.Type. Dispatches a StatusUpdated event with the StatusInfo rollup (derived from Ready) on every observable condition change, not just Ready transitions. Listeners that only care about Ready can filter on the StatusInfo payload; CEL-based ReadyExpr watchers need the broader notification so a Healthy condition flip (for example) wakes them.
An identical re-write of the same condition is a no-op (no event).
SetCondition does NOT require the object to be in the store — callers may legitimately establish initial state before AddObject lands (e.g. tests). The FailedResources rollup filters phantoms by intersecting against the object map at read time.
func (*Store) Snapshot ¶
func (s *Store) Snapshot(id manifest.NamedResource) (manifest.BaseManifest, []Condition)
Snapshot returns the manifest and conditions for id captured under a single RLock acquisition. Use this when a caller needs a consistent view of both — e.g. depwait's CEL projection reads status.conditions AND metadata.labels in one expression, and independent GetObject / GetConditions calls can mix a fresh object snapshot with stale conditions (or vice versa) if a writer interleaves. Returns nil object and nil conditions for unknown ids.
Single-shard: both objects[id] and conditions[id] live in the same shard since both are keyed by id.Kind.
func (*Store) UpdateStatus ¶
func (s *Store) UpdateStatus(id manifest.NamedResource, status Status, message string)
UpdateStatus records a Ready-condition transition and dispatches a StatusUpdated event when the StatusInfo rollup changes. Internally the (status, message) pair is stored as a metav1.Condition so future callers (ReadyExpr CEL, SOPS detection, healthChecks) can see the rich state via GetConditions.
type Unsubscribe ¶
type Unsubscribe func()
Unsubscribe removes a listener. It is safe to call from inside the listener.