tamga-sdk 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.
- tamga_sdk-0.1.0/.github/CODEOWNERS +15 -0
- tamga_sdk-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +84 -0
- tamga_sdk-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- tamga_sdk-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +48 -0
- tamga_sdk-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +25 -0
- tamga_sdk-0.1.0/.github/dependabot.yml +23 -0
- tamga_sdk-0.1.0/.github/workflows/ci.yml +78 -0
- tamga_sdk-0.1.0/.github/workflows/release.yml +61 -0
- tamga_sdk-0.1.0/.gitignore +17 -0
- tamga_sdk-0.1.0/.release-please-manifest.json +3 -0
- tamga_sdk-0.1.0/CHANGELOG.md +26 -0
- tamga_sdk-0.1.0/CLAUDE.md +198 -0
- tamga_sdk-0.1.0/CONTRIBUTING.md +69 -0
- tamga_sdk-0.1.0/LICENSE +21 -0
- tamga_sdk-0.1.0/PKG-INFO +156 -0
- tamga_sdk-0.1.0/README.md +120 -0
- tamga_sdk-0.1.0/SECURITY.md +73 -0
- tamga_sdk-0.1.0/examples/checkout_and_verify.py +54 -0
- tamga_sdk-0.1.0/examples/heartbeat_scheduler.py +56 -0
- tamga_sdk-0.1.0/examples/machine_activation_flow.py +48 -0
- tamga_sdk-0.1.0/examples/offline_proof.py +45 -0
- tamga_sdk-0.1.0/examples/validate_license.py +47 -0
- tamga_sdk-0.1.0/pyproject.toml +109 -0
- tamga_sdk-0.1.0/release-please-config.json +15 -0
- tamga_sdk-0.1.0/src/tamga/__init__.py +57 -0
- tamga_sdk-0.1.0/src/tamga/checkout/__init__.py +13 -0
- tamga_sdk-0.1.0/src/tamga/checkout/_envelope.py +63 -0
- tamga_sdk-0.1.0/src/tamga/checkout/license_file.py +174 -0
- tamga_sdk-0.1.0/src/tamga/checkout/machine_file.py +232 -0
- tamga_sdk-0.1.0/src/tamga/client.py +1028 -0
- tamga_sdk-0.1.0/src/tamga/crypto/__init__.py +27 -0
- tamga_sdk-0.1.0/src/tamga/crypto/aes_gcm.py +31 -0
- tamga_sdk-0.1.0/src/tamga/crypto/ecdsa.py +64 -0
- tamga_sdk-0.1.0/src/tamga/crypto/ed25519.py +39 -0
- tamga_sdk-0.1.0/src/tamga/crypto/hkdf.py +39 -0
- tamga_sdk-0.1.0/src/tamga/crypto/naive_key.py +40 -0
- tamga_sdk-0.1.0/src/tamga/crypto/rsa.py +77 -0
- tamga_sdk-0.1.0/src/tamga/errors.py +167 -0
- tamga_sdk-0.1.0/src/tamga/models/__init__.py +47 -0
- tamga_sdk-0.1.0/src/tamga/models/license.py +136 -0
- tamga_sdk-0.1.0/src/tamga/models/machine.py +134 -0
- tamga_sdk-0.1.0/src/tamga/models/policy.py +243 -0
- tamga_sdk-0.1.0/src/tamga/models/validation.py +154 -0
- tamga_sdk-0.1.0/src/tamga/proof.py +183 -0
- tamga_sdk-0.1.0/src/tamga/py.typed +0 -0
- tamga_sdk-0.1.0/src/tamga/transport.py +232 -0
- tamga_sdk-0.1.0/tests/__init__.py +1 -0
- tamga_sdk-0.1.0/tests/conftest.py +113 -0
- tamga_sdk-0.1.0/tests/fixtures/.gitkeep +0 -0
- tamga_sdk-0.1.0/tests/test_client_config.py +50 -0
- tamga_sdk-0.1.0/tests/test_components.py +96 -0
- tamga_sdk-0.1.0/tests/test_entitlements.py +145 -0
- tamga_sdk-0.1.0/tests/test_errors.py +118 -0
- tamga_sdk-0.1.0/tests/test_license_checkin.py +69 -0
- tamga_sdk-0.1.0/tests/test_license_checkout.py +246 -0
- tamga_sdk-0.1.0/tests/test_license_validation.py +190 -0
- tamga_sdk-0.1.0/tests/test_machine_checkout.py +324 -0
- tamga_sdk-0.1.0/tests/test_machine_management.py +158 -0
- tamga_sdk-0.1.0/tests/test_offline_proof.py +221 -0
- tamga_sdk-0.1.0/tests/test_policy_enums.py +83 -0
- tamga_sdk-0.1.0/tests/test_processes.py +122 -0
- tamga_sdk-0.1.0/tests/test_transport.py +151 -0
- tamga_sdk-0.1.0/uv.lock +1260 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# See https://docs.github.com/articles/about-codeowners for syntax.
|
|
2
|
+
# Later matches take precedence.
|
|
3
|
+
|
|
4
|
+
* @tamga-sh/sdk-maintainers
|
|
5
|
+
|
|
6
|
+
# Crypto-bearing paths require a security-focused reviewer on every change —
|
|
7
|
+
# mirrors the mandatory security-reviewer gate in docs/plans/tamga-python.plan.md
|
|
8
|
+
# Section 4 for Sections E, F, H.
|
|
9
|
+
/src/tamga/crypto/ @tamga-sh/sdk-maintainers @tamga-sh/security
|
|
10
|
+
/src/tamga/checkout/ @tamga-sh/sdk-maintainers @tamga-sh/security
|
|
11
|
+
/src/tamga/proof.py @tamga-sh/sdk-maintainers @tamga-sh/security
|
|
12
|
+
|
|
13
|
+
/.github/workflows/ @tamga-sh/sdk-maintainers
|
|
14
|
+
/release-please-config.json @tamga-sh/sdk-maintainers
|
|
15
|
+
/.release-please-manifest.json @tamga-sh/sdk-maintainers
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: Bug Report
|
|
2
|
+
description: Report a problem with this SDK
|
|
3
|
+
title: "[Bug]: "
|
|
4
|
+
labels: ["bug", "triage"]
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: |
|
|
9
|
+
Thanks for taking the time to report a bug! Please fill out the sections below as completely as possible — a minimal reproduction is the single most useful thing you can include.
|
|
10
|
+
- type: textarea
|
|
11
|
+
id: description
|
|
12
|
+
attributes:
|
|
13
|
+
label: Description
|
|
14
|
+
description: A clear and concise description of what the bug is.
|
|
15
|
+
validations:
|
|
16
|
+
required: true
|
|
17
|
+
- type: textarea
|
|
18
|
+
id: reproduction
|
|
19
|
+
attributes:
|
|
20
|
+
label: Steps to Reproduce
|
|
21
|
+
description: Minimal, self-contained code sample that reproduces the issue.
|
|
22
|
+
render: "python"
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
25
|
+
- type: textarea
|
|
26
|
+
id: expected
|
|
27
|
+
attributes:
|
|
28
|
+
label: Expected Behavior
|
|
29
|
+
validations:
|
|
30
|
+
required: true
|
|
31
|
+
- type: textarea
|
|
32
|
+
id: actual
|
|
33
|
+
attributes:
|
|
34
|
+
label: Actual Behavior
|
|
35
|
+
description: What actually happened, including the full error/exception/stack trace if there is one.
|
|
36
|
+
render: shell
|
|
37
|
+
validations:
|
|
38
|
+
required: true
|
|
39
|
+
- type: input
|
|
40
|
+
id: sdk-version
|
|
41
|
+
attributes:
|
|
42
|
+
label: SDK Version
|
|
43
|
+
placeholder: "e.g. 1.0.3"
|
|
44
|
+
validations:
|
|
45
|
+
required: true
|
|
46
|
+
- type: input
|
|
47
|
+
id: runtime-version
|
|
48
|
+
attributes:
|
|
49
|
+
label: Python Version
|
|
50
|
+
placeholder: "e.g. 3.12.1"
|
|
51
|
+
validations:
|
|
52
|
+
required: true
|
|
53
|
+
- type: input
|
|
54
|
+
id: os
|
|
55
|
+
attributes:
|
|
56
|
+
label: OS
|
|
57
|
+
placeholder: "e.g. macOS 15, Ubuntu 24.04, Windows 11"
|
|
58
|
+
validations:
|
|
59
|
+
required: false
|
|
60
|
+
- type: dropdown
|
|
61
|
+
id: area
|
|
62
|
+
attributes:
|
|
63
|
+
label: Area
|
|
64
|
+
description: Which part of the SDK does this affect, if known?
|
|
65
|
+
multiple: true
|
|
66
|
+
options:
|
|
67
|
+
- License validation / check-in / check-out
|
|
68
|
+
- Machine management (activation, heartbeats, components, processes)
|
|
69
|
+
- Offline license/machine file verification
|
|
70
|
+
- Offline proof generation/verification
|
|
71
|
+
- Entitlements
|
|
72
|
+
- Auth transports
|
|
73
|
+
- Other / not sure
|
|
74
|
+
validations:
|
|
75
|
+
required: false
|
|
76
|
+
- type: checkboxes
|
|
77
|
+
id: terms
|
|
78
|
+
attributes:
|
|
79
|
+
label: Checklist
|
|
80
|
+
options:
|
|
81
|
+
- label: I have searched existing issues to make sure this is not a duplicate.
|
|
82
|
+
required: true
|
|
83
|
+
- label: I have included a minimal reproduction and the version fields above.
|
|
84
|
+
required: true
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: Feature Request
|
|
2
|
+
description: Suggest an addition to this SDK
|
|
3
|
+
title: "[Feature]: "
|
|
4
|
+
labels: ["enhancement", "triage"]
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: |
|
|
9
|
+
Thanks for suggesting an improvement! Please fill out the sections below.
|
|
10
|
+
- type: textarea
|
|
11
|
+
id: problem
|
|
12
|
+
attributes:
|
|
13
|
+
label: Problem / Motivation
|
|
14
|
+
description: What are you trying to do, and why doesn't the current SDK support it?
|
|
15
|
+
validations:
|
|
16
|
+
required: true
|
|
17
|
+
- type: textarea
|
|
18
|
+
id: solution
|
|
19
|
+
attributes:
|
|
20
|
+
label: Proposed Solution
|
|
21
|
+
description: What you want to happen — a proposed method signature or API shape if you have one in mind.
|
|
22
|
+
validations:
|
|
23
|
+
required: true
|
|
24
|
+
- type: textarea
|
|
25
|
+
id: server-check
|
|
26
|
+
attributes:
|
|
27
|
+
label: Is this feature actually available server-side?
|
|
28
|
+
description: >-
|
|
29
|
+
Before requesting an SDK feature, please check tamga-api's docs/sdk.md
|
|
30
|
+
(https://github.com/tamga-sh/tamga-api/blob/main/docs/sdk.md), especially the
|
|
31
|
+
"Known Server-Side Gaps" section — several documented server features are not
|
|
32
|
+
actually wired up server-side yet. A feature request for one of those will be
|
|
33
|
+
tracked as blocked on server-side work rather than implemented here.
|
|
34
|
+
validations:
|
|
35
|
+
required: false
|
|
36
|
+
- type: textarea
|
|
37
|
+
id: alternatives
|
|
38
|
+
attributes:
|
|
39
|
+
label: Alternatives Considered
|
|
40
|
+
validations:
|
|
41
|
+
required: false
|
|
42
|
+
- type: checkboxes
|
|
43
|
+
id: terms
|
|
44
|
+
attributes:
|
|
45
|
+
label: Checklist
|
|
46
|
+
options:
|
|
47
|
+
- label: I have searched existing issues to make sure this is not a duplicate.
|
|
48
|
+
required: true
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
<!-- What does this PR do, and why? -->
|
|
4
|
+
|
|
5
|
+
## Related plan section(s)
|
|
6
|
+
|
|
7
|
+
<!-- e.g. Section E (License Checkout Crypto) — see docs/plans/tamga-python.plan.md -->
|
|
8
|
+
|
|
9
|
+
## Checklist
|
|
10
|
+
|
|
11
|
+
- [ ] `uv run ruff check .` passes
|
|
12
|
+
- [ ] `uv run ruff format --check .` passes
|
|
13
|
+
- [ ] `uv run mypy src/` passes
|
|
14
|
+
- [ ] `uv run pytest --cov=tamga --cov-fail-under=80` passes
|
|
15
|
+
- [ ] Tests were written alongside the implementation (TDD), not bolted on after
|
|
16
|
+
- [ ] If this touches `src/tamga/crypto/`, `src/tamga/checkout/`, or `src/tamga/proof.py`: a
|
|
17
|
+
`security-reviewer` pass was requested and CRITICAL/HIGH findings addressed (see
|
|
18
|
+
`CONTRIBUTING.md` and `docs/plans/tamga-python.plan.md` Section 4)
|
|
19
|
+
- [ ] Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/)
|
|
20
|
+
- [ ] Plan checkboxes updated in `docs/plans/tamga-python.plan.md`, with an inline note for any
|
|
21
|
+
deviation from the plan's literal wording
|
|
22
|
+
|
|
23
|
+
## Test plan
|
|
24
|
+
|
|
25
|
+
<!-- How did you verify this works? -->
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "pip"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
groups:
|
|
8
|
+
dev-dependencies:
|
|
9
|
+
patterns:
|
|
10
|
+
- "ruff"
|
|
11
|
+
- "mypy"
|
|
12
|
+
- "pytest*"
|
|
13
|
+
open-pull-requests-limit: 10
|
|
14
|
+
labels:
|
|
15
|
+
- "dependencies"
|
|
16
|
+
|
|
17
|
+
- package-ecosystem: "github-actions"
|
|
18
|
+
directory: "/"
|
|
19
|
+
schedule:
|
|
20
|
+
interval: "weekly"
|
|
21
|
+
labels:
|
|
22
|
+
- "dependencies"
|
|
23
|
+
- "ci"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
test:
|
|
18
|
+
name: test (py${{ matrix.python-version }})
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
strategy:
|
|
21
|
+
fail-fast: false
|
|
22
|
+
matrix:
|
|
23
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Install uv
|
|
28
|
+
uses: astral-sh/setup-uv@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: ${{ matrix.python-version }}
|
|
31
|
+
|
|
32
|
+
- name: Install dependencies
|
|
33
|
+
run: uv sync --all-extras --dev
|
|
34
|
+
|
|
35
|
+
- name: ruff check
|
|
36
|
+
run: uv run ruff check .
|
|
37
|
+
|
|
38
|
+
- name: ruff format --check
|
|
39
|
+
run: uv run ruff format --check .
|
|
40
|
+
|
|
41
|
+
- name: mypy
|
|
42
|
+
run: uv run mypy src/
|
|
43
|
+
|
|
44
|
+
- name: pytest (with coverage gate)
|
|
45
|
+
run: uv run pytest --cov=tamga --cov-fail-under=80 --cov-report=xml
|
|
46
|
+
|
|
47
|
+
- name: Upload coverage
|
|
48
|
+
uses: codecov/codecov-action@v4
|
|
49
|
+
with:
|
|
50
|
+
files: coverage.xml
|
|
51
|
+
fail_ci_if_error: false
|
|
52
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
53
|
+
# Required when CODECOV_TOKEN is the org's shared Global Upload
|
|
54
|
+
# Token (one token for every Tamga SDK repo) rather than a
|
|
55
|
+
# per-repo token — without it Codecov can't tell which repo an
|
|
56
|
+
# upload belongs to.
|
|
57
|
+
slug: tamga-sh/tamga-python
|
|
58
|
+
|
|
59
|
+
smoke:
|
|
60
|
+
name: smoke (${{ matrix.os }})
|
|
61
|
+
runs-on: ${{ matrix.os }}
|
|
62
|
+
strategy:
|
|
63
|
+
fail-fast: false
|
|
64
|
+
matrix:
|
|
65
|
+
os: [macos-latest, windows-latest]
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v4
|
|
68
|
+
|
|
69
|
+
- name: Install uv
|
|
70
|
+
uses: astral-sh/setup-uv@v5
|
|
71
|
+
with:
|
|
72
|
+
python-version: "3.12"
|
|
73
|
+
|
|
74
|
+
- name: Install dependencies
|
|
75
|
+
run: uv sync --all-extras --dev
|
|
76
|
+
|
|
77
|
+
- name: pytest
|
|
78
|
+
run: uv run pytest
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
# Runs on every push to main: opens/updates a release PR that accumulates
|
|
13
|
+
# the changelog, or (when a release PR is merged) tags + creates the
|
|
14
|
+
# GitHub Release directly.
|
|
15
|
+
release-please:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
outputs:
|
|
18
|
+
release_created: ${{ steps.release.outputs.release_created }}
|
|
19
|
+
steps:
|
|
20
|
+
- uses: googleapis/release-please-action@v4
|
|
21
|
+
id: release
|
|
22
|
+
with:
|
|
23
|
+
release-type: python
|
|
24
|
+
config-file: release-please-config.json
|
|
25
|
+
manifest-file: .release-please-manifest.json
|
|
26
|
+
|
|
27
|
+
# Runs only right after release-please's own step above actually created a
|
|
28
|
+
# release (i.e. this run was the release-PR-merge push) — builds and
|
|
29
|
+
# publishes to PyPI via Trusted Publishing (OIDC), no long-lived API token
|
|
30
|
+
# stored as a repo secret.
|
|
31
|
+
#
|
|
32
|
+
# Deliberately gated via this job's `needs`/`if` on the release-please
|
|
33
|
+
# job's output, in the SAME workflow run, rather than a separate
|
|
34
|
+
# `on: release: types: [published]` trigger: release-please-action
|
|
35
|
+
# creates that GitHub Release using this workflow's own GITHUB_TOKEN, and
|
|
36
|
+
# GitHub does not let GITHUB_TOKEN-authored events trigger further
|
|
37
|
+
# workflow runs (loop-prevention) — so a `release: published` trigger
|
|
38
|
+
# here would simply never fire.
|
|
39
|
+
publish:
|
|
40
|
+
needs: release-please
|
|
41
|
+
if: needs.release-please.outputs.release_created == 'true'
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
environment: pypi
|
|
44
|
+
permissions:
|
|
45
|
+
contents: read
|
|
46
|
+
id-token: write # required for PyPI Trusted Publishing
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
|
|
50
|
+
- name: Install uv
|
|
51
|
+
uses: astral-sh/setup-uv@v5
|
|
52
|
+
with:
|
|
53
|
+
python-version: "3.12"
|
|
54
|
+
|
|
55
|
+
- name: Build sdist + wheel
|
|
56
|
+
run: uv build
|
|
57
|
+
|
|
58
|
+
- name: Publish to PyPI
|
|
59
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
60
|
+
with:
|
|
61
|
+
packages-dir: dist/
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. This file is maintained by
|
|
4
|
+
[release-please](https://github.com/googleapis/release-please) from
|
|
5
|
+
[Conventional Commits](https://www.conventionalcommits.org/) history — do not hand-edit entries
|
|
6
|
+
below the `[Unreleased]` header.
|
|
7
|
+
|
|
8
|
+
## 0.1.0 (2026-08-11)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* implement license/machine validation, checkout crypto, and error model (Sections B-K) ([d0524d8](https://github.com/tamga-sh/tamga-python/commit/d0524d822e4b323b071906e2b19a71fe1a90d069))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **ci:** gate PyPI publish on release-please's own job output ([b352422](https://github.com/tamga-sh/tamga-python/commit/b35242271ffb6dfb845cadd07dda9aacfc6791ae))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Documentation
|
|
22
|
+
|
|
23
|
+
* add README, examples, CHANGELOG, CONTRIBUTING, SECURITY, GitHub templates (Sections L-M) ([6307ceb](https://github.com/tamga-sh/tamga-python/commit/6307cebc047d7ab3d90fa41b2185baac0632b6ff))
|
|
24
|
+
* remove dead docs/plans references, add status badges where CI is live ([7971490](https://github.com/tamga-sh/tamga-python/commit/79714904468fc6abcf5893c53a4b491e32eca768))
|
|
25
|
+
|
|
26
|
+
## [Unreleased]
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project
|
|
6
|
+
|
|
7
|
+
`tamga-python` is the official Python SDK for Tamga, a license-management API. It is one of the
|
|
8
|
+
five independently hand-written SDKs in the Tamga family (alongside `tamga-rust`, `tamga-go`,
|
|
9
|
+
`tamga-dotnet`, `tamga-js`) — unlike `tamga-c`/`tamga-java`/`tamga-swift`, this package
|
|
10
|
+
re-implements the full validation and cryptographic-verification surface natively in Python rather
|
|
11
|
+
than binding to the Rust reference implementation via FFI.
|
|
12
|
+
|
|
13
|
+
Full task-by-task build plan: [`docs/plans/tamga-python.plan.md`](docs/plans/tamga-python.plan.md).
|
|
14
|
+
Authoritative wire-level protocol reference (endpoints, field names, enum values, and the
|
|
15
|
+
**Known Server-Side Gaps** section — read that before touching anything protocol-shaped):
|
|
16
|
+
[`tamga-api`'s `docs/sdk.md`](https://github.com/tamga-sh/tamga-api/blob/main/docs/sdk.md).
|
|
17
|
+
|
|
18
|
+
**Repo status:** scaffold only. Every module under `src/tamga/` is a typed stub — real
|
|
19
|
+
docstrings and signatures, `raise NotImplementedError`/`...` bodies. Do not assume any endpoint or
|
|
20
|
+
crypto path actually works; check the plan file's checkbox state before relying on something.
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
Pure Python, no Rust extension, no native build step. `hatchling` build backend, src-layout.
|
|
25
|
+
`cryptography` (pyca) supplies every crypto primitive (Ed25519, RSA-PKCS1/PSS, ECDSA-P256,
|
|
26
|
+
AES-256-GCM, HKDF); `httpx` supplies the HTTP transport.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
src/tamga/
|
|
30
|
+
├── __init__.py # public re-exports: TamgaClient, TamgaConfig, errors, __version__
|
|
31
|
+
├── py.typed # PEP 561 marker
|
|
32
|
+
├── client.py # TamgaClient façade + namespaced sub-clients (licenses/machines/
|
|
33
|
+
│ # components/processes/entitlements) + all endpoint methods
|
|
34
|
+
├── transport.py # httpx wiring, 5 auth transports, header handling
|
|
35
|
+
├── proof.py # offline proof payload build + verify
|
|
36
|
+
├── errors.py # TamgaError hierarchy, JSON:API error envelope parsing
|
|
37
|
+
├── models/
|
|
38
|
+
│ ├── validation.py # ValidationCode (24 members), ValidationMeta, ValidationResult
|
|
39
|
+
│ ├── license.py # LicenseResource, LicenseScope, LicenseFileResource
|
|
40
|
+
│ ├── machine.py # MachineResource, ComponentResource, ProcessResource, HeartbeatStatus
|
|
41
|
+
│ └── policy.py # PolicyResource + policy-derived enums, Entitlement
|
|
42
|
+
├── crypto/
|
|
43
|
+
│ ├── ed25519.py # Ed25519 verify (license checkout, one of 4 machine-checkout schemes)
|
|
44
|
+
│ ├── rsa.py # RSA-PKCS1v15 + RSA-PSS verify (machine checkout, offline proof)
|
|
45
|
+
│ ├── ecdsa.py # ECDSA-P256 verify (machine checkout)
|
|
46
|
+
│ ├── aes_gcm.py # AES-256-GCM decrypt (both checkout flows)
|
|
47
|
+
│ ├── hkdf.py # HKDF-SHA256 machine-file key derivation
|
|
48
|
+
│ └── naive_key.py # non-KDF zero-pad/truncate license-file key derivation
|
|
49
|
+
└── checkout/
|
|
50
|
+
├── license_file.py # .lic parse + verify pipeline
|
|
51
|
+
└── machine_file.py # machine-file parse + multi-scheme verify pipeline
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Vertical-ish grouping, not one flat client.** `TamgaClient` exposes `.licenses`, `.machines`,
|
|
55
|
+
`.components`, `.processes`, `.entitlements` sub-clients instead of one giant method namespace —
|
|
56
|
+
mirrors the resource grouping in `docs/sdk.md`, keep new endpoint methods on the matching
|
|
57
|
+
sub-client rather than bolting everything onto `TamgaClient` directly.
|
|
58
|
+
|
|
59
|
+
**Crypto stays out of `client.py`.** Signature verification and key derivation live under
|
|
60
|
+
`crypto/`, one primitive per file, so a security review can be scoped to exactly the files that
|
|
61
|
+
changed. `checkout/license_file.py` and `checkout/machine_file.py` orchestrate `crypto/` calls but
|
|
62
|
+
never implement a primitive inline.
|
|
63
|
+
|
|
64
|
+
## Dev Commands
|
|
65
|
+
|
|
66
|
+
This repo uses [`uv`](https://docs.astral.sh/uv/) for environment and dependency management.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
uv sync --all-extras --dev # install runtime + dev deps into .venv
|
|
70
|
+
uv run pytest # run tests
|
|
71
|
+
uv run pytest --cov=tamga --cov-fail-under=80 --cov-report=term-missing # tests + coverage gate
|
|
72
|
+
uv run ruff check . # lint
|
|
73
|
+
uv run ruff check --fix . # lint, auto-fix
|
|
74
|
+
uv run ruff format . # format
|
|
75
|
+
uv run ruff format --check . # format check (CI mode, no writes)
|
|
76
|
+
uv run mypy src/ # type check (strict mode — see pyproject.toml [tool.mypy])
|
|
77
|
+
uv build # build sdist + wheel
|
|
78
|
+
uv publish # manual/local publish — see "Release" below
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
There is no single `just check`-style umbrella command yet (unlike `tamga-api`) — CI runs each
|
|
82
|
+
step above individually; see `.github/workflows/ci.yml` for the exact order
|
|
83
|
+
(ruff check → ruff format --check → mypy → pytest+coverage).
|
|
84
|
+
|
|
85
|
+
## GOTCHAS
|
|
86
|
+
|
|
87
|
+
### This SDK's own crypto traps
|
|
88
|
+
|
|
89
|
+
- **Signing-message trap (license checkout).** The Ed25519 signature in a `.lic` file covers
|
|
90
|
+
`enc`'s **base64 string bytes** (`enc.encode("ascii")`), not `base64.b64decode(enc)`. Getting
|
|
91
|
+
this backwards makes every signature fail to verify even with the correct key and data. See
|
|
92
|
+
`checkout/license_file.py`'s module docstring and the dedicated regression test called for in
|
|
93
|
+
the plan (Section E).
|
|
94
|
+
- **Naive key derivation is not a KDF (license checkout).** `crypto/naive_key.py` must NOT become
|
|
95
|
+
a real hash/KDF. It is `license.key`'s raw UTF-8 bytes, zero-padded/truncated to exactly 32
|
|
96
|
+
bytes — matching the server's exact (deliberately naive) transform. "Fixing" this into PBKDF2 or
|
|
97
|
+
SHA-256 breaks decryption against every license file the server has ever issued.
|
|
98
|
+
- **Byte-exact serialization (offline proof).** The RSA signature over an offline-proof payload
|
|
99
|
+
covers `{"account":{...},"machine":{...},"dataset":...}` serialized in exactly that key order.
|
|
100
|
+
Field *presence* matching isn't enough — reordering the same fields into valid-but-different JSON
|
|
101
|
+
must produce a different signed message. Build via an explicitly ordered structure +
|
|
102
|
+
`json.dumps(..., separators=(",", ":"))`, never rely on incidental dict-ordering.
|
|
103
|
+
- **`RSA_2048_JWT_RS256` is rejected, not unsupported-and-ignored.** Machine-checkout scheme
|
|
104
|
+
dispatch must raise `SchemeNotSupportedError` for this scheme explicitly — don't let it fall
|
|
105
|
+
through to a different verifier, and don't silently skip verification.
|
|
106
|
+
- **`pid` is a string on the wire.** `ProcessResource.pid` and `processes.create(..., pid=...)` are
|
|
107
|
+
typed `str`, matching the server exactly. Reject `int` input at the call boundary; don't
|
|
108
|
+
`str()`-coerce it — that hides a caller bug instead of surfacing it.
|
|
109
|
+
- **`"DENY_ACCESS"` / `"NO_RESURRECTION"` are not real enum variants.** Freshly-created policies
|
|
110
|
+
report these as their `overage_strategy`/`heartbeat_resurrection_strategy` defaults, but neither
|
|
111
|
+
string is a valid member of `OverageStrategy`/`HeartbeatResurrectionStrategy`. Both silently
|
|
112
|
+
behave as `NO_OVERAGE`/`NO_REVIVE` server-side — `PolicyResource` parsing must apply that
|
|
113
|
+
fallback, not trust the field name's implication that access is denied by default.
|
|
114
|
+
|
|
115
|
+
### `docs/sdk.md` "Known Server-Side Gaps" that apply to this repo
|
|
116
|
+
|
|
117
|
+
Only the gaps relevant to this SDK's actual scope (license validation, checkout, machine
|
|
118
|
+
management, entitlements) are listed — see the upstream doc for the full list, including
|
|
119
|
+
analytics/EE items that don't touch this package at all.
|
|
120
|
+
|
|
121
|
+
- **Auth is not enforced server-side on license/machine endpoints** (gap #3). Send
|
|
122
|
+
`Authorization: License <key>` (or another transport) on every call anyway — it's
|
|
123
|
+
forward-compatible for when enforcement lands, but don't build any test or example that asserts
|
|
124
|
+
a *missing* credential gets rejected today; it won't be.
|
|
125
|
+
- **Only 14 of 24 `ValidationCode` values are reachable** (gap #4). Model all 24 in
|
|
126
|
+
`ValidationCode` with a lenient unknown-value fallback, but don't write tests or docs implying
|
|
127
|
+
the other 10 (`BANNED`, `ENTITLEMENTS_MISSING`, `TOO_MANY_USERS`, `HEARTBEAT_DEAD`,
|
|
128
|
+
`HEARTBEAT_NOT_STARTED`, `FINGERPRINT_SCOPE_MISMATCH`, `COMPONENTS_SCOPE_MISMATCH`,
|
|
129
|
+
`CHECKSUM_SCOPE_MISMATCH`, `VERSION_SCOPE_MISMATCH`, plus `NOT_FOUND` which comes back as a raw
|
|
130
|
+
HTTP 404 instead) can currently occur.
|
|
131
|
+
- **No in-app rate limiting; `429` is never returned** (gap #5). Do not implement client-side
|
|
132
|
+
429/backoff handling, `X-RateLimit-*` header parsing, or retry-with-backoff logic — there is
|
|
133
|
+
nothing on the other end to trigger it, and code written to expect it will just be dead code.
|
|
134
|
+
- **`Tamga-Environment` header is not implemented** (gap #7). Don't add it to `transport.py`'s
|
|
135
|
+
request headers even though it's documented as a planned EE feature — no server code path reads
|
|
136
|
+
it yet.
|
|
137
|
+
- **`heartbeat_status` ignores `policy.heartbeat_duration`** (gap #8). The window is a hardcoded
|
|
138
|
+
600s for machines regardless of what a license's policy declares. `HeartbeatScheduler`'s
|
|
139
|
+
recommended interval (~200s) is sized against that hardcoded constant, not against
|
|
140
|
+
`policy.heartbeat_duration` — don't wire the scheduler to read the policy value, it wouldn't
|
|
141
|
+
matter server-side anyway.
|
|
142
|
+
- **RFC 9421 response signing is dead code** (gap #6) and the auto-update/release-check endpoint
|
|
143
|
+
is unusable (`docs/sdk.md` §12). Neither is in this SDK's scope at all — there is no
|
|
144
|
+
`releases` sub-client and none should be added until the server side is real.
|
|
145
|
+
|
|
146
|
+
## Testing
|
|
147
|
+
|
|
148
|
+
- Coverage gate: `--cov-fail-under=80`, enforced in CI via `pytest --cov=tamga --cov-fail-under=80`.
|
|
149
|
+
Run the same command locally before opening a PR.
|
|
150
|
+
- Fixtures live in `tests/conftest.py` (mock-transport HTTP client, throwaway Ed25519/RSA/ECDSA
|
|
151
|
+
keypairs) and `tests/fixtures/` (sample `.lic`/machine-file certificates, canned JSON:API error
|
|
152
|
+
bodies) — reuse these rather than hand-rolling new keypairs or HTTP mocks per test file.
|
|
153
|
+
`httpx.MockTransport` is a hard requirement for HTTP tests; do not spin up a real server or mock
|
|
154
|
+
at the `requests` level.
|
|
155
|
+
- Sections E, F, H (all three crypto-bearing sections) require a **mandatory, non-skippable**
|
|
156
|
+
`security-reviewer` pass before merge — see `docs/plans/tamga-python.plan.md` Section 4. A
|
|
157
|
+
`python-reviewer`-only pass is not sufficient for those sections.
|
|
158
|
+
- Golden-byte/known-answer tests matter more than structural-equality tests for the crypto paths —
|
|
159
|
+
e.g. the offline-proof payload test must assert an exact expected byte string, and the HKDF
|
|
160
|
+
derivation test must assert an exact 32-byte key for a fixed input, not just "produces 32 bytes".
|
|
161
|
+
|
|
162
|
+
## Critical Dependency Notes
|
|
163
|
+
|
|
164
|
+
- **`cryptography` (pyca) is the sole crypto dependency.** It wraps OpenSSL/BoringSSL under the
|
|
165
|
+
hood and covers all four algorithm families this SDK needs (Ed25519, RSA, ECDSA, AES-GCM) plus
|
|
166
|
+
HKDF. Do not add `pynacl`, `pycryptodome`, or any other crypto library as an alternate/backup for
|
|
167
|
+
something `cryptography` already covers — that doubles the audit surface for no benefit.
|
|
168
|
+
- **`httpx`, not `requests`.** Async-capable, has first-class `MockTransport` support for tests,
|
|
169
|
+
and is what `transport.py` and `tests/conftest.py` are built around. Don't introduce `requests`
|
|
170
|
+
or `urllib3` calls anywhere in `src/tamga/`.
|
|
171
|
+
- **`mypy --strict`.** Every public function needs real type hints, not `Any` escape hatches,
|
|
172
|
+
except where a value is genuinely opaque server-side (e.g. token strings — see the `tok-` prefix
|
|
173
|
+
gotcha below).
|
|
174
|
+
- **mypy is pinned to `<2`.** mypy 2.0 dropped support for checking against `python_version =
|
|
175
|
+
"3.9"` (our `[tool.mypy]` target, matching `requires-python`) — running `uv run mypy src/` with
|
|
176
|
+
an unpinned/2.x mypy either errors outright or silently checks against the wrong Python version.
|
|
177
|
+
Don't remove the `<2` pin without also bumping `requires-python` and the CI matrix's floor.
|
|
178
|
+
- **Every issued server token is `tok-`-prefixed regardless of documented type.** Do not build
|
|
179
|
+
prefix-based token type detection into `transport.py`; treat all bearer tokens as opaque strings.
|
|
180
|
+
|
|
181
|
+
## Release
|
|
182
|
+
|
|
183
|
+
`release-please` (config: `release-please-config.json`, manifest:
|
|
184
|
+
`.release-please-manifest.json`) tracks `pyproject.toml`'s `version` and
|
|
185
|
+
`src/tamga/__init__.py`'s `__version__` together, opening/updating a release PR with the generated
|
|
186
|
+
changelog on every push to `main`. Publishing to PyPI happens on `release: published` via
|
|
187
|
+
`pypa/gh-action-pypi-publish@release/v1` using **PyPI Trusted Publishing (OIDC)** —
|
|
188
|
+
there is no `PYPI_API_TOKEN` secret in this repo, and one should not be added.
|
|
189
|
+
|
|
190
|
+
**Manual/local publish** (only if ever needed outside CI): `uv publish`, not `twine` — keep the
|
|
191
|
+
tooling consistent between CI and any manual escape hatch.
|
|
192
|
+
|
|
193
|
+
## Branch & Commit Convention
|
|
194
|
+
|
|
195
|
+
Branches: `feat/*`, `fix/*`, `chore/*`, `refactor/*`, `docs/*`
|
|
196
|
+
Commits: [Conventional Commits](https://www.conventionalcommits.org/) format (`feat: …`, `fix: …`,
|
|
197
|
+
etc.) — `release-please` parses these to compute the next version and changelog entry, so
|
|
198
|
+
non-conforming commit messages silently produce no release.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Contributing to tamga-python
|
|
2
|
+
|
|
3
|
+
## Dev setup
|
|
4
|
+
|
|
5
|
+
This repo uses [`uv`](https://docs.astral.sh/uv/) for environment and dependency management.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv sync --all-extras --dev # install runtime + dev deps into .venv
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
There is no single `just check`-style umbrella command yet — run each step individually, in the
|
|
14
|
+
order CI runs them (`.github/workflows/ci.yml`):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv run ruff check . # lint
|
|
18
|
+
uv run ruff format --check . # format check
|
|
19
|
+
uv run mypy src/ # type check (strict)
|
|
20
|
+
uv run pytest --cov=tamga --cov-fail-under=80 --cov-report=term-missing # tests + coverage
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Auto-fix locally before committing:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv run ruff check --fix .
|
|
27
|
+
uv run ruff format .
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Test-driven development
|
|
31
|
+
|
|
32
|
+
Write the test in the same task as the implementation, not after — see
|
|
33
|
+
[`docs/plans/tamga-python.plan.md`](docs/plans/tamga-python.plan.md) for the section-by-section
|
|
34
|
+
task breakdown. Fixtures live in `tests/conftest.py` (mock-transport HTTP client via
|
|
35
|
+
`httpx.MockTransport`, throwaway Ed25519/RSA/ECDSA keypairs) — reuse these rather than
|
|
36
|
+
hand-rolling new ones per test file.
|
|
37
|
+
|
|
38
|
+
Golden-byte/known-answer tests matter more than structural-equality tests for the crypto paths —
|
|
39
|
+
e.g. the offline-proof payload test asserts an exact expected byte string, and the HKDF
|
|
40
|
+
derivation test asserts an exact 32-byte key for a fixed input, not just "produces 32 bytes".
|
|
41
|
+
|
|
42
|
+
## Crypto changes require a security review
|
|
43
|
+
|
|
44
|
+
Any change touching `src/tamga/crypto/`, `src/tamga/checkout/`, or `src/tamga/proof.py` requires
|
|
45
|
+
a `security-reviewer` pass before merge — a general code-quality review alone is not sufficient.
|
|
46
|
+
See [`docs/plans/tamga-python.plan.md`](docs/plans/tamga-python.plan.md) Section 4 (Quality
|
|
47
|
+
Gates) and [`SECURITY.md`](SECURITY.md) for the specific assumptions those files encode.
|
|
48
|
+
|
|
49
|
+
## Pull request expectations
|
|
50
|
+
|
|
51
|
+
- Conventional Commits format (`feat: …`, `fix: …`, `docs: …`, etc.) — `release-please` parses
|
|
52
|
+
commit history directly to compute the next version and generate `CHANGELOG.md`; a
|
|
53
|
+
non-conforming commit type can silently skip a release.
|
|
54
|
+
- Required checks before requesting review (these are also what CI enforces — see
|
|
55
|
+
`.github/workflows/ci.yml`): `ruff check`, `ruff format --check`, `mypy src/`, and
|
|
56
|
+
`pytest --cov=tamga --cov-fail-under=80` all passing. Branch protection on `main` should require
|
|
57
|
+
all of these plus the full Python version matrix (3.9–3.13) before merge.
|
|
58
|
+
- Keep PRs scoped to one plan section (or one bug/feature) where practical — crypto-bearing
|
|
59
|
+
sections in particular should not be batched with unrelated changes, so a security review can
|
|
60
|
+
stay scoped to exactly the files that changed.
|
|
61
|
+
|
|
62
|
+
## Branch & commit convention
|
|
63
|
+
|
|
64
|
+
Branches: `feat/*`, `fix/*`, `chore/*`, `refactor/*`, `docs/*`.
|
|
65
|
+
|
|
66
|
+
## Release
|
|
67
|
+
|
|
68
|
+
Handled by CI (`release-please` + PyPI Trusted Publishing) — see [`CLAUDE.md`](CLAUDE.md)'s
|
|
69
|
+
"Release" section. Manual/local publish, if ever needed: `uv build && uv publish` (not `twine`).
|