dnsid 0.26.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 (172) hide show
  1. dnsid-0.26.0/.gitattributes +2 -0
  2. dnsid-0.26.0/.github/CODEOWNERS +1 -0
  3. dnsid-0.26.0/.github/dependabot.yml +10 -0
  4. dnsid-0.26.0/.github/workflows/codeql.yml +33 -0
  5. dnsid-0.26.0/.github/workflows/compliance.yaml +14 -0
  6. dnsid-0.26.0/.github/workflows/lint-test.yml +57 -0
  7. dnsid-0.26.0/.github/workflows/regen-docs.yml +73 -0
  8. dnsid-0.26.0/.github/workflows/release-pr.yml +91 -0
  9. dnsid-0.26.0/.github/workflows/release.yml +160 -0
  10. dnsid-0.26.0/.gitignore +57 -0
  11. dnsid-0.26.0/.gitleaksignore +4 -0
  12. dnsid-0.26.0/CHANGELOG.md +560 -0
  13. dnsid-0.26.0/CODE_OF_CONDUCT.md +85 -0
  14. dnsid-0.26.0/COMPATIBILITY.md +153 -0
  15. dnsid-0.26.0/CONTRIBUTING.md +104 -0
  16. dnsid-0.26.0/LICENSE.txt +202 -0
  17. dnsid-0.26.0/NOTICE.txt +504 -0
  18. dnsid-0.26.0/PKG-INFO +827 -0
  19. dnsid-0.26.0/README.md +792 -0
  20. dnsid-0.26.0/SECURITY.md +85 -0
  21. dnsid-0.26.0/cliff.toml +47 -0
  22. dnsid-0.26.0/dnsid/__init__.py +387 -0
  23. dnsid-0.26.0/dnsid/_crypto.py +249 -0
  24. dnsid-0.26.0/dnsid/_default_resolver.py +251 -0
  25. dnsid-0.26.0/dnsid/_https_client.py +363 -0
  26. dnsid-0.26.0/dnsid/_utils.py +283 -0
  27. dnsid-0.26.0/dnsid/_verification_budget.py +170 -0
  28. dnsid-0.26.0/dnsid/agent_status.py +25 -0
  29. dnsid-0.26.0/dnsid/aws_kms_key_provider.py +611 -0
  30. dnsid-0.26.0/dnsid/c2sp_tlog/__init__.py +297 -0
  31. dnsid-0.26.0/dnsid/c2sp_tlog/canonical.py +152 -0
  32. dnsid-0.26.0/dnsid/c2sp_tlog/checkpoint.py +92 -0
  33. dnsid-0.26.0/dnsid/c2sp_tlog/checkpoint_store.py +127 -0
  34. dnsid-0.26.0/dnsid/c2sp_tlog/errors.py +137 -0
  35. dnsid-0.26.0/dnsid/c2sp_tlog/event_codec.py +472 -0
  36. dnsid-0.26.0/dnsid/c2sp_tlog/lr.py +185 -0
  37. dnsid-0.26.0/dnsid/c2sp_tlog/managed_verification_registry.py +176 -0
  38. dnsid-0.26.0/dnsid/c2sp_tlog/merkle.py +111 -0
  39. dnsid-0.26.0/dnsid/c2sp_tlog/policy.py +408 -0
  40. dnsid-0.26.0/dnsid/c2sp_tlog/prepared.py +589 -0
  41. dnsid-0.26.0/dnsid/c2sp_tlog/proof.py +95 -0
  42. dnsid-0.26.0/dnsid/c2sp_tlog/reader.py +1486 -0
  43. dnsid-0.26.0/dnsid/c2sp_tlog/resource_fetcher.py +299 -0
  44. dnsid-0.26.0/dnsid/c2sp_tlog/signed_note.py +159 -0
  45. dnsid-0.26.0/dnsid/c2sp_tlog/sqlite_checkpoint_store.py +220 -0
  46. dnsid-0.26.0/dnsid/c2sp_tlog/stream_bundle.py +759 -0
  47. dnsid-0.26.0/dnsid/c2sp_tlog/stream_source.py +336 -0
  48. dnsid-0.26.0/dnsid/c2sp_tlog/stream_verifier.py +674 -0
  49. dnsid-0.26.0/dnsid/c2sp_tlog/tiles.py +83 -0
  50. dnsid-0.26.0/dnsid/c2sp_tlog/trust_profile.py +154 -0
  51. dnsid-0.26.0/dnsid/c2sp_tlog/verification_registry.py +277 -0
  52. dnsid-0.26.0/dnsid/config_loading.py +742 -0
  53. dnsid-0.26.0/dnsid/conformance.py +51 -0
  54. dnsid-0.26.0/dnsid/enums.py +149 -0
  55. dnsid-0.26.0/dnsid/exceptions.py +190 -0
  56. dnsid-0.26.0/dnsid/http_signatures.py +889 -0
  57. dnsid-0.26.0/dnsid/interfaces.py +585 -0
  58. dnsid-0.26.0/dnsid/jose.py +509 -0
  59. dnsid-0.26.0/dnsid/local_key_provider.py +616 -0
  60. dnsid-0.26.0/dnsid/manager.py +2422 -0
  61. dnsid-0.26.0/dnsid/models.py +2315 -0
  62. dnsid-0.26.0/dnsid/oidc.py +1029 -0
  63. dnsid-0.26.0/dnsid/py.typed +0 -0
  64. dnsid-0.26.0/dnsid/registry.py +69 -0
  65. dnsid-0.26.0/dnsid/registry_client.py +1759 -0
  66. dnsid-0.26.0/dnsid/retry.py +86 -0
  67. dnsid-0.26.0/dnsid/safe_transport.py +385 -0
  68. dnsid-0.26.0/dnsid/wba_signer.py +435 -0
  69. dnsid-0.26.0/dnsid/wba_verifier.py +447 -0
  70. dnsid-0.26.0/dnsid/web_bot_auth.py +625 -0
  71. dnsid-0.26.0/docs/AGENTS.md +50 -0
  72. dnsid-0.26.0/docs/quickstart.md +229 -0
  73. dnsid-0.26.0/docs/reference/configuration.md +335 -0
  74. dnsid-0.26.0/docs/reference/core.md +643 -0
  75. dnsid-0.26.0/docs/reference/errors.md +339 -0
  76. dnsid-0.26.0/docs/reference/interfaces.md +638 -0
  77. dnsid-0.26.0/docs/reference/key-providers.md +572 -0
  78. dnsid-0.26.0/docs/reference/nav.json +58 -0
  79. dnsid-0.26.0/docs/reference/overview.md +60 -0
  80. dnsid-0.26.0/docs/reference/profile-http-signatures.md +260 -0
  81. dnsid-0.26.0/docs/reference/profile-jose.md +160 -0
  82. dnsid-0.26.0/docs/reference/profile-oidc.md +295 -0
  83. dnsid-0.26.0/docs/reference/profile-web-bot-auth.md +418 -0
  84. dnsid-0.26.0/docs/reference/records.md +412 -0
  85. dnsid-0.26.0/docs/reference/registry.md +628 -0
  86. dnsid-0.26.0/docs/reference/transparency-log.md +1884 -0
  87. dnsid-0.26.0/docs/reference/verification.md +254 -0
  88. dnsid-0.26.0/docs/sdk-reference.md +14 -0
  89. dnsid-0.26.0/docs/security.md +403 -0
  90. dnsid-0.26.0/examples/a2a/README.md +116 -0
  91. dnsid-0.26.0/examples/a2a/agent.py +163 -0
  92. dnsid-0.26.0/examples/a2a/main.py +84 -0
  93. dnsid-0.26.0/examples/a2a/run.sh +112 -0
  94. dnsid-0.26.0/examples/a2a/server.py +394 -0
  95. dnsid-0.26.0/examples/a2a/startup.py +174 -0
  96. dnsid-0.26.0/examples/local-key-provider/README.md +20 -0
  97. dnsid-0.26.0/examples/local-key-provider/main.py +80 -0
  98. dnsid-0.26.0/examples/oidc/README.md +46 -0
  99. dnsid-0.26.0/examples/oidc/main.py +93 -0
  100. dnsid-0.26.0/examples/published-signing/README.md +147 -0
  101. dnsid-0.26.0/examples/published-signing/aws-kms.json +23 -0
  102. dnsid-0.26.0/examples/published-signing/file.json +19 -0
  103. dnsid-0.26.0/examples/published-signing/main.py +43 -0
  104. dnsid-0.26.0/examples/trust-policy/README.md +29 -0
  105. dnsid-0.26.0/examples/trust-policy/main.py +50 -0
  106. dnsid-0.26.0/examples/validate-domain/README.md +32 -0
  107. dnsid-0.26.0/examples/validate-domain/main.py +33 -0
  108. dnsid-0.26.0/examples/webbotauth/README.md +29 -0
  109. dnsid-0.26.0/examples/webbotauth/main.py +90 -0
  110. dnsid-0.26.0/pyproject.toml +75 -0
  111. dnsid-0.26.0/scripts/gen_docs.py +728 -0
  112. dnsid-0.26.0/tests/__init__.py +0 -0
  113. dnsid-0.26.0/tests/_config.py +13 -0
  114. dnsid-0.26.0/tests/_registry_http.py +23 -0
  115. dnsid-0.26.0/tests/c2sp_fixtures.py +63 -0
  116. dnsid-0.26.0/tests/conftest.py +130 -0
  117. dnsid-0.26.0/tests/test_aws_kms_key_provider.py +663 -0
  118. dnsid-0.26.0/tests/test_c2sp_logical_identity.py +161 -0
  119. dnsid-0.26.0/tests/test_c2sp_managed_trust.py +150 -0
  120. dnsid-0.26.0/tests/test_c2sp_migration.py +692 -0
  121. dnsid-0.26.0/tests/test_c2sp_resource_fetcher.py +146 -0
  122. dnsid-0.26.0/tests/test_c2sp_stream_bundle.py +514 -0
  123. dnsid-0.26.0/tests/test_c2sp_tlog.py +2235 -0
  124. dnsid-0.26.0/tests/test_c2sp_trust_profile.py +111 -0
  125. dnsid-0.26.0/tests/test_c2sp_verification_registry.py +500 -0
  126. dnsid-0.26.0/tests/test_config_loading.py +879 -0
  127. dnsid-0.26.0/tests/test_corrected_boundaries.py +186 -0
  128. dnsid-0.26.0/tests/test_counterparty_acceptance.py +504 -0
  129. dnsid-0.26.0/tests/test_crypto.py +338 -0
  130. dnsid-0.26.0/tests/test_default_dns_resolver.py +68 -0
  131. dnsid-0.26.0/tests/test_domain_log.py +261 -0
  132. dnsid-0.26.0/tests/test_event_signing.py +325 -0
  133. dnsid-0.26.0/tests/test_gen_docs.py +19 -0
  134. dnsid-0.26.0/tests/test_generated_kid_is_thumbprint.py +10 -0
  135. dnsid-0.26.0/tests/test_http_signing.py +827 -0
  136. dnsid-0.26.0/tests/test_https_client.py +469 -0
  137. dnsid-0.26.0/tests/test_jwk_from_dict.py +8 -0
  138. dnsid-0.26.0/tests/test_jws.py +166 -0
  139. dnsid-0.26.0/tests/test_jwt.py +282 -0
  140. dnsid-0.26.0/tests/test_key_rotation.py +847 -0
  141. dnsid-0.26.0/tests/test_local_key_provider.py +270 -0
  142. dnsid-0.26.0/tests/test_local_key_provider_cli.py +261 -0
  143. dnsid-0.26.0/tests/test_manager.py +562 -0
  144. dnsid-0.26.0/tests/test_oidc.py +791 -0
  145. dnsid-0.26.0/tests/test_post_sg_fanout.py +239 -0
  146. dnsid-0.26.0/tests/test_presentation_transport.py +227 -0
  147. dnsid-0.26.0/tests/test_public_api.py +106 -0
  148. dnsid-0.26.0/tests/test_published_signing_example.py +64 -0
  149. dnsid-0.26.0/tests/test_registration_groundwork.py +276 -0
  150. dnsid-0.26.0/tests/test_registration_groundwork_regressions.py +230 -0
  151. dnsid-0.26.0/tests/test_registry_client.py +1520 -0
  152. dnsid-0.26.0/tests/test_registry_errors.py +75 -0
  153. dnsid-0.26.0/tests/test_retry.py +148 -0
  154. dnsid-0.26.0/tests/test_signed_async_http_client.py +222 -0
  155. dnsid-0.26.0/tests/test_sqlite_checkpoint_store.py +209 -0
  156. dnsid-0.26.0/tests/test_ssrf_guard.py +308 -0
  157. dnsid-0.26.0/tests/test_trust_policy_example.py +34 -0
  158. dnsid-0.26.0/tests/test_txt_record.py +632 -0
  159. dnsid-0.26.0/tests/test_unified_registration.py +279 -0
  160. dnsid-0.26.0/tests/test_utils.py +203 -0
  161. dnsid-0.26.0/tests/test_verification_budget.py +98 -0
  162. dnsid-0.26.0/tests/test_verification_coalescing.py +459 -0
  163. dnsid-0.26.0/tests/test_verify_domain.py +1345 -0
  164. dnsid-0.26.0/tests/test_wba_contracts.py +442 -0
  165. dnsid-0.26.0/tests/test_wba_signer.py +295 -0
  166. dnsid-0.26.0/tests/test_wba_verifier.py +548 -0
  167. dnsid-0.26.0/tests/test_web_bot_auth.py +828 -0
  168. dnsid-0.26.0/tests/vectors/c2sp-event-identity.json +37 -0
  169. dnsid-0.26.0/tests/vectors/c2sp-managed-trust-selection.json +85 -0
  170. dnsid-0.26.0/tests/vectors/c2sp-stream-bundle-v1.json +68 -0
  171. dnsid-0.26.0/tests/vectors/generate_c2sp_stream_bundle.py +258 -0
  172. dnsid-0.26.0/uv.lock +779 -0
