PtahSchemaApproval
PtahSchemaApproval is a namespaced resource in operator.ptah.run, served as v1alpha1. kubectl knows it as ptahschemaapprovals, or ptahapprove for short.
This page is generated from the API types by make docs-reference. The shipped CRDs carry no descriptions, so this is where the field documentation lives.
Examples
Section titled “Examples”An approval authorizes one plan, once. It carries the decision – which schema, which plan, and that plan’s fingerprint – and who made it. The admission webhook holds the three to the plan it names and refuses any that disagrees, then stamps who you are and when from the authenticated request.
What a person writes
Section titled “What a person writes”Three identifiers. Read the plan first, then copy them out of the resources the
operator already published. If kubectl ptah plan prints an Unread fields: line,
do not approve the plan until a plugin at the operator’s version has read it whole:
admission holds these identifiers to the plan and does not read the plan.
kubectl ptah plan application -n application
kubectl -n application get ptahschema application \ -o jsonpath='{.metadata.uid}{"\n"}{.status.plan.name}{"\n"}{.status.plan.uid}{"\n"}'kubectl -n application get ptahschemaplan <plan-name> \ -o jsonpath='{.spec.fingerprint}{"\n"}'apiVersion: operator.ptah.run/v1alpha1kind: PtahSchemaApprovalmetadata: name: approve-application-1 namespace: applicationspec: schemaRef: name: application uid: 4f2c9e1a-5b6d-4a7e-9c31-0d8f2b6a4e57 planRef: name: ptah-plan-71c480df93d6ae2f14efe3c4 uid: 9a1b3c5d-7e9f-4012-83a4-5c6d7e8f9a0b # spec.fingerprint of that plan, not a digest of the SQL. planFingerprint: sha256:71c480df93d6ae2f14efe3c44baabb7d3bc5d0e2de07d0e7a9b1a6cbd5f7ca3fAn approval that names a plan the schema has moved past is refused. So is one whose fingerprint does not match the plan it names, one whose plan belongs to another schema, and one that names a plan by a UID the plan no longer has. The fingerprint is the plan’s complete identity: it binds the artifact, the observed and desired state, the policy, the verification policy, the target identity and the execution binding – the executor image, the Ptah version, the runner protocol and the controller-state version – and a plan is immutable, so naming it by UID and fingerprint names every one of those. A change to any of them produces another plan with another fingerprint, and the approval stays with the one it named. The manager’s own image and revision are not in it: a manager release that changes only those, a patch or a security fix, keeps the approval and applies the plan it names.
What the cluster stores
Section titled “What the cluster stores”The same object, read back after admission. The three fields above are unchanged; the stamp was added, and nothing was copied from the plan.
apiVersion: operator.ptah.run/v1alpha1kind: PtahSchemaApprovalmetadata: name: approve-application-1 namespace: applicationspec: schemaRef: name: application uid: 4f2c9e1a-5b6d-4a7e-9c31-0d8f2b6a4e57 planRef: name: ptah-plan-71c480df93d6ae2f14efe3c4 uid: 9a1b3c5d-7e9f-4012-83a4-5c6d7e8f9a0b planFingerprint: sha256:71c480df93d6ae2f14efe3c44baabb7d3bc5d0e2de07d0e7a9b1a6cbd5f7ca3f # Stamped from the authenticated request, not from the document. approver: username: jane@example.com uid: 1b9d6bcf-bbfd-4b2d-9b5d-ab8dfbbd4bed groups: - schema-approvers approvedAt: "2026-09-20T09:14:02Z" mutationRequestUID: 6f4b2a18-8c3e-4d5a-b1f7-2e0c9d8a7b64What the decision was made under – the artifact digest, the state fingerprints, the execution binding – is read off the plan the approval names:
kubectl -n application get ptahschemaplan ptah-plan-71c480df93d6ae2f14efe3c4 \ -o jsonpath='{.spec.artifactDigest}{"\n"}{.spec.executionBindingID}{"\n"}{.spec.executorImage}{"\n"}'| Field | Type | What it does |
|---|---|---|
spec.approvedAt |
string, required |
ApprovedAt is when that stamp was made, by the same webhook. |
spec.approver |
object, required |
Approver is stamped by the mutating webhook from the authenticated request. Whatever an API client writes here is replaced. |
spec.approver.groups |
[]string |
Groups the authenticated user belonged to at that moment. |
spec.approver.uid |
string |
UID of that user, where the authenticator provides one. |
spec.approver.username |
string, required |
Username the API server authenticated the request as. |
spec.mutationRequestUID |
string, required |
MutationRequestUID records the mutating AdmissionReview that stamped the authenticated identity. Kubernetes creates a distinct AdmissionReview UID for the later validating webhook, so the validator checks this field is present while matching identity against its own authenticated UserInfo. |
spec.planFingerprint |
string, required |
PlanFingerprint is the plan’s spec.fingerprint: its complete approval identity. A plan under a different binding has a different fingerprint, so an approval that names this one cannot carry over to it. |
spec.planRef |
object, required |
PlanRef is the exact plan being approved. A plan is immutable, so this names bytes rather than an intention. |
spec.planRef.name |
string, required |
Name of the referenced object in the same namespace. |
spec.planRef.uid |
string, required |
UID the object had when the reference was written. An object deleted and recreated under the same name is a different object, and this says so. |
spec.schemaRef |
object, required |
SchemaRef is the resource the approved change belongs to. |
spec.schemaRef.name |
string, required |
Name of the referenced object in the same namespace. |
spec.schemaRef.uid |
string, required |
UID the object had when the reference was written. An object deleted and recreated under the same name is a different object, and this says so. |
status
Section titled “status”| Field | Type | What it does |
|---|---|---|
status.conditions |
[]object |
Conditions say whether the binding was Accepted, has been Consumed by an apply, or went Stale because the database, the artifact or the policy moved before it could run. |
status.conditions[].lastTransitionTime |
string, required |
lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. |
status.conditions[].message |
string, required |
message is a human readable message indicating details about the transition. This may be an empty string. |
status.conditions[].observedGeneration |
integer |
observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. |
status.conditions[].reason |
string, required |
reason contains a programmatic identifier indicating the reason for the condition’s last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. |
status.conditions[].status |
string, required, one of True, False, Unknown |
status of the condition, one of True, False, Unknown. |
status.conditions[].type |
string, required |
type of condition in CamelCase or in foo.example.com/CamelCase. |
status.observedGeneration |
integer |
ObservedGeneration is the approval generation this status was written for. An approval is immutable, so it moves only when the object is first reconciled. |