Skip to content

Retire a used approval

An approval describes one plan against one observed state. Applying it moves that state.

What it shows. Why a signature cannot authorize twice, and what the operator does with one that no longer fits.

A replay of a verified run, not a live cluster. 11 checks held while it was recorded. Play types it out in the frame below; the transcript is the same session, already there.

sh · stale-approval
# A plan is waiting for a decision, and it is named by its fingerprint.$ kubectl -n "$NAMESPACE" get ptahschema storefront -o jsonpath='{.status.phase}{"\n"}{.status.plan.fingerprint}{"\n"}'AwaitingApprovalsha256:9860bfd7acb4dd956fb56f55c219ed996b97cc5b0a0ff6c8e69be0a381725a31 # Approve it, bound to that schema, that plan and that fingerprint.$ kubectl -n "$NAMESPACE" get ptahschema storefront -o json > demo/.lab/schema.jsonkubectl -n "$NAMESPACE" get ptahschemaplan \  "$(jq -r .status.plan.name demo/.lab/schema.json)" -o json > demo/.lab/plan.jsonjq -n --slurpfile schema demo/.lab/schema.json --slurpfile plan demo/.lab/plan.json '  {    apiVersion: "operator.ptah.run/v1alpha1", kind: "PtahSchemaApproval",    metadata: {name: "storefront-v1"},    spec: {      schemaRef: {name: $schema[0].metadata.name, uid: $schema[0].metadata.uid},      planRef:   {name: $plan[0].metadata.name,   uid: $plan[0].metadata.uid},      planFingerprint: $plan[0].spec.fingerprint    }  }' | kubectl -n "$NAMESPACE" create -f -ptahschemaapproval.operator.ptah.run/storefront-v1 created # It converges, and the approval records that it was the one dispatched.$ kubectl -n "$NAMESPACE" get ptahschemaapproval storefront-v1 -o json \  | jq -r '.status.conditions[] | "\(.type)\t\(.status)\t\(.reason)"'Accepted	False	PlanNoLongerCurrentStale	True	PlanNoLongerCurrentConsumed	True	DispatchCommitted # And already stale. It described a plan against the database before the apply.$ kubectl -n "$NAMESPACE" get ptahschemaapproval storefront-v1 -o json \  | jq -r '.status.conditions[] | select(.type == "Stale") | .message'The approved plan no longer matches the current database state # So it cannot authorize a second time. Change the desired state and see.$ eval "$(demo/bin/lab credentials)"export PTAH_OCI_USERNAME PTAH_OCI_PASSWORD PTAH_OCI_REGISTRYDIGEST=$(ptah schema push "oci://$PTAH_OCI_REGISTRY/schemas/demo:v2-$$" \  --schema-file demo/schemas/v2.sql --dialect postgres --plain-http \  | sed -n 's/^Digest: //p')kubectl -n "$NAMESPACE" patch ptahschema storefront --type=merge \  -p "{\"spec\":{\"desired\":{\"ociRef\":\"oci://$REGISTRY_IN_CLUSTER/schemas/demo@$DIGEST\"}}}"ptahschema.operator.ptah.run/storefront patched # A new plan, and the operator waits again. Nothing carries over.$ kubectl -n "$NAMESPACE" get ptahschema storefront -o json \  | jq -r '.status.phase, .status.plan.fingerprint'AwaitingApprovalsha256:3c7654c03b31f6591337cadfbca8f8fe08f7bf2ff191786c9caa339e44a06b38 # And the change nobody approved has not run.$ kubectl -n "$NAMESPACE" exec deploy/demo-psql -- psql -c "\d customers"             Table "public.customers" Column |  Type  | Collation | Nullable | Default--------+--------+-----------+----------+--------- id     | bigint |           | not null | email  | text   |           | not null |Indexes:    "customers_pkey" PRIMARY KEY, btree (id)

What was checked

Every step states what has to hold before its output may be published. A condition is read as its type, status and reason on the live object, never as a phrase in a message.

StepClaimHeld
1exits 0, prints "AwaitingApproval", prints "sha256:"yes
2exits 0, prints "created"yes
3ptahschema/storefront: InSync=True (ScopedConverged)yes
3exits 0, prints "Consumed", prints "DispatchCommitted"yes
3ptahschemaapproval/storefront-v1: Consumed=True (DispatchCommitted)yes
4exits 0, prints "no longer matches"yes
4ptahschemaapproval/storefront-v1: Stale=True (PlanNoLongerCurrent)yes
5exits 0, prints "patched"yes
6ptahschema/storefront: ApprovalRequired=True (Waiting)yes
6exits 0, prints "AwaitingApproval"yes
7exits 0, never prints "signed_up_at"yes

What it ran against

  • Kubernetes1.37.0
  • Operatorb98f92bed240
  • Ptahv0.13.0
  • Executorsha256:f68968558f06
  • Recorded2026-10-10
  • Scenarios at0f7a06595d8b

What the commands read

A session is repeated, not admired, so every name in it is one your own environment can carry. There is nothing else: no helper of ours stands between a command and the cluster, and the recorder refuses a step that reads a name this list does not hold. The commands arekubectl, ptah and kubectl ptah, each of which you install once; the last one is the operator’s own read-only client.

  • KUBECONFIGthe cluster the commands run against
  • NAMESPACEthe namespace the schema and its database credentials live in
  • OPERATOR_NAMESPACEwhere the chart installed the operator
  • CONTROLLERthe controller Deployment’s name, from that release
  • REGISTRY_IN_CLUSTERthe registry address a Pod in the cluster resolves
  • PTAH_OCI_REGISTRYthe registry address the push goes to
  • PTAH_OCI_USERNAMEthe account that push uses
  • PTAH_OCI_PASSWORDits password, which no command prints

A session also uses demo/bin/lab in two places, and both are the lab handing over what it generated rather than doing the demonstration’s work:lab credentials prints the three registry values above, and lab manifestfills the published template with values the step states. Rundemo/acceptance/reproduce.sh to watch the same scenario repeated from a directory where neither exists, by an account that may not create a Job, with the plan approved by a second account that may create neither a schema nor a Job.

The scenario is demo/scenarios/stale-approval.yaml, and make demo replays the whole set against a lab of your own. All recorded runs.