Lifecycle Policy Protection Invariant
URI: https://docs.ankatech.co/errors/lifecycle-policy-protection-invariant
HTTP Status: 400 Bad Request
When you see this
The lifecycle policy you submitted would let a key that is not in use apply protection. Nothing was stored: the policy in force is unchanged.
Only an ACTIVE alias over a PRIMARY material applies protection. Every lifecycle policy, tenant and deployment alike, must therefore set DENY for the four operations that apply protection — ENCRYPT, SIGN, REENCRYPT and RESIGN — in every other status:
| Dimension | Statuses where the four protect operations must be DENY |
|---|---|
alias | SUSPENDED, RETIRED, DELETED_LOGICAL |
material | PENDING_PRIMARY, RETIRED, REVOKED, PENDING_DESTRUCTION, DESTROYED |
This follows NIST SP 800-57 Part 1 §5.3: a key past its originator-usage period may still process information it already protected, but never applies new protection.
The other rows are not judged. DECRYPT and VERIFY stay yours to allow on a suspended or retired key, so data can still be read, and re-encrypted or re-signed away from such a key (the source of a re-encryption is judged as DECRYPT, of a re-signing as VERIFY). ROTATE and the lifecycle-management operations are not judged either.
The check runs on both lifecycle policy writes, before anything is read or stored. On a tenant write it runs before the deployment ceiling check, so a tenant policy that breaks both is answered with this 400, not with Lifecycle Policy Subset Violation.
The rule does not depend on the policy alone: the key service refuses ENCRYPT, SIGN and the target of a re-encryption or re-signing with a non-ACTIVE alias or a non-PRIMARY material whatever a stored policy says, with its generic 403.
Common Causes
- Allowing
ENCRYPTorSIGNon aSUSPENDEDalias so that applications keep working during an investigation. - Allowing
REENCRYPTorRESIGNon aRETIREDalias or material, intending to migrate data off the key: migration off a key is governed by itsDECRYPTandVERIFYrows. - Copying a whole row from
ACTIVE/PRIMARYinto the other statuses.
Response Example
{
"type": "https://docs.ankatech.co/errors/lifecycle-policy-protection-invariant",
"title": "Lifecycle Policy Protection Invariant",
"status": 400,
"detail": "The lifecycle policy allows ENCRYPT, SIGN, REENCRYPT or RESIGN on a key that is not in use, in 2 cells. Protection is applied only by an ACTIVE alias over a PRIMARY material: set these cells to DENY.",
"instance": "/api/v3/admin/tenants/550e8400-e29b-41d4-a716-446655440000/policies/lifecycle",
"timestamp": 1790000000,
"extensions": {
"violations": [
{ "dimension": "alias", "operation": "ENCRYPT", "status": "SUSPENDED" },
{ "dimension": "material", "operation": "SIGN", "status": "PENDING_PRIMARY" }
],
"requestId": "3f7c2a91-6d4e-4b8a-9c15-0e2d8b7a4f63"
}
}
Content-Type: application/problem+json — the response follows RFC 9457 Problem Details.
| Extension | Meaning |
|---|---|
violations | Each offending cell as {dimension, operation, status}: dimension is alias or material, operation one of ENCRYPT, SIGN, REENCRYPT, RESIGN, and status the status where the policy set ALLOW. Listed alias first, then material, in operation and status order |
requestId | The request's correlation identifier |
detail is fixed text apart from the cell count; read the cells from extensions.violations, never from detail.
How to Resolve
- Set every cell listed in
extensions.violationstoDENY. - To keep data readable on a suspended or retired key, allow
DECRYPTorVERIFYin that status instead; re-encryption and re-signing away from the key are judged on those rows. - Re-submit the whole policy.
Operations that emit it
| Method | Endpoint |
|---|---|
| PUT | /api/v3/admin/tenants/{tenantId}/policies/lifecycle |
| PUT | /api/v3/admin/platform/deployment/policies/lifecycle |
Related
- Lifecycle Policy Subset Violation — a tenant policy that allows a cell the deployment policy denies.
For full schema definitions, examples, and interactive testing, see the Developer Hub Reference.