Skip to content

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.

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.

Three identifiers. Read the plan first, then copy them out of the resources the operator already published:

Terminal window
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/v1alpha1
kind: PtahSchemaApproval
metadata:
name: approve-application-1
namespace: application
spec:
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:71c480df93d6ae2f14efe3c44baabb7d3bc5d0e2de07d0e7a9b1a6cbd5f7ca3f

An 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.

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/v1alpha1
kind: PtahSchemaApproval
metadata:
name: approve-application-1
namespace: application
spec:
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-2e0c9d8a7b64

What the decision was made under – the artifact digest, the state fingerprints, the execution binding – is read off the plan the approval names:

Terminal window
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.
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.