@@ -0,0 +1,2 @@
1
+ # Generated API reference — collapsed by default in PR diffs.
2
+ docs/reference/** linguist-generated=true
@@ -0,0 +1 @@
1
+ * @dnsid-ai/sdk-maintainers
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ - package-ecosystem: "github-actions"
8
+ directory: "/"
9
+ schedule:
10
+ interval: "weekly"
@@ -0,0 +1,33 @@
1
+ name: CodeQL
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ schedule:
9
+ - cron: '27 3 * * 1'
10
+
11
+ permissions:
12
+ security-events: write
13
+ packages: read
14
+ actions: read
15
+ contents: read
16
+
17
+ jobs:
18
+ analyze:
19
+ name: Analyze (${{ matrix.language }})
20
+ runs-on: ubuntu-latest
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ language: ['python']
25
+ steps:
26
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27
+ - uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
28
+ with:
29
+ languages: ${{ matrix.language }}
30
+ - uses: github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
31
+ - uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
32
+ with:
33
+ category: /language:${{ matrix.language }}
@@ -0,0 +1,14 @@
1
+ name: Compliance
2
+ on: pull_request
3
+ permissions:
4
+ contents: read
5
+ issues: write
6
+ jobs:
7
+ compliance:
8
+ # The private compliance harness is not available to fork pull requests.
9
+ if: github.event.pull_request.head.repo.full_name == github.repository
10
+ uses: dnsid-ai/dnsid-sdk-compliance/.github/workflows/sdk-compliance.yaml@74b48988742602e9ef95eec04f5638b366491ff2
11
+ with:
12
+ sdk: py
13
+ ref: ${{ github.event.pull_request.head.sha }}
14
+ base-ref: ${{ github.event.pull_request.base.sha }}
@@ -0,0 +1,57 @@
1
+ name: Lint and Test
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ lint-test:
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ python-version: ["3.11", "3.12", "3.13"]
19
+ steps:
20
+ - name: Checkout repository
21
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22
+
23
+ - name: Setup Python
24
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+ cache: pip
28
+
29
+ - name: Install dependencies
30
+ run: pip install -e ".[dev]"
31
+
32
+ # Scans the installed environment (runtime + dev). Accepted risks: add
33
+ # `--ignore-vuln <ID>` here with a comment linking the advisory.
34
+ - name: Dependency vulnerability audit
35
+ if: matrix.python-version == '3.13'
36
+ run: pip install pip-audit && pip-audit
37
+
38
+ - name: Ruff lint check
39
+ run: ruff check .
40
+
41
+ - name: Mypy type check
42
+ run: mypy dnsid
43
+
44
+ - name: Run tests
45
+ run: pytest
46
+
47
+ # Advisory only: the regen-docs workflow pushes regenerated reference
48
+ # docs to same-repo PR branches automatically. The warning matters for
49
+ # fork PRs, where the bot cannot push.
50
+ - name: Docs freshness (generated reference)
51
+ if: matrix.python-version == '3.13'
52
+ run: |
53
+ python scripts/gen_docs.py
54
+ git diff --exit-code docs/reference/ || {
55
+ echo "::warning::docs/reference/ is stale — the auto-regen workflow pushes the fix to same-repo PRs; fork authors run python scripts/gen_docs.py and commit the result"
56
+ exit 0
57
+ }
@@ -0,0 +1,73 @@
1
+ name: Auto-regenerate reference docs
2
+
3
+ # On every same-repo PR push, regenerate docs/reference/ and, when the output
4
+ # changed, push the fix to the PR branch — contributors never run the docs
5
+ # generator by hand. Fork PRs are skipped (GITHUB_TOKEN cannot push to forks);
6
+ # the freshness warning in lint-test.yml still tells fork authors the manual
7
+ # command.
8
+ #
9
+ # Loop guard: the job skips when the actor is github-actions[bot] (GraphQL-created
10
+ # commits do re-trigger pull_request), and a re-run would find no diff anyway. The same property means CI results shown on the PR
11
+ # belong to the contributor's last commit; the bot commit only touches
12
+ # docs/reference/, which no test job consumes.
13
+ on:
14
+ pull_request:
15
+
16
+ permissions:
17
+ contents: write
18
+
19
+ concurrency:
20
+ group: regen-docs-${{ github.event.pull_request.number }}
21
+ cancel-in-progress: true
22
+
23
+ jobs:
24
+ regen:
25
+ # actor guard: createCommitOnBranch commits DO re-trigger pull_request, unlike GITHUB_TOKEN git pushes
26
+ if: github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'github-actions[bot]'
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
30
+ with:
31
+ ref: ${{ github.event.pull_request.head.ref }}
32
+
33
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
34
+ with:
35
+ python-version: "3.13"
36
+ cache: pip
37
+
38
+ - name: Install dependencies
39
+ run: pip install -e ".[dev]"
40
+
41
+ - name: Regenerate reference
42
+ run: python scripts/gen_docs.py
43
+
44
+ # Commit through the GraphQL API instead of `git commit`: commits created
45
+ # by createCommitOnBranch are signed by GitHub's web-flow key, so the bot
46
+ # commit is Verified and satisfies signed-commit branch protection. A
47
+ # GITHUB_TOKEN commit still does not re-trigger this workflow.
48
+ - name: Commit and push if changed
49
+ env:
50
+ GH_TOKEN: ${{ github.token }}
51
+ BRANCH: ${{ github.event.pull_request.head.ref }}
52
+ run: |
53
+ git add -A docs/reference/
54
+ if git diff --cached --quiet; then
55
+ echo "reference is current"
56
+ exit 0
57
+ fi
58
+ git diff --cached --name-only --diff-filter=AM -z \
59
+ | while IFS= read -r -d '' f; do base64 -w0 < "$f" > /tmp/b64; jq -n --arg p "$f" --rawfile c /tmp/b64 '{path: $p, contents: $c}'; done \
60
+ | jq -s . > /tmp/additions.json
61
+ git diff --cached --name-only --diff-filter=D -z \
62
+ | jq -Rs 'split("\u0000") | map(select(length > 0) | {path: .})' > /tmp/deletions.json
63
+ jq -n --arg repo "$GITHUB_REPOSITORY" --arg branch "$BRANCH" --arg head "$(git rev-parse HEAD)" \
64
+ --slurpfile additions /tmp/additions.json --slurpfile deletions /tmp/deletions.json '{
65
+ query: "mutation($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { commit { oid } } }",
66
+ variables: { input: {
67
+ branch: { repositoryNameWithOwner: $repo, branchName: $branch },
68
+ expectedHeadOid: $head,
69
+ message: { headline: "docs: regenerate reference" },
70
+ fileChanges: { additions: $additions[0], deletions: $deletions[0] }
71
+ } }
72
+ }' > /tmp/commit.json
73
+ gh api graphql --input /tmp/commit.json --jq '.data.createCommitOnBranch.commit.oid'
@@ -0,0 +1,91 @@
1
+ name: Update Release PR
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ permissions:
8
+ contents: write
9
+ pull-requests: write
10
+
11
+ jobs:
12
+ update-release-pr:
13
+ if: "!startsWith(github.event.head_commit.message, 'chore: update changelog and version') && !startsWith(github.event.head_commit.message, 'Release v')"
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17
+ with:
18
+ fetch-depth: 0
19
+
20
+ - name: Install git-cliff
21
+ uses: taiki-e/install-action@861a07ce7084f55488e375df125cdc99bba60eb7 # v2
22
+ with:
23
+ tool: git-cliff
24
+
25
+ - name: Setup Python
26
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
27
+ with:
28
+ python-version: "3.11"
29
+
30
+ - name: Compute next version and changelog
31
+ id: prep
32
+ run: |
33
+ NEXT=$(git cliff --bumped-version | head -1)
34
+ [ -n "$NEXT" ] || { echo "ERROR: git-cliff --bumped-version returned an empty version"; exit 1; }
35
+
36
+ CURRENT="v$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")"
37
+ if [ "$NEXT" = "$CURRENT" ] && git rev-parse "refs/tags/${CURRENT}" >/dev/null 2>&1; then
38
+ echo "No releasable commits found (computed: ${NEXT}, current: ${CURRENT}). Skipping."
39
+ echo "skip=true" >> "$GITHUB_OUTPUT"
40
+ else
41
+ echo "skip=false" >> "$GITHUB_OUTPUT"
42
+ echo "version=$NEXT" >> "$GITHUB_OUTPUT"
43
+ if [ "$NEXT" = "$CURRENT" ]; then
44
+ python - "$CURRENT" <<'PY'
45
+ import sys
46
+ from pathlib import Path
47
+
48
+ path = Path("CHANGELOG.md")
49
+ changelog = path.read_text()
50
+ marker = f"## [{sys.argv[1].removeprefix('v')}]"
51
+ start = changelog.find(marker)
52
+ if start != -1:
53
+ next_start = changelog.find("\n## [", start + 1)
54
+ path.write_text(changelog[:start] + changelog[next_start + 1 if next_start != -1 else len(changelog):])
55
+ PY
56
+ fi
57
+ git cliff --unreleased --tag "$NEXT" --prepend CHANGELOG.md
58
+ python - "$NEXT" <<'PY' > /tmp/release-notes.md
59
+ import sys
60
+ from pathlib import Path
61
+
62
+ version = sys.argv[1].removeprefix("v")
63
+ changelog = Path("CHANGELOG.md").read_text()
64
+ marker = f"## [{version}]"
65
+ start = changelog.find(marker)
66
+ if start == -1:
67
+ raise SystemExit(f"ERROR: {marker} not found in CHANGELOG.md")
68
+
69
+ next_start = changelog.find("\n## [", start + 1)
70
+ section = changelog[start : next_start if next_start != -1 else len(changelog)]
71
+ print(section.strip())
72
+ PY
73
+ fi
74
+
75
+ - name: Bump pyproject.toml version
76
+ if: steps.prep.outputs.skip != 'true'
77
+ run: |
78
+ VERSION="${{ steps.prep.outputs.version }}"
79
+ VERSION_BARE="${VERSION#v}"
80
+ sed -i "s/^version = \".*\"/version = \"${VERSION_BARE}\"/" pyproject.toml
81
+
82
+ - name: Open or update release PR
83
+ if: steps.prep.outputs.skip != 'true'
84
+ uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8
85
+ with:
86
+ branch: release/next
87
+ sign-commits: true
88
+ title: "Release ${{ steps.prep.outputs.version }}"
89
+ body-path: /tmp/release-notes.md
90
+ commit-message: "chore: update changelog and version"
91
+ labels: autorelease
@@ -0,0 +1,160 @@
1
+ name: Release
2
+
3
+ # Triggered when the "release/next" PR is merged into main, or manually.
4
+ #
5
+ # The `publish-pypi` job uploads the same checked wheel + sdist to PyPI via
6
+ # trusted publishing (OIDC; no API token stored here). It is gated on the
7
+ # repository variable PYPI_PUBLISHING == "enabled" so releases keep working
8
+ # while the PyPI project name / trusted publisher are pending. To enable:
9
+ # 1. PyPI project `dnsid` → Publishing → add GitHub publisher
10
+ # (owner dnsid-ai, repo dnsid-py, workflow release.yml, environment pypi)
11
+ # 2. Repo Settings → Environments → create `pypi` (add required reviewers if desired)
12
+ # 3. Repo Settings → Variables → PYPI_PUBLISHING = enabled
13
+
14
+ on:
15
+ pull_request:
16
+ types: [closed]
17
+ branches: [main]
18
+ workflow_dispatch:
19
+
20
+ permissions:
21
+ attestations: write
22
+ contents: write
23
+ id-token: write
24
+
25
+ jobs:
26
+ release:
27
+ if: >-
28
+ (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') ||
29
+ (github.event.pull_request.merged == true &&
30
+ github.event.pull_request.head.ref == 'release/next' &&
31
+ github.event.pull_request.head.repo.full_name == github.repository)
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
35
+ with:
36
+ fetch-depth: 0
37
+
38
+ - name: Setup Python
39
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
40
+ with:
41
+ python-version: "3.11"
42
+
43
+ - name: Read version
44
+ id: version
45
+ run: |
46
+ VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
47
+ [ -n "$VERSION" ] || { echo "ERROR: could not read version from pyproject.toml"; exit 1; }
48
+ echo "version=v${VERSION}" >> "$GITHUB_OUTPUT"
49
+
50
+ - name: Extract release notes
51
+ run: |
52
+ python - "${{ steps.version.outputs.version }}" <<'PY' > /tmp/release-notes.md
53
+ import sys
54
+ from pathlib import Path
55
+
56
+ version = sys.argv[1].removeprefix("v")
57
+ changelog = Path("CHANGELOG.md").read_text()
58
+ marker = f"## [{version}]"
59
+ start = changelog.find(marker)
60
+ if start == -1:
61
+ raise SystemExit(f"ERROR: {marker} not found in CHANGELOG.md")
62
+
63
+ next_start = changelog.find("\n## [", start + 1)
64
+ section = changelog[start : next_start if next_start != -1 else len(changelog)]
65
+ print(section.strip())
66
+ PY
67
+ echo "--- release notes ---"
68
+ cat /tmp/release-notes.md
69
+
70
+ - name: Verify release tag
71
+ id: tag
72
+ run: |
73
+ TAG="${{ steps.version.outputs.version }}"
74
+ if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then
75
+ [ "$(git rev-list -n 1 "${TAG}")" = "$GITHUB_SHA" ] || {
76
+ echo "ERROR: ${TAG} does not point at release commit ${GITHUB_SHA}"
77
+ exit 1
78
+ }
79
+ echo "exists=true" >> "$GITHUB_OUTPUT"
80
+ else
81
+ echo "exists=false" >> "$GITHUB_OUTPUT"
82
+ fi
83
+
84
+ - name: Install build tools
85
+ run: python -m pip install build twine
86
+
87
+ - name: Build and check package
88
+ run: |
89
+ python -m build
90
+ python -m twine check dist/*
91
+
92
+ - name: Smoke test wheel
93
+ run: |
94
+ python -m venv /tmp/dnsid-smoke
95
+ /tmp/dnsid-smoke/bin/pip install dist/*.whl
96
+ (cd /tmp && /tmp/dnsid-smoke/bin/python -c "import dnsid")
97
+
98
+ - name: Upload distribution artifacts
99
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
100
+ with:
101
+ name: dist
102
+ path: dist/
103
+ if-no-files-found: error
104
+
105
+ - name: Attest package provenance
106
+ if: github.event.repository.private == false
107
+ uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
108
+ with:
109
+ subject-path: dist/*
110
+
111
+ - name: Tag release
112
+ if: steps.tag.outputs.exists != 'true'
113
+ run: |
114
+ git config user.name "github-actions[bot]"
115
+ git config user.email "github-actions[bot]@users.noreply.github.com"
116
+ git tag -a "${{ steps.version.outputs.version }}" -m "Release ${{ steps.version.outputs.version }}"
117
+ git push origin "${{ steps.version.outputs.version }}"
118
+
119
+ - name: Generate SBOM (CycloneDX)
120
+ uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2
121
+ with:
122
+ path: .
123
+ format: cyclonedx-json
124
+ output-file: sbom.cdx.json
125
+ upload-artifact: false
126
+
127
+ - name: Create GitHub Release
128
+ run: |
129
+ if gh release view "${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
130
+ echo "Release ${{ steps.version.outputs.version }} already exists, skipping"
131
+ else
132
+ gh release create "${{ steps.version.outputs.version }}" \
133
+ dist/* sbom.cdx.json \
134
+ --verify-tag \
135
+ --title "Release ${{ steps.version.outputs.version }}" \
136
+ --notes-file /tmp/release-notes.md
137
+ fi
138
+ env:
139
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
140
+
141
+ publish-pypi:
142
+ needs: release
143
+ if: vars.PYPI_PUBLISHING == 'enabled'
144
+ runs-on: ubuntu-latest
145
+ environment:
146
+ name: pypi
147
+ url: https://pypi.org/project/dnsid/
148
+ permissions:
149
+ id-token: write
150
+ steps:
151
+ - name: Download distribution artifacts
152
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
153
+ with:
154
+ name: dist
155
+ path: dist/
156
+
157
+ - name: Publish to PyPI (trusted publishing)
158
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
159
+ with:
160
+ attestations: true
@@ -0,0 +1,57 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.so
7
+ *.egg
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ wheels/
12
+ sdist/
13
+ *.egg-link
14
+ pip-wheel-metadata/
15
+ MANIFEST
16
+
17
+ # Virtual environments
18
+ .venv/
19
+ venv/
20
+ env/
21
+ ENV/
22
+
23
+ # Testing / coverage
24
+ .pytest_cache/
25
+ .coverage
26
+ .coverage.*
27
+ htmlcov/
28
+ coverage.xml
29
+ *.xml
30
+
31
+ # Type checking
32
+ .mypy_cache/
33
+ .dmypy.json
34
+ .pytype/
35
+
36
+ # Ruff / linters
37
+ .ruff_cache/
38
+
39
+ # Distribution / packaging
40
+ *.tar.gz
41
+ *.whl
42
+
43
+ # Testnet runtime files (generated keys, certs)
44
+ .testnet/
45
+ keys.json
46
+
47
+ # IDE
48
+ .vscode/
49
+ .idea/
50
+ *.swp
51
+ *.swo
52
+ .DS_Store
53
+
54
+ # Local scratch (never committed)
55
+ .claude/
56
+ /TASK*.md
57
+ /*_ISSUES.md
@@ -0,0 +1,4 @@
1
+ # false positives: AWS KMS key_spec enum, not a secret
2
+ tests/test_aws_kms_key_provider.py:generic-api-key:75
3
+ tests/test_aws_kms_key_provider.py:generic-api-key:95
4
+ tests/test_aws_kms_key_provider.py:generic-api-key:311