s-corekit 0.0.1__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.
corekit/__init__.py ADDED
@@ -0,0 +1,168 @@
1
+ """corekit — ОСНОВАНИЕ экосистемы китов. Ноль зависимостей, дешёвый импорт.
2
+
3
+ ФОРМА ГРАФА::
4
+
5
+ clikit adapterkit <- ветки-оболочки
6
+ \\ /
7
+ \\ /
8
+ librarykit <- ОРКЕСТРАТОР (сеть, сессии, браузер)
9
+ |
10
+ corekit <- ОСНОВАНИЕ (значения и правила)
11
+
12
+ Что попало в основание и по какому признаку. Ровно одно: **сущность не тянет
13
+ реализацию**. Здесь живут ЗНАЧЕНИЯ и ЧИСТЫЕ ПРАВИЛА — то, что нужно и тому, кто
14
+ поднимает браузер, и тому, кто просто читает конфиг:
15
+
16
+ * :mod:`corekit.context` — `ExecutionContext`: чей вызов, где его состояние,
17
+ куда он ходит в сеть. ОДИН на экосистему, с синонимами имён обеих школ
18
+ (``tenant``/``tenant_id``, ``profile``/``profile_name``);
19
+ * :mod:`corekit.dto` — `SessionRef` / `Creds` / `RequestBody`;
20
+ * :mod:`corekit.limits` — `QuotaScope` / `QuotaInfo` / `LimitSpec`;
21
+ * :mod:`corekit.enums` — `PaginationMode` / `AuthMode` / `TransportKind`;
22
+ * :mod:`corekit.errors` — ЕДИНАЯ иерархия исключений (`CliError` и потомки);
23
+ * :mod:`corekit.redaction` — маскировка секретов в тексте/заголовках/теле;
24
+ * :mod:`corekit.diagnosis` — таксономия причин (`Reason`) и состояние доступа
25
+ (`AccessState`/`AccessVerdict`) — классификация по УЛИКЕ, без сети.
26
+
27
+ ЧЕГО ЗДЕСЬ НЕТ И НЕ БУДЕТ: httpx, pydantic, cryptography, keyring, playwright —
28
+ никаких зависимостей вообще (``dependencies = []`` в pyproject, проверено
29
+ тестом). Это не аскеза, а условие: основание ставится туда, где чужие колёса
30
+ поставить нельзя.
31
+
32
+ ЦЕНА ИМПОРТА (замер, а не обещание; Python 3.14, Windows, холодный
33
+ интерпретатор, 3 прогона). Пол stdlib, который основание тянет законно
34
+ (``dataclasses``/``pathlib``/``enum``/``re``/``json``/``datetime``) — 77-85 мс на
35
+ этой машине. СОБСТВЕННАЯ цена ``import corekit`` поверх этого пола — 24-30 мс;
36
+ для сравнения ``import librarykit`` поверх того же пола — 151-162 мс. Полный
37
+ импорт: corekit ~105 мс против librarykit ~246 мс. Порог зафиксирован тестом
38
+ ``tests/test_stdlib_only.py`` и меряется ИМЕННО над полом — абсолютное число
39
+ здесь описывает машину, а не код.
40
+
41
+ СОВМЕСТИМОСТЬ. Ни одно имя отсюда не «переехало» для потребителя:
42
+ `librarykit.contract`, `librarykit.errors`, `librarykit.redaction` и
43
+ `librarykit.diagnosis.*` реэкспортируют ТЕ ЖЕ объекты (не копии), поэтому
44
+ ``isinstance``/``except``/``is``-сравнения работают через любой из путей.
45
+ """
46
+ from __future__ import annotations
47
+
48
+ from corekit.context import DEFAULT_TENANT, ExecutionContext
49
+ from corekit.diagnosis import (
50
+ ACCESS_STATE_MESSAGES,
51
+ REASON_MESSAGES,
52
+ REASON_ORDER,
53
+ REASON_RANK,
54
+ AccessState,
55
+ AccessVerdict,
56
+ Reason,
57
+ Verdict,
58
+ classify_access,
59
+ classify_failure,
60
+ describe_egress,
61
+ is_egress_failure,
62
+ is_launch_failure,
63
+ resolve,
64
+ state_from_reason,
65
+ stronger,
66
+ verdict_from_error,
67
+ )
68
+ from corekit.dto import Creds, RequestBody, SessionRef
69
+ from corekit.enums import AuthMode, PaginationMode, RequestExecutionMode, TransportKind
70
+ from corekit.errors import (
71
+ RETRYABLE,
72
+ AdapterError,
73
+ ApiError,
74
+ AuthRequired,
75
+ Blocked,
76
+ CliError,
77
+ GraphQLError,
78
+ NotFound,
79
+ NotFoundError,
80
+ NotImplementedYet,
81
+ ProcessError,
82
+ ProfileBroken,
83
+ RateLimited,
84
+ ServerError,
85
+ SessionEgressMismatch,
86
+ SessionExpired,
87
+ TransportError,
88
+ ValidationError,
89
+ )
90
+ from corekit.limits import LimitSpec, QuotaInfo, QuotaScope
91
+ from corekit.redaction import (
92
+ BODY_FIELD_PATTERNS,
93
+ HEADER_BLACKLIST,
94
+ SECRET_MASK,
95
+ excerpt_response_body,
96
+ sanitize_body,
97
+ sanitize_headers,
98
+ scrub_credentials,
99
+ truncate_body,
100
+ )
101
+
102
+ __version__ = "0.0.1"
103
+
104
+ __all__ = [
105
+ "__version__",
106
+ # контекст исполнения
107
+ "ExecutionContext",
108
+ "DEFAULT_TENANT",
109
+ # базовые DTO
110
+ "SessionRef",
111
+ "Creds",
112
+ "RequestBody",
113
+ # квоты / лимиты
114
+ "QuotaScope",
115
+ "QuotaInfo",
116
+ "LimitSpec",
117
+ # перечисления «талии»
118
+ "PaginationMode",
119
+ "AuthMode",
120
+ "TransportKind",
121
+ "RequestExecutionMode",
122
+ # единая иерархия ошибок
123
+ "CliError",
124
+ "ApiError",
125
+ "AuthRequired",
126
+ "SessionExpired",
127
+ "SessionEgressMismatch",
128
+ "ProfileBroken",
129
+ "RateLimited",
130
+ "ValidationError",
131
+ "NotImplementedYet",
132
+ "ProcessError",
133
+ "AdapterError",
134
+ "TransportError",
135
+ "NotFound",
136
+ "NotFoundError",
137
+ "Blocked",
138
+ "ServerError",
139
+ "GraphQLError",
140
+ "RETRYABLE",
141
+ # диагностика: причина захода + состояние доступа
142
+ "Reason",
143
+ "Verdict",
144
+ "REASON_ORDER",
145
+ "REASON_RANK",
146
+ "REASON_MESSAGES",
147
+ "resolve",
148
+ "stronger",
149
+ "AccessState",
150
+ "AccessVerdict",
151
+ "ACCESS_STATE_MESSAGES",
152
+ "classify_access",
153
+ "state_from_reason",
154
+ "classify_failure",
155
+ "describe_egress",
156
+ "is_egress_failure",
157
+ "is_launch_failure",
158
+ "verdict_from_error",
159
+ # редакция секретов
160
+ "SECRET_MASK",
161
+ "HEADER_BLACKLIST",
162
+ "BODY_FIELD_PATTERNS",
163
+ "sanitize_headers",
164
+ "sanitize_body",
165
+ "truncate_body",
166
+ "excerpt_response_body",
167
+ "scrub_credentials",
168
+ ]
corekit/context.py ADDED
@@ -0,0 +1,268 @@
1
+ """ExecutionContext — ЕДИНЫЙ контекст одного вызова (ОСНОВАНИЕ экосистемы).
2
+
3
+ Здесь живёт КАНОН контекста всей экосистемы китов. Модуль намеренно НИЧЕГО не
4
+ знает про носитель (`contextvars`), про
5
+ HTTP-клиент и про пути: он даёт ЗНАЧЕНИЕ. Носитель и резолверы ресурсов остаются
6
+ слоем выше (`librarykit.context`) — иначе основание потянуло бы за собой сеть и
7
+ файловую систему.
8
+
9
+ ТОЛЬКО stdlib: ``dataclasses`` + ``pathlib`` + ``typing``. Ни httpx, ни pydantic,
10
+ ни platformdirs.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from collections.abc import Callable, Mapping
15
+ from dataclasses import FrozenInstanceError, dataclass, field, replace
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ __all__ = [
20
+ "DEFAULT_TENANT",
21
+ "ExecutionContext",
22
+ ]
23
+
24
+ #: Идентификатор ЕДИНСТВЕННОГО тенанта в локальном (CLI) режиме. Дефолт всех
25
+ #: контекстов и `SessionRef`: пока он не изменён, раскладка путей и индекс
26
+ #: байт-в-байт совпадают с тем, что было до введения мультитенантности.
27
+ DEFAULT_TENANT = "local"
28
+
29
+
30
+ #: Сентинел «аргумент не передан» для `ExecutionContext.__init__`: отличает
31
+ #: «поле оставили по умолчанию» от «поле передали, и значение совпало с дефолтом».
32
+ #: Без него нельзя решить конфликт синонимов (`tenant` vs `tenant_id`) честно.
33
+ _UNSET: Any = object()
34
+
35
+
36
+ def _pick_alias(
37
+ canonical_name: str, canonical: Any, alias_name: str, alias: Any, default: Any
38
+ ) -> Any:
39
+ """Значение поля, заданного одним из двух синонимичных имён.
40
+
41
+ Правило простое и без «победителей»: задано одно — берём его; задано оба и
42
+ они РАЗНЫЕ — `TypeError`. Молчаливый выбор одного из двух — ровно та ошибка,
43
+ из-за которой мост clikit↔librarykit терял тенанта (см. `ExecutionContext`).
44
+ """
45
+ has_canonical = canonical is not _UNSET
46
+ has_alias = alias is not None
47
+ if has_canonical and has_alias and canonical != alias:
48
+ raise TypeError(
49
+ f"ExecutionContext: {canonical_name}={canonical!r} и {alias_name}={alias!r} — "
50
+ f"это одно и то же поле под двумя именами, но значения разные. "
51
+ f"Передай что-то одно."
52
+ )
53
+ if has_canonical:
54
+ return canonical
55
+ if has_alias:
56
+ return alias
57
+ return default
58
+
59
+
60
+ @dataclass(frozen=True, slots=True, init=False)
61
+ class ExecutionContext:
62
+ """Контекст ОДНОГО вызова: чей он, где его состояние, куда он ходит в сеть.
63
+
64
+ Ядро мультитенантности (Этап 2). До него всё это текло через ``os.environ`` и
65
+ модульные global'ы — а значит, было ПРОЦЕССНЫМ: два тенанта в одном хосте
66
+ (или две корутины) переписывали друг другу профиль, ``{BRAND}_HOME``,
67
+ ``ALL_PROXY`` и флаг раскрытия секретов. `ExecutionContext` делает эти
68
+ величины ЯВНЫМИ, а скоупит их по `contextvars` слой выше (носитель —
69
+ :mod:`librarykit.context`), а не по процессу.
70
+
71
+ Поля:
72
+
73
+ - ``tenant_id`` — владелец вызова; в CLI ровно один — ``"local"``
74
+ (`DEFAULT_TENANT`), и тогда все раскладки/пути совпадают с досегодняшними;
75
+ - ``profile_name`` — профиль (набор сессий) внутри тенанта;
76
+ - ``home_dir`` — КОРЕНЬ состояния тенанта (вместо чтения ``{BRAND}_HOME``
77
+ в глубине кода);
78
+ - ``proxy`` — egress ИМЕННО ЭТОГО вызова (вместо ``ALL_PROXY`` /
79
+ ``install_opener`` на весь процесс); передаётся аргументом в транспорт;
80
+ - ``output_mode`` — режим вывода (``"json"``/``"text"``) для CLI-границы;
81
+ - ``reveal_secrets`` — раскрывать ли секреты в выводе (вместо global ``_REVEAL``);
82
+ - ``deadline`` — абсолютный дедлайн вызова, ``time.monotonic()``-шкала
83
+ (вместо global ``_DEADLINE``); ``None`` = без дедлайна;
84
+ - ``trace_id`` — сквозной идентификатор вызова для логов/трейсинга;
85
+ - ``brand`` — имя продукта/CLI, под которым контекст собран: по нему
86
+ потребитель понимает, «его» ли это контекст (``clikit.context.context_for``),
87
+ и не подхватывает чужой профиль. ``""`` = нейтральный, ничей;
88
+ - ``redactor`` — функция редакции данных вывода (``None`` — не
89
+ редактировать). РЕСУРС-исключение: живёт в контексте, потому что решение
90
+ «маскировать ли» принимается на границе вывода, а не в глубине;
91
+ - ``extra`` — произвольные поля потребителя, ядром не
92
+ интерпретируются.
93
+
94
+ **ЕДИНСТВЕННОСТЬ.** Это ОДИН на экосистему `ExecutionContext`.
95
+ Раньше их было два — свой у `clikit` (``brand``/``tenant``/``profile``/
96
+ ``redactor``/``extra``) плюс мост, который переносил значения по таблице
97
+ синонимов и молча терял ровно то, ради чего существовал. Здесь тот и другой
98
+ набор полей сведены в один тип, а ИМЕНА обеих школ поддержаны как синонимы:
99
+
100
+ ================== ================== ===============================
101
+ канон (corekit) синоним (clikit) где принимается
102
+ ================== ================== ===============================
103
+ ``tenant_id`` ``tenant`` конструктор, `evolve`, чтение
104
+ ``profile_name`` ``profile`` конструктор, `evolve`, чтение
105
+ ================== ================== ===============================
106
+
107
+ Синоним — не второе поле, а другое ИМЯ того же (``ctx.tenant is
108
+ ctx.tenant_id``). Передать оба разом можно, только если значения совпадают:
109
+ иначе `TypeError` — тихо выбирать «победителя» тут нельзя, это и была
110
+ исходная болезнь моста.
111
+
112
+ ``profile_name`` по умолчанию — ПУСТАЯ строка, а не ``"default"``: пустое
113
+ значит «профиль не выбран», и потребитель волен подставить свой дефолт
114
+ (`clikit.config.active_profile` по пустому уходит в общий конфиг-каталог, а
115
+ не в ``profiles/default/``). Свойство ``profile`` отдаёт такое значение как
116
+ ``None`` — форма, к которой привык clikit.
117
+
118
+ ``frozen=True, slots=True`` — как `SessionRef`/`Endpoint`: объект неизменяем,
119
+ поэтому его безопасно шарить между потоками и задачами (Этап 1), а «изменение»
120
+ делается порождением потомка (``dataclasses.replace`` →
121
+ :func:`librarykit.context.derive_context`).
122
+
123
+ .. note:: **Почему тут НЕТ поля ``http_client``.**
124
+
125
+ DTO ядра — ЗНАЧЕНИЕ: неизменяемое, хешируемое, дешёвое для копирования,
126
+ безопасное для логов и для передачи между потоками. HTTP-клиент — РЕСУРС с
127
+ жизненным циклом (пул соединений, привязка к event loop, обязательный
128
+ ``aclose``). Положив его полем, мы бы: (1) сделали ``replace()`` источником
129
+ утечки сокетов (клон разделяет пул, но никто не владеет закрытием);
130
+ (2) привязали контекст к конкретному event loop, хотя контекст живёт и в
131
+ потоках; (3) сломали дешёвое копирование и сериализацию контекста.
132
+ Поэтому клиент РЕЗОЛВИТСЯ ПО контексту, а не хранится В нём: см.
133
+ :func:`librarykit.context.http_client_for_context` (реестр «ключ контекста
134
+ → клиент» с явным :func:`librarykit.context.aclose_context_clients`) и
135
+ :func:`librarykit.context.transport_kwargs`.
136
+ """
137
+
138
+ tenant_id: str = DEFAULT_TENANT
139
+ profile_name: str = ""
140
+ home_dir: Path | None = field(default_factory=Path.home)
141
+ proxy: str | None = None
142
+ output_mode: str = "json"
143
+ reveal_secrets: bool = False
144
+ deadline: float | None = None
145
+ trace_id: str = ""
146
+ brand: str = ""
147
+ redactor: Callable[[Any], Any] | None = None
148
+ extra: Mapping[str, Any] = field(default_factory=dict)
149
+
150
+ def __init__(
151
+ self,
152
+ tenant_id: str = _UNSET,
153
+ profile_name: str = _UNSET,
154
+ home_dir: Path | None = _UNSET,
155
+ proxy: str | None = None,
156
+ output_mode: str = "json",
157
+ reveal_secrets: bool = False,
158
+ deadline: float | None = None,
159
+ trace_id: str = "",
160
+ brand: str = "",
161
+ redactor: Callable[[Any], Any] | None = None,
162
+ extra: Mapping[str, Any] | None = None,
163
+ *,
164
+ tenant: str | None = None,
165
+ profile: str | None = None,
166
+ ) -> None:
167
+ """Собрать контекст; ``tenant``/``profile`` — синонимы канонических имён.
168
+
169
+ Порядок ПОЗИЦИОННЫХ параметров исторический, а новые поля
170
+ (``brand``/``redactor``/``extra``) добавлены В КОНЕЦ — поэтому и
171
+ ``ExecutionContext("acme", "main")``, и любой прежний вызов по именам
172
+ продолжают значить ровно то же самое.
173
+ """
174
+ object.__setattr__(
175
+ self, "tenant_id", _pick_alias("tenant_id", tenant_id, "tenant", tenant, DEFAULT_TENANT)
176
+ )
177
+ object.__setattr__(
178
+ self, "profile_name", _pick_alias("profile_name", profile_name, "profile", profile, "")
179
+ )
180
+ object.__setattr__(
181
+ self, "home_dir", Path.home() if home_dir is _UNSET else home_dir
182
+ )
183
+ object.__setattr__(self, "proxy", proxy)
184
+ object.__setattr__(self, "output_mode", output_mode)
185
+ object.__setattr__(self, "reveal_secrets", reveal_secrets)
186
+ object.__setattr__(self, "deadline", deadline)
187
+ object.__setattr__(self, "trace_id", trace_id)
188
+ object.__setattr__(self, "brand", brand)
189
+ object.__setattr__(self, "redactor", redactor)
190
+ object.__setattr__(self, "extra", {} if extra is None else extra)
191
+
192
+ # --- синонимы имён (clikit-школа): ЧТЕНИЕ того же поля, не второе поле --- #
193
+ @property
194
+ def tenant(self) -> str:
195
+ """Синоним `tenant_id` (имя из clikit)."""
196
+ return self.tenant_id
197
+
198
+ @property
199
+ def profile(self) -> str | None:
200
+ """Синоним `profile_name`; пустое значение отдаётся как ``None``."""
201
+ return self.profile_name or None
202
+
203
+ def evolve(self, **changes: Any) -> ExecutionContext:
204
+ """Потомок с изменёнными полями (принимает и синонимы ``tenant``/``profile``).
205
+
206
+ То же, что `dataclasses.replace`, но понимает clikit-имена: ``replace``
207
+ работает строго по именам полей и на ``profile=`` упал бы.
208
+ """
209
+ for alias, canonical in (("tenant", "tenant_id"), ("profile", "profile_name")):
210
+ if alias in changes:
211
+ value = changes.pop(alias)
212
+ changes[canonical] = "" if value is None and canonical == "profile_name" else value
213
+ return replace(self, **changes)
214
+
215
+ def __hash__(self) -> int:
216
+ """Хеш по ЗНАЧИМЫМ координатам вызова (без ``redactor``/``extra``).
217
+
218
+ Сгенерированный dataclass-хеш взял бы ВСЕ поля и падал бы на ``extra``
219
+ (обычный ``dict`` не хешируется), а `ExecutionContext` обязан оставаться
220
+ хешируемым: он ложится в ключи реестров (см.
221
+ `librarykit.context.http_client_for_context`) и в множества в тестах.
222
+ Опущены ровно два поля-«довеска»: функция редакции и мешок потребителя —
223
+ они не меняют, ЧЕЙ это вызов и куда он ходит.
224
+ """
225
+ return hash(
226
+ (
227
+ self.tenant_id,
228
+ self.profile_name,
229
+ self.home_dir,
230
+ self.proxy,
231
+ self.output_mode,
232
+ self.reveal_secrets,
233
+ self.deadline,
234
+ self.trace_id,
235
+ self.brand,
236
+ )
237
+ )
238
+
239
+
240
+ def _seal_frozen(cls: type) -> type:
241
+ """Сделать «заморозку» полной: присвоение ЛЮБОГО имени → `FrozenInstanceError`.
242
+
243
+ Ловушка CPython (проверено на 3.14): у ``@dataclass(frozen=True, slots=True)``
244
+ сгенерированный ``__setattr__`` замыкается на ИСХОДНЫЙ класс, а ``slots=True``
245
+ затем создаёт НОВЫЙ. Поэтому ветка «не поле — отдай родителю» уходит в
246
+ ``super(старый_класс, self)`` и вылетает
247
+ ``TypeError: super(type, obj): obj is not an instance…`` вместо внятного
248
+ отказа. Присвоение полю (``ctx.tenant_id = …``) отрабатывает верно — падает
249
+ только присвоение ИМЕНИ ВНЕ полей, а у `ExecutionContext` такие имена
250
+ появились: синонимы-свойства ``tenant``/``profile``.
251
+
252
+ Ставим свои ``__setattr__``/``__delattr__``: единый внятный отказ для всего.
253
+ Инициализация от этого не страдает — она идёт через `object.__setattr__`
254
+ (см. ``__init__``), а не через класс.
255
+ """
256
+
257
+ def _no_setattr(self: Any, name: str, value: Any) -> None:
258
+ raise FrozenInstanceError(f"cannot assign to field {name!r}")
259
+
260
+ def _no_delattr(self: Any, name: str) -> None:
261
+ raise FrozenInstanceError(f"cannot delete field {name!r}")
262
+
263
+ cls.__setattr__ = _no_setattr # type: ignore[method-assign, assignment]
264
+ cls.__delattr__ = _no_delattr # type: ignore[method-assign, assignment]
265
+ return cls
266
+
267
+
268
+ _seal_frozen(ExecutionContext)
@@ -0,0 +1,103 @@
1
+ """ЧИСТАЯ диагностика доступа: таксономия причин + классификация по УЛИКАМ.
2
+
3
+ Что здесь есть и почему это ОСНОВАНИЕ, а не «утиль»:
4
+
5
+ * :mod:`corekit.diagnosis.verdict` — таксономия причин (`Reason`/`Verdict`) и
6
+ ПОРЯДОК их разбора как ДАННЫЕ (``REASON_ORDER``), а не как проза в комментариях;
7
+ * :mod:`corekit.diagnosis.egress` — разбор ТЕКСТА ошибки: «лёг выход» против
8
+ «сервер спрашивает, кто ты» против «браузер не поднялся»;
9
+ * :mod:`corekit.diagnosis.access` — состояние ДОСТУПА (`AccessState`/
10
+ `AccessVerdict`): что именно мертво — сессия, эндпоинт или инфраструктура.
11
+
12
+ Ни один из трёх не ходит в сеть, не поднимает браузер и не читает файлы: на вход
13
+ им дают статус/тело/URL/текст исключения, на выход они дают вердикт С УЛИКОЙ.
14
+ Именно поэтому они годятся в основание — их можно позвать откуда угодно, включая
15
+ процесс, где нет ни httpx, ни playwright.
16
+
17
+ .. note:: Ссылки вида ``librarykit.diagnosis.geo`` / ``librarykit.diagnosis.landing``
18
+ в докстрингах ниже — указатели на слои ВЫШЕ (кто этими примитивами
19
+ пользуется и что достраивает). Импортов туда нет и быть не может: контракт
20
+ ``0a`` в `import-linter` роняет прогон на первой же такой строке.
21
+ """
22
+ from __future__ import annotations
23
+
24
+ from corekit.diagnosis.access import (
25
+ ACCESS_STATE_MESSAGES,
26
+ ENDPOINT_DEAD_MARKERS,
27
+ LOGIN_HOST_MARKERS,
28
+ SESSION_DEAD_MARKERS,
29
+ TOKEN_EXPIRED_MARKERS,
30
+ AccessState,
31
+ AccessVerdict,
32
+ classify_access,
33
+ state_from_reason,
34
+ )
35
+ from corekit.diagnosis.egress import (
36
+ AUTH_MARKERS,
37
+ AUTH_STATUS_CODES,
38
+ AUTH_TIMEOUT_PHRASES,
39
+ EGRESS_MARKERS,
40
+ EGRESS_STATUS_CODES,
41
+ KIND_AUTH,
42
+ KIND_EGRESS,
43
+ KIND_UNKNOWN,
44
+ LAUNCH_FAILURE_MARKERS,
45
+ LAYER_NAME_MARKERS,
46
+ PROXY_AUTH_PHRASES,
47
+ WEAK_EGRESS_MARKERS,
48
+ classify_failure,
49
+ describe_egress,
50
+ is_egress_failure,
51
+ is_launch_failure,
52
+ probe_egress_alive,
53
+ verdict_from_error,
54
+ )
55
+ from corekit.diagnosis.verdict import (
56
+ REASON_MESSAGES,
57
+ REASON_ORDER,
58
+ REASON_RANK,
59
+ Reason,
60
+ Verdict,
61
+ resolve,
62
+ stronger,
63
+ )
64
+
65
+ __all__ = [
66
+ # taxonomy: причины, порядок, вердикт
67
+ "Reason",
68
+ "Verdict",
69
+ "REASON_ORDER",
70
+ "REASON_RANK",
71
+ "REASON_MESSAGES",
72
+ "resolve",
73
+ "stronger",
74
+ # access: ЧТО мертво — сессия / эндпоинт / инфраструктура
75
+ "AccessState",
76
+ "AccessVerdict",
77
+ "ACCESS_STATE_MESSAGES",
78
+ "ENDPOINT_DEAD_MARKERS",
79
+ "LOGIN_HOST_MARKERS",
80
+ "SESSION_DEAD_MARKERS",
81
+ "TOKEN_EXPIRED_MARKERS",
82
+ "classify_access",
83
+ "state_from_reason",
84
+ # egress: транспорт vs авторизация vs «движок не поднялся»
85
+ "KIND_AUTH",
86
+ "KIND_EGRESS",
87
+ "KIND_UNKNOWN",
88
+ "AUTH_MARKERS",
89
+ "AUTH_STATUS_CODES",
90
+ "AUTH_TIMEOUT_PHRASES",
91
+ "EGRESS_MARKERS",
92
+ "EGRESS_STATUS_CODES",
93
+ "LAUNCH_FAILURE_MARKERS",
94
+ "LAYER_NAME_MARKERS",
95
+ "PROXY_AUTH_PHRASES",
96
+ "WEAK_EGRESS_MARKERS",
97
+ "classify_failure",
98
+ "describe_egress",
99
+ "is_egress_failure",
100
+ "is_launch_failure",
101
+ "probe_egress_alive",
102
+ "verdict_from_error",
103
+ ]