typeset

package
v0.41.0 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package typeset is GitOps Reverser's single decision surface for "is this resource type followable, and if not, what is the one reason it is not?".

It is the greenfield model from docs/spec/type-followability.md. Every served type carries one TypeRecord with one Followability: a verdict, a one-line summary, and the full funnel-ordered list of requirement checks. That single value replaces the old split between a "requirements" table and a "health conditions" table — the failing check is the explanation.

The package is deliberately a leaf: it depends only on apimachinery schema, never on a Kubernetes client, controller runtime, or the watch manager. That lets both the live cluster path (internal/watch) and the no-cluster manifest analyzer share one decision surface and one reason-code vocabulary.

Index

Constants

View Source
const RemovalGrace = 60 * time.Second

RemovalGrace is how long a previously-live type that stops being observed is held as retained before it leaves the live set. It is product safety, not tuning, so it is a fixed constant: it stops a short discovery blink from turning into a large Git sweep. See the "Live set and the 60-second grace" section of the design.

View Source
const ScaleSourceBuiltinRegistry = "builtin-registry"

ScaleSourceBuiltinRegistry labels a ScaleBinding resolved from the built-in registry below, so it reads identically to a CRD-sourced binding for the writer.

View Source
const SettleWindow = 5 * time.Second

SettleWindow is how long a type must stay continuously followable before the registry emits a TypeActivated for it. It governs ACTIVATION, not removal: a flapping or just-appeared type does not drive a per-type reconcile on a state that is about to change again. Like RemovalGrace it is product safety, not tuning, so it is a fixed constant — but it is deliberately short where the grace is long. See docs/spec/type-lifecycle-events-and-wobble-settling.md (Proposal 2).

Variables

This section is empty.

Functions

This section is empty.

Types

type Check

type Check struct {
	Requirement Requirement
	Result      Result
	Reason      Reason // empty on pass/skip; otherwise the single reason code
	Detail      string // bounded human detail, e.g. "patch"
}

Check is one requirement's evaluated result in the funnel.

func (Check) Failed

func (c Check) Failed() bool

Failed reports whether the check is a hard fail.

type Confidence

type Confidence string

Confidence records how strongly the origin classification is held: observed from direct CRD/APIService evidence, inferred from the group/version alone, or unknown.

const (
	// ConfidenceObserved is backed by direct evidence (a CRD or APIService object).
	ConfidenceObserved Confidence = "observed"
	// ConfidenceInferred is derived from the group/version shape, without an object.
	ConfidenceInferred Confidence = "inferred"
	// ConfidenceUnknown is no basis for classification.
	ConfidenceUnknown Confidence = "unknown"
)

type Entry

type Entry struct {
	GVK          schema.GroupVersionKind
	GVR          schema.GroupVersionResource
	Namespaced   bool
	Verbs        []string
	Preferred    bool
	Subresource  bool
	Allowed      bool   // product policy permits mirroring this resource
	PolicyReason string // why it is not allowed, when Allowed is false
	Degraded     bool   // the backing group/version is currently degraded
	// Sensitive reports whether this resource must use the encrypted Git write path.
	// It is a startup-known policy fact, applied by the entry builder (the catalog
	// applies the configured SensitiveResourcePolicy), not inferred inside typeset.
	Sensitive bool
}

Entry is one served API resource's raw facts — the neutral input to the scan. Both the live discovery catalog and a serialized snapshot convert their resources to this shape, so observation-building (identity uniqueness, origin, scale, policy) lives in exactly one place and the live and fixture paths agree on every verdict.

type EventKind

type EventKind string

EventKind names a per-type lifecycle transition the registry emits. The events are transitions between the existing verdicts (no new verdict vocabulary): the registry is the single component that owns the decision, so it computes the transition once and names it, instead of every consumer re-detecting the same edge by diffing tables.

const (
	// TypeActivated fires when a type has been continuously Followable for the settle
	// window: it is healthy and stable, so M12 may schedule its (re)reconcile.
	TypeActivated EventKind = "TypeActivated"
	// TypeWobbling fires on Followable -> Retained: a transient unserved blip. Do NOT sweep;
	// postpone the type's reconcile and keep its informers up until it settles or drops.
	TypeWobbling EventKind = "TypeWobbling"
	// TypeRecovered fires on Retained -> Followable: the wobble resolved. It collapses into
	// a fresh TypeActivated once the settle window elapses again.
	TypeRecovered EventKind = "TypeRecovered"
	// TypeRemoved fires when a previously-live type leaves the live set because its removal
	// grace elapsed (absence-expired): it is genuinely gone, so M12 sweeps THIS type only.
	TypeRemoved EventKind = "TypeRemoved"
	// TypeRefused fires when a previously-live type fails a permanent check: never watch it,
	// drop its informers, surface it in status.
	TypeRefused EventKind = "TypeRefused"
)

