Skip to content
PtahOperator

Condition reasons

Kubernetes Conditions are the operator’s stable machine interface. Consumers should evaluate the complete type, status, and reason tuple and use observedGeneration to decide whether it describes the current spec. Condition messages are bounded diagnostic text and may become more detailed without an API version change.

The Go API exports every value below as a typed ConditionReason constant. Existing values are never repurposed. Adding a reason is backward compatible; renaming, removing, or changing the meaning of one requires an API compatibility decision.

Reason Stable meaning
Active Reconciliation is not suspended.
ApplyDisabled Policy records plans but forbids Apply.
ApplyOutcomeUnknown Apply failed without proof of whether mutation occurred.
ApplyPending A non-destructive plan is eligible for automatic Apply.
ApprovalRevoked A reserved approval became invalid before dispatch.
ApprovedPlan The exact current approved plan is applying.
ArtifactUnverified No plan is usable because artifact verification did not succeed.
AwaitingApproval The current immutable plan needs a new exact approval.
ConfigurationError Controller-side configuration or declared input is invalid.
ConvergedAfterUnknownOutcome Read-only proof found convergence after an unattributable Apply outcome.
CurrentPlan An approval exactly matches the current immutable plan.
DesiredStateChanged New desired inputs arrived after older work completed.
DestructiveChangesDisabled A destructive plan is blocked by policy.
DigestPinned A requested OCI reference resolved to immutable content.
DispatchCommitted One exact approval was consumed by an Apply dispatch boundary.
ExecutionBindingChanged Evidence or approval belongs to a retired runtime identity.
HistoryAhead The database records a migration the artifact does not carry.
HistoryDirty A failed or interrupted migration run left a revision row behind.
HistoryMatched The revision table and the migration artifact agree, and nothing is pending.
HistoryModified An applied migration no longer matches the file that recorded it.
HistoryOutOfOrder The artifact carries a migration below a version the database has applied.
InputsChanged An operation result was discarded because its desired inputs changed.
InSync Independent read-only planning proved convergence.
JobCompleted Apply exited and independent convergence proof remains pending.
LeaseContinuityLost Database lock ownership was not continuous, so evidence was discarded.
MigrationsPending The migration artifact carries versions the database has not applied.
NoChanges Scoped planning produced no executable statements.
NotRequired Policy allows this non-destructive plan without separate approval.
Observed A database observation completed successfully.
OperationFailed A read-only or mutating operation failed.
OperationInProgress A durable operation claim is active.
OutcomeUnknown Apply attribution is uncertain and only read-only proof may proceed.
Pending Resolved artifact content has not yet been verified.
PlanNoLongerCurrent An approval’s plan binding is no longer current.
PlanReady An exact immutable plan is ready for approval.
PolicyBlocked Destructive-change policy blocks the current plan.
PolicyChanged Verification policy identity or bytes changed.
PolicyRefused Artifact verification policy explicitly refused the artifact.
PolicySatisfied Artifact type and verification policy checks succeeded.
ProofInputsChanged Post-Apply proof will restart from its durable immutable binding.
ProtectedTable The artifact asks for a change to a table spec.policy.protectedTables fences off.
Published Exact plan bytes were committed to immutable storage.
RealmConflict More than one resource claims this database and at least one has not set spec.target.sharedRealm.
RefreshFailed A previously resolved source could not be refreshed.
Refreshing The requested source is being resolved again.
RefreshSuspended Source refresh stopped before dispatch because reconciliation was suspended.
Requested The spec requests suspension.
ResolveFailed No immutable source resolution is available.
Satisfied Current plan approval requirements passed final validation.
ScopedChanges The authoritative managed scope differs from desired state.
ScopedConverged The authoritative managed scope has no changes.
ScopedPlanPending Raw observation completed and scoped planning remains pending.
SourceFreshnessUnknown Currentness cannot be proven because source refresh did not complete.
SourceRefreshPending Dependent currentness is unknown during source refresh.
SourceResolutionUnknown Convergence cannot be evaluated without a resolved source.
SourceUnresolved A plan cannot be produced without a resolved source.
Stale The current plan became stale before Apply.
StaleObservation Database state must be observed again before planning.
StalePlan Ready is false because the plan became stale before Apply.
Succeeded The latest operation cleared the ReconciliationFailed Condition.
SupersededApproval A duplicate approval lost to another approval for the same plan.
SupportedEngine The selected database engine has an implemented operator lifecycle.
Suspended Reconciliation is suspended and no new operation may start.
UnsupportedEngine The selected engine has no implemented operator lifecycle.
VerifyingConvergence Apply completed but independent read-only proof has not.
Waiting The controller is waiting for an approval bound to the current plan.

HistoryDirty and HistoryModified are the two readings of a revision table that a PtahMigration will not act on. Both set Blocked=True, leave Ready false, publish no plan, and dispatch nothing.

The operator never writes to the revision table to clear either one. A dirty row says a run stopped without recording what it did, and a modified version says an applied migration’s file no longer matches what the database recorded for it; in both cases the answer is in the database, not in the resource. The operator keeps reading the history at spec.interval, so a database somebody fixed converges on its own, and a spec edit is not part of the recovery.

Run versioned migrations carries what each state means and which Ptah commands end it.

A migration that failed and committed nothing

Section titled “A migration that failed and committed nothing”

OperationFailed with a run that recorded nothing is the shape of a refusal rather than of a broken statement:

lastRun: outcome Failed
"A migration failed and committed nothing; 0 migrations before it are recorded applied"

On MySQL or MariaDB the usual cause is the transaction mode. Those engines have no transactional DDL, so Ptah will not run a file it cannot witness as one unit and says so before any statement executes. The resource reports the refusal faithfully; what it cannot show you is the sentence, because the container that holds the database URL does not put its output in a status.

Name the mode and the run proceeds:

spec:
policy:
transactionMode: none

Choosing a transaction mode says what that costs — with no transaction to unwind, a file that fails halfway leaves what it already committed.

A run that failed on a statement looks different: migrations before the failing one are recorded applied, and the count in the message is not zero.

ProtectedTable sets Blocked=True and leaves Ready false, publishes no plan, and dispatches nothing. Unlike the destructive-change policy, it has no approval that overrides it: while the table is listed, no plan may change it, and the operator keeps reading at spec.interval without making progress.

Two things end it, both of them spec edits by a person: take the table out of spec.policy.protectedTables, or express the change as a versioned migration instead. Protecting a table says which to choose.

spec.target.engine accepts a bounded engine identifier so GitOps tools can store desired state before that engine is implemented. An unknown value does not cause database or registry access. After all previously dispatched Apply work and its mandatory read-only proof reach a safe boundary, the controller sets all of the following for the current generation:

  • EngineSupported=False with reason UnsupportedEngine;
  • Ready=False with reason UnsupportedEngine;
  • phase=Blocked and status.observedGeneration=metadata.generation.

The same boundary marks DatabaseReachable, DriftDetected, and InSync unknown, makes PlanReady, ApprovalRequired, and Applying false, and prevents new operation Jobs. If an older plan exists, the controller first persists this non-authorizing status, then marks approvals for the schema stale in bounded passes and clears the current plan pointer. An approval CREATE that crossed the status fence is retired on its watch-triggered reconciliation even after that pointer is gone. Changing the spec back to PostgreSQL or MySQL sets EngineSupported=True with reason SupportedEngine and restarts the complete workflow at Resolve.