s-corekit 0.0.2__tar.gz → 0.0.3__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.
@@ -1,5 +1,23 @@
1
1
  # CHANGELOG — s-corekit
2
2
 
3
+ ## [0.0.3] — 2026-08-08
4
+
5
+ ### Добавлено
6
+
7
+ - `SessionRef` получил две координаты: `instance` — конкретный вход
8
+ многоинстансного сервиса (портал Битрикса, национальный домен), `stage` —
9
+ стадия сессии (боевая или черновик записи трафика). Обе добавлены В КОНЕЦ и
10
+ с дефолтами, как когда-то `tenant`: позиционный
11
+ `SessionRef(profile, social, account_id)` из чужих навыков и уже записанные
12
+ адреса продолжают работать.
13
+ - `DEFAULT_STAGE` — стадия боевой сессии (`"live"`), дефолт адреса.
14
+
15
+ Зачем. Стадия до сих пор занимала поле `profile`, и два смысла в одном поле
16
+ означали адрес, по которому нельзя спросить «дай боевую сессию ЭТОГО
17
+ аккаунта», не зная заранее, как её назвал пишущий. Порталы одного продукта
18
+ разводились ВТОРЫМ именем сервиса — от этого у аккаунта появлялась вторая
19
+ сессия, о которой никто не знал.
20
+
3
21
  ## [0.0.2] — 2026-08-04
4
22
 
5
23
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: s-corekit
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: ОСНОВАНИЕ китов: ExecutionContext, базовые DTO (SessionRef/Creds/LimitSpec/QuotaInfo), единая иерархия ошибок и чистая классификация состояния доступа (AccessState/AccessVerdict). Только stdlib.
5
5
  Author: Dmitry
6
6
  License: MIT
@@ -45,7 +45,7 @@
45
45
  """
46
46
  from __future__ import annotations
47
47
 
48
- from corekit.context import DEFAULT_TENANT, ExecutionContext
48
+ from corekit.context import DEFAULT_STAGE, DEFAULT_TENANT, ExecutionContext
49
49
  from corekit.diagnosis import (
50
50
  ACCESS_STATE_MESSAGES,
51
51
  REASON_MESSAGES,
@@ -99,12 +99,13 @@ from corekit.redaction import (
99
99
  truncate_body,
100
100
  )
101
101
 
102
- __version__ = "0.0.2"
102
+ __version__ = "0.0.3"
103
103
 
104
104
  __all__ = [
105
105
  "__version__",
106
106
  # контекст исполнения
107
107
  "ExecutionContext",
108
+ "DEFAULT_STAGE",
108
109
  "DEFAULT_TENANT",
109
110
  # базовые DTO
110
111
  "SessionRef",
@@ -17,6 +17,7 @@ from pathlib import Path
17
17
  from typing import Any
18
18
 
19
19
  __all__ = [
20
+ "DEFAULT_STAGE",
20
21
  "DEFAULT_TENANT",
21
22
  "ExecutionContext",
22
23
  ]
@@ -26,6 +27,16 @@ __all__ = [
26
27
  #: байт-в-байт совпадают с тем, что было до введения мультитенантности.
27
28
  DEFAULT_TENANT = "local"
28
29
 
30
+ #: Стадия БОЕВОЙ сессии — той, которой работают. Дефолт `SessionRef`: пока
31
+ #: стадию не назвали явно, адрес совпадает с досегодняшним, и все уже
32
+ #: записанные сессии читаются как боевые (чем они и были).
33
+ #:
34
+ #: Вторая стадия — черновик записи трафика (`capture`): вход туда может не
35
+ #: удаться (в окне осталась форма), и такой снимок не должен затирать рабочую
36
+ #: сессию. Стадий ровно две по замыслу: третья означала бы, что мы моделируем
37
+ #: здесь жизненный цикл, а не разводим «проверено» и «ещё нет».
38
+ DEFAULT_STAGE = "live"
39
+
29
40
 
30
41
  #: Сентинел «аргумент не передан» для `ExecutionContext.__init__`: отличает
31
42
  #: «поле оставили по умолчанию» от «поле передали, и значение совпало с дефолтом».
@@ -16,7 +16,7 @@ from collections.abc import Mapping
16
16
  from dataclasses import dataclass, field
17
17
  from typing import Any
18
18
 
19
- from corekit.context import DEFAULT_TENANT
19
+ from corekit.context import DEFAULT_STAGE, DEFAULT_TENANT
20
20
 
21
21
  __all__ = [
22
22
  "Creds",
@@ -44,12 +44,32 @@ class SessionRef:
44
44
  дефолтном ``"local"`` раскладка и ключ байт-в-байт прежние (сегмент не
45
45
  добавляется), поэтому существующие сессии читаются как раньше. Пересборка
46
46
  индекса из каталога — `SessionStore.reindex` (понимает обе глубины).
47
+
48
+ ``instance`` — КОНКРЕТНЫЙ вход сервиса, у которого их много: портал
49
+ Битрикса (``acme.bitrix24.ru``), национальный домен, отдельная установка.
50
+ Продукт при этом один, и разводить такие входы вторым ИМЕНЕМ сервиса
51
+ (``bitrix24-kz``) нельзя: имена размножаются, а у одного аккаунта появляется
52
+ вторая сессия, о которой никто не знает. Пусто — у сервиса один вход, и
53
+ адрес совпадает с прежним.
54
+
55
+ ``stage`` — стадия сессии: боевая (:data:`~corekit.context.DEFAULT_STAGE`)
56
+ или черновик записи трафика (``capture``). Стадия обязана быть ОТДЕЛЬНОЙ
57
+ координатой, а не значением ``profile``: профиль отвечает на вопрос «чей
58
+ это вход», стадия — «проверен ли он». Совмещая два смысла в одном поле,
59
+ получаешь адрес, по которому нельзя спросить «дай боевую сессию ЭТОГО
60
+ аккаунта», не зная заранее, как её назвал пишущий.
61
+
62
+ Обе координаты добавлены В КОНЕЦ и с дефолтами — по той же причине, что и
63
+ ``tenant``: позиционные вызовы, сравнение старых объектов и уже записанные
64
+ адреса продолжают работать.
47
65
  """
