Skip to main content

s-leasekit

Единый верификатор лиза способности — один и тот же код проверки на обоих потребителях: skillery-cli (запуск навыка, демон) и gateway (load_capability).

Версия: 0.1.0 · Лицензия: MIT · Зависимости: cryptography (Ed25519) — и всё.

Реализует контракт лиза способности (внутренний документ capability-lease-contract.md, 2026-08-06): TTL 24 ч, grace = 0, гейт включается точечно (requires_lease), NO_GRANT/GRANT_REVOKED наружу не различаются.

Зачем отдельный пакет

skillkit — библиотека CLI, adapterkit — библиотека gateway; общей у них нет. Две независимые реализации проверки подписи и срока разъедутся — вопрос не «если», а «когда». И разъедутся они в сторону «пускаем»: лишний отказ замечают в тот же день, лишний пропуск не замечает никто. Поэтому реализация одна, и она здесь.

Быстрый старт

from pathlib import Path
from leasekit import lease_gate, LeaseDenied

try:
    lease = lease_gate(
        "grok_transcriber",                    # имя = ключ entry-point skillery.plugins
        store=Path.home() / ".skillery",       # каталог состояния
        subject=current_user_subject,          # "user:1042" — текущая учётка
        device_id=client_device_id,            # справочно: расхождение = WARN
        hub_offset=hub_time_offset,            # из заголовка Date последнего ответа хаба
    )
except LeaseDenied as denied:
    log.warning("lease denied: %s", denied.verdict.value)
    raise SystemExit(denied.message)           # текст уже содержит следующее действие

# сюда попадаем ТОЛЬКО с проверенным лизом
run_capability(lease.claims.cap)

Где ставить гейт (§9.2 контракта):

  • CLIcommands/run.py::cmd_run, перед _spawn(argv);
  • gatewaygateway/plugins.py::load_capability, перед ep.load().

Перечисление способностей гейтом не закрывается: список того, что установлено на машине, секретом не является.

Почему проверку нельзя обойти по невнимательности

Форма публичного API — не вкусовщина, а способ убрать целые классы ошибок:

Решение Что оно предотвращает
verify_lease / lease_gate поднимают LeaseDenied, а не возвращают вердикт проигнорированный return-код молча пускает; проигнорированное исключение останавливает процесс
«Проверить» и «получить claims» — одно действие: LeaseClaims достаётся только из VerifiedLease нельзя разобрать payload, «забыв» проверить его; функции «просто распарсить» в публичном API нет
subjectобязательный именованный аргумент без значения по умолчанию единственную привязку, по которой отказывают (§2.4), нельзя пропустить
Время берётся из LeaseClock, а не из now: int применение монотонного пола — обязанность кита, а не вызывающего
DEVICE_MISMATCHне вердикт, а LeaseWarning естественный код if verdict is not VALID: deny не начнёт отказывать там, где спека велит пропускать
Диагностический путь называется inspect_lease и возвращает другой тип (LeaseCheck) «взять не ту функцию» не выглядит как рабочий код: LeaseCheck возвращается и при отказе
Любая неоднозначность → отказ (MALFORMED) битый JSON, обрезанный файл, чужой алгоритм, отсутствующий claim не превращаются в молчаливый пропуск

Монотонный пол времени

Лиз проверяется по локальным часам — других на офлайн-машине нет. Три правила §5, и без всех трёх контур дырявый:

  1. допуск ±300 с на nbf/exp — обычный перекос часов не даёт отказа на границе;
  2. смещение от часов хабаoffset = hub_now − local_now из заголовка Date последнего успешного ответа; спасает машины с систематически сбитыми часами (VM без NTP, севшая CMOS) без ослабления допуска;
  3. монотонный пол hub_time_floor — максимальное время, когда-либо подтверждённое хабом.
effective_now = max(local_now + offset, hub_time_floor)   # LeaseClock.effective_now

Пол только растёт (LeaseStore.raise_floor никогда не уменьшает) и живёт в leases.json рядом с лизами. Без него весь контур снимается одной командой «перевести часы назад»: истёкший лиз оживает, и срок доступа перестаёт что-либо значить.

Две ситуации разделены намеренно, хотя обе — отказ:

  • часы ниже пола сверх допуска → CLOCK_SUSPECT («похоже, часы идут неверно»);
  • лиз истёк, а часы правдоподобныEXPIRED («срок истёк, подключитесь к сети»).

Отказ с неверной причиной отправляет пользователя искать несуществующий отзыв доступа.

Вердикты

Вердикт Поведение
VALID пропустить, в сеть не ходить
MISSING лиза нет в хранилище
EXPIRED exp (+grace) позади
NOT_YET_VALID nbf впереди сверх допуска
SUBJECT_MISMATCH лиз выдан другому пользователю
CAPABILITY_MISMATCH лиз выписан на другую способность
ISSUER_MISMATCH другой хаб (проверяется, только если issuer задан)
SIGNATURE_INVALID подпись не сходится или algEdDSA (включая none)
UNKNOWN_KID ключ неизвестен; с refresh_jwks — ровно один повтор, иначе отказ
UNSUPPORTED_VER ver лиза новее клиента
CLOCK_SUSPECT часы ниже пола или неправдоподобно впереди
MALFORMED битый токен/JSON/нет обязательного claim

