{
  "description": "Auto-generated derived type for PostgresPolicyPlanSpec via `CustomResource`",
  "properties": {
    "spec": {
      "description": "Spec for a `PostgresPolicyPlan` custom resource.\n\nRepresents a computed reconciliation plan for a `PostgresPolicy`. Plans are\ncreated by the operator and may require explicit approval before execution.",
      "properties": {
        "managedDatabaseIdentity": {
          "description": "Database identity string for disambiguation in multi-db setups.",
          "type": "string"
        },
        "origin": {
          "description": "Origin of this plan. Omitted for ordinary durable reconciliation plans.",
          "nullable": true,
          "properties": {
            "baseContentDigest": {
              "description": "Canonical content digest of the *policy* content this candidate plan\nwas computed against — the applied base. A candidate is a complete\ndesired-state snapshot, so an approval is only meaningful against the\nbase it was reviewed on: identical SQL effects do not prove the\nsnapshot still preserves everything the base has come to manage since.\nPromotion refuses to adopt a plan whose base pin no longer matches the\ncontent the policy carried before the merge, and planning supersedes\nthe plan as soon as the base moves. Absent for non-candidate origins.",
              "maxLength": 128,
              "nullable": true,
              "type": "string"
            },
            "contentDigest": {
              "description": "Canonical content digest of the originating candidate, and the encoding\nit was computed under.\n\nThis is what binds the reviewed plan to the content that will later be\npromoted. It lives on the origin rather than in an annotation because a\npromotion check that can be edited by anyone holding `patch` is not a\nbinding at all. Both fields are absent for non-candidate origins.",
              "maxLength": 128,
              "nullable": true,
              "type": "string"
            },
            "contentDigestEncoding": {
              "description": "Version tag of the encoding `contentDigest` was computed under.\nDigests from different encodings are never comparable, so promotion\nrecognition only matches digests carrying the same tag. Absent for\nnon-candidate origins.",
              "maxLength": 64,
              "nullable": true,
              "type": "string"
            },
            "kind": {
              "description": "Kind of the resource that requested this plan.",
              "maxLength": 63,
              "type": "string"
            },
            "name": {
              "description": "Name of the originating resource.",
              "maxLength": 253,
              "type": "string"
            },
            "policyUid": {
              "description": "UID of the `PostgresPolicy` the candidate proposes content for. The\nplan's `spec.policyRef` names it; the UID is what survives a\ndelete-and-recreate of the same name.",
              "maxLength": 63,
              "nullable": true,
              "type": "string"
            },
            "uid": {
              "description": "UID binding the plan to this exact originating resource.",
              "maxLength": 63,
              "type": "string"
            }
          },
          "required": [
            "kind",
            "name",
            "uid"
          ],
          "type": "object"
        },
        "ownedRoles": {
          "default": [],
          "description": "Roles that this plan covers.",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "ownedSchemas": {
          "default": [],
          "description": "Schemas that this plan covers.",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "policyGeneration": {
          "description": "The policy's `.metadata.generation` at plan time.",
          "format": "int64",
          "type": "integer"
        },
        "policyRef": {
          "description": "Reference to the policy that generated this plan.",
          "properties": {
            "name": {
              "description": "Name of the originating PostgresPolicy in the same namespace.",
              "type": "string"
            }
          },
          "required": [
            "name"
          ],
          "type": "object"
        },
        "reconciliationMode": {
          "description": "Reconciliation mode used for this plan.",
          "enum": [
            "authoritative",
            "additive",
            "adopt"
          ],
          "type": "string"
        },
        "scope": {
          "description": "Narrow execution scope for a non-durable plan.",
          "nullable": true,
          "properties": {
            "bundleHash": {
              "description": "Digest binding an ephemeral plan to its resolved membership bundle.",
              "type": "string"
            },
            "kind": {
              "description": "Execution scope identifier for the ephemeral membership bundle.",
              "type": "string"
            },
            "operation": {
              "description": "Ephemeral bundle operation, when this is an ephemeral plan.",
              "enum": [
                "Activate",
                "Revoke"
              ],
              "type": "string"
            }
          },
          "required": [
            "bundleHash",
            "kind",
            "operation"
          ],
          "type": "object"
        }
      },
      "required": [
        "managedDatabaseIdentity",
        "policyGeneration",
        "policyRef",
        "reconciliationMode"
      ],
      "type": "object",
      "x-kubernetes-validations": [
        {
          "message": "plan origin is immutable once set",
          "rule": "!has(oldSelf.origin) || (has(self.origin) && self.origin == oldSelf.origin)"
        }
      ]
    },
    "status": {
      "description": "Status of a `PostgresPolicyPlan` resource.\n\nThe decision rules below are the same grammar `EphemeralAccessRequest`\nuses: a decision is terminal, `Approved` and `Denied` cannot both be true,\nand the deciding identity is recorded in the same admitted write. What CEL\ncannot do is check *who* is writing — see [`DecisionActor`].",
      "nullable": true,
      "properties": {
        "appliedAt": {
          "description": "Timestamp when the plan was applied (if applicable).",
          "nullable": true,
          "type": "string"
        },
        "applyingSince": {
          "description": "Timestamp when the plan entered Applying phase (for stuck detection).",
          "nullable": true,
          "type": "string"
        },
        "changeDigest": {
          "description": "Canonical semantic digest of the plan's typed effects, bound to the\nreconciliation mode and target database identity.\n\nThis is the approval identity: a decision approves these effects, and\nexecution proceeds only when the recomputed digest still matches. It is\nstable across recomputation of unchanged effects — notably for password\nchanges, which bind the password *source* rather than the derived\nverifier. See `pgroles_core::approval`.",
          "nullable": true,
          "type": "string"
        },
        "changeDigestEncoding": {
          "description": "Version tag of the encoding `change_digest` was computed under. Digests\nfrom different encodings are never comparable.",
          "nullable": true,
          "type": "string"
        },
        "changeSummary": {
          "description": "Summary of changes in this plan.",
          "nullable": true,
          "properties": {
            "default_privileges_revoked": {
              "default": 0,
              "description": "Number of default privilege revoke steps.",
              "format": "int32",
              "type": "integer"
            },
            "default_privileges_set": {
              "default": 0,
              "description": "Number of default privilege grant steps.",
              "format": "int32",
              "type": "integer"
            },
            "grants_added": {
              "default": 0,
              "description": "Number of object privilege grant steps.",
              "format": "int32",
              "type": "integer"
            },
            "grants_revoked": {
              "default": 0,
              "description": "Number of object privilege revoke steps.",
              "format": "int32",
              "type": "integer"
            },
            "members_added": {
              "default": 0,
              "description": "Number of membership additions.",
              "format": "int32",
              "type": "integer"
            },
            "members_removed": {
              "default": 0,
              "description": "Number of membership removals.",
              "format": "int32",
              "type": "integer"
            },
            "passwords_set": {
              "default": 0,
              "description": "Number of password updates.",
              "format": "int32",
              "type": "integer"
            },
            "roles_altered": {
              "default": 0,
              "description": "Number of role attribute or configuration changes.",
              "format": "int32",
              "type": "integer"
            },
            "roles_created": {
              "default": 0,
              "description": "Number of role creations.",
              "format": "int32",
              "type": "integer"
            },
            "roles_dropped": {
              "default": 0,
              "description": "Number of role drops.",
              "format": "int32",
              "type": "integer"
            },
            "schema_owners_altered": {
              "default": 0,
              "description": "Number of schema ownership changes.",
              "format": "int32",
              "type": "integer"
            },
            "schemas_created": {
              "default": 0,
              "description": "Number of schema creations.",
              "format": "int32",
              "type": "integer"
            },
            "sessions_terminated": {
              "default": 0,
              "description": "Number of session-termination steps.",
              "format": "int32",
              "type": "integer"
            },
            "total": {
              "default": 0,
              "description": "Number of all planned change steps.",
              "format": "int32",
              "type": "integer"
            }
          },
          "type": "object"
        },
        "computedAt": {
          "description": "Timestamp when the plan was computed.",
          "nullable": true,
          "type": "string"
        },
        "conditions": {
          "default": [],
          "description": "Standard conditions: Computed, Applied, and the terminal decision\nconditions `Approved` / `Denied`.",
          "items": {
            "description": "A condition on the `PostgresPolicy` resource.",
            "properties": {
              "last_transition_time": {
                "description": "Last time the condition transitioned.",
                "nullable": true,
                "type": "string"
              },
              "message": {
                "description": "Human-readable message.",
                "nullable": true,
                "type": "string"
              },
              "reason": {
                "description": "Human-readable reason for the condition.",
                "nullable": true,
                "type": "string"
              },
              "status": {
                "description": "Status: \"True\", \"False\", or \"Unknown\".",
                "type": "string"
              },
              "type": {
                "description": "Controller-defined condition type, such as Ready, Reconciling, or Degraded.\nThis is an open vocabulary; consult status guidance for operational meanings.",
                "type": "string"
              }
            },
            "required": [
              "status",
              "type"
            ],
            "type": "object"
          },
          "maxItems": 16,
          "type": "array"
        },
        "decidedBy": {
          "description": "Kubernetes identity which approved or denied this plan.\n\nWritten in the same status update as the terminal decision, and\nwrite-once thereafter. The supplied Kyverno reference policy overwrites\nit from authenticated admission `userInfo`; without that admission layer\nit is an assertion by whoever wrote the status, not a verified identity.",
          "nullable": true,
          "properties": {
            "groups": {
              "default": [],
              "description": "Kubernetes groups recorded for this actor.",
              "items": {
                "maxLength": 256,
                "minLength": 1,
                "type": "string"
              },
              "maxItems": 64,
              "type": "array"
            },
            "uid": {
              "description": "Optional Kubernetes user UID recorded by admission.",
              "maxLength": 128,
              "nullable": true,
              "type": "string"
            },
            "username": {
              "description": "Kubernetes username asserted for this actor; authenticated only when enforced by admission.",
              "maxLength": 512,
              "minLength": 1,
              "type": "string"
            }
          },
          "required": [
            "username"
          ],
          "type": "object"
        },
        "failedAt": {
          "description": "Timestamp when the plan entered Failed phase (for dedup window).",
          "nullable": true,
          "type": "string"
        },
        "lastError": {
          "description": "Error message if apply failed.",
          "nullable": true,
          "type": "string"
        },
        "passwordSourceDigest": {
          "description": "Diagnostic SHA-256 digest of the password source-version map. Contains\nno password material. A mismatch identifies credential-source changes;\napproval remains bound by changeDigest. Absent on older plans.",
          "nullable": true,
          "type": "string"
        },
        "phase": {
          "default": "Pending",
          "description": "Phase: Pending, Approved, Applying, Applied, Failed, Superseded.",
          "enum": [
            "Pending",
            "Approved",
            "Applying",
            "Applied",
            "Failed",
            "Superseded",
            "Rejected"
          ],
          "type": "string"
        },
        "physicalIdentityAvailable": {
          "description": "Whether the physical identity was readable when this plan was computed.\n\nRecorded explicitly rather than inferred from\n`target_physical_identity` being set, so that \"the identifier could not\nbe read\" is distinguishable from \"this plan predates the field\". The\ndifference matters at execution: a plan that had the identifier and now\ndoes not is a downgrade and fails closed.",
          "nullable": true,
          "type": "boolean"
        },
        "redactedSqlHash": {
          "description": "SHA-256 hash of the redacted SQL preview bytes. This is for storage\nintegrity only; approval and deduplication use `change_digest`.",
          "nullable": true,
          "type": "string"
        },
        "revalidatedAt": {
          "description": "When the plan was most recently confirmed current.",
          "nullable": true,
          "type": "string"
        },
        "revalidatedGeneration": {
          "description": "The owning object's `.metadata.generation` this plan was most recently\nconfirmed current against — the policy's for an ordinary plan, the\ncandidate's for a candidate-origin plan.\n\nA pending policy plan is revalidated on every reconcile. When the policy\nchanges but the resulting effects do not, the plan — and any decision\nrecorded on it — is retained and this advances to the new generation.\nA candidate's spec is immutable, so a candidate plan's provenance is\nstamped once at creation; its ongoing revalidation is the digest\ndeduplication itself. It is provenance, never approval identity:\n`change_digest` is what a decision binds.",
          "format": "int64",
          "nullable": true,
          "type": "integer"
        },
        "sqlHash": {
          "description": "SHA-256 hash of the planned SQL. Retained as a diagnostic for the\npreview artifact; it is **not** the approval identity, because rendered\nSQL embeds a freshly salted SCRAM verifier for every password change.\nUse `change_digest` for approval and deduplication.",
          "nullable": true,
          "type": "string"
        },
        "sqlInline": {
          "description": "Inline SQL for small plans (below a size threshold).",
          "nullable": true,
          "type": "string"
        },
        "sqlOriginalBytes": {
          "description": "Uncompressed byte length of the redacted SQL preview.",
          "format": "int64",
          "nullable": true,
          "type": "integer"
        },
        "sqlRef": {
          "description": "Reference to ConfigMap containing the full SQL (for large plans).",
          "nullable": true,
          "properties": {
            "compression": {
              "description": "Compression used for the referenced SQL content. Missing means older\nuncompressed ConfigMap data.",
              "enum": [
                "gzip",
                null
              ],
              "nullable": true,
              "type": "string"
            },
            "key": {
              "description": "Data key containing SQL in the referenced ConfigMap.",
              "type": "string"
            },
            "name": {
              "description": "Name of the ConfigMap containing the rendered SQL.",
              "type": "string"
            }
          },
          "required": [
            "key",
            "name"
          ],
          "type": "object"
        },
        "sqlStatements": {
          "description": "Number of SQL statements in the plan (after wildcard expansion).\nMay be significantly larger than `changeSummary.total` when wildcard\ngrants expand to many per-object statements.",
          "format": "int64",
          "nullable": true,
          "type": "integer"
        },
        "sqlStoredBytes": {
          "description": "Stored byte length of the SQL preview after inline/truncation/compression.",
          "format": "int64",
          "nullable": true,
          "type": "integer"
        },
        "sqlTruncated": {
          "default": false,
          "description": "True when the SQL preview was truncated because the full redacted SQL\ncould not be persisted within Kubernetes object limits.",
          "type": "boolean"
        },
        "targetLogicalFingerprint": {
          "description": "Fingerprint of the resolved connection endpoint (host, port, database)\nthis plan was computed against.",
          "nullable": true,
          "type": "string"
        },
        "targetPhysicalIdentity": {
          "description": "`pg_control_system().system_identifier` as read from the target when\nthis plan was computed — the storage lineage the approval is bound to.\nAbsent on engines that do not expose it.",
          "nullable": true,
          "type": "string"
        }
      },
      "type": "object",
      "x-kubernetes-validations": [
        {
          "message": "Approved=True and Denied=True are mutually exclusive",
          "rule": "!(self.conditions.exists(c, c.type == 'Approved' && c.status == 'True') && self.conditions.exists(c, c.type == 'Denied' && c.status == 'True'))"
        },
        {
          "message": "plan decisions are terminal",
          "rule": "oldSelf.conditions.filter(c, (c.type == 'Approved' || c.type == 'Denied') && c.status == 'True').map(c, c.type) == self.conditions.filter(c, (c.type == 'Approved' || c.type == 'Denied') && c.status == 'True').map(c, c.type) || oldSelf.conditions.filter(c, (c.type == 'Approved' || c.type == 'Denied') && c.status == 'True').size() == 0"
        },
        {
          "message": "decision identity is write-once",
          "rule": "!has(oldSelf.decidedBy) || (has(self.decidedBy) && self.decidedBy == oldSelf.decidedBy)"
        },
        {
          "message": "a terminal plan decision and decidedBy identity must be recorded together",
          "rule": "self.conditions.exists(c, (c.type == 'Approved' || c.type == 'Denied') && c.status == 'True') == has(self.decidedBy)"
        },
        {
          "message": "changeDigest is write-once",
          "rule": "!has(oldSelf.changeDigest) || (has(self.changeDigest) && self.changeDigest == oldSelf.changeDigest)"
        },
        {
          "message": "changeDigestEncoding is write-once",
          "rule": "!has(oldSelf.changeDigestEncoding) || (has(self.changeDigestEncoding) && self.changeDigestEncoding == oldSelf.changeDigestEncoding)"
        },
        {
          "message": "targetPhysicalIdentity is write-once",
          "rule": "!has(oldSelf.targetPhysicalIdentity) || (has(self.targetPhysicalIdentity) && self.targetPhysicalIdentity == oldSelf.targetPhysicalIdentity)"
        },
        {
          "message": "targetLogicalFingerprint is write-once",
          "rule": "!has(oldSelf.targetLogicalFingerprint) || (has(self.targetLogicalFingerprint) && self.targetLogicalFingerprint == oldSelf.targetLogicalFingerprint)"
        },
        {
          "message": "physicalIdentityAvailable is write-once",
          "rule": "!has(oldSelf.physicalIdentityAvailable) || (has(self.physicalIdentityAvailable) && self.physicalIdentityAvailable == oldSelf.physicalIdentityAvailable)"
        }
      ]
    }
  },
  "required": [
    "spec"
  ],
  "title": "PostgresPolicyPlan",
  "type": "object"
}
