"""NPE sidecar adapter (fail-closed). Production ``crypto.mode=npe`` must not fall back to lab-xor. If the ``npe`` binary is missing, raise NpeRequired. Live ns1 must not set this until CI review (see UserReview.MD). """ from __future__ import annotations # annotations as strings import json # encode sidecar request import shutil # look up npe on PATH import subprocess # run sidecar from typing import Any, Dict, Mapping # types class NpeRequired(RuntimeError): """Raised when NPE is required but the sidecar is not usable.""" def npe_bin() -> str: """Return path to npe or raise.""" path = shutil.which("npe") # PATH lookup if not path: # missing raise NpeRequired("npe binary not on PATH; attach sidecar") # fail closed return path # found def seal_npe(to: str, body: Mapping[str, Any]) -> str: """Ask sidecar to HPKE-seal body for mailbox ``to``. Returns hex ct.""" raw = json.dumps(body, sort_keys=True, separators=(",", ":")).encode() # canonical body proc = subprocess.run( # npe CLI contract: stdin body, arg dest [npe_bin(), "seal", "--to", to], input=raw, capture_output=True, timeout=15, check=False, ) if proc.returncode != 0: # sidecar failed raise NpeRequired("npe seal failed: " + proc.stderr.decode(errors="replace")[:200]) return proc.stdout.strip().decode() # hex or token from sidecar