Предупреждения (LeaseWarning, не влияют на решение): DEVICE_MISMATCH, NO_TIME_FLOOR.

Что кит НЕ делает — и почему

  • Не ходит в сеть. Ни за лизом, ни за JWKS. У CLI и gateway разные транспорты, таймауты и политика ретраев; тащить сюда сетевой слой значило бы тащить и его зависимости обоим потребителям.
  • Не выдаёт лизы. Единственная точка решения «право действует» — хаб (is_grant_effective). Модуль leasekit.testing подписывает лизы только для тестов — вынесен в отдельный модуль, чтобы не попадаться в подсказках IDE в проде.
  • Не решает, когда удалять лиз. Различение «хаб ответил нет» (удалить немедленно) и «хаб недоступен» (жить по имеющемуся лизу до exp) требует знания об ответе сети — это знает потребитель. Кит лишь выполняет drop() корректно и атомарно.
  • Не хранит гранты и не знает про биллинг. Лиз — булево «можно», а не счётчик и не квота: счётчик требует онлайна на каждый вызов, то есть ровно того, от чего отказались.
  • Не зависит от skillkit/adapterkit. Он должен ставиться обоим потребителям независимо; общая зависимость снизу — ровно то, ради чего кит существует.
  • Не защищает от владельца машины. Файлы способности лежат на диске, интерпретатор открыт, проверку можно вырезать из своей копии клиента. Лиз делает честного клиента честным и ставит праву конечный срок — не более.
  • Не гейтит перечисление установленного и не отзывается точечно (CRL нет, §6.2).

Расхождения со спекой

Спека писалась до кода; ниже — где реализация от неё отходит и почему.

  1. Сигнатуры публичных функций. Спека §3.1 обещает verify_lease(...) -> LeaseVerdict и lease_gate(...) -> None. Реализовано: обе поднимают LeaseDenied и возвращают VerifiedLease. Возврат вердикта-энума — это return-код, который можно не проверить; в контуре, где «лишний пропуск никто не заметит», такой API работает против собственной цели. Диагностический возврат вердикта сохранён под другим именем — inspect_lease() -> LeaseCheck.
  2. now: intclock: LeaseClock. Голое now перекладывает применение монотонного пола на вызывающего, то есть на два места, которые разъедутся.
  3. DEVICE_MISMATCH — не вердикт, а предупреждение (обоснование выше).
  4. Добавлены вердикты MALFORMED, CAPABILITY_MISMATCH, NOT_YET_VALID, ISSUER_MISMATCH. Спека §9.2 их не перечисляет, но требует fail-closed на любую нераспознанную ситуацию; отдельные коды нужны, чтобы отказ объяснял причину.
  5. lease_gate(subject=...) обязателен. В спеке подпись гейта субъекта не содержит — но SUBJECT_MISMATCH без субъекта непроверяем, а «взять текущую учётку самому» кит не может: он не знает, где она лежит у каждого потребителя.
  6. Хранилище (LeaseStore) вошло в кит, хотя §3.1 упоминает только две функции. Формат leases.json и дисциплина «пол только растёт» — ровно то, что разъедется в двух копиях. Политика удаления при этом осталась у потребителей.
  7. refresh_jwks как callback. Спека требует «при живой сети обновить JWKS и повторить проверку ОДИН раз». Сеть — не дело кита, но правило «ровно один повтор» держится здесь, а не в двух копиях.

Тесты

uv sync --extra dev
uv run pytest -q

Ключевые случаи — не формальность, а то, ради чего контур существует: перевод часов назад не оживляет истёкший лиз; расхождение client_device_id даёт предупреждение, а не отказ; подделанная подпись, alg: none и неизвестный kid отвергаются.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

s_leasekit-0.1.1.tar.gz (71.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

s_leasekit-0.1.1-py3-none-any.whl (30.9 kB view details)

Uploaded Python 3

File details

Details for the file s_leasekit-0.1.1.tar.gz.

File metadata

  • Download URL: s_leasekit-0.1.1.tar.gz
  • Upload date:
  • Size: 71.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for s_leasekit-0.1.1.tar.gz
Algorithm Hash digest
SHA256 9ba338ad4c71c4105443df49f4ec207efa6a9897462dde8642f763374ce04665
MD5 060b54f96bdb83e465676c6f5e939d7f
BLAKE2b-256 de3a07547b536469c0585ad11adad1144bb967bd404d51544b6a945acd48bf56

See more details on using hashes here.

Provenance

The following attestation bundles were made for s_leasekit-0.1.1.tar.gz:

Publisher: publish.yml on zZZTeJleTTy3uKZZz/leasekit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file s_leasekit-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: s_leasekit-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 30.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for s_leasekit-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 42774262254d657a6b55dc73bd33b2f50736d6c049429ea0729058662894227f
MD5 5cb75308bf83a9aca097d9f849db944a
BLAKE2b-256 79062dcfb92653ef31139e661586bc375b8fed188aa5bda498f34ac370173b98

See more details on using hashes here.

Provenance

The following attestation bundles were made for s_leasekit-0.1.1-py3-none-any.whl:

Publisher: publish.yml on zZZTeJleTTy3uKZZz/leasekit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.1.2

2 files

This release

0.1.1 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page