-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaccess.py
More file actions
103 lines (74 loc) · 3.79 KB
/
Copy pathaccess.py
File metadata and controls
103 lines (74 loc) · 3.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
"""access.py — Wiederverwendbarer Retrieval-Filter für Wissensschichten (Epic #28 / T3).
Single source of truth für die visibility-Leiter (importiert von lint-links.py & lint-tree.py)
und Durchsetzung des Sichtbarkeitsmodells *vor* der Kontextbefüllung:
Eine Seite ist für einen Agenten lesbar ⇔
rank(visibility) <= rank(clearance) UND node(seite) ∈ read_scope
Wichtig (OKF / safe by default): unbekannte/fehlende Werte **fail closed**:
- fehlende/ungültige Seiten-`visibility` → restriktivste Stufe (`personal`) → eher verbergen
- fehlende/ungültige Agenten-`clearance` → niedrigste Stufe (`public`) → weniger Zugriff
Reine Standardbibliothek — ohne Abhängigkeiten importierbar und testbar.
"""
from __future__ import annotations
from dataclasses import dataclass
# Sichtbarkeits-Schichten, von offen (Index 0) nach restriktiv (Index -1).
VISIBILITY_LEVELS = ["public", "customer", "internal", "team", "personal"]
DEFAULT_VISIBILITY = VISIBILITY_LEVELS[-1] # fehlende Seiten-visibility → restriktivste Stufe
LEAST_CLEARANCE = VISIBILITY_LEVELS[0] # fehlende Agenten-clearance → geringster Zugriff
SESSION = "session" # ephemerer Sandbox-Node (kein Persist im Baum)
def rank(level: str) -> int:
"""Sensitivitäts-Rang einer visibility-Stufe (höher = restriktiver)."""
return VISIBILITY_LEVELS.index(level)
def _clean(value: str) -> str:
return value.strip().strip('"').strip("'").lower()
def normalize_visibility(value: str | None) -> str:
"""Seiten-visibility normalisieren. Unbekannt/fehlend → restriktivster Default (verbergen)."""
if value is None:
return DEFAULT_VISIBILITY
v = _clean(value)
return v if v in VISIBILITY_LEVELS else DEFAULT_VISIBILITY
def normalize_clearance(value: str | None) -> str:
"""Agenten-clearance normalisieren. Unbekannt/fehlend → geringster Zugriff (fail closed)."""
if value is None:
return LEAST_CLEARANCE
v = _clean(value)
return v if v in VISIBILITY_LEVELS else LEAST_CLEARANCE
@dataclass(frozen=True)
class AgentProfile:
"""Capability eines Agenten (T2): clearance + read/write-Scope."""
id: str
clearance: str
read_scope: frozenset[str]
write_scope: str = SESSION
@property
def clearance_rank(self) -> int:
return rank(self.clearance)
@dataclass(frozen=True)
class Page:
"""Minimal-Sicht auf eine Wiki-Seite für die Zugriffsprüfung."""
node: str
visibility: str
ref: str = "" # optional: Pfad/ID für Reporting
def can_read(page: Page, agent: AgentProfile) -> bool:
"""True, wenn der Agent die Seite lesen darf. Fail closed bei unbekannten Werten."""
if page.node not in agent.read_scope:
return False
return rank(normalize_visibility(page.visibility)) <= rank(normalize_clearance(agent.clearance))
def filter_readable(pages, agent: AgentProfile) -> list[Page]:
"""Filtert eine Seiten-Sequenz auf die für den Agenten lesbaren Seiten (Retrieval-Gate)."""
return [p for p in pages if can_read(p, agent)]
def can_write(agent: AgentProfile, node: str) -> bool:
"""True, wenn der Agent in `node` schreiben darf (nur in den eigenen write_scope)."""
return node == agent.write_scope
def load_agents(tree_data: dict) -> dict[str, AgentProfile]:
"""Baut AgentProfile-Objekte aus einem geparsten vault-tree.yaml (Schlüssel: agent-id)."""
out: dict[str, AgentProfile] = {}
for a in tree_data.get("agents", []) or []:
if not isinstance(a, dict) or not a.get("id"):
continue
out[a["id"]] = AgentProfile(
id=a["id"],
clearance=normalize_clearance(a.get("clearance")),
read_scope=frozenset(a.get("read_scope", []) or []),
write_scope=a.get("write_scope", SESSION),
)
return out