FAQ
Questions about the Ptah CLI itself – schema formats, migrations, dialects – live in the FAQ on the Ptah documentation site.
What it is, and what it is not
Section titled “What it is, and what it is not”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.
Does the operator create the database?
Section titled “Does the operator create the database?”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.
Can I run this in production?
Section titled “Can I run this in production?”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.
Installing and upgrading
Section titled “Installing and upgrading”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.
Artifacts and the registry
Section titled “Artifacts and the registry”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.
Plans and approvals
Section titled “Plans and approvals”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.
How do I see the SQL a plan holds?
Section titled “How do I see the SQL a plan holds?”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.
When something stops
Section titled “When something stops”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.
Databases and access
Section titled “Databases and access”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.
Does the login need superuser?
Section titled “Does the login need superuser?”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.
Reading status, and reporting
Section titled “Reading status, and reporting”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.