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/__init__.py +60 -0
- rpp/adapters/__init__.py +14 -0
- rpp/adapters/filesystem.py +151 -0
- rpp/adapters/memory.py +95 -0
- rpp/address.py +249 -0
- rpp/cli.py +608 -0
- rpp/i18n.py +426 -0
- rpp/resolver.py +216 -0
- rpp/visual.py +389 -0
- rpp_protocol-0.1.5.dist-info/METADATA +390 -0
- rpp_protocol-0.1.5.dist-info/RECORD +14 -0
- rpp_protocol-0.1.5.dist-info/WHEEL +4 -0
- rpp_protocol-0.1.5.dist-info/entry_points.txt +2 -0
- rpp_protocol-0.1.5.dist-info/licenses/LICENSE +219 -0
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)
|