"""Native KV rotation: replace a declared field without touching siblings. This updates OpenBao custody only. Catalog ``rotation.owner`` remains the provider/workload owner; this engine does not roll consumers or revoke a provider credential. Auth-capability lanes rotate via wrap/handoff, not here. """ from __future__ import annotations from pathlib import Path from secrets_engine.catalog import CatalogEntry from secrets_engine.errors import ProvisioningError from secrets_engine.openbao import OpenBaoClient from secrets_engine.provision import provision_from_file def render_rotate_plan(entry: CatalogEntry, field: str) -> str: owner = str((entry.rotation or {}).get("owner") or "") lines = [ f"Rotate plan for lane '{entry.id}'", f" field: {field}", f" target: {entry.mount}/{entry.path}", f" owner: {owner or '-'}", " siblings: preserved (CAS patch)", " workload delivery: not mutated", ] return "\n".join(lines) def rotate_from_file( client: OpenBaoClient, entry: CatalogEntry, field: str, file_path: Path ) -> str: """Replace one declared KV field. Returns the field name only.""" if not entry.stores_kv_value(): raise ProvisioningError( f"lane '{entry.id}' is {entry.kind}; rotate native KV via this " "command, or wrap/handoff for auth-capability material" ) if field not in entry.fields: raise ProvisioningError( f"field '{field}' not declared in lane '{entry.id}' fields {entry.fields}" ) return provision_from_file(client, entry, field, file_path)