generated from coulomb/repo-seed
feat(WP-0011): warden route lookup CLI over the pointer catalog
Add a read-only `warden route` command group (list/show/find) that reads registry/routing/catalog.yaml and tells a worker which subsystem owns a need and which wiki/canon doc to follow. ops-warden still executes exactly one lane (SSH); routed entries return a pointer and never call any subsystem. - src/warden/routing/: models.py + catalog.py loader; enforces the no-double-source rule (non-SSH entries with steps/cert_command fail validation), dup-id and schema checks. - route list (active-only unless --all, --tag), route show (SSH appends steps + cert pattern; routed ends with "next action on <owner> — see <wiki_ref>"), route find (keyword ranking, --json). - tests/test_routing.py: load/validation, find ranking, CLI JSON shapes, plus a drift guard (every wiki_ref anchor resolves; every entry has a reviewed date). - Docs: wiki/AccessRouting.md CLI section, README quick reference, SCOPE A3 -> A4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
49
src/warden/routing/models.py
Normal file
49
src/warden/routing/models.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user