Exact-plan approvals
An approval is an independent authorization object, not a Boolean field on the schema. The approver must explicitly select all three stable identifiers:
- schema name and UID;
- plan name and UID;
- plan fingerprint.
The admission webhook reads the current plan directly from the API server and fills the remaining derived bindings when they are omitted. It never silently corrects a conflicting value. The validating webhook then checks the complete post-mutation object against the current schema, plan storage commit, observed database state, artifact digest, policy bytes, and execution images.
Approvals are required where spec.policy.apply asks for them. That field is
on the desired-state resource, so whoever may edit a PtahSchema or a
PtahMigration may also select Always and have non-destructive plans applied
without any approval. If this guide’s gate is meant to be one a person cannot
step around, install the administrator-owned policy in
examples/approval-policy-guard.yaml
as well; the security model
says why RBAC alone does not do it.
Start from the minimal approval example in examples/approval.yaml. Obtain the
values only after reviewing the plan:
kubectl -n application get ptahschema application \ -o jsonpath='{.metadata.uid}{"\n"}{.status.plan.name}{"\n"}{.status.plan.uid}{"\n"}{.status.plan.fingerprint}{"\n"}'kubectl -n application get ptahschemaplan <plan-name> -o yamlThe plan resource contains immutable chunk names and digests; exact SQL is in
those controller-owned ConfigMaps. The built-in approver ClusterRole does not
grant cluster-wide ConfigMap access. Before review, a namespace administrator
must grant get on every current chunk name to the approver. Start from the
least-privilege Role template in examples/approver-plan-reader-role.yaml,
copy all .spec.chunks[*].name values into resourceNames, and bind that Role
only to the reviewer. Replace the Role for the next plan. A broader Role that
can read every ConfigMap in an application namespace is easier to operate but
also exposes unrelated configuration.
Once the access is granted, read the plan with
kubectl ptah, which is a plugin the reviewer
installs once:
kubectl ptah plan application --current -n applicationIt reads every chunk, checks each against the digest and size the plan records,
joins them in index order and checks the whole document against
spec.contentDigest before printing a line. An approval of hashes without
reading the SQL they refer to is not an independent review, and neither is
reading one chunk of a plan that has several.
Fill those values in the approval and use server-side dry run to inspect the object after authenticated identity and derived bindings are stamped:
kubectl apply --server-side --dry-run=server -f examples/approval.yaml -o yamlkubectl apply -f examples/approval.yamlCreation is rejected if the plan is already stale, its immutable storage is
not committed, the verification-policy ConfigMap changed, or a supplied
derived field conflicts. Creation is also rejected unless the schema is
currently waiting for exactly one approval and no operation or recorded
approval already owns that decision. Concurrent duplicates are retired, and
the accepted approval is consumed only at the persisted Apply dispatch
boundary. Updates cannot change spec; create a new approval for a new plan.
The chart’s optional approver ClusterRole grants read access to schemas and plan metadata plus create access to approvals, but it has no binding and no ConfigMap permission. Bind approval permission only to authenticated identities that are independent from routine desired-state writers, and grant plan-chunk access separately in each application namespace.