"""Data model for routing catalog entries. A `RouteEntry` is a pointer: it names the owner and the authoritative doc for a credential need. Only the SSH lane (`warden_executes: true`) may carry an authored `steps` block and a `cert_command` pattern — every other entry is identifiers and pointers only (the no-double-source rule, enforced in `catalog.py`). """ from __future__ import annotations from dataclasses import dataclass, field from typing import List, Optional @dataclass class RouteEntry: id: str title: str need_keywords: List[str] owner_repo: str subsystem: str warden_executes: bool wiki_ref: str canon_ref: str reviewed: str status: str # "active" | "draft" # SSH lane only — None/empty for routed (non-executed) needs. steps: List[str] = field(default_factory=list) cert_command: Optional[str] = None @property def is_active(self) -> bool: return self.status == "active" def match_score(self, tokens: List[str]) -> int: """Keyword-overlap score against need_keywords, title, and id. Pure ranking helper — no I/O, no external calls. """ haystack = set(k.lower() for k in self.need_keywords) haystack.update(self.id.lower().replace("-", " ").split()) haystack.update(self.title.lower().replace("-", " ").split()) score = 0 for tok in tokens: t = tok.lower() if t in haystack: score += 2 elif any(t in h or h in t for h in haystack): score += 1 return score