Skip to content
PtahOperator

FAQ

Tags

Questions about the Ptah CLI itself – schema formats, migrations, dialects – live in the FAQ on the Ptah documentation site.

What does the operator actually do, and is it a second Ptah?

Section titled “What does the operator actually do, and is it a second Ptah?”

NoIt is a control plane that converges a database on a desired schema published as an OCI artifact: it resolves the tag to a digest, verifies the artifact, observes the database, publishes a plan, applies the approved plan, and observes again. The operator does not contain Ptah; it runs one as a Job.

We use versioned migrations. Can I point PtahSchema at a migration directory so the operator runs up?

Section titled “We use versioned migrations. Can I point PtahSchema at a migration directory so the operator runs up?”

NoPtahSchema reconciles a desired schema, and no resource applies a migration directory today. Run ptah migrations up against a pinned artifact from your own pipeline or Job; that is a different delivery route, not different content for PtahSchema.

NoIt needs a network route to the target and a namespace-local Secret holding the URL selected by spec.target.urlFrom. The target can be a managed service, a private endpoint, or a database outside Kubernetes. NetworkPolicy, TLS, privileges, backups, and high availability stay with the platform owner. See Operations.

The API is v1alpha1 and the project calls itself an implementation preview until its database end-to-end matrix is green and a release is published. Read Releases and provenance and Ptah compatibility before you decide, and treat an unverified combination as untested.

Ptah’s documentation version selector does not change the operator docs. Is it broken?

Section titled “Ptah’s documentation version selector does not change the operator docs. Is it broken?”

NoThe operator tracks its own releases, and its documentation is the authority on what a given operator version supports. Ptah’s selector does not select an operator version.

Installation fails because three values have no default. Why not ship defaults?

Section titled “Installation fails because three values have no default. Why not ship defaults?”

image.digest, execution.executorImage, and execution.runnerImage are registry manifest digests, and execution.ptahVersion is the identity verified from the executor image’s own provenance. A default would let the chart assign a version identity to a digest nobody measured. All four are recorded in every plan, approval, Job, and applied status.

I upgraded the Ptah CLI. Do I upgrade the operator at the same time?

Section titled “I upgraded the Ptah CLI. Do I upgrade the operator at the same time?”

Do not match version numbers: the two projects release independently, and the CLI you use to build an artifact is a different question from the executor the installation binds. Read the compatibility table, which separates a declared promise, a verified measurement, and an untested combination.

The compatibility table lists nothing for my combination. Does that mean it is incompatible?

Section titled “The compatibility table lists nothing for my combination. Does that mean it is incompatible?”

NoAbsent is neither supported nor refused: a combination no row mentions is untested. A version with nothing verified carries unverifiedReason naming the check that has not run.

Which Kubernetes versions are supported, and how does the window move?

Section titled “Which Kubernetes versions are supported, and how does the window move?”

The supported minor window is defined in Kubernetes support. A change adds the new minor and removes the oldest one atomically, and only after the whole real-cluster matrix succeeds.

Registry authentication fails even though the credentials are right. What is missing?

Section titled “Registry authentication fails even though the credentials are right. What is missing?”

Every registry-authentication Secret must include registry: <host[:port]> matching the OCI client’s effective request authority, which is not always what you typed: a source under oci://docker.io/... needs registry-1.docker.io. A missing, malformed, or mismatched authority stops the Job before any registry request.

I moved the tag and my existing approval stopped working. Why?

Section titled “I moved the tag and my existing approval stopped working. Why?”

Every reconciliation interval resolves the reference again, and a moved tag clears dependent plan and applied evidence before repeating verification and observation. An old approval cannot authorize a plan built from new bytes. Pin a digest when you do not want the reference to move.

My policy requires a pinned digest and a tag that resolved fine was still refused. Why?

Section titled “My policy requires a pinned digest and a tag that resolved fine was still refused. Why?”

Native verification inspects the resolved digest, and require_digest_pin also evaluates the reference you originally requested. A tag, or an implicit latest, is refused even when it resolved; an explicit digest stays eligible. See Operations.

The registry was unreachable and the status went Unknown instead of keeping the last good result. Why?

Section titled “The registry was unreachable and the status went Unknown instead of keeping the last good result. Why?”

Registry failures are fail-closed. The last source, plan, and applied record stay as historical evidence, but they are not a claim about the present, so ArtifactResolved becomes Unknown with reason RefreshFailed and no Verify, Observe, Plan, or Apply follows. After connectivity returns the operator resolves and verifies again.

An approval names one plan, and the reviewer reads the SQL rather than a hash.

My approval was rejected. What does it have to name?

Section titled “My approval was rejected. What does it have to name?”

An approval is an independent immutable resource that must select the schema name and UID, the plan name and UID, and the plan fingerprint. Creation is rejected if the plan is already stale, its storage is not committed, the policy ConfigMap changed, a derived field conflicts, or the schema is not waiting for exactly one approval. Updates cannot change spec; create a new approval for a new plan. See Exact-plan approvals.

The reviewer can see the plan but not the SQL. Is that intended?

Section titled “The reviewer can see the plan but not the SQL. Is that intended?”

YesThe plan resource carries immutable chunk names and digests, and the SQL lives in controller-owned ConfigMaps that the built-in approver ClusterRole deliberately cannot read. A namespace administrator grants get on the current chunk names through a least-privilege Role, replaced for the next plan. Once granted, kubectl ptah plan <schema> --current reads it; approving hashes without reading the SQL is not an independent review.

kubectl ptah plan <schema> -n <namespace> prints it, and --applied prints what the last confirmed apply ran instead of what would run next. The plugin is a read-only client published with each release and installed once; it reads every chunk, checks each against the digest and size the plan records, joins them in order and checks the whole document before printing a line. Decoding the chunk ConfigMaps by hand is not the supported way to read a plan, and it is wrong for any plan larger than one chunk: the document is split by bytes, so a boundary can fall inside a SQL string, a JSON escape or a multi-byte character.

The operator recorded a plan and refuses to apply it. Why?

Section titled “The operator recorded a plan and refuses to apply it. Why?”

Destructive plans are disabled by default, and enabling them still requires an approval bound to the exact plan bytes. Check the condition reason: policy states appear as ApplyDisabled, DestructiveChangesDisabled, or AwaitingApproval.

My plan was rejected as too large. What is the limit?

Section titled “My plan was rejected as too large. What is the limit?”

An executable plan is limited to 8 MiB including the trailing newline, and the runner refuses a larger native plan before publication. Accepted bytes are stored in immutable 512 KiB ConfigMap chunks that stay below the Kubernetes object-size limit after encoding. Split the change across artifacts.

Old PtahSchemaPlan objects are piling up. Do I clean them?

Section titled “Old PtahSchemaPlan objects are piling up. Do I clean them?”

They are owned by the schema and can stay as audit evidence until garbage collection removes the owner. Only the exact UID and fingerprint in status.plan are current, so read that rather than the newest object you find.

Every refusal here is deliberate: the operator would rather stop than mutate on an uncertain reading.

An apply failed and the operator will not retry it. Why not run the plan again?

Section titled “An apply failed and the operator will not retry it. Why not run the plan again?”

Once a mutating child may have been dispatched, a missing, timed-out, or malformed outcome is ApplyOutcomeUnknown: whether the mutation happened is not known. The controller preserves the immutable apply holder and permits only a fresh observation, because a blind replay could perform the change twice. See Operations.

Does the operator roll back a failed change?

Section titled “Does the operator roll back a failed change?”

NoThere is no automatic rollback. Repair the desired artifact or the database deliberately, and let the next observation produce a new plan.

I set spec.suspend=true and now another schema in the same database is blocked. Is that a bug?

Section titled “I set spec.suspend=true and now another schema in the same database is blocked. Is that a bug?”

NoWhen an apply still needs convergence proof, suspension retains and renews that operation’s database-realm Lease, which blocks later mutations in the same realm until the resource is resumed and the read-only proof completes, or until it is deleted. Releasing the Lease early would let an intervening apply contaminate the audit result.

I deleted the PtahSchema and the tables are still there. Why no cleanup?

Section titled “I deleted the PtahSchema and the tables are still there. Why no cleanup?”

NoDeleting a PtahSchema never runs SQL, and neither does suspending one. The finalizer only observes an already active operation and releases coordination safely; Kubernetes then garbage-collects plans and Jobs while database objects stay untouched. Dropping data has to be a separate, deliberate action. See Operations.

Ptah supports my database, and the operator reports UnsupportedEngine. Why?

Section titled “Ptah supports my database, and the operator reports UnsupportedEngine. Why?”

The operator’s support contract is narrower than the CLI’s on purpose: an engine is supported only when the whole lifecycle is green on every supported Kubernetes minor, which today means PostgreSQL 17.x and MySQL 8.4.x LTS. An unknown engine value is stored without any database or registry access and sets phase=Blocked. Changing the spec back to a supported engine restarts the workflow at Resolve.

Two PtahSchema resources reach the same physical database through different URLs. What do I set?

Section titled “Two PtahSchema resources reach the same physical database through different URLs. What do I set?”

Set one stable coordinationKey for every URL alias that reaches the same database. Coordination is keyed on that value, so distinct aliases without it look like distinct databases and lose their mutual exclusion.

No, and it should not have it. On PostgreSQL, make the login the owner of the managed objects or a member of a dedicated no-login owner role, then grant only connect, schema usage, object creation, and the catalog visibility needed to inspect them. Widen to extensions, roles, or other schemas only when the artifact manages those object kinds.

Can the controller read my database credentials?

Section titled “Can the controller read my database credentials?”

NoDatabase work runs in short-lived Jobs, the controller itself has no permission to read database Secrets, and registry and database credentials are kept apart from each other. Credentials never appear in status, Events, plan resources, or command arguments.

What should my automation read to decide whether a schema is converged?

Section titled “What should my automation read to decide whether a schema is converged?”

Read the full condition tuple of type, status, and reason, and use observedGeneration to tell whether it describes the current spec. Condition messages are diagnostic text and may gain detail without an API version change; the reasons are the stable interface.

I found a bug in the operator. Where does it go?

Section titled “I found a bug in the operator. Where does it go?”

The operator is a separate repository, github.com/stokaro/ptah-operator, with its own issues and releases, so a report filed against the Ptah CLI has to be moved before anyone can act on it. Include the condition type, status, and reason, the status.observedGeneration, and the manager and executor image digests.