type Followability

type Followability struct {
	Verdict Verdict
	Summary string // one line, e.g. "not followable — missing required verb: patch"
	Checks  []Check
}

Followability answers "can I act on this, and why not?" in one value.

func Evaluate

func Evaluate(obs Observation) Followability

Evaluate reduces one Observation into its Followability: every requirement check in funnel order, plus the mechanical verdict and one-line summary. It is the single decision point — there is no second "inspect" pass.

func (Followability) Check

func (f Followability) Check(req Requirement) (Check, bool)

Check returns the evaluated check for a requirement, if present.

func (Followability) FirstFailure

func (f Followability) FirstFailure() (Check, bool)

FirstFailure returns the first failed check in funnel order, if any.

type GitTargetRef

type GitTargetRef string

GitTargetRef identifies the GitTarget that asserts demand. The Materializer treats it as an opaque key — whether the caller passes a UID, a namespaced name, or a resolved internal id is its choice (the L-1 open question); the leaf never interprets it.

type Identity

type Identity struct {
	GVK   schema.GroupVersionKind
	GVR   schema.GroupVersionResource
	Scope Scope
}

Identity is the one true name of a type. For a followable type the GVK <-> GVR bijection is closed, so GVK and GVR always round-trip.

type LifecycleEvent

type LifecycleEvent struct {
	Kind       EventKind
	GVK        schema.GroupVersionKind
	GVR        schema.GroupVersionResource
	From       Verdict
	To         Verdict
	Reason     Reason
	Generation uint64
	At         time.Time
}

LifecycleEvent is one named transition between verdicts for a single type. It carries the identity, the verdicts it crossed, the single machine-readable reason for a failure, the scan generation the transition was computed at, and the time it was observed.

type Lookup

type Lookup interface {
	Ready() bool
	ByGVK(gvk schema.GroupVersionKind) (TypeRecord, bool)
}

Lookup is the minimal followability surface every consumer reads: "is this type followable, and what is its resolved identity?". It replaces the old mapping.ResourceMapper contract — there is one notion of followable, and callers gate on TypeRecord.Followable() rather than interpreting a status vocabulary.

Ready reports whether the backing scan holds trusted data. A not-ready Lookup is the "structure-only / no API source" mode: it cannot judge followability, so a consumer must not draw a watched/unwatched (or destructive) conclusion from it.

type MaterializationEvent

type MaterializationEvent struct {
	Kind  MaterializationEventKind
	GVR   schema.GroupVersionResource
	Phase Phase
	// RV is the checkpoint revision the type serves after the transition: the new rv on
	// TypeSynced, the prior (still-served) rv during a re-anchor, or empty when no
	// checkpoint exists.
	RV string
	At time.Time
}

MaterializationEvent is one named transition on the materialization axis for a single type. It mirrors LifecycleEvent's shape: identity, the new phase it lands in, the checkpoint revision it now serves (set on TypeSynced, otherwise the prior checkpoint or empty), and the time it was observed.

type MaterializationEventKind

type MaterializationEventKind string

MaterializationEventKind names a per-type transition the Materializer emits on the materialization axis. It mirrors the followability EventKind vocabulary (lifecycle.go) but is a distinct, second axis: these events report demand-driven checkpoint progress, never a followability decision. The Materializer owns the transition, computes it once, and names it, so a driver never re-detects the edge by diffing phase tables.

const (
	// SyncRequested fires when a claimed, followable type needs a (re)sync the driver
	// should pick up: Dormant -> Requested on the first claim (T1), or a still-claimed
	// Synced type flagged for a periodic re-anchor by the sweep (T4). The driver learns
	// what to sync from this event or from PendingSyncs.
	SyncRequested MaterializationEventKind = "SyncRequested"
	// SyncStarted fires when the driver begins a sync — a streaming-list watch, LIST only
	// as fallback: Requested -> Syncing (first sync) or Synced -> Resyncing (re-anchor). A
	// re-anchor keeps serving the prior checkpoint until it swaps in (DEC-L2 / L5).
	SyncStarted MaterializationEventKind = "SyncStarted"
	// TypeSynced fires when a checkpoint lands: Syncing/Resyncing -> Synced at rv R. It is
	// the completion handshake (L4) — the driver wakes every GitTarget claiming the type.
	TypeSynced MaterializationEventKind = "TypeSynced"
	// SyncFailed fires when a sync errors: Syncing/Resyncing -> Failing. A first-sync
	// failure leaves no checkpoint (consumers hold); a re-anchor failure keeps the prior
	// checkpoint served (L5). Per-type isolation (L6): siblings are unaffected.
	SyncFailed MaterializationEventKind = "SyncFailed"
	// Released fires when a checkpoint is dropped: Synced/Requested/Failing -> Dormant.
	// Either the sweep found no live claim (demand GC, T5) or a followability event
	// force-released the type (TypeRemoved/TypeRefused). The claim itself may survive.
	Released MaterializationEventKind = "Released"
	// Unclaimed fires when a type's LAST claim is withdrawn — the sweep's lease GC removed the
	// final claimant (>=1 -> 0). It is the demand-gate CLOSE edge: the driver maps it to
	// gate.Unrequire, so a type stops being mirrored once no GitTarget wants it. It is deliberately
	// distinct from Released, which is a CHECKPOINT drop: a followability wobble (TypeRemoved) force-
	// releases the checkpoint while the claim survives, and such a type must keep being mirrored — so
	// the gate flag tracks the claim (Unclaimed), never the checkpoint (Released). The open edge has
	// no event: the watch layer Requires synchronously on Declare (see DeclareForGitTarget).
	Unclaimed MaterializationEventKind = "Unclaimed"
)

type MaterializationObserver

type MaterializationObserver func(MaterializationEvent)

MaterializationObserver receives materialization events from the Materializer. Like a followability Observer it is invoked after the new phase is published, serialized with other operations, so an observer may read the Materializer but must NOT block (a real consumer enqueues and returns). See Materializer.Subscribe.

type Materializer

type Materializer struct {
	// contains filtered or unexported fields
}

Materializer owns the claim table keyed (GitTargetRef, GVR) and the per-type materialization phase. It is safe for concurrent readers and callers: mu guards the state, dispatchMu serializes whole operations and their event dispatch so observers see transitions in order and never interleaved — the same discipline the Registry uses.

func NewMaterializer

func NewMaterializer() *Materializer

NewMaterializer builds an empty Materializer with a real clock.

func (*Materializer) BeginSync

func (m *Materializer) BeginSync(gvr schema.GroupVersionResource) bool

BeginSync advances a type into a sync the driver is starting (T2/T4): Requested -> Syncing for a first sync, Synced (with a pending re-anchor) -> Resyncing, or a Failing retry into whichever of the two its checkpoint state implies. It reports whether a sync actually started, so the driver only opens its streaming-list watch when the phase agreed. A frozen (wobbling) type never starts a sync — a fill against an unserved type is untrustworthy (DEC-L4).

func (*Materializer) Checkpoint

func (m *Materializer) Checkpoint(gvr schema.GroupVersionResource) (string, bool)

Checkpoint reports the revision of the checkpoint a type currently serves and whether one exists. A Synced type serves its current rv; a Resyncing or Failing type still serves the prior rv (L5); every other phase serves nothing.

func (*Materializer) Claimants

Claimants returns, sorted, the GitTargets that currently hold a claim on a type — including stale (un-renewed) claims not yet GC'd by a sweep. It is the per-type demand view (L10) and the seam the L-6 visibility step builds on.

func (*Materializer) Declare

func (m *Materializer) Declare(ref GitTargetRef, desired []schema.GroupVersionResource)

Declare records a GitTarget's entire desired type-set as a self-renewing lease (DEC-L3). It is claim + renew + implicit withdrawal in one idempotent call: every GVR in desired is (re)claimed at now, and any GVR the GitTarget previously declared but omits this time is simply left un-renewed and ages out at the next sweep. Re-sending the same set is a no-op beyond renewal, so it is safe to call every reconcile.

A claim is recorded regardless of followability (DEC-L9): claiming a refused or not-yet-discovered type is allowed and drives a sync the moment it becomes followable.

func (*Materializer) Inventory

func (m *Materializer) Inventory() []TypeMaterialization

Inventory returns, sorted by GVR, the materialization status of every type the Materializer tracks (claimed or ever-followable). It is the per-type visibility query (L10) the watch layer turns into metrics and a bounded per-GitTarget status roll-up. It is bounded by the catalog (~hundreds), never by demand history.

func (*Materializer) OnLifecycleEvent

func (m *Materializer) OnLifecycleEvent(ev LifecycleEvent)

OnLifecycleEvent is the followability gate (DEC-L4): it is an Observer the future driver wires onto Registry.Subscribe, so the Materializer consumes the same lifecycle vocabulary instead of inventing its own. It never re-derives followability — it only translates a transition into its effect on the materialization axis.

func (*Materializer) PendingSyncs

func (m *Materializer) PendingSyncs() []schema.GroupVersionResource

