s-orchestrationkit 0.1.0__tar.gz
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.
- s_orchestrationkit-0.1.0/.gitignore +10 -0
- s_orchestrationkit-0.1.0/.gitlab-ci.yml +101 -0
- s_orchestrationkit-0.1.0/.importlinter +69 -0
- s_orchestrationkit-0.1.0/.skillgateignore +12 -0
- s_orchestrationkit-0.1.0/CHANGELOG.md +18 -0
- s_orchestrationkit-0.1.0/LICENSE +21 -0
- s_orchestrationkit-0.1.0/PKG-INFO +67 -0
- s_orchestrationkit-0.1.0/README.md +56 -0
- s_orchestrationkit-0.1.0/orchestrationkit/__init__.py +47 -0
- s_orchestrationkit-0.1.0/orchestrationkit/entities.py +176 -0
- s_orchestrationkit-0.1.0/orchestrationkit/enums.py +79 -0
- s_orchestrationkit-0.1.0/orchestrationkit/fanout.py +181 -0
- s_orchestrationkit-0.1.0/orchestrationkit/orchestration/__init__.py +43 -0
- s_orchestrationkit-0.1.0/orchestrationkit/orchestration/health.py +428 -0
- s_orchestrationkit-0.1.0/orchestrationkit/orchestration/onboarding.py +473 -0
- s_orchestrationkit-0.1.0/orchestrationkit/orchestration/session_loader.py +121 -0
- s_orchestrationkit-0.1.0/orchestrationkit/ports.py +117 -0
- s_orchestrationkit-0.1.0/orchestrationkit/protocols.py +183 -0
- s_orchestrationkit-0.1.0/pyproject.toml +63 -0
- s_orchestrationkit-0.1.0/scripts/check_dist_leaks.py +180 -0
- s_orchestrationkit-0.1.0/scripts/release_guard.py +235 -0
- s_orchestrationkit-0.1.0/tests/__init__.py +0 -0
- s_orchestrationkit-0.1.0/tests/conftest.py +34 -0
- s_orchestrationkit-0.1.0/tests/test_fanout.py +188 -0
- s_orchestrationkit-0.1.0/tests/test_orchestration.py +757 -0
- s_orchestrationkit-0.1.0/uv.lock +334 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# СГЕНЕРИРОВАНО из devcontour/tools/kits/template/gitlab-ci.yml — не правь здесь.
|
|
2
|
+
# Правка идёт в шаблон, затем `python tools/kits/apply_template.py <кит>`.
|
|
3
|
+
# Сверка: `python tools/kits/apply_template.py <кит> --check` (код 1 при расхождении).
|
|
4
|
+
#
|
|
5
|
+
# Автопубликация на PyPI по семвер-тегу vX.Y.Z через Trusted Publishing (OIDC).
|
|
6
|
+
# Три стадии, и это не украшение: версии на PyPI неизменяемы, и шесть китов
|
|
7
|
+
# уезжали туда без единого теста (browserkit 0.0.18, netkit ≤0.0.12 — оба в бою).
|
|
8
|
+
# gates — тесты, линтер, слои, doctor: на каждый push и на тег
|
|
9
|
+
# release-guard — тег == версия во всех местах (pyproject/uv.lock/CHANGELOG)
|
|
10
|
+
# publish-pypi — только после двух первых; версия, уже лежащая на PyPI, не
|
|
11
|
+
# публикуется повторно (тег «догоняет» выпуск, а не роняет CI);
|
|
12
|
+
# после сборки и до обмена токена — check_dist_leaks.py
|
|
13
|
+
# (личные данные разработчика в собранном дистрибутиве)
|
|
14
|
+
# Токены НИГДЕ не хранятся: GitLab выдаёт короткоживущий OIDC-токен (PYPI_ID_TOKEN),
|
|
15
|
+
# он одноразово обменивается на PyPI API-токен. Издатель настраивается на pypi.org:
|
|
16
|
+
# Manage -> Publishing -> GitLab (namespace=S-kits, project=orchestrationkit,
|
|
17
|
+
# top-level pipeline file=.gitlab-ci.yml, environment — пусто).
|
|
18
|
+
# Дистрибутив на PyPI: s-orchestrationkit.
|
|
19
|
+
stages: [test, guard, publish]
|
|
20
|
+
|
|
21
|
+
.uv:
|
|
22
|
+
image: ghcr.io/astral-sh/uv:python3.12-bookworm
|
|
23
|
+
|
|
24
|
+
gates:
|
|
25
|
+
extends: .uv
|
|
26
|
+
stage: test
|
|
27
|
+
rules:
|
|
28
|
+
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
|
29
|
+
- if: $CI_PIPELINE_SOURCE == "push"
|
|
30
|
+
script:
|
|
31
|
+
# dev-зависимости у китов объявлены двумя способами — extra `dev` и группа
|
|
32
|
+
# `[dependency-groups] dev`; --all-extras покрывает первое, группу добавляем
|
|
33
|
+
# только если она объявлена, иначе uv падает «Group dev is not defined».
|
|
34
|
+
- uv sync --all-extras $(python3 -c "import tomllib;print('--group dev' if 'dev' in tomllib.load(open('pyproject.toml','rb')).get('dependency-groups',{}) else '')")
|
|
35
|
+
- uv run pytest tests/ -q
|
|
36
|
+
# Линтер по ВСЕМУ пакету, а не по списку файлов: сужение до «файлов волны 0»
|
|
37
|
+
# держалось девять китов и ни разу не было расширено — сужение приживается,
|
|
38
|
+
# расширение нет.
|
|
39
|
+
- uv run ruff check .
|
|
40
|
+
# Границы слоёв: контракт в .importlinter (ставится тем же шаблоном).
|
|
41
|
+
# — доп. пакеты через --with (пусто у большинства
|
|
42
|
+
# китов; adapterkit — пример кита, которому нужен сосед на прогоне
|
|
43
|
+
# lint-imports, см. apply_template.py --extra-with).
|
|
44
|
+
- uv run --with import-linter lint-imports
|
|
45
|
+
# Гейт 1, часть «согласованность локальных venv» (kitsctl doctor). Скрипт
|
|
46
|
+
# раздаётся шаблоном не всем: в раннере видно ОДИН репозиторий, соседних
|
|
47
|
+
# venv'ов там нет, — поэтому шаг условный и молча пропускается, когда
|
|
48
|
+
# инструмента у кита нет. Настоящее место doctor — локальный прогон перед
|
|
49
|
+
# тегом; см. docs/kits/SAFETY-NET.md, «Известные ограничения».
|
|
50
|
+
- 'if [ -f scripts/kitsctl.py ]; then python3 scripts/kitsctl.py doctor; else echo "kitsctl doctor — инструмента нет в репозитории, пропуск (см. SAFETY-NET.md)"; fi'
|
|
51
|
+
|
|
52
|
+
release-guard:
|
|
53
|
+
extends: .uv
|
|
54
|
+
stage: guard
|
|
55
|
+
needs: [gates]
|
|
56
|
+
rules:
|
|
57
|
+
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
|
58
|
+
script:
|
|
59
|
+
- python3 scripts/release_guard.py "$CI_COMMIT_TAG"
|
|
60
|
+
|
|
61
|
+
publish-pypi:
|
|
62
|
+
extends: .uv
|
|
63
|
+
stage: publish
|
|
64
|
+
needs: [release-guard]
|
|
65
|
+
rules:
|
|
66
|
+
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
|
67
|
+
id_tokens:
|
|
68
|
+
PYPI_ID_TOKEN:
|
|
69
|
+
aud: pypi
|
|
70
|
+
script:
|
|
71
|
+
- |
|
|
72
|
+
DIST=$(python3 -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['name'])")
|
|
73
|
+
VER="${CI_COMMIT_TAG#v}"
|
|
74
|
+
if python3 - "$DIST" "$VER" <<'PY'
|
|
75
|
+
import json, sys, urllib.request
|
|
76
|
+
dist, ver = sys.argv[1], sys.argv[2]
|
|
77
|
+
try:
|
|
78
|
+
data = json.load(urllib.request.urlopen(f"https://pypi.org/pypi/{dist}/json"))
|
|
79
|
+
except Exception:
|
|
80
|
+
sys.exit(1)
|
|
81
|
+
sys.exit(0 if ver in data.get("releases", {}) else 1)
|
|
82
|
+
PY
|
|
83
|
+
then echo "версия $VER уже на PyPI — публикацию пропускаем"; exit 0; fi
|
|
84
|
+
- uv build
|
|
85
|
+
# Последний рубеж перед необратимым шагом (версии на PyPI неизменяемы,
|
|
86
|
+
# yank — не удаление). Прецедент — leasekit: реальный email и домашний
|
|
87
|
+
# путь разработчика уехали в публичный индекс из тестовых фикстур. Ловит
|
|
88
|
+
# по классам, не по строкам; см. docs/kits/SAFETY-NET.md.
|
|
89
|
+
- python3 scripts/check_dist_leaks.py dist/
|
|
90
|
+
- |
|
|
91
|
+
export UV_PUBLISH_TOKEN=$(python3 - <<'PY'
|
|
92
|
+
import json, os, urllib.request
|
|
93
|
+
req = urllib.request.Request(
|
|
94
|
+
"https://pypi.org/_/oidc/mint-token",
|
|
95
|
+
data=json.dumps({"token": os.environ["PYPI_ID_TOKEN"]}).encode(),
|
|
96
|
+
headers={"Content-Type": "application/json"},
|
|
97
|
+
)
|
|
98
|
+
print(json.load(urllib.request.urlopen(req))["token"])
|
|
99
|
+
PY
|
|
100
|
+
)
|
|
101
|
+
- uv publish
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# СГЕНЕРИРОВАНО из devcontour/tools/kits/template/importlinter.ini — не правь
|
|
2
|
+
# здесь. Правка идёт в шаблон, затем `python tools/kits/apply_template.py <кит>`.
|
|
3
|
+
#
|
|
4
|
+
# Заготовка контракта слоёв. Запуск: `uv run --with import-linter lint-imports`.
|
|
5
|
+
#
|
|
6
|
+
# Контракт ниже — минимум, который годится ЛЮБОМУ киту и не требует ручной
|
|
7
|
+
# настройки: «orchestrationkit не импортирует те киты экосистемы, которых нет в его
|
|
8
|
+
# объявленных зависимостях». Скрытая зависимость — это кит, который работает у
|
|
9
|
+
# автора (сосед стоит в venv) и разваливается у потребителя на первом импорте;
|
|
10
|
+
# именно так ломались pypi-publish и gemini-chat.
|
|
11
|
+
#
|
|
12
|
+
# Список forbidden_modules подставлен установщиком из реестра китов на момент
|
|
13
|
+
# установки: все известные пакеты китов МИНУС собственный МИНУС объявленные в
|
|
14
|
+
# pyproject зависимости. Появилась новая честная зависимость — объяви её в
|
|
15
|
+
# pyproject и переустанови шаблон, а не правь этот файл.
|
|
16
|
+
#
|
|
17
|
+
# Собственные контракты кита (layers/eager_forbidden/declared_dependencies,
|
|
18
|
+
# как у adapterkit) дописываются НИЖЕ отдельными секциями прямо в этом файле.
|
|
19
|
+
# Установщик сверяет файл целиком, поэтому `--check` после такой правки
|
|
20
|
+
# закономерно покажет расхождение с каноном — это ожидаемо и осознанно
|
|
21
|
+
# игнорируется в отчёте ревью, см. docs/kits/SAFETY-NET.md, «Что делать при
|
|
22
|
+
# расхождении» (п.2).
|
|
23
|
+
|
|
24
|
+
[importlinter]
|
|
25
|
+
# include_external_packages нужен: forbidden_modules перечисляет пакеты,
|
|
26
|
+
# внешние относительно root_packages.
|
|
27
|
+
include_external_packages = True
|
|
28
|
+
root_packages =
|
|
29
|
+
orchestrationkit
|
|
30
|
+
exclude_type_checking_imports = True
|
|
31
|
+
|
|
32
|
+
[importlinter:contract:no-undeclared-kits]
|
|
33
|
+
name = orchestrationkit не импортирует киты, которых нет в его зависимостях
|
|
34
|
+
type = forbidden
|
|
35
|
+
source_modules =
|
|
36
|
+
orchestrationkit
|
|
37
|
+
forbidden_modules =
|
|
38
|
+
accountpoolkit
|
|
39
|
+
adapterkit
|
|
40
|
+
agentskit
|
|
41
|
+
aichatkit
|
|
42
|
+
authkit_client
|
|
43
|
+
authkit_contracts
|
|
44
|
+
authkit_server
|
|
45
|
+
b24kit
|
|
46
|
+
browserkit
|
|
47
|
+
chatkit
|
|
48
|
+
clientkit
|
|
49
|
+
clikit
|
|
50
|
+
emojikit
|
|
51
|
+
gitkit
|
|
52
|
+
installerkit
|
|
53
|
+
iokit
|
|
54
|
+
issuekit
|
|
55
|
+
leasekit
|
|
56
|
+
librarykit
|
|
57
|
+
maxkit
|
|
58
|
+
modelkit
|
|
59
|
+
netkit
|
|
60
|
+
ormkit
|
|
61
|
+
persistkit
|
|
62
|
+
servicekit
|
|
63
|
+
sessionkit
|
|
64
|
+
skillkit
|
|
65
|
+
socialkit
|
|
66
|
+
storagekit
|
|
67
|
+
telemetrykit
|
|
68
|
+
totp_client
|
|
69
|
+
worker_service
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Исключения для секрет-скана s-skillgate перед push. Глобы — от корня репо.
|
|
2
|
+
#
|
|
3
|
+
# scripts/check_dist_leaks.py — сам гейт утечки личных данных (сетка
|
|
4
|
+
# безопасности китов, docs/kits/SAFETY-NET.md в devcontour), копия из
|
|
5
|
+
# tools/kits/template/check_dist_leaks.py. Ловит по классам, не по строкам:
|
|
6
|
+
# windows-путь с числовым именем пользователя, личный ящик на публичном
|
|
7
|
+
# провайдере, юникс-домашний путь. Regex-паттерны и пример подсказки
|
|
8
|
+
# (windows-путь с числовым account, «/home/user») по определению совпадают
|
|
9
|
+
# с собственным денилистом — иначе гейт не проверял бы то, ради чего написан (та же
|
|
10
|
+
# ситуация, что и с tests/golden/ у самого check_dist_leaks.py). Файл
|
|
11
|
+
# копируется буквально из канона — правки идут в шаблон, не сюда.
|
|
12
|
+
scripts/check_dist_leaks.py
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Формат — [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/).
|
|
4
|
+
|
|
5
|
+
## [0.1.0] — 2026-09-08
|
|
6
|
+
|
|
7
|
+
Первый выпуск. Волна 5 платформы китов (Atlas #2710, Ruling 5-3): связный
|
|
8
|
+
кластер оркестрации выделен из `librarykit` (`orchestration/*`, `entities.py`,
|
|
9
|
+
`ports.py`, `enums.py`, `protocols.py`, `fanout.py`, 1 763 строки) — три
|
|
10
|
+
внешних потребителя (`adapterkit`, `gws`, `bublictr`).
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `orchestrationkit.orchestration` — `SessionLoader`, `HealthMonitor`,
|
|
15
|
+
`OnboardingService`/`OnboardingDeps`.
|
|
16
|
+
- `orchestrationkit.entities`, `orchestrationkit.ports`, `orchestrationkit.enums`,
|
|
17
|
+
`orchestrationkit.protocols`, `orchestrationkit.fanout`.
|
|
18
|
+
- Сетка безопасности (`apply_template.py . --namespace S-kits`).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dmitry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: s-orchestrationkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Развязанные оркестраторы КОРНЯ китов (SessionLoader/HealthMonitor/OnboardingService) + сущности/порты/перечисления/протоколы/веер проб — связный кластер, переехавший из librarykit (волна 5, Ruling 5-3), три внешних потребителя.
|
|
5
|
+
Author: Dmitry
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: s-corekit>=0.0.13
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# orchestrationkit
|
|
13
|
+
|
|
14
|
+
Развязанные оркестраторы КОРНЯ китов экосистемы S-kits: `SessionLoader`,
|
|
15
|
+
`HealthMonitor`, `OnboardingService` — плюс их общий фундамент (нейтральные
|
|
16
|
+
сущности, порты, канонические перечисления, контракты и внутренний веер
|
|
17
|
+
параллельных проб).
|
|
18
|
+
|
|
19
|
+
## Откуда
|
|
20
|
+
|
|
21
|
+
Родился волной 5 платформы китов (Atlas #2710, `docs/superpowers/plans/
|
|
22
|
+
2026-09-08-kits-wave5-librarykit.md`, Ruling 5-3) выделением из `librarykit`
|
|
23
|
+
связного кластера в 1 763 строки, у которого было ТРИ разных внешних
|
|
24
|
+
потребителя (`adapterkit`, `gws`, `bublictr`) — оркестрация стоит на сущностях
|
|
25
|
+
и портах, протоколы — контракт для неё же, веер проб — её внутренний примитив.
|
|
26
|
+
Это случай «новый дом», а не «раскидать по чужим китам».
|
|
27
|
+
|
|
28
|
+
## Состав
|
|
29
|
+
|
|
30
|
+
- `orchestrationkit.orchestration` — `SessionLoader`, `HealthMonitor` (+
|
|
31
|
+
`classify_health_state`, `register_health_probe`/`get_health_probe`,
|
|
32
|
+
`GenericAdapterHealthProbe`), `OnboardingService`/`OnboardingDeps`.
|
|
33
|
+
- `orchestrationkit.entities` — домен-нейтральные носители данных (`Session`,
|
|
34
|
+
`SessionContext`, `HealthReport`, `RawHealthStatus`, ...).
|
|
35
|
+
- `orchestrationkit.ports` — Protocol-порты под оркестрацию (репозитории,
|
|
36
|
+
probe, secret store).
|
|
37
|
+
- `orchestrationkit.enums` — канонические перечисления состояний
|
|
38
|
+
(`HealthState`, `SsoState`).
|
|
39
|
+
- `orchestrationkit.protocols` — контракты подключения и здоровья.
|
|
40
|
+
- `orchestrationkit.fanout` — веер параллельных проб (внутренний примитив
|
|
41
|
+
здоровья, используется `HealthMonitor`).
|
|
42
|
+
|
|
43
|
+
## Закон слоёв
|
|
44
|
+
|
|
45
|
+
Только `corekit` (единственная точка — `corekit.dto.SessionRef` в
|
|
46
|
+
`protocols.py`). Никакого импорта `librarykit`/`adapterkit`/`clikit`/
|
|
47
|
+
`bublictr` — доменные привязки (реестр адаптеров, `Operation`/`TargetKind`,
|
|
48
|
+
координаты `SessionRef`, audit-store, браузерный профиль) инъектируются
|
|
49
|
+
доменом при инстанцировании. Контракт закреплён `.importlinter`.
|
|
50
|
+
|
|
51
|
+
## Совместимость
|
|
52
|
+
|
|
53
|
+
`librarykit.orchestration`/`.entities`/`.ports`/`.enums`/`.protocols`/`.fanout`
|
|
54
|
+
остаются алиасами (`sys.modules`-подмена) на этот кит — прежние импорты
|
|
55
|
+
работают байт-в-байт.
|
|
56
|
+
|
|
57
|
+
## Разработка
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
uv sync
|
|
61
|
+
uv run pytest -q
|
|
62
|
+
uv run ruff check .
|
|
63
|
+
uv run lint-imports
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Сетка безопасности (CI/release_guard/check_dist_leaks/importlinter) — из
|
|
67
|
+
`devcontour/tools/kits/template/`, см. `docs/kits/SAFETY-NET.md` в devcontour.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# orchestrationkit
|
|
2
|
+
|
|
3
|
+
Развязанные оркестраторы КОРНЯ китов экосистемы S-kits: `SessionLoader`,
|
|
4
|
+
`HealthMonitor`, `OnboardingService` — плюс их общий фундамент (нейтральные
|
|
5
|
+
сущности, порты, канонические перечисления, контракты и внутренний веер
|
|
6
|
+
параллельных проб).
|
|
7
|
+
|
|
8
|
+
## Откуда
|
|
9
|
+
|
|
10
|
+
Родился волной 5 платформы китов (Atlas #2710, `docs/superpowers/plans/
|
|
11
|
+
2026-09-08-kits-wave5-librarykit.md`, Ruling 5-3) выделением из `librarykit`
|
|
12
|
+
связного кластера в 1 763 строки, у которого было ТРИ разных внешних
|
|
13
|
+
потребителя (`adapterkit`, `gws`, `bublictr`) — оркестрация стоит на сущностях
|
|
14
|
+
и портах, протоколы — контракт для неё же, веер проб — её внутренний примитив.
|
|
15
|
+
Это случай «новый дом», а не «раскидать по чужим китам».
|
|
16
|
+
|
|
17
|
+
## Состав
|
|
18
|
+
|
|
19
|
+
- `orchestrationkit.orchestration` — `SessionLoader`, `HealthMonitor` (+
|
|
20
|
+
`classify_health_state`, `register_health_probe`/`get_health_probe`,
|
|
21
|
+
`GenericAdapterHealthProbe`), `OnboardingService`/`OnboardingDeps`.
|
|
22
|
+
- `orchestrationkit.entities` — домен-нейтральные носители данных (`Session`,
|
|
23
|
+
`SessionContext`, `HealthReport`, `RawHealthStatus`, ...).
|
|
24
|
+
- `orchestrationkit.ports` — Protocol-порты под оркестрацию (репозитории,
|
|
25
|
+
probe, secret store).
|
|
26
|
+
- `orchestrationkit.enums` — канонические перечисления состояний
|
|
27
|
+
(`HealthState`, `SsoState`).
|
|
28
|
+
- `orchestrationkit.protocols` — контракты подключения и здоровья.
|
|
29
|
+
- `orchestrationkit.fanout` — веер параллельных проб (внутренний примитив
|
|
30
|
+
здоровья, используется `HealthMonitor`).
|
|
31
|
+
|
|
32
|
+
## Закон слоёв
|
|
33
|
+
|
|
34
|
+
Только `corekit` (единственная точка — `corekit.dto.SessionRef` в
|
|
35
|
+
`protocols.py`). Никакого импорта `librarykit`/`adapterkit`/`clikit`/
|
|
36
|
+
`bublictr` — доменные привязки (реестр адаптеров, `Operation`/`TargetKind`,
|
|
37
|
+
координаты `SessionRef`, audit-store, браузерный профиль) инъектируются
|
|
38
|
+
доменом при инстанцировании. Контракт закреплён `.importlinter`.
|
|
39
|
+
|
|
40
|
+
## Совместимость
|
|
41
|
+
|
|
42
|
+
`librarykit.orchestration`/`.entities`/`.ports`/`.enums`/`.protocols`/`.fanout`
|
|
43
|
+
остаются алиасами (`sys.modules`-подмена) на этот кит — прежние импорты
|
|
44
|
+
работают байт-в-байт.
|
|
45
|
+
|
|
46
|
+
## Разработка
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
uv sync
|
|
50
|
+
uv run pytest -q
|
|
51
|
+
uv run ruff check .
|
|
52
|
+
uv run lint-imports
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Сетка безопасности (CI/release_guard/check_dist_leaks/importlinter) — из
|
|
56
|
+
`devcontour/tools/kits/template/`, см. `docs/kits/SAFETY-NET.md` в devcontour.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""orchestrationkit — развязанные оркестраторы КОРНЯ китов: домен-нейтральны,
|
|
2
|
+
переиспользуемы любым потребителем.
|
|
3
|
+
|
|
4
|
+
Родился волной 5 платформы китов (Atlas #2710, Ruling 5-3) выделением связного
|
|
5
|
+
кластера из `librarykit`: `SessionLoader`/`HealthMonitor`/`OnboardingService`
|
|
6
|
+
(`orchestrationkit.orchestration`) + носители данных (`orchestrationkit.entities`),
|
|
7
|
+
порты (`orchestrationkit.ports`), канонические перечисления (`orchestrationkit.enums`),
|
|
8
|
+
контракты подключения и здоровья (`orchestrationkit.protocols`) и внутренний
|
|
9
|
+
примитив параллельных проб (`orchestrationkit.fanout`) — три разных внешних
|
|
10
|
+
потребителя (adapterkit, gws, bublictr) держали один и тот же код по чужому
|
|
11
|
+
адресу.
|
|
12
|
+
|
|
13
|
+
**Закон слоёв.** `orchestrationkit` не знает ни одного другого кита экосистемы,
|
|
14
|
+
кроме `corekit` (только `corekit.dto.SessionRef` в `protocols.py` — тонкая точка,
|
|
15
|
+
не библиотека целиком). Никакого импорта `librarykit`/`adapterkit`/`clikit`/
|
|
16
|
+
`bublictr` — оркестраторы стоят на нейтральных `Session`/`SessionContext`/
|
|
17
|
+
`HealthReport`, а доменные привязки (реестр адаптеров, enum'ы `Operation`/
|
|
18
|
+
`TargetKind`, координаты `SessionRef`, audit-store, браузерный профиль)
|
|
19
|
+
ИНЪЕКТИРУЮТСЯ доменом при инстанцировании.
|
|
20
|
+
|
|
21
|
+
`librarykit.orchestration`/`librarykit.entities`/`librarykit.ports`/
|
|
22
|
+
`librarykit.enums`/`librarykit.protocols`/`librarykit.fanout` остаются АЛИАСАМИ
|
|
23
|
+
на этот кит (`sys.modules`-подмена, не реэкспорт) — старые формы доступа
|
|
24
|
+
работают байт-в-байт для 54 потребителей librarykit.
|
|
25
|
+
|
|
26
|
+
Версия читается из метаданных установленного дистрибутива
|
|
27
|
+
(``importlib.metadata``), а не из литерала в коде — второй источник правды
|
|
28
|
+
здесь намеренно не заведён (см. `docs/kits/SAFETY-NET.md` в devcontour,
|
|
29
|
+
release_guard проверяет это на теге).
|
|
30
|
+
"""
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
34
|
+
|
|
35
|
+
#: Не `__version__ = "0.0.0+unknown"` строкой-литералом — гейт релиза
|
|
36
|
+
#: (`scripts/release_guard.py`) ищет ЛЮБУЮ строку вида `^__version__\s*=\s*"`
|
|
37
|
+
#: как признак второго источника правды и откажет тегу, даже если это только
|
|
38
|
+
#: аварийный fallback редактируемой установки. Вынесено в константу, чтобы
|
|
39
|
+
#: строка присваивания `__version__` не начиналась с кавычки.
|
|
40
|
+
_UNBUILT_FALLBACK = "0.0.0+unknown"
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
__version__ = version("s-orchestrationkit")
|
|
44
|
+
except PackageNotFoundError: # pragma: no cover — редактируемая установка без сборки
|
|
45
|
+
__version__ = _UNBUILT_FALLBACK
|
|
46
|
+
|
|
47
|
+
__all__: list[str] = []
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Домен-нейтральные data-классы оркестраторов — почва под будущий переезд.
|
|
2
|
+
|
|
3
|
+
Здесь живут НЕЙТРАЛЬНЫЕ копии данных, на которых стоят оркестраторы
|
|
4
|
+
(`OnboardingService`/`HealthMonitor`/`SessionLoader`): `Session`,
|
|
5
|
+
`SessionChanges`, `SessionContext`, `HealthReport`. Развязка от домена:
|
|
6
|
+
|
|
7
|
+
- поле `network` / `target` — ``str`` (машинное имя сети), НЕ доменный enum
|
|
8
|
+
(`NetworkId`/`TargetKind`); оркестратор не должен знать конкретный домен;
|
|
9
|
+
- `HealthReport.state` — `orchestrationkit.enums.HealthState` (канон-нейтральный enum,
|
|
10
|
+
реэкспортируется доменом).
|
|
11
|
+
|
|
12
|
+
ЗАКОН СЛОЁВ: только stdlib (+ `orchestrationkit.enums`); никаких импортов из
|
|
13
|
+
adapterkit/clikit/bublictr. Это data-carriers, поэтому stdlib `@dataclass`
|
|
14
|
+
(orchestrationkit — «почти-stdlib», pydantic НЕ зависимость пакета).
|
|
15
|
+
|
|
16
|
+
Нота: на момент переноса доменные `bublictr.core.entities.Session`/...
|
|
17
|
+
ОСТАЮТСЯ pydantic+`NetworkId` (домен использует ``session.network.value`` и
|
|
18
|
+
сравнения ``== NetworkId.X`` в ~40 файлах + 1168 тестах — развязка bublictr.Session
|
|
19
|
+
на ``str`` слишком инвазивна). Поэтому эти классы — ОТДЕЛЬНЫЕ нейтральные формы
|
|
20
|
+
ПОЗЖЕ, когда оркестраторы переедут на них. `HealthState` же канонизирован и
|
|
21
|
+
реэкспортирован доменом (identity-equal) уже сейчас.
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from datetime import datetime
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from orchestrationkit.enums import HealthState
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class Session:
|
|
35
|
+
"""Логин/токены/storage_state одного аккаунта в одной сети (нейтрально).
|
|
36
|
+
|
|
37
|
+
Нейтральный аналог `bublictr.core.entities.Session`: `network` — ``str``
|
|
38
|
+
(машинное имя сети), а не `NetworkId`. Остальные поля 1:1 с доменной
|
|
39
|
+
версией (включая V5-координаты unified session storage).
|
|
40
|
+
|
|
41
|
+
Несколько Binding могут разделять один Session. `storage_path` — JSON-файл с
|
|
42
|
+
cookies/state; секреты — в keyring под `keyring_ref`.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
id: str # uuid4
|
|
46
|
+
network: str # машинное имя сети ("vk" / "telegram" / ...)
|
|
47
|
+
storage_path: Path
|
|
48
|
+
created_at: datetime
|
|
49
|
+
keyring_ref: str | None = None
|
|
50
|
+
account_handle: str | None = None
|
|
51
|
+
account_external_id: str | None = None
|
|
52
|
+
last_refreshed_at: datetime | None = None
|
|
53
|
+
expires_at: datetime | None = None
|
|
54
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
55
|
+
# --- V5: координаты unified session storage ---
|
|
56
|
+
profile_id: str | None = None
|
|
57
|
+
account_id: str | None = None
|
|
58
|
+
session_dir_path: Path | None = None
|
|
59
|
+
auth_provider_id: int | None = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class SessionChanges:
|
|
64
|
+
"""DTO частичного обновления `Session` (нейтрально).
|
|
65
|
+
|
|
66
|
+
Только non-None поля применяются. Immutable после create (не входят сюда):
|
|
67
|
+
id, network, storage_path, created_at.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
keyring_ref: str | None = None
|
|
71
|
+
account_handle: str | None = None
|
|
72
|
+
account_external_id: str | None = None
|
|
73
|
+
last_refreshed_at: datetime | None = None
|
|
74
|
+
expires_at: datetime | None = None
|
|
75
|
+
metadata: dict[str, Any] | None = None
|
|
76
|
+
# --- V5 ---
|
|
77
|
+
profile_id: str | None = None
|
|
78
|
+
account_id: str | None = None
|
|
79
|
+
session_dir_path: Path | None = None
|
|
80
|
+
auth_provider_id: int | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class SessionContext:
|
|
85
|
+
"""Полный контекст для DI в адаптер — `Session` + storage_state + secrets.
|
|
86
|
+
|
|
87
|
+
Нейтральный аналог `bublictr.usecases.session_loader.SessionContext`.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
session: Session
|
|
91
|
+
storage_state: dict[str, Any]
|
|
92
|
+
secrets: dict[str, str] # secret_name → value, e.g. {"access_token": "..."}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class RawHealthStatus:
|
|
97
|
+
"""RAW-результат health-check адаптера ДО классификации (нейтрально).
|
|
98
|
+
|
|
99
|
+
Это «сырая» форма, которую отдаёт ``NetworkAdapter.health_check()`` и которую
|
|
100
|
+
жуёт :func:`orchestrationkit.orchestration.health.classify_health_state` → выдаёт
|
|
101
|
+
`HealthState`. Нейтральный аналог `bublictr.core.entities.HealthStatus`:
|
|
102
|
+
`network` — ``str`` (машинное имя сети), а не `NetworkId`.
|
|
103
|
+
|
|
104
|
+
Развязка vs `orchestrationkit.protocols.HealthStatus`: тот — УЖЕ классифицированный
|
|
105
|
+
DTO (несёт `state: HealthState`, для `HealthProtocol`); ЭТОТ — сырой вход
|
|
106
|
+
классификатора (несёт `healthy: bool` + `rate_limit_remaining`, без `state`).
|
|
107
|
+
Раньше оба назывались `HealthStatus` — конфликт снят переименованием этой,
|
|
108
|
+
RAW-формы, в `RawHealthStatus` (старое имя — deprecated-алиас ниже).
|
|
109
|
+
|
|
110
|
+
`classify_health_state` читает поля по duck-typing (`.auth_valid`/
|
|
111
|
+
`.api_reachable`/`.notes`/`.rate_limit_remaining`/`.healthy`), поэтому
|
|
112
|
+
одинаково принимает и эту форму, и доменный pydantic-`HealthStatus` bublictr
|
|
113
|
+
(тот несёт сверху `network: NetworkId` + `raw` + `model_dump` — их домен
|
|
114
|
+
оставляет себе, развязка их не трогает).
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
network: str
|
|
118
|
+
healthy: bool
|
|
119
|
+
auth_valid: bool
|
|
120
|
+
api_reachable: bool
|
|
121
|
+
rate_limit_remaining: int | None = None
|
|
122
|
+
notes: list[str] = field(default_factory=list)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class HealthReport:
|
|
127
|
+
"""Результат периодической проверки binding'а (нейтрально).
|
|
128
|
+
|
|
129
|
+
Нейтральный аналог `bublictr.core.entities.HealthReport`: `network` — ``str``,
|
|
130
|
+
`state` — `orchestrationkit.enums.HealthState` (канон). Добавляет binding/profile
|
|
131
|
+
контекст, классифицированный state и временные метки для trending.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
binding_id: str
|
|
135
|
+
profile_id: str
|
|
136
|
+
network: str
|
|
137
|
+
state: HealthState
|
|
138
|
+
auth_valid: bool
|
|
139
|
+
api_reachable: bool
|
|
140
|
+
checked_at: datetime
|
|
141
|
+
rate_limit_remaining: int | None = None
|
|
142
|
+
retry_after_sec: int | None = None
|
|
143
|
+
notes: list[str] = field(default_factory=list)
|
|
144
|
+
next_check_at: datetime | None = None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
__all__ = [
|
|
148
|
+
"Session",
|
|
149
|
+
"SessionChanges",
|
|
150
|
+
"SessionContext",
|
|
151
|
+
"RawHealthStatus",
|
|
152
|
+
"HealthReport",
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def __getattr__(name: str) -> Any:
|
|
157
|
+
"""PEP 562: deprecated-алиас `HealthStatus` → `RawHealthStatus`.
|
|
158
|
+
|
|
159
|
+
RAW-снимок здоровья переименован в `RawHealthStatus`, чтобы снять конфликт
|
|
160
|
+
имён с классифицированным DTO `orchestrationkit.protocols.HealthStatus`. Старое имя
|
|
161
|
+
остаётся импортируемым (`from orchestrationkit.entities import HealthStatus`) ради
|
|
162
|
+
обратной совместимости, но предупреждает о переезде.
|
|
163
|
+
"""
|
|
164
|
+
if name == "HealthStatus":
|
|
165
|
+
import warnings
|
|
166
|
+
|
|
167
|
+
warnings.warn(
|
|
168
|
+
"orchestrationkit.entities.HealthStatus переименован в RawHealthStatus. "
|
|
169
|
+
"Имя HealthStatus теперь закреплено за классифицированным DTO "
|
|
170
|
+
"orchestrationkit.protocols.HealthStatus; RAW-снимок стал RawHealthStatus. "
|
|
171
|
+
"Обнови импорт на RawHealthStatus — старый алиас будет удалён позже.",
|
|
172
|
+
DeprecationWarning,
|
|
173
|
+
stacklevel=2,
|
|
174
|
+
)
|
|
175
|
+
return RawHealthStatus
|
|
176
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|