validators.py python
44 lines 1.6 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 13 hours ago
1 """Owner/repo/path validators and allowlist helpers (§HGD.4.2, §HGD.5.3)."""
2
3 from __future__ import annotations
4
5 import re
6
7 OWNER_REPO_SEGMENT_RE = re.compile(r"^[A-Za-z0-9._-]+$")
8 ALLOWLIST_ENTRY_RE = re.compile(r"^[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)?$")
9
10 DEFAULT_ORG_ENUMERATION_CAP = 100
11 DEFAULT_ROADMAP_PATH = "docs/ROADMAP.md"
12 DEFAULT_HANDOVER_PATH = "docs/OVERSEER-HANDOVER.md"
13 MARKER_PATH = ".overseer/config.yaml"
14
15
16 def valid_owner_repo_segment(value: str) -> bool:
17 """Return whether ``value`` matches the frozen owner/repo segment pattern."""
18 return bool(value) and bool(OWNER_REPO_SEGMENT_RE.match(value))
19
20
21 def parse_allowlist_entry(entry: str) -> tuple[str, str | None]:
22 """Parse an allowlist entry into ``(owner, repo_or_None)``.
23
24 Raises ``ValueError`` when the entry does not match the frozen shape.
25 """
26 if not isinstance(entry, str) or not entry.strip():
27 raise ValueError("empty allowlist entry")
28 text = entry.strip()
29 if not ALLOWLIST_ENTRY_RE.match(text):
30 raise ValueError(f"invalid allowlist entry: {entry!r}")
31 if "/" in text:
32 owner, repo = text.split("/", 1)
33 return owner, repo
34 return text, None
35
36
37 def validate_allowlist(entries: list[str]) -> list[tuple[str, str | None]]:
38 """Validate and return normalized allowlist pairs."""
39 return [parse_allowlist_entry(e) for e in entries]
40
41
42 def unknown_query_keys(query: dict[str, list[str]], *, allowed: frozenset[str] = frozenset()) -> list[str]:
43 """Return query keys not in ``allowed`` (empty allowed → any key is unknown)."""
44 return sorted(k for k in query if k not in allowed)
File History 1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 13 hours ago