PendingSyncs returns, sorted, the types that need the driver to (re)start a sync: a Requested first sync, a Failing retry, or a Synced type the sweep flagged for a re-anchor. A frozen (wobbling) type is never pending. This is the driver's "what needs a (re)sync?" query (L4), the pull complement to the SyncRequested push.

func (*Materializer) Phase

Phase reports a type's current materialization phase. The bool is false for a type the Materializer has never seen a claim or lifecycle event for (implicitly Dormant).

func (*Materializer) RequestResync

func (m *Materializer) RequestResync(gvr schema.GroupVersionResource) bool

RequestResync flags a claimed, Synced, unfrozen type for an immediate re-anchor and emits SyncRequested — the same transition the periodic sweep applies, available on demand. The ingestion layer uses it as the late-event nudge: an audit event whose RV arrived below its type stream's high-water is diverted (rejected from the main stream) and never replayed, so only a fresh checkpoint folds its effect in; without the nudge the next periodic sweep (~1h) is the backstop and the mirror serves stale state until then. It reports whether a resync was actually requested; any other phase is a no-op (a sync already in flight or pending re-anchor will land at a revision at or above the late event's, which already covers it).

func (*Materializer) RestoreSynced

func (m *Materializer) RestoreSynced(gvr schema.GroupVersionResource, rv string)

RestoreSynced rebuilds a type's materialization phase from the durable checkpoint state on boot (DEC-L6): it marks the type Synced at rv WITHOUT a fill, so a restart resumes serving a standing checkpoint instead of re-listing the world. It is the in-memory half of the HA seam — the authoritative phase/rv lives in Redis (:objects:state); the watch layer reads it and replays it here, keeping this leaf free of any client. followable is set true so a later periodic re-anchor can run and a subsequent TypeActivated for an already-Synced type is a no-op. It emits no event (a silent boot restore is not a transition a driver should act on) and is a no-op for an empty rv. The caller must invoke it at boot, before the first followability Update and before the sweep/driver start, so it never races a live transition.

func (*Materializer) Subscribe

func (m *Materializer) Subscribe(obs MaterializationObserver)

Subscribe registers an observer for every materialization event from subsequent operations. Observers are invoked outside the Materializer's read/write lock (so they may read it) but under its dispatch serialization, so a slow observer stalls the caller — keep them non-blocking.

func (*Materializer) Sweep

func (m *Materializer) Sweep()

Sweep is the one periodic pass that does both jobs (DEC-L5): it first GCs leases that were not renewed since the previous sweep, then for each type branches on whether a live claim remains — re-anchor the still-wanted, release the no-longer-wanted. The caller drives the cadence (the ~1h interval), so the release grace is exactly one interval with no dedicated constant. A frozen (wobbling) type is swept over: nothing is re-synced or released against an unserved type.

func (*Materializer) SyncFailed

func (m *Materializer) SyncFailed(gvr schema.GroupVersionResource)

SyncFailed records a sync error: Syncing/Resyncing -> Failing. The checkpointRV is left untouched, so a first-sync failure serves nothing (consumers hold) while a re-anchor failure keeps serving the prior checkpoint (L5). The type re-surfaces in PendingSyncs for the driver to retry after its backoff. It is a no-op unless a sync was in flight.

func (*Materializer) SyncSucceeded

func (m *Materializer) SyncSucceeded(gvr schema.GroupVersionResource, rv string)

SyncSucceeded lands a checkpoint at rv: Syncing/Resyncing -> Synced. rv is the sync's pinned revision — the initial-events-end bookmark resourceVersion of the streaming-list watch (or the LIST revision on the fallback path). On a re-anchor it swaps the served revision to rv (L5). It is a no-op unless a sync was in flight.

type Observation

type Observation struct {
	Identity Identity
	Origin   Origin

	Preferred    bool
	Verbs        []string
	Subresources Subresources

	// served / trusted / stable facts.
	Served          bool // discovery currently serves this as a top-level resource
	SubresourceOnly bool // the kind is served only as a subresource
	Trusted         bool // backing group/version came from trusted, non-degraded discovery
	CatalogReady    bool // the catalog has accepted any trusted discovery data
	AbsenceExpired  bool // the type is mid-disappearance and the removal grace has elapsed

	// identity facts.
	GVKUnique         bool   // exactly one GVR serves this GVK
	GVRUnique         bool   // this GVR resolves back to exactly one Kind
	GVKConflictDetail string // e.g. "widgets, widgetz" when GVKUnique is false
	GVRConflictDetail string // e.g. "Widget, Gadget" when GVRUnique is false

	// policy facts (computed by the registry from group/resource).
	Denied             bool
	DenyDetail         string
	Sensitive          bool
	SensitiveSupported bool
}

