rpp-protocol 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rpp/i18n.py ADDED
@@ -0,0 +1,426 @@
1
+ """
2
+ RPP Internationalization Module
3
+
4
+ Provides translations for CLI output in multiple languages.
5
+ Zero dependencies - uses simple dictionary-based approach.
6
+
7
+ Supported languages:
8
+ - en: English (default)
9
+ - ar-gulf: Gulf Arabic (UAE, Qatar, Kuwait, Bahrain)
10
+ - ar-hejaz: Hejazi Arabic (Western Saudi Arabia)
11
+ - es: Spanish
12
+ - ru: Russian
13
+ """
14
+
15
+ from typing import Dict
16
+
17
+ # Default language
18
+ DEFAULT_LANG = "en"
19
+
20
+ # Supported languages
21
+ SUPPORTED_LANGS = ["en", "ar-gulf", "ar-hejaz", "es", "ru"]
22
+
23
+ # Translation dictionaries
24
+ TRANSLATIONS: Dict[str, Dict[str, str]] = {
25
+ "en": {
26
+ # Status messages
27
+ "ok": "OK",
28
+ "fail": "FAIL",
29
+ "encode": "ENCODE",
30
+ "decode": "DECODE",
31
+ "resolve": "RESOLVE",
32
+
33
+ # Field labels
34
+ "shell": "shell",
35
+ "theta": "theta",
36
+ "phi": "phi",
37
+ "harmonic": "harmonic",
38
+ "allowed": "allowed",
39
+ "route": "route",
40
+ "reason": "reason",
41
+
42
+ # Shell names
43
+ "shell_hot": "Hot",
44
+ "shell_warm": "Warm",
45
+ "shell_cold": "Cold",
46
+ "shell_frozen": "Frozen",
47
+
48
+ # Sector names
49
+ "sector_gene": "Gene",
50
+ "sector_memory": "Memory",
51
+ "sector_witness": "Witness",
52
+ "sector_dream": "Dream",
53
+ "sector_bridge": "Bridge",
54
+ "sector_guardian": "Guardian",
55
+ "sector_emergence": "Emergence",
56
+ "sector_meta": "Meta",
57
+
58
+ # Grounding levels
59
+ "grounding_grounded": "Grounded",
60
+ "grounding_transitional": "Transitional",
61
+ "grounding_abstract": "Abstract",
62
+ "grounding_ethereal": "Ethereal",
63
+
64
+ # Demo/Tutorial
65
+ "demo_title": "Rotational Packet Protocol",
66
+ "demo_subtitle": "28-bit Semantic Addressing",
67
+ "demo_tagline": "Consent-Aware Routing",
68
+ "routing_decision": "ROUTING DECISION",
69
+ "demonstration_complete": "Demonstration Complete",
70
+ "consent_level": "Consent Level",
71
+
72
+ # Scenarios
73
+ "scenario_1_title": "SCENARIO 1: Allowed Read (Grounded Consent)",
74
+ "scenario_2_title": "SCENARIO 2: Denied Write (Ethereal - Consent Required)",
75
+ "scenario_3_title": "SCENARIO 3: Cold Storage Routing",
76
+
77
+ # Key takeaways
78
+ "takeaway_grounded": "Low phi (Grounded) = immediate access allowed",
79
+ "takeaway_ethereal": "High phi (Ethereal) = explicit consent required",
80
+ "takeaway_cold": "Cold shell = routed to archive storage",
81
+
82
+ # Reasons
83
+ "reason_read_memory": "read permitted via memory",
84
+ "reason_read_archive": "read permitted via archive",
85
+ "reason_write_ethereal": "Write to ethereal zone requires explicit consent",
86
+
87
+ # Tutorial
88
+ "tutorial_welcome": "Welcome to the RPP Tutorial",
89
+ "tutorial_what_is": "What is RPP?",
90
+ "tutorial_address": "The 28-bit Address",
91
+ "tutorial_resolver": "The Resolver",
92
+ "tutorial_try_it": "Try it yourself",
93
+
94
+ # Version
95
+ "version": "version",
96
+ },
97
+
98
+ "ar-gulf": {
99
+ # Status messages - Gulf Arabic
100
+ "ok": "تمام",
101
+ "fail": "فشل",
102
+ "encode": "ترميز",
103
+ "decode": "فك الترميز",
104
+ "resolve": "حل",
105
+
106
+ # Field labels
107
+ "shell": "الطبقة",
108
+ "theta": "ثيتا",
109
+ "phi": "فاي",
110
+ "harmonic": "التوافقي",
111
+ "allowed": "مسموح",
112
+ "route": "المسار",
113
+ "reason": "السبب",
114
+
115
+ # Shell names
116
+ "shell_hot": "ساخن",
117
+ "shell_warm": "دافئ",
118
+ "shell_cold": "بارد",
119
+ "shell_frozen": "مجمد",
120
+
121
+ # Sector names
122
+ "sector_gene": "الجين",
123
+ "sector_memory": "الذاكرة",
124
+ "sector_witness": "الشاهد",
125
+ "sector_dream": "الحلم",
126
+ "sector_bridge": "الجسر",
127
+ "sector_guardian": "الحارس",
128
+ "sector_emergence": "الظهور",
129
+ "sector_meta": "ميتا",
130
+
131
+ # Grounding levels
132
+ "grounding_grounded": "مؤرض",
133
+ "grounding_transitional": "انتقالي",
134
+ "grounding_abstract": "مجرد",
135
+ "grounding_ethereal": "أثيري",
136
+
137
+ # Demo/Tutorial
138
+ "demo_title": "بروتوكول الحزمة الدورانية",
139
+ "demo_subtitle": "عنونة دلالية 28-بت",
140
+ "demo_tagline": "توجيه واعي بالموافقة",
141
+ "routing_decision": "قرار التوجيه",
142
+ "demonstration_complete": "اكتمل العرض",
143
+ "consent_level": "مستوى الموافقة",
144
+
145
+ # Scenarios
146
+ "scenario_1_title": "السيناريو 1: قراءة مسموحة (موافقة مؤرضة)",
147
+ "scenario_2_title": "السيناريو 2: كتابة مرفوضة (أثيري - موافقة مطلوبة)",
148
+ "scenario_3_title": "السيناريو 3: توجيه التخزين البارد",
149
+
150
+ # Key takeaways
151
+ "takeaway_grounded": "فاي منخفض (مؤرض) = وصول فوري مسموح",
152
+ "takeaway_ethereal": "فاي عالي (أثيري) = موافقة صريحة مطلوبة",
153
+ "takeaway_cold": "طبقة باردة = توجيه إلى الأرشيف",
154
+
155
+ # Reasons
156
+ "reason_read_memory": "القراءة مسموحة عبر الذاكرة",
157
+ "reason_read_archive": "القراءة مسموحة عبر الأرشيف",
158
+ "reason_write_ethereal": "الكتابة للمنطقة الأثيرية تتطلب موافقة صريحة",
159
+
160
+ # Tutorial
161
+ "tutorial_welcome": "مرحباً بك في درس RPP",
162
+ "tutorial_what_is": "ما هو RPP؟",
163
+ "tutorial_address": "العنوان 28-بت",
164
+ "tutorial_resolver": "المحلل",
165
+ "tutorial_try_it": "جربها بنفسك",
166
+
167
+ # Version
168
+ "version": "الإصدار",
169
+ },
170
+
171
+ "ar-hejaz": {
172
+ # Status messages - Hejazi Arabic
173
+ "ok": "تمام",
174
+ "fail": "ما زبط",
175
+ "encode": "ترميز",
176
+ "decode": "فك الترميز",
177
+ "resolve": "حل",
178
+
179
+ # Field labels
180
+ "shell": "الطبقة",
181
+ "theta": "ثيتا",
182
+ "phi": "فاي",
183
+ "harmonic": "التوافقي",
184
+ "allowed": "مسموح",
185
+ "route": "المسار",
186
+ "reason": "السبب",
187
+
188
+ # Shell names
189
+ "shell_hot": "حار",
190
+ "shell_warm": "دافي",
191
+ "shell_cold": "بارد",
192
+ "shell_frozen": "مجمد",
193
+
194
+ # Sector names
195
+ "sector_gene": "الجين",
196
+ "sector_memory": "الذاكرة",
197
+ "sector_witness": "الشاهد",
198
+ "sector_dream": "الحلم",
199
+ "sector_bridge": "الجسر",
200
+ "sector_guardian": "الحارس",
201
+ "sector_emergence": "الظهور",
202
+ "sector_meta": "ميتا",
203
+
204
+ # Grounding levels
205
+ "grounding_grounded": "متأصل",
206
+ "grounding_transitional": "انتقالي",
207
+ "grounding_abstract": "مجرد",
208
+ "grounding_ethereal": "أثيري",
209
+
210
+ # Demo/Tutorial
211
+ "demo_title": "بروتوكول الحزمة الدورانية",
212
+ "demo_subtitle": "عنونة دلالية 28-بت",
213
+ "demo_tagline": "توجيه بموافقة",
214
+ "routing_decision": "قرار التوجيه",
215
+ "demonstration_complete": "خلص العرض",
216
+ "consent_level": "مستوى الموافقة",
217
+
218
+ # Scenarios
219
+ "scenario_1_title": "السيناريو 1: قراءة مسموحة (موافقة متأصلة)",
220
+ "scenario_2_title": "السيناريو 2: كتابة مرفوضة (أثيري - تبي موافقة)",
221
+ "scenario_3_title": "السيناريو 3: توجيه للتخزين البارد",
222
+
223
+ # Key takeaways
224
+ "takeaway_grounded": "فاي واطي (متأصل) = دخول فوري مسموح",
225
+ "takeaway_ethereal": "فاي عالي (أثيري) = لازم موافقة صريحة",
226
+ "takeaway_cold": "طبقة باردة = يروح للأرشيف",
227
+
228
+ # Reasons
229
+ "reason_read_memory": "القراءة مسموحة من الذاكرة",
230
+ "reason_read_archive": "القراءة مسموحة من الأرشيف",
231
+ "reason_write_ethereal": "الكتابة للمنطقة الأثيرية تبي موافقة صريحة",
232
+
233
+ # Tutorial
234
+ "tutorial_welcome": "أهلاً فيك في درس RPP",
235
+ "tutorial_what_is": "ايش هو RPP؟",
236
+ "tutorial_address": "العنوان 28-بت",
237
+ "tutorial_resolver": "المحلل",
238
+ "tutorial_try_it": "جربها بنفسك",
239
+
240
+ # Version
241
+ "version": "الإصدار",
242
+ },
243
+
244
+ "es": {
245
+ # Status messages - Spanish
246
+ "ok": "OK",
247
+ "fail": "ERROR",
248
+ "encode": "CODIFICAR",
249
+ "decode": "DECODIFICAR",
250
+ "resolve": "RESOLVER",
251
+
252
+ # Field labels
253
+ "shell": "capa",
254
+ "theta": "theta",
255
+ "phi": "phi",
256
+ "harmonic": "armónico",
257
+ "allowed": "permitido",
258
+ "route": "ruta",
259
+ "reason": "razón",
260
+
261
+ # Shell names
262
+ "shell_hot": "Caliente",
263
+ "shell_warm": "Tibio",
264
+ "shell_cold": "Frío",
265
+ "shell_frozen": "Congelado",
266
+
267
+ # Sector names
268
+ "sector_gene": "Gen",
269
+ "sector_memory": "Memoria",
270
+ "sector_witness": "Testigo",
271
+ "sector_dream": "Sueño",
272
+ "sector_bridge": "Puente",
273
+ "sector_guardian": "Guardián",
274
+ "sector_emergence": "Emergencia",
275
+ "sector_meta": "Meta",
276
+
277
+ # Grounding levels
278
+ "grounding_grounded": "Arraigado",
279
+ "grounding_transitional": "Transicional",
280
+ "grounding_abstract": "Abstracto",
281
+ "grounding_ethereal": "Etéreo",
282
+
283
+ # Demo/Tutorial
284
+ "demo_title": "Protocolo de Paquete Rotacional",
285
+ "demo_subtitle": "Direccionamiento Semántico de 28 bits",
286
+ "demo_tagline": "Enrutamiento Consciente del Consentimiento",
287
+ "routing_decision": "DECISIÓN DE ENRUTAMIENTO",
288
+ "demonstration_complete": "Demostración Completa",
289
+ "consent_level": "Nivel de Consentimiento",
290
+
291
+ # Scenarios
292
+ "scenario_1_title": "ESCENARIO 1: Lectura Permitida (Consentimiento Arraigado)",
293
+ "scenario_2_title": "ESCENARIO 2: Escritura Denegada (Etéreo - Requiere Consentimiento)",
294
+ "scenario_3_title": "ESCENARIO 3: Enrutamiento a Almacenamiento Frío",
295
+
296
+ # Key takeaways
297
+ "takeaway_grounded": "Phi bajo (Arraigado) = acceso inmediato permitido",
298
+ "takeaway_ethereal": "Phi alto (Etéreo) = se requiere consentimiento explícito",
299
+ "takeaway_cold": "Capa fría = enrutado al archivo",
300
+
301
+ # Reasons
302
+ "reason_read_memory": "lectura permitida vía memoria",
303
+ "reason_read_archive": "lectura permitida vía archivo",
304
+ "reason_write_ethereal": "Escritura a zona etérea requiere consentimiento explícito",
305
+
306
+ # Tutorial
307
+ "tutorial_welcome": "Bienvenido al Tutorial de RPP",
308
+ "tutorial_what_is": "¿Qué es RPP?",
309
+ "tutorial_address": "La Dirección de 28 bits",
310
+ "tutorial_resolver": "El Resolutor",
311
+ "tutorial_try_it": "Pruébalo tú mismo",
312
+
313
+ # Version
314
+ "version": "versión",
315
+ },
316
+
317
+ "ru": {
318
+ # Status messages - Russian
319
+ "ok": "ОК",
320
+ "fail": "ОШИБКА",
321
+ "encode": "КОДИРОВАТЬ",
322
+ "decode": "ДЕКОДИРОВАТЬ",
323
+ "resolve": "РАЗРЕШИТЬ",
324
+
325
+ # Field labels
326
+ "shell": "оболочка",
327
+ "theta": "тета",
328
+ "phi": "фи",
329
+ "harmonic": "гармоника",
330
+ "allowed": "разрешено",
331
+ "route": "маршрут",
332
+ "reason": "причина",
333
+
334
+ # Shell names
335
+ "shell_hot": "Горячий",
336
+ "shell_warm": "Тёплый",
337
+ "shell_cold": "Холодный",
338
+ "shell_frozen": "Замороженный",
339
+
340
+ # Sector names
341
+ "sector_gene": "Ген",
342
+ "sector_memory": "Память",
343
+ "sector_witness": "Свидетель",
344
+ "sector_dream": "Сон",
345
+ "sector_bridge": "Мост",
346
+ "sector_guardian": "Страж",
347
+ "sector_emergence": "Возникновение",
348
+ "sector_meta": "Мета",
349
+
350
+ # Grounding levels
351
+ "grounding_grounded": "Заземлённый",
352
+ "grounding_transitional": "Переходный",
353
+ "grounding_abstract": "Абстрактный",
354
+ "grounding_ethereal": "Эфирный",
355
+
356
+ # Demo/Tutorial
357
+ "demo_title": "Протокол Ротационных Пакетов",
358
+ "demo_subtitle": "28-битная Семантическая Адресация",
359
+ "demo_tagline": "Маршрутизация с Учётом Согласия",
360
+ "routing_decision": "РЕШЕНИЕ О МАРШРУТИЗАЦИИ",
361
+ "demonstration_complete": "Демонстрация Завершена",
362
+ "consent_level": "Уровень Согласия",
363
+
364
+ # Scenarios
365
+ "scenario_1_title": "СЦЕНАРИЙ 1: Разрешённое Чтение (Заземлённое Согласие)",
366
+ "scenario_2_title": "СЦЕНАРИЙ 2: Запрещённая Запись (Эфирный - Требуется Согласие)",
367
+ "scenario_3_title": "СЦЕНАРИЙ 3: Маршрутизация в Холодное Хранилище",
368
+
369
+ # Key takeaways
370
+ "takeaway_grounded": "Низкий phi (Заземлённый) = немедленный доступ разрешён",
371
+ "takeaway_ethereal": "Высокий phi (Эфирный) = требуется явное согласие",
372
+ "takeaway_cold": "Холодная оболочка = маршрут в архив",
373
+
374
+ # Reasons
375
+ "reason_read_memory": "чтение разрешено через память",
376
+ "reason_read_archive": "чтение разрешено через архив",
377
+ "reason_write_ethereal": "Запись в эфирную зону требует явного согласия",
378
+
379
+ # Tutorial
380
+ "tutorial_welcome": "Добро пожаловать в Учебник RPP",
381
+ "tutorial_what_is": "Что такое RPP?",
382
+ "tutorial_address": "28-битный Адрес",
383
+ "tutorial_resolver": "Резолвер",
384
+ "tutorial_try_it": "Попробуйте сами",
385
+
386
+ # Version
387
+ "version": "версия",
388
+ },
389
+ }
390
+
391
+
392
+ def get_text(key: str, lang: str = DEFAULT_LANG) -> str:
393
+ """
394
+ Get translated text for a key.
395
+
396
+ Falls back to English if key not found in requested language.
397
+ Falls back to key itself if not found in English either.
398
+ """
399
+ if lang not in TRANSLATIONS:
400
+ lang = DEFAULT_LANG
401
+
402
+ translations = TRANSLATIONS[lang]
403
+ if key in translations:
404
+ return translations[key]
405
+
406
+ # Fallback to English
407
+ if lang != DEFAULT_LANG and key in TRANSLATIONS[DEFAULT_LANG]:
408
+ return TRANSLATIONS[DEFAULT_LANG][key]
409
+
410
+ # Fallback to key itself
411
+ return key
412
+
413
+
414
+ def t(key: str, lang: str = DEFAULT_LANG) -> str:
415
+ """Alias for get_text - shorter for convenience."""
416
+ return get_text(key, lang)
417
+
418
+
419
+ def get_supported_languages() -> list:
420
+ """Return list of supported language codes."""
421
+ return SUPPORTED_LANGS.copy()
422
+
423
+
424
+ def is_rtl(lang: str) -> bool:
425
+ """Check if language is right-to-left."""
426
+ return lang.startswith("ar")
rpp/resolver.py ADDED
@@ -0,0 +1,216 @@
1
+ """
2
+ RPP Resolver
3
+
4
+ The resolver translates RPP addresses into routing decisions.
5
+ It returns exactly: allowed (bool), route (str or null), reason (str).
6
+
7
+ This is the core of RPP's bridge architecture - it routes TO storage,
8
+ it does not provide storage itself.
9
+ """
10
+
11
+ from dataclasses import dataclass
12
+ from typing import Optional, Dict, Any, Protocol
13
+ from rpp.address import from_raw, RPPAddress, is_valid_address
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class ResolveResult:
18
+ """
19
+ Result of resolving an RPP address.
20
+
21
+ Attributes:
22
+ allowed: Whether the operation is permitted
23
+ route: Backend route path (null if denied or no route)
24
+ reason: Human-readable explanation
25
+ """
26
+
27
+ allowed: bool
28
+ route: Optional[str]
29
+ reason: str
30
+
31
+ def to_dict(self) -> Dict[str, Any]:
32
+ """Return as JSON-serializable dictionary."""
33
+ return {
34
+ "allowed": self.allowed,
35
+ "route": self.route,
36
+ "reason": self.reason,
37
+ }
38
+
39
+ def to_line(self) -> str:
40
+ """Return as single-line plain text."""
41
+ route_str = self.route if self.route else "null"
42
+ return f"allowed={self.allowed} route={route_str} reason={self.reason}"
43
+
44
+
45
+ class BackendAdapter(Protocol):
46
+ """Protocol for storage backend adapters."""
47
+
48
+ name: str
49
+
50
+ def is_available(self) -> bool:
51
+ """Check if backend is available."""
52
+ ...
53
+
54
+
55
+ class RPPResolver:
56
+ """
57
+ RPP address resolver.
58
+
59
+ Resolves addresses to allow/deny/route decisions based on:
60
+ - Shell (storage tier)
61
+ - Theta (sector - determines consent requirements)
62
+ - Phi (grounding level - determines access restrictions)
63
+ - Registered backend adapters
64
+ """
65
+
66
+ def __init__(self) -> None:
67
+ self._adapters: Dict[int, BackendAdapter] = {}
68
+ self._default_shell_routes = {
69
+ 0: "memory", # Hot: in-memory
70
+ 1: "filesystem", # Warm: local disk
71
+ 2: "archive", # Cold: archive storage
72
+ 3: "glacier", # Frozen: deep archive
73
+ }
74
+
75
+ def register_adapter(self, shell: int, adapter: BackendAdapter) -> None:
76
+ """Register a backend adapter for a shell tier."""
77
+ if not (0 <= shell <= 3):
78
+ raise ValueError(f"Shell must be 0-3, got {shell}")
79
+ self._adapters[shell] = adapter
80
+
81
+ def resolve(
82
+ self,
83
+ address: int,
84
+ operation: str = "read",
85
+ context: Optional[Dict[str, Any]] = None,
86
+ ) -> ResolveResult:
87
+ """
88
+ Resolve an RPP address to a routing decision.
89
+
90
+ Args:
91
+ address: 28-bit RPP address
92
+ operation: "read", "write", "delete"
93
+ context: Optional context (consent level, etc.)
94
+
95
+ Returns:
96
+ ResolveResult with allowed, route, and reason
97
+ """
98
+ context = context or {}
99
+
100
+ # Validate address
101
+ if not is_valid_address(address):
102
+ return ResolveResult(
103
+ allowed=False,
104
+ route=None,
105
+ reason="Invalid address: must be 0-0x0FFFFFFF",
106
+ )
107
+
108
+ # Decode address
109
+ addr = from_raw(address)
110
+
111
+ # Check consent requirements based on phi
112
+ consent_result = self._check_consent(addr, operation, context)
113
+ if consent_result is not None:
114
+ return consent_result
115
+
116
+ # Determine route based on shell
117
+ route = self._get_route(addr)
118
+ if route is None:
119
+ return ResolveResult(
120
+ allowed=False,
121
+ route=None,
122
+ reason=f"No backend available for shell {addr.shell}",
123
+ )
124
+
125
+ # Build full path
126
+ path = self._build_path(addr, route)
127
+
128
+ return ResolveResult(
129
+ allowed=True,
130
+ route=path,
131
+ reason=f"{operation} permitted via {route}",
132
+ )
133
+
134
+ def _check_consent(
135
+ self,
136
+ addr: RPPAddress,
137
+ operation: str,
138
+ context: Dict[str, Any],
139
+ ) -> Optional[ResolveResult]:
140
+ """
141
+ Check consent requirements based on phi (grounding level).
142
+
143
+ Low phi (0-127): Grounded - more accessible
144
+ High phi (384-511): Ethereal - restricted
145
+
146
+ Returns None if consent is sufficient, ResolveResult if denied.
147
+ """
148
+ # Emergency override bypasses all consent checks
149
+ if context.get("emergency_override") is True:
150
+ return None
151
+
152
+ # Very high phi blocks all writes except with emergency override (checked above)
153
+ if addr.phi >= 480 and operation == "write":
154
+ return ResolveResult(
155
+ allowed=False,
156
+ route=None,
157
+ reason=f"Write to high ethereal (phi={addr.phi}) blocked without emergency override",
158
+ )
159
+
160
+ # High phi requires explicit consent for writes
161
+ if addr.phi >= 384 and operation == "write":
162
+ consent = context.get("consent", "none")
163
+ if consent not in ("full", "explicit"):
164
+ return ResolveResult(
165
+ allowed=False,
166
+ route=None,
167
+ reason=f"Write to ethereal zone (phi={addr.phi}) requires explicit consent",
168
+ )
169
+
170
+ return None
171
+
172
+ def _get_route(self, addr: RPPAddress) -> Optional[str]:
173
+ """Get the backend route for an address based on shell."""
174
+ # Check for registered adapter
175
+ if addr.shell in self._adapters:
176
+ adapter = self._adapters[addr.shell]
177
+ if adapter.is_available():
178
+ return adapter.name
179
+
180
+ # Fall back to default route
181
+ return self._default_shell_routes.get(addr.shell)
182
+
183
+ def _build_path(self, addr: RPPAddress, backend: str) -> str:
184
+ """Build the full route path."""
185
+ return f"{backend}://{addr.sector_name.lower()}/{addr.grounding_level.lower()}/{addr.theta}_{addr.phi}_{addr.harmonic}"
186
+
187
+
188
+ # Module-level convenience function
189
+ _default_resolver: Optional[RPPResolver] = None
190
+
191
+
192
+ def get_resolver() -> RPPResolver:
193
+ """Get or create the default resolver instance."""
194
+ global _default_resolver
195
+ if _default_resolver is None:
196
+ _default_resolver = RPPResolver()
197
+ return _default_resolver
198
+
199
+
200
+ def resolve(
201
+ address: int,
202
+ operation: str = "read",
203
+ context: Optional[Dict[str, Any]] = None,
204
+ ) -> ResolveResult:
205
+ """
206
+ Resolve an RPP address using the default resolver.
207
+
208
+ Args:
209
+ address: 28-bit RPP address
210
+ operation: "read", "write", "delete"
211
+ context: Optional context (consent level, etc.)
212
+
213
+ Returns:
214
+ ResolveResult with allowed, route, and reason
215
+ """
216
+ return get_resolver().resolve(address, operation, context)