def normalize(
    self,
    *,
    op: CipherOp,
    alg: Optional[Alg] = None,
    key: Optional[KeyRef] = None,
    params: Optional[ParamMapping] = None,
    dialect: Optional[str] = None,
) -> NormalizedDescriptor:
    allowed = set(self.supports().get(op, ()))
    chosen = alg or self.default_alg(op, for_key=key)
    if chosen not in allowed:
        raise ValueError(f"{chosen=} not allowed by FIPS 140-3 for {op=}")
    resolved = dict(params or {})
    if chosen.endswith("GCM"):
        resolved.setdefault("tagBits", self.policy()["aead_tag_bits"])
        resolved.setdefault("nonceLen", 12)
    if chosen.startswith("PS"):
        resolved.setdefault("saltBits", int(chosen[-3:]))
        resolved.setdefault("hash", "SHA" + chosen[-3:])
    if chosen.startswith("ES"):
        resolved.setdefault("hash", "SHA" + chosen[-3:])
    return {
        "op": op,
        "alg": chosen,
        "dialect": "jwa" if dialect is None else dialect,
        "mapped": {"jwa": chosen, "provider": chosen},
        "params": resolved,
        "constraints": {
            "minKeyBits": self.policy()["min_rsa_bits"],
            "curves": self.policy()["allowed_curves"],
            "hashes": self.policy()["hashes"],
        },
        "policy": self.policy(),
    }