Observation is the raw per-type facts the funnel reduces into a Followability. It is built by the scan (discovery + CRD/APIService evidence + the built-in scale registry + product policy) and carries every fact a check needs, so Evaluate is a pure function with no side inputs. The registry owns how observations are built; the funnel owns only how they are judged.

func ObservationsFromEntries

func ObservationsFromEntries(entries []Entry, catalogReady bool) []Observation

ObservationsFromEntries projects served entries into one Observation per top-level type — the "Scan -> Observation" reduction. Subresources are folded into their parent's record, never emitted as their own observation. catalogReady reports whether the backing scan holds trusted data, feeding the trusted requirement's catalog-unavailable distinction.

type Observer

type Observer func(LifecycleEvent)

Observer receives lifecycle events from the registry. It is invoked by Update after the new records are published, in generation order and serialized with other updates, so an observer may read the registry but must NOT block the updater (a real consumer enqueues the event and returns). See Registry.Subscribe.

type Origin

type Origin struct {
	Kind       OriginKind
	Confidence Confidence
	// Evidence is bounded human detail, e.g. crontabs.stable.example.com.
	Evidence string
}

Origin is the provenance of a served type plus how strongly it is held.

type OriginKind

type OriginKind string

OriginKind classifies where a served type comes from.

const (
	// OriginBuiltin is a core or built-in Kubernetes API group/version.
	OriginBuiltin OriginKind = "builtin"
	// OriginCRD is a type backed by a CustomResourceDefinition.
	OriginCRD OriginKind = "crd"
	// OriginAggregated is a type served by an aggregated API server (APIService).
	OriginAggregated OriginKind = "aggregated"
	// OriginUnknown is a served type the scan could not classify; it fails the
	// origin requirement.
	OriginUnknown OriginKind = "unknown"
)

type Phase

type Phase string

Phase is where a type sits on the materialization axis. It is orthogonal to the followability Verdict: a Followable type may be Dormant (unclaimed) and a claimed type may be Dormant (not yet followable). See the §3 table of the design doc.

const (
	// PhaseDormant is the resting state: no live claim, or not yet followable. No
	// checkpoint, not reconcile-serviceable.
	PhaseDormant Phase = "Dormant"
	// PhaseRequested has ≥1 claim and is followable, queued for a first sync that has not
	// started. No checkpoint yet.
	PhaseRequested Phase = "Requested"
	// PhaseSyncing has its first checkpoint sync (a consistent LIST today; a streaming-list watch
	// once Rec 6 lands) in flight. Still nothing to serve, so consumers hold (L4).
	PhaseSyncing Phase = "Syncing"
	// PhaseSynced has a checkpoint at rv R and is reconcile-serviceable.
	PhaseSynced Phase = "Synced"
	// PhaseResyncing has a periodic re-anchor sync in flight; the PRIOR checkpoint is
	// still served until the new one swaps in (L5).
	PhaseResyncing Phase = "Resyncing"
	// PhaseFailing had its last sync error and is awaiting a backoff retry. A prior
	// checkpoint (if any) keeps serving (L5/L6).
	PhaseFailing Phase = "Failing"
)

type Reason

type Reason string

Reason is the single machine-readable cause of a failed check. It is the one vocabulary used everywhere a type is turned away — lookups, the live-set report, and operator status — so "why isn't this picked up?" always has the same answer.

const (
	// ReasonNotServed — trusted discovery has no top-level resource for this kind.
	ReasonNotServed Reason = "not-served"
	// ReasonSubresourceOnly — the kind is served only as a subresource.
	ReasonSubresourceOnly Reason = "subresource-only"
	// ReasonDiscoveryDegraded — discovery currently fails for the backing group/version.
	ReasonDiscoveryDegraded Reason = "discovery-degraded"
	// ReasonCatalogUnavailable — no trusted catalog data exists yet.
	ReasonCatalogUnavailable Reason = "catalog-unavailable"
	// ReasonAbsenceExpired — the type disappeared and the removal grace has elapsed.
	ReasonAbsenceExpired Reason = "absence-expired"
	// ReasonGVKNotUnique — the GVK is served by more than one GVR.
	ReasonGVKNotUnique Reason = "gvk-not-unique"
	// ReasonGVRNotUnique — the GVR resolves to more than one Kind.
	ReasonGVRNotUnique Reason = "gvr-not-unique"
	// ReasonScopeUnknown — discovery did not establish a namespaced/cluster scope.
	ReasonScopeUnknown Reason = "scope-unknown"
	// ReasonMissingVerb — discovery does not advertise a required verb (Detail names it).
	ReasonMissingVerb Reason = "missing-verb"
	// ReasonOriginUnknown — the served type could not be classified.
	ReasonOriginUnknown Reason = "origin-unknown"
	// ReasonDeniedByPolicy — product policy refuses to mirror this type.
	ReasonDeniedByPolicy Reason = "denied-by-policy"
	// ReasonSensitiveUnsupported — sensitive type without supported write handling.
	ReasonSensitiveUnsupported Reason = "sensitive-unsupported"
	// ReasonScalePathUnresolved — scale is used but the parent replica path is unknown.
	ReasonScalePathUnresolved Reason = "scale-path-unresolved"
)

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry is the single decision surface: it turns observations plus the live-set grace into one TypeRecord per known type, and answers the lookups every consumer reads. It owns identity, the live set, and the removal grace; consumers never recompute followability, they read it.

Additions are fast and removals are slow: a newly observed followable type enters the live set immediately, while a previously-live type that stops being observed is held as retained for RemovalGrace before it drops. The clock is injectable so the grace is deterministic in tests.

Registry is safe for concurrent readers and a single updater.

func NewRegistry

func NewRegistry() *Registry

NewRegistry builds an empty registry with the fixed removal grace and a real clock.

func NewSnapshotRegistry

func NewSnapshotRegistry(snap Snapshot) *Registry

NewSnapshotRegistry builds a Registry from a Snapshot. A NotReady snapshot yields an unpublished (structure-only) registry; otherwise the entries are projected into observations and published at the snapshot's generation.

As a fixture convenience, a top-level entry that declares no Verbs is assumed to advertise the verbs a followable type needs — a snapshot opts a resource in by setting Allowed, and spelling out get/list/watch on every fixture would be noise. Set Verbs explicitly to model a verb-poor resource.

func (*Registry) All

func (r *Registry) All() []TypeRecord

All returns every known record — followable, retained, and refused — for inventory and "why not" views.

func (*Registry) ByGVK

func (r *Registry) ByGVK(gvk schema.GroupVersionKind) (TypeRecord, bool)

ByGVK returns the record for a kind. The bool reports whether the kind is known to the registry at all; callers gate behaviour on record.Followable(). When a kind is served by more than one resource every such record is refused with gvk-not-unique, and the deterministic first (by GVR) is returned.

func (*Registry) ByGVR

ByGVR returns the record for a resource. The bool reports whether the resource is known to the registry at all.

func (*Registry) ByGroupResource

func (r *Registry) ByGroupResource(group, resource string) []TypeRecord

ByGroupResource returns the records — one per served version, sorted by GVR — for a version-less (group, resource) pair, the shape per-type audit-stream keys carry. Existing TypeRecord fields answer everything else a caller needs: the version is Identity.GVR.Version, plus Preferred and Followable(). Records held under the removal grace are included (last-known versions), which is exactly the wobble-friendly answer a stream-key resolution wants during a discovery blink. Deliberately the WHOLE version surface: no version parameters thread through any other signature.

func (*Registry) Followable

func (r *Registry) Followable() []TypeRecord

Followable returns every live record (verdict followable or retained), sorted by identity. It is the inventory the informer set and snapshot scope derive from.

func (*Registry) Generation

func (r *Registry) Generation() uint64

Generation reports the catalog generation the current records were resolved at.

func (*Registry) Ready

func (r *Registry) Ready() bool

Ready reports whether the registry has accepted any observation set.

func (*Registry) Revision

func (r *Registry) Revision() uint64

Revision reports the registry's change-of-decision counter. It bumps whenever the followable membership changes or the scan generation moves, so a consumer that caches a projection of the registry can gate its rebuild on this value and still react to a retention-grace drop that happens without any discovery change.

func (*Registry) Subscribe

func (r *Registry) Subscribe(obs Observer)

Subscribe registers an observer for every lifecycle event from subsequent Updates. Register before the first Update to observe cold-start activations. Observers are invoked outside the registry's read/write lock (so they may read the registry) but under the updater's serialization, so a slow observer stalls the updater — keep them non-blocking.

func (*Registry) Update

func (r *Registry) Update(observations []Observation, generation uint64)

Update replaces the observation set for a new catalog generation and applies the live-set grace. Every observation becomes a record at this generation; a previously-live type missing from the set is re-judged as retained (within the grace) or dropped (once the grace elapses). The first Update marks the registry ready.

func (*Registry) UpdateFromScan

func (r *Registry) UpdateFromScan(scan Scan)

UpdateFromScan publishes one normalized discovery scan, applying the unified "additions fast, removals slow" policy in this one place:

  • entries in the scan become fresh trusted records (additions fast);
  • a previously-known record whose group/version FAILED keeps its last-known facts marked untrusted — VerdictRetained for as long as the error persists (retain-on-error, moved here from the catalog);
  • a previously-known record whose group/version was scanned (or which is gone from a complete scan) and is missing goes absent and rides the EXISTING RemovalGrace — Retained for the grace, then dropped (removals slow, with no instant prune anywhere);
  • on an incomplete scan, an unscanned, non-failed group/version's records are carried unchanged: an incomplete scan judges nothing it did not see.

A record already inside its removal grace is never resurrected by a carry-forward; its absence clock keeps running until the type is freshly observed.

type Requirement

type Requirement string

Requirement is one named check in the followability funnel.

const (
	// RequirementServed — discovery serves this as a top-level resource.
	RequirementServed Requirement = "served"
	// RequirementTrusted — the backing group/version came from trusted discovery.
	RequirementTrusted Requirement = "trusted"
	// RequirementStable — the type is not mid-disappearance, or is inside the grace.
	RequirementStable Requirement = "stable"
	// RequirementIdentity — GVK <-> GVR is 1:1 in both directions.
	RequirementIdentity Requirement = "identity"
	// RequirementScope — the type is known namespaced or cluster-scoped.
	RequirementScope Requirement = "scope"
	// RequirementVerbs — discovery advertises get, list, watch, patch.
	RequirementVerbs Requirement = "verbs"
	// RequirementOrigin — classified builtin, crd, or aggregated with evidence.
	RequirementOrigin Requirement = "origin"
	// RequirementPolicy — product policy permits mirroring this type.
	RequirementPolicy Requirement = "policy"
	// RequirementSensitivity — not sensitive, or sensitivity is supported.
	RequirementSensitivity Requirement = "sensitivity"
	// RequirementScale — scale is unused, or its parent replica path is known.
	RequirementScale Requirement = "scale"
)

type Result

type Result string

Result is one requirement's outcome.

const (
	// ResultPass — the requirement is satisfied.
	ResultPass Result = "pass"
	// ResultFail — the requirement is not satisfied; Reason names the single cause.
	ResultFail Result = "fail"
	// ResultSkip — the requirement does not apply (e.g. scale when scale is unused).
	ResultSkip Result = "skip"
	// ResultUnknown — the requirement could not be assessed.
	ResultUnknown Result = "unknown"
)

type ScaleBinding

type ScaleBinding struct {
	Enabled     bool
	Source      string // discovery | crd | builtin-registry | aggregated | unknown
	ResponseGVK schema.GroupVersionKind

	SpecReplicasPath   string
	StatusReplicasPath string
	SelectorPath       string
	SelectorKind       string // serialized-string | label-selector | unknown

	// Usable is true only when a /scale audit event can be mapped back to a durable
	// parent field. False feeds the scale requirement's scale-path-unresolved reason.
	Usable bool
}

ScaleBinding is the only subresource fact the writer needs: where a /scale mutation lands on the parent's desired state. SpecReplicasPath drives the scale write path; the selector facts are for reporting. See docs/spec/type-followability.md.

func BuiltinScale

func BuiltinScale(group, resource string) (ScaleBinding, bool)

BuiltinScale returns the /scale binding for a currently-served built-in scalable resource identified by API group and plural resource. ok is false for any other resource — a CRD, an aggregated API, or a non-scalable built-in — in which case the scale event must be resolved elsewhere or dropped, never defaulted to .spec.replicas. It is the single source of built-in scale facts, shared by the cluster registry (origin/scale enrichment) and the audit consumer (scale write).

type Scan

type Scan struct {
	// Entries are the resources this scan served, policy-annotated. All are trusted
	// facts: a failed group/version contributes no entries (the registry carries its
	// last-known records forward instead).
	Entries []Entry
	// ScannedGroupVersions are the group/versions this scan returned a (possibly
	// empty) resource list for. A previously-known record of a scanned group/version
	// that is missing from Entries is meaningfully absent — the removal grace judges
	// it — even on an otherwise incomplete scan.
	ScannedGroupVersions []schema.GroupVersion
	// FailedGroupVersions are the group/versions discovery reported as failed
	// (IsGroupDiscoveryFailedError). Their previously-known records are retained with
	// last-known facts, marked untrusted, for as long as the failure persists.
	FailedGroupVersions []schema.GroupVersion
	// Complete reports a scan with no discovery error: only then is a wholly
	// unscanned group/version's disappearance meaningful (and even then it rides the
	// removal grace, never an instant prune).
	Complete bool
	// Generation is the catalog's scan generation — bumped by the caller only when
	// the normalized facts changed, so registry revisions do not churn on steady
	// rescans.
	Generation uint64
}

Scan is one normalized discovery result — the per-scan facts a catalog scan produces, with no judgement attached. The catalog stays a thin normalizer; ALL cross-scan judgement ("additions fast, removals slow": retain-on-error, the removal grace for omissions) is applied by Registry.UpdateFromScan. See docs/spec/typeset-owns-discovery-grace.md.

type Scope

type Scope string

Scope is whether a type is namespaced, cluster-scoped, or not yet known. Unknown feeds the scope requirement's scope-unknown reason; discovery normally resolves it, so Unknown is reserved for synthetic or un-enriched observations.

const (
	// ScopeNamespaced is a namespaced resource (lives inside a namespace).
	ScopeNamespaced Scope = "Namespaced"
	// ScopeCluster is a cluster-scoped resource.
	ScopeCluster Scope = "ClusterScoped"
	// ScopeUnknown is a type whose scope discovery has not established.
	ScopeUnknown Scope = "Unknown"
)

type Snapshot

type Snapshot struct {
	// Entries are the served resources the snapshot declares. Allowed defaults to
	// false on a zero Entry, so fixtures opt resources in explicitly.
	Entries []Entry
	// DegradedGroupVersions mark group/versions whose discovery is modeled as failed;
	// their entries are observed as untrusted (retained rather than freshly followable).
	DegradedGroupVersions []schema.GroupVersion
	// NotReady models a scan with no trusted data yet — the structure-only mode — so
	// the resulting Lookup is never ready and judges nothing.
	NotReady bool
	// Generation is the reported scan generation.
	Generation uint64
}

Snapshot is a serialized, scan-shaped fixture for a non-live Lookup. It is an explicit test/review input, not live discovery: it can model old clusters, partial catalogs, policy exclusions, and ambiguity on purpose, but must not be mistaken for proof about a running cluster.

type StatusFact

type StatusFact struct {
	Enabled bool
}

StatusFact records whether a type exposes a /status subresource. It is reporting only — GitOps Reverser never writes /status — so it carries no write path.

type Subresources

type Subresources struct {
	Status StatusFact
	Scale  ScaleBinding
}

Subresources are folded into the parent record, never followed as their own types.

type TypeMaterialization

type TypeMaterialization struct {
	GVR          schema.GroupVersionResource
	Phase        Phase
	CheckpointRV string
	Followable   bool
	Claimants    []GitTargetRef
}

TypeMaterialization is one type's materialization status for the visibility surface (L10): its phase, the checkpoint revision it serves (empty when none), whether it is currently followable, and the GitTargets claiming it (stale claims included, so a claim on a non-followable type is visible as the claim-vs-refused mismatch).

func (TypeMaterialization) Serviceable

func (t TypeMaterialization) Serviceable() bool

Serviceable reports whether the type currently holds a usable checkpoint and so can answer a reconcile read now. It is true exactly when CheckpointRV is set: a Synced type, a Resyncing type (still serving the prior checkpoint while it refreshes), and a Failing type that has a prior checkpoint. It is false for Dormant/Requested/Syncing and for a Failing type that never landed a first checkpoint. This is the single predicate the status roll-up buckets on, so a periodic re-anchor (Synced→Resyncing→Synced) never flaps the derived liveness signal. See docs/spec/status-conditions-guide.md §3.2.

type TypeRecord

type TypeRecord struct {
	Identity     Identity
	Origin       Origin
	Preferred    bool
	Verbs        []string
	Subresources Subresources
	Sensitive    bool

	Followability Followability
	Generation    uint64
}

TypeRecord is the unit everything passes around. It answers "can I act on this?" and "why not?" in one object, so the safe path and the diagnostic path are the same call.

func (TypeRecord) Followable

func (r TypeRecord) Followable() bool

Followable is the safe-path helper. Most callers never inspect Verdict directly: a followable or retained type is live, everything else is not.

type Verdict

type Verdict string

Verdict is the top-level answer for one type. Every other surface (health level, status condition, "why ignored?" diagnostic) is a rendering of this.

const (
	// VerdictFollowable means every required check passed; the type is in the live set.
	VerdictFollowable Verdict = "followable"
	// VerdictRetained means a transient check (served/trusted) is failing now but the
	// removal grace has not elapsed; the type is still treated as live.
	VerdictRetained Verdict = "retained"
	// VerdictRefused means a permanent check failed; the type will not be followed.
	VerdictRefused Verdict = "refused"
	// VerdictUnknown means the registry could not assess the type (catalog unavailable).
	VerdictUnknown Verdict = "unknown"
)

Jump to

Keyboard shortcuts

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