idempotency

package
v0.0.0-...-2769864 Latest Latest
Warning

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

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

Documentation

Overview

Package idempotency provides two CI-gate checks for the merge/apply convention in AGENTS.md's "Server-side apply: keep applied fields idempotent; put observations in status":

  • CheckApplyIdempotent: a byte-identical re-apply of an SSA payload must be a true no-op, with no resourceVersion bump. Catches a volatile value — time.Now(), a random ID, a recomputed digest — leaking into a field a client server-side-applies.
  • CheckReconcileConverges: a reconciler must stop rewriting an object once its desired state is reached. Catches a controller that unconditionally restamps an observed-at or status field on every pass, producing a reconcile storm.

Each guards a real production bug: `oap agent install` SSA-applying an `oap-source` annotation embedding `installedAt: time.Now()`, fixed by moving the timestamp to controller-owned status; and the guardian AgentSessionGrants controller stamping a fresh ObservedSchemaWrittenAt every reconcile rather than only on the False→True transition, a ~5s-forever storm.

Each check splits into an error-returning core (Check*) and a require-based wrapper (Require*), so this package's OWN tests can assert the core DETECTS a violation — something a t.FailNow-calling helper could never do.

No build tag and no envtest import; the checks need only a client.Client and, for CheckReconcileConverges, a reconcile.Reconciler. Both are nonetheless meaningful ONLY against a real apiserver — see each function's doc.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CheckApplyIdempotent

func CheckApplyIdempotent(ctx context.Context, c client.Client, want client.Object, fieldManager string) error

CheckApplyIdempotent SSA-applies want TWICE under fieldManager and errors if the second apply mutated the live object relative to the first, i.e. bumped resourceVersion — the signature of a volatile value leaking into an applied field.

want is DeepCopy'd before EACH apply. An SSA Patch overwrites the pointer it is given with the server's response, so reusing it for the second apply would re-apply what the FIRST apply produced, never testing whether a fresh construction of "the same" desired state actually diverges.

Any resourceVersion already on want is cleared before each apply: resourceVersion is never a field an apply payload should carry, and a stale one would raise a conflict unrelated to the question being asked.

MUST run against a REAL apiserver. The fake client bumps resourceVersion on every accepted Patch instead of reproducing the apiserver's byte-equality no-op, so against it this check passes vacuously.

func CheckReconcileConverges

func CheckReconcileConverges(ctx context.Context, c client.Client, r reconcile.Reconciler, req reconcile.Request, obj client.Object, maxSteps int) error

CheckReconcileConverges reconciles req against r up to maxSteps times, Get'ing obj's namespaced name after each pass, and returns nil the moment TWO CONSECUTIVE passes leave resourceVersion unchanged — steady state, nothing left to write. It errors if the reconciler never stabilizes: a reconcile storm, where the controller rewrites the object on every pass, typically by restamping a timestamp unconditionally rather than on a real transition.

obj is a prototype only, DeepCopy'd before each Get so the result matches whatever type the caller passed; its contents are ignored.

maxSteps must be >= 2, since two consecutive passes cannot be observed in fewer than two Reconcile calls.

MUST run against a REAL apiserver, for the same reason as CheckApplyIdempotent: convergence means the status Patch computed an empty diff and the apiserver skipped the write, which only etcd3's no-op detection reproduces. The fake client bumps resourceVersion on every accepted Patch, so it reports "never converges" even for a correct controller.

func RequireApplyIdempotent

func RequireApplyIdempotent(t *testing.T, ctx context.Context, c client.Client, want client.Object, fieldManager string)

RequireApplyIdempotent is the require-based wrapper over CheckApplyIdempotent. See that function for the full contract.

func RequireReconcileConverges

func RequireReconcileConverges(t *testing.T, ctx context.Context, c client.Client, r reconcile.Reconciler, req reconcile.Request, obj client.Object, maxSteps int)

RequireReconcileConverges is the require-based wrapper over CheckReconcileConverges for production tests. See CheckReconcileConverges's doc comment for the full contract (real apiserver required, maxSteps >= 2, etc).

Types

This section is empty.

Jump to

Keyboard shortcuts

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