"""Parse the slot written by the Orchestrator to Redis into the slot v2 structure (contract C1). Both shapes are accepted so the gateway and the Orchestrator can be deployed in any order (SIENTIAPDE-1974): - v2: each tag carries `names` (one entry per collection / tag name / model); - v1 (legacy): each tag carries `tag_name` + `topics`, and the server carries the file-based `cert_path`. v1 is converted to v2: one `names` entry per topic, and `use_security` is true when the server had a certificate path (the certificate itself now always comes from MinIO via CertProvider, never from that path). """ SERVER_FIELDS = ('server_id', 'url', 'server_uri', 'use_security', 'subscription_period_ms') def _parse_names(address: str, tag: dict) -> list[dict]: names = tag.get('names') if names: return names topics = tag.get('topics') if topics and tag.get('tag_name'): return [{'collection': topic, 'tag_name': tag['tag_name']} for topic in topics] raise ValueError(f'tag {address!r} has no names') def _parse_tag(address: str, tag: dict) -> dict: return { 'frequency': tag['frequency'], 'names': _parse_names(address, tag), 'active_read': tag.get('active_read', {'enabled': False}), } def _parse_server(name: str, server: dict) -> dict: parsed = {field: server[field] for field in SERVER_FIELDS if field in server} parsed['name'] = server.get('name', name) if 'use_security' not in parsed: parsed['use_security'] = bool(server.get('cert_path')) parsed['tags'] = {addr: _parse_tag(addr, tag) for addr, tag in server['tags'].items()} return parsed def parse_slot(raw: dict) -> dict: """Normalize a raw slot (v1 or v2) into `{server_name: {..., tags: {addr: tag}}}`.""" return {name: _parse_server(name, server) for name, server in raw.items()}