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.
Files changed (63) hide show
  1. tamga_sdk-0.1.0/.github/CODEOWNERS +15 -0
  2. tamga_sdk-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +84 -0
  3. tamga_sdk-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  4. tamga_sdk-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +48 -0
  5. tamga_sdk-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +25 -0
  6. tamga_sdk-0.1.0/.github/dependabot.yml +23 -0
  7. tamga_sdk-0.1.0/.github/workflows/ci.yml +78 -0
  8. tamga_sdk-0.1.0/.github/workflows/release.yml +61 -0
  9. tamga_sdk-0.1.0/.gitignore +17 -0
  10. tamga_sdk-0.1.0/.release-please-manifest.json +3 -0
  11. tamga_sdk-0.1.0/CHANGELOG.md +26 -0
  12. tamga_sdk-0.1.0/CLAUDE.md +198 -0
  13. tamga_sdk-0.1.0/CONTRIBUTING.md +69 -0
  14. tamga_sdk-0.1.0/LICENSE +21 -0
  15. tamga_sdk-0.1.0/PKG-INFO +156 -0
  16. tamga_sdk-0.1.0/README.md +120 -0
  17. tamga_sdk-0.1.0/SECURITY.md +73 -0
  18. tamga_sdk-0.1.0/examples/checkout_and_verify.py +54 -0
  19. tamga_sdk-0.1.0/examples/heartbeat_scheduler.py +56 -0
  20. tamga_sdk-0.1.0/examples/machine_activation_flow.py +48 -0
  21. tamga_sdk-0.1.0/examples/offline_proof.py +45 -0
  22. tamga_sdk-0.1.0/examples/validate_license.py +47 -0
  23. tamga_sdk-0.1.0/pyproject.toml +109 -0
  24. tamga_sdk-0.1.0/release-please-config.json +15 -0
  25. tamga_sdk-0.1.0/src/tamga/__init__.py +57 -0
  26. tamga_sdk-0.1.0/src/tamga/checkout/__init__.py +13 -0
  27. tamga_sdk-0.1.0/src/tamga/checkout/_envelope.py +63 -0
  28. tamga_sdk-0.1.0/src/tamga/checkout/license_file.py +174 -0
  29. tamga_sdk-0.1.0/src/tamga/checkout/machine_file.py +232 -0
  30. tamga_sdk-0.1.0/src/tamga/client.py +1028 -0
  31. tamga_sdk-0.1.0/src/tamga/crypto/__init__.py +27 -0
  32. tamga_sdk-0.1.0/src/tamga/crypto/aes_gcm.py +31 -0
  33. tamga_sdk-0.1.0/src/tamga/crypto/ecdsa.py +64 -0
  34. tamga_sdk-0.1.0/src/tamga/crypto/ed25519.py +39 -0
  35. tamga_sdk-0.1.0/src/tamga/crypto/hkdf.py +39 -0
  36. tamga_sdk-0.1.0/src/tamga/crypto/naive_key.py +40 -0
  37. tamga_sdk-0.1.0/src/tamga/crypto/rsa.py +77 -0
  38. tamga_sdk-0.1.0/src/tamga/errors.py +167 -0
  39. tamga_sdk-0.1.0/src/tamga/models/__init__.py +47 -0
  40. tamga_sdk-0.1.0/src/tamga/models/license.py +136 -0
  41. tamga_sdk-0.1.0/src/tamga/models/machine.py +134 -0
  42. tamga_sdk-0.1.0/src/tamga/models/policy.py +243 -0
  43. tamga_sdk-0.1.0/src/tamga/models/validation.py +154 -0
  44. tamga_sdk-0.1.0/src/tamga/proof.py +183 -0
  45. tamga_sdk-0.1.0/src/tamga/py.typed +0 -0
  46. tamga_sdk-0.1.0/src/tamga/transport.py +232 -0
  47. tamga_sdk-0.1.0/tests/__init__.py +1 -0
  48. tamga_sdk-0.1.0/tests/conftest.py +113 -0
  49. tamga_sdk-0.1.0/tests/fixtures/.gitkeep +0 -0
  50. tamga_sdk-0.1.0/tests/test_client_config.py +50 -0
  51. tamga_sdk-0.1.0/tests/test_components.py +96 -0
  52. tamga_sdk-0.1.0/tests/test_entitlements.py +145 -0
  53. tamga_sdk-0.1.0/tests/test_errors.py +118 -0
  54. tamga_sdk-0.1.0/tests/test_license_checkin.py +69 -0
  55. tamga_sdk-0.1.0/tests/test_license_checkout.py +246 -0
  56. tamga_sdk-0.1.0/tests/test_license_validation.py +190 -0
  57. tamga_sdk-0.1.0/tests/test_machine_checkout.py +324 -0
  58. tamga_sdk-0.1.0/tests/test_machine_management.py +158 -0
  59. tamga_sdk-0.1.0/tests/test_offline_proof.py +221 -0
  60. tamga_sdk-0.1.0/tests/test_policy_enums.py +83 -0
  61. tamga_sdk-0.1.0/tests/test_processes.py +122 -0
  62. tamga_sdk-0.1.0/tests/test_transport.py +151 -0
  63. 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,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security Vulnerability
4
+ url: https://github.com/tamga-sh/tamga-python/security/policy
5
+ about: Please report security vulnerabilities privately — do not open a public issue.
@@ -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,17 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ venv/
6
+ dist/
7
+ build/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ .pytest_cache/
11
+ .coverage
12
+ .coverage.*
13
+ htmlcov/
14
+ coverage.xml
15
+ .env
16
+ *.log
17
+ .DS_Store
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.1.0"
3
+ }
@@ -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`).