48
66
 
49
67
  profile: str
50
68
  social: str
51
69
  account_id: str
52
70
  tenant: str = DEFAULT_TENANT
71
+ instance: str = ""
72
+ stage: str = DEFAULT_STAGE
53
73
 
54
74
 
55
75
  @dataclass(frozen=True, slots=True)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "s-corekit"
7
- version = "0.0.2"
7
+ version = "0.0.3"
8
8
  description = "ОСНОВАНИЕ китов: ExecutionContext, базовые DTO (SessionRef/Creds/LimitSpec/QuotaInfo), единая иерархия ошибок и чистая классификация состояния доступа (AccessState/AccessVerdict). Только stdlib."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -0,0 +1,65 @@
1
+ """Адрес сессии: две новые координаты не ломают ни одного старого вызова.
2
+
3
+ Почему это проверяется отдельно. `SessionRef` — общий словарь между китами,
4
+ навыками и хранилищем: его собирают в одном месте, а разбирают в четырёх. Любая
5
+ правка формы здесь дороже обычной: старый позиционный вызов, лежащий в чужом
6
+ навыке, обязан продолжить работать, а уже записанные адреса — читаться.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import dataclasses
11
+
12
+ import pytest
13
+
14
+ from corekit import DEFAULT_STAGE, DEFAULT_TENANT, SessionRef
15
+
16
+
17
+ def test_old_positional_call_still_works() -> None:
18
+ """Тройка без новых координат собирается как раньше — и это боевая сессия."""
19
+ ref = SessionRef("default", "grok", "me")
20
+
21
+ assert (ref.profile, ref.social, ref.account_id) == ("default", "grok", "me")
22
+ assert ref.tenant == DEFAULT_TENANT
23
+ assert ref.instance == ""
24
+ assert ref.stage == DEFAULT_STAGE
25
+
26
+
27
+ def test_stage_makes_two_different_addresses() -> None:
28
+ """Черновик и боевая — РАЗНЫЕ адреса одного аккаунта, а не один.
29
+
30
+ Ровно это и защищает рабочую сессию от неудачного захода в capture.
31
+ """
32
+ live = SessionRef("default", "grok", "me")
33
+ draft = SessionRef("default", "grok", "me", stage="capture")
34
+
35
+ assert live != draft
36
+
37
+
38
+ def test_instance_separates_portals_of_one_service() -> None:
39
+ """Два портала одного продукта — разные адреса при одном имени сервиса."""
40
+ acme = SessionRef("default", "bitrix24", "me", instance="acme.bitrix24.ru")
41
+ beta = SessionRef("default", "bitrix24", "me", instance="beta.bitrix24.kz")
42
+
43
+ assert acme != beta
44
+ assert acme.social == beta.social == "bitrix24"
45
+
46
+
47
+ def test_ref_stays_hashable_and_frozen() -> None:
48
+ """Адрес остаётся значением: годится в ключ и не меняется на ходу."""
49
+ ref = SessionRef("default", "grok", "me", stage="capture")
50
+
51
+ assert {ref: 1}[SessionRef("default", "grok", "me", stage="capture")] == 1
52
+ with pytest.raises(dataclasses.FrozenInstanceError):
53
+ ref.stage = "live" # type: ignore[misc]
54
+
55
+
56
+ def test_new_fields_are_last() -> None:
57
+ """Новые координаты стоят В КОНЦЕ — иначе позиционные вызовы молча съедут.
58
+
59
+ Проверка не про эстетику: `SessionRef("default", "grok", "me")` живёт в
60
+ чужих навыках, и перестановка полей превратила бы имя сервиса в стадию,
61
+ не вызвав ни одной ошибки.
62
+ """
63
+ names = [f.name for f in dataclasses.fields(SessionRef)]
64
+
65
+ assert names == ["profile", "social", "account_id", "tenant", "instance", "stage"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes