sourcelock 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 (136) hide show
  1. sourcelock-0.1.0/.github/workflows/ci.yml +104 -0
  2. sourcelock-0.1.0/.github/workflows/release.yml +210 -0
  3. sourcelock-0.1.0/.gitignore +20 -0
  4. sourcelock-0.1.0/ADAPTER_GUIDE.md +449 -0
  5. sourcelock-0.1.0/LICENSE +21 -0
  6. sourcelock-0.1.0/MORNING-REPORT.md +168 -0
  7. sourcelock-0.1.0/PKG-INFO +272 -0
  8. sourcelock-0.1.0/README.md +232 -0
  9. sourcelock-0.1.0/RELEASING.md +130 -0
  10. sourcelock-0.1.0/action/action.yml +133 -0
  11. sourcelock-0.1.0/action/dry-run.sh +48 -0
  12. sourcelock-0.1.0/action/run_doctor.py +414 -0
  13. sourcelock-0.1.0/docs/ci.md +161 -0
  14. sourcelock-0.1.0/examples/sourcelock-example-adapter/README.md +74 -0
  15. sourcelock-0.1.0/examples/sourcelock-example-adapter/pyproject.toml +21 -0
  16. sourcelock-0.1.0/examples/sourcelock-example-adapter/sourcelock_example_adapter.py +203 -0
  17. sourcelock-0.1.0/hc_source/__init__.py +5 -0
  18. sourcelock-0.1.0/hc_source/adapters/__init__.py +500 -0
  19. sourcelock-0.1.0/hc_source/adapters/_demo.py +258 -0
  20. sourcelock-0.1.0/hc_source/adapters/_demo_fixture.json +25 -0
  21. sourcelock-0.1.0/hc_source/adapters/_leie_sample.csv +15 -0
  22. sourcelock-0.1.0/hc_source/adapters/codes.py +1232 -0
  23. sourcelock-0.1.0/hc_source/adapters/coverage.py +1569 -0
  24. sourcelock-0.1.0/hc_source/adapters/hcc.py +1450 -0
  25. sourcelock-0.1.0/hc_source/adapters/leie.py +1310 -0
  26. sourcelock-0.1.0/hc_source/adapters/provider.py +1159 -0
  27. sourcelock-0.1.0/hc_source/cache.py +664 -0
  28. sourcelock-0.1.0/hc_source/cli.py +959 -0
  29. sourcelock-0.1.0/hc_source/cli_manifest.py +207 -0
  30. sourcelock-0.1.0/hc_source/data/codes/hcpcs_2026q3.csv.gz +0 -0
  31. sourcelock-0.1.0/hc_source/data/codes/icd10cm_fy2026.csv.gz +0 -0
  32. sourcelock-0.1.0/hc_source/data/codes/icd10cm_fy2027.csv.gz +0 -0
  33. sourcelock-0.1.0/hc_source/data/codes/manifest.json +75 -0
  34. sourcelock-0.1.0/hc_source/data/codes/regenerate.py +291 -0
  35. sourcelock-0.1.0/hc_source/data/hcc/hcc_data.json.zlib +0 -0
  36. sourcelock-0.1.0/hc_source/doctor.py +472 -0
  37. sourcelock-0.1.0/hc_source/guard.py +877 -0
  38. sourcelock-0.1.0/hc_source/http.py +541 -0
  39. sourcelock-0.1.0/hc_source/interfaces.py +395 -0
  40. sourcelock-0.1.0/hc_source/lockfile.py +236 -0
  41. sourcelock-0.1.0/hc_source/manifest.py +422 -0
  42. sourcelock-0.1.0/hc_source/mcp_server.py +203 -0
  43. sourcelock-0.1.0/hc_source/npi.py +50 -0
  44. sourcelock-0.1.0/hc_source/receipts.py +74 -0
  45. sourcelock-0.1.0/hc_source/schemas.py +339 -0
  46. sourcelock-0.1.0/pyproject.toml +53 -0
  47. sourcelock-0.1.0/qa/FIX-WAVE-2-BRIEF.md +54 -0
  48. sourcelock-0.1.0/qa/breaker-round1.md +456 -0
  49. sourcelock-0.1.0/scripts/check_wheel.py +380 -0
  50. sourcelock-0.1.0/scripts/wheel_check.sh +35 -0
  51. sourcelock-0.1.0/source-lock.json +152 -0
  52. sourcelock-0.1.0/tests/adapters/README.md +3 -0
  53. sourcelock-0.1.0/tests/adapters/test_codes.py +560 -0
  54. sourcelock-0.1.0/tests/adapters/test_coverage.py +923 -0
  55. sourcelock-0.1.0/tests/adapters/test_hcc.py +1131 -0
  56. sourcelock-0.1.0/tests/adapters/test_leie.py +953 -0
  57. sourcelock-0.1.0/tests/adapters/test_provider.py +736 -0
  58. sourcelock-0.1.0/tests/conftest.py +102 -0
  59. sourcelock-0.1.0/tests/fixtures/README.md +4 -0
  60. sourcelock-0.1.0/tests/fixtures/codes/cdc_root_listing.html +3 -0
  61. sourcelock-0.1.0/tests/fixtures/codes/cms_landing_trimmed.html +100 -0
  62. sourcelock-0.1.0/tests/fixtures/coverage/README.md +34 -0
  63. sourcelock-0.1.0/tests/fixtures/coverage/articles_report_trimmed.json +55 -0
  64. sourcelock-0.1.0/tests/fixtures/coverage/contract_types.json +1 -0
  65. sourcelock-0.1.0/tests/fixtures/coverage/error_400_invalid_ncdid.json +1 -0
  66. sourcelock-0.1.0/tests/fixtures/coverage/error_401_lcd_no_token.json +15 -0
  67. sourcelock-0.1.0/tests/fixtures/coverage/final_lcds_report_trimmed.json +69 -0
  68. sourcelock-0.1.0/tests/fixtures/coverage/lcd_related_documents_33818.json +1 -0
  69. sourcelock-0.1.0/tests/fixtures/coverage/lcd_related_documents_head.csv +4 -0
  70. sourcelock-0.1.0/tests/fixtures/coverage/local_data_schedule.json +1 -0
  71. sourcelock-0.1.0/tests/fixtures/coverage/ncd_detail_11.json +1 -0
  72. sourcelock-0.1.0/tests/fixtures/coverage/ncd_report_trimmed.json +61 -0
  73. sourcelock-0.1.0/tests/fixtures/coverage/ncd_zip_range_headers.json +11 -0
  74. sourcelock-0.1.0/tests/fixtures/coverage/openapi_spec_trimmed.json +106 -0
  75. sourcelock-0.1.0/tests/fixtures/coverage/states.json +1 -0
  76. sourcelock-0.1.0/tests/fixtures/coverage/whats_new_local_trimmed.json +55 -0
  77. sourcelock-0.1.0/tests/fixtures/coverage/whats_new_national.json +1 -0
  78. sourcelock-0.1.0/tests/fixtures/hcc/VERIFIED_ADDENDUM.md +88 -0
  79. sourcelock-0.1.0/tests/fixtures/hcc/derive_report.txt +17 -0
  80. sourcelock-0.1.0/tests/fixtures/hcc/head_C2419P1M.csv +25 -0
  81. sourcelock-0.1.0/tests/fixtures/hcc/head_C2824T2N.csv +25 -0
  82. sourcelock-0.1.0/tests/fixtures/hcc/head_F2425P1M.txt +30 -0
  83. sourcelock-0.1.0/tests/fixtures/hcc/head_F2826T1N.txt +30 -0
  84. sourcelock-0.1.0/tests/fixtures/hcc/mirror_py2026.bin +1 -0
  85. sourcelock-0.1.0/tests/fixtures/hcc/mirror_sw2025.bin +1 -0
  86. sourcelock-0.1.0/tests/fixtures/hcc/mirror_sw2026.bin +3 -0
  87. sourcelock-0.1.0/tests/fixtures/hcc/mirror_sw2026_republished.bin +2 -0
  88. sourcelock-0.1.0/tests/fixtures/hcc/regenerate_vendored_data.py +503 -0
  89. sourcelock-0.1.0/tests/fixtures/leie/provenance.json +18 -0
  90. sourcelock-0.1.0/tests/fixtures/leie/updated_drift.csv +16 -0
  91. sourcelock-0.1.0/tests/fixtures/leie/updated_ok.csv +15 -0
  92. sourcelock-0.1.0/tests/fixtures/leie/updated_schema_changed.csv +15 -0
  93. sourcelock-0.1.0/tests/fixtures/leie/updated_unquoted.csv +15 -0
  94. sourcelock-0.1.0/tests/fixtures/leie/waivers_page.html +8 -0
  95. sourcelock-0.1.0/tests/fixtures/provider/PROVENANCE.md +22 -0
  96. sourcelock-0.1.0/tests/fixtures/provider/lookup_npi_missing.json +1 -0
  97. sourcelock-0.1.0/tests/fixtures/provider/lookup_npi_ok.json +1 -0
  98. sourcelock-0.1.0/tests/fixtures/provider/npi_api_error_noversion.json +1 -0
  99. sourcelock-0.1.0/tests/fixtures/provider/npi_files.html +128 -0
  100. sourcelock-0.1.0/tests/fixtures/provider/npi_files_v1_only.html +128 -0
  101. sourcelock-0.1.0/tests/fixtures/provider/pecos_dataset_resources.json +1 -0
  102. sourcelock-0.1.0/tests/fixtures/provider/pecos_filter_miss.json +1 -0
  103. sourcelock-0.1.0/tests/fixtures/provider/pecos_filter_ok.json +1 -0
  104. sourcelock-0.1.0/tests/fixtures/provider/pecos_size1.json +1 -0
  105. sourcelock-0.1.0/tests/fixtures/provider/pecos_stats.json +1 -0
  106. sourcelock-0.1.0/tests/test_action.py +422 -0
  107. sourcelock-0.1.0/tests/test_adapter_contract.py +103 -0
  108. sourcelock-0.1.0/tests/test_cache.py +1037 -0
  109. sourcelock-0.1.0/tests/test_canary_severity.py +625 -0
  110. sourcelock-0.1.0/tests/test_cli.py +212 -0
  111. sourcelock-0.1.0/tests/test_cli_failure_modes.py +188 -0
  112. sourcelock-0.1.0/tests/test_codes_cpt_claim.py +311 -0
  113. sourcelock-0.1.0/tests/test_demo_adapter.py +120 -0
  114. sourcelock-0.1.0/tests/test_doctor.py +195 -0
  115. sourcelock-0.1.0/tests/test_doctor_classification.py +215 -0
  116. sourcelock-0.1.0/tests/test_doctor_honesty.py +192 -0
  117. sourcelock-0.1.0/tests/test_guard.py +138 -0
  118. sourcelock-0.1.0/tests/test_guard_bypass.py +179 -0
  119. sourcelock-0.1.0/tests/test_guard_must_pass.py +145 -0
  120. sourcelock-0.1.0/tests/test_http.py +100 -0
  121. sourcelock-0.1.0/tests/test_http_limits.py +219 -0
  122. sourcelock-0.1.0/tests/test_lock_init_safety.py +294 -0
  123. sourcelock-0.1.0/tests/test_manifest.py +371 -0
  124. sourcelock-0.1.0/tests/test_mcp_server.py +126 -0
  125. sourcelock-0.1.0/tests/test_no_echo_residue.py +205 -0
  126. sourcelock-0.1.0/tests/test_no_input_echo.py +472 -0
  127. sourcelock-0.1.0/tests/test_npi.py +42 -0
  128. sourcelock-0.1.0/tests/test_offline_doctor.py +161 -0
  129. sourcelock-0.1.0/tests/test_packaging.py +212 -0
  130. sourcelock-0.1.0/tests/test_plugin_discovery.py +612 -0
  131. sourcelock-0.1.0/tests/test_receipt_provenance.py +123 -0
  132. sourcelock-0.1.0/tests/test_release_workflow.py +329 -0
  133. sourcelock-0.1.0/tests/test_schemas.py +213 -0
  134. sourcelock-0.1.0/tests/test_shortcuts.py +491 -0
  135. sourcelock-0.1.0/tests/test_stale_canaries.py +199 -0
  136. sourcelock-0.1.0/tests/test_strict_discovery.py +117 -0
@@ -0,0 +1,104 @@
1
+ # Tests on every supported Python, then the wheel-install check that
2
+ # pyproject.toml used to claim existed.
3
+ #
4
+ # The two jobs answer different questions. `tests` asks "does the code in this
5
+ # checkout work". `wheel` asks "does the artifact we would publish work" -- it
6
+ # builds the wheel, installs THAT wheel into a fresh venv outside the checkout,
7
+ # and proves the installed copy carries every vendored data file, loads every
8
+ # adapter, answers on every CLI command, and declares the dependencies
9
+ # pyproject declares. Those are separable failures: the last wheel anyone built
10
+ # from this repo passed the test suite and was still missing `cryptography` and
11
+ # the manifest modules.
12
+ name: ci
13
+
14
+ on:
15
+ push:
16
+ branches: [master, main]
17
+ pull_request:
18
+ workflow_dispatch:
19
+
20
+ permissions:
21
+ contents: read
22
+
23
+ concurrency:
24
+ group: "ci-${{ github.ref }}"
25
+ cancel-in-progress: true
26
+
27
+ jobs:
28
+ tests:
29
+ name: "pytest (py${{ matrix.python-version }})"
30
+ runs-on: ubuntu-latest
31
+ strategy:
32
+ fail-fast: false
33
+ matrix:
34
+ # Floor comes from pyproject's requires-python (>=3.11); the ceiling is
35
+ # the newest release we have actually run the suite on.
36
+ python-version: ["3.11", "3.12", "3.13"]
37
+ steps:
38
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
39
+ - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
40
+ with:
41
+ python-version: "${{ matrix.python-version }}"
42
+ - name: "Install with dev extras"
43
+ run: |
44
+ python -m pip install --upgrade pip
45
+ python -m pip install -e ".[dev]"
46
+ - name: "pytest"
47
+ # The suite is offline by contract (tests/conftest.py blocks sockets),
48
+ # so a network blip here is a real failure, not flake to be retried.
49
+ run: python -m pytest
50
+
51
+ wheel:
52
+ name: "wheel install (py${{ matrix.python-version }})"
53
+ runs-on: ubuntu-latest
54
+ strategy:
55
+ fail-fast: false
56
+ matrix:
57
+ python-version: ["3.11", "3.12", "3.13"]
58
+ steps:
59
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
60
+ - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
61
+ with:
62
+ python-version: "${{ matrix.python-version }}"
63
+ - name: "Build the wheel, install it clean, and check it"
64
+ run: bash scripts/wheel_check.sh python
65
+
66
+ action:
67
+ name: "composite action, offline"
68
+ runs-on: ubuntu-latest
69
+ steps:
70
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
71
+ - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
72
+ with:
73
+ python-version: "3.12"
74
+ - name: "run_doctor.py against a saved report"
75
+ # The action's annotation and exit-code logic, with no network and no
76
+ # CMS: a saved report in, workflow commands out. Live doctor belongs on
77
+ # a schedule (docs/ci.md), not on every push -- a CI job that fails when
78
+ # CMS has a bad afternoon is a job people learn to ignore.
79
+ run: |
80
+ python - <<'PY'
81
+ import json, subprocess, sys
82
+ report = {
83
+ "exit_code": 1,
84
+ "lockfile_present": True,
85
+ "summary": {"ok": 1, "drift": 1, "schema_changed": 0, "unreachable": 0,
86
+ "unpinned": 0, "stale": 0, "error": 0, "fallback": 0},
87
+ "load_errors": [],
88
+ "results": [
89
+ {"canary_id": "demo.fixture_release", "source_id": "demo", "status": "ok",
90
+ "observed": "r1", "expected": "r1", "checked_at": "2026-01-01T00:00:00+00:00",
91
+ "remediation": "", "warnings": [], "fallback_used": False},
92
+ {"canary_id": "demo.fixture_hash", "source_id": "demo", "status": "drift",
93
+ "observed": "b", "expected": "a", "checked_at": "2026-01-01T00:00:00+00:00",
94
+ "remediation": "re-pin with `hc-source lock init`", "warnings": [],
95
+ "fallback_used": False},
96
+ ],
97
+ }
98
+ open("report.json", "w").write(json.dumps(report))
99
+ proc = subprocess.run([sys.executable, "action/run_doctor.py", "--from-json", "report.json"],
100
+ capture_output=True, text=True)
101
+ print(proc.stdout, proc.stderr)
102
+ assert proc.returncode == 1, proc.returncode
103
+ assert "::error title=demo.fixture_hash [drift]" in proc.stdout
104
+ PY
@@ -0,0 +1,210 @@
1
+ # Tag -> release. One push, and the artifact people install exists.
2
+ #
3
+ # git tag v0.1.0 && git push origin v0.1.0
4
+ #
5
+ # What happens: the full test suite runs across the same Python matrix ci.yml
6
+ # uses, the tag is checked against pyproject's version, the sdist and wheel are
7
+ # built once, the wheel is installed clean and checked (scripts/check_wheel.py
8
+ # -- the same check CI runs), SHA256SUMS is generated, and a GitHub Release is
9
+ # created carrying all three. Publishing to PyPI is a separate job gated the
10
+ # same way, using Trusted Publishing (OIDC) so there is no long-lived token to
11
+ # leak. See RELEASING.md.
12
+ #
13
+ # Nothing downstream of `tests` can start until every matrix leg is green: a
14
+ # tag that fails on 3.11 must not produce a release just because 3.13 passed.
15
+ # The build happens ONCE and every downstream job consumes that artifact. A
16
+ # second `python -m build` would produce a second wheel, and then the checksums
17
+ # in the release would describe bytes nobody verified.
18
+ name: release
19
+
20
+ on:
21
+ push:
22
+ tags: ["v*"]
23
+ workflow_dispatch:
24
+ inputs:
25
+ tag:
26
+ description: "Existing tag to build (workflow_dispatch only)."
27
+ required: true
28
+
29
+ permissions:
30
+ contents: read
31
+
32
+ jobs:
33
+ tests:
34
+ name: "pytest (py${{ matrix.python-version }})"
35
+ runs-on: ubuntu-latest
36
+ strategy:
37
+ fail-fast: false
38
+ matrix:
39
+ # Same floor/ceiling as ci.yml's `tests` job. A release built from a
40
+ # tag that only passed on one Python is not "tested" in any sense
41
+ # RELEASING.md's promises rely on -- mirror the matrix, don't shrink it.
42
+ python-version: ["3.11", "3.12", "3.13"]
43
+ steps:
44
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
45
+ with:
46
+ ref: "${{ github.event.inputs.tag || github.ref }}"
47
+
48
+ - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
49
+ with:
50
+ python-version: "${{ matrix.python-version }}"
51
+
52
+ - name: "Install with dev extras"
53
+ run: |
54
+ python -m pip install --upgrade pip
55
+ python -m pip install -e ".[dev]"
56
+
57
+ - name: "pytest"
58
+ # Offline by contract (tests/conftest.py blocks sockets): a network
59
+ # blip here is a real failure, not flake to retry past.
60
+ run: python -m pytest
61
+
62
+ build:
63
+ name: "build + verify"
64
+ needs: tests
65
+ runs-on: ubuntu-latest
66
+ outputs:
67
+ version: "${{ steps.version.outputs.version }}"
68
+ steps:
69
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
70
+ with:
71
+ ref: "${{ github.event.inputs.tag || github.ref }}"
72
+
73
+ - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
74
+ with:
75
+ python-version: "3.12"
76
+
77
+ - name: "The tag and pyproject must agree"
78
+ env:
79
+ RAW_TAG: "${{ github.event.inputs.tag || github.ref_name }}"
80
+ run: |
81
+ set -euo pipefail
82
+ python - <<'PY'
83
+ import os, re, sys, tomllib
84
+ raw = os.environ["RAW_TAG"]
85
+ if not re.fullmatch(r"v[0-9A-Za-z.+_-]{1,60}", raw):
86
+ sys.exit(f"refusing to build from tag {raw!r}: expected vMAJOR.MINOR.PATCH")
87
+ tag = raw[1:]
88
+ declared = tomllib.load(open("pyproject.toml", "rb"))["project"]["version"]
89
+ if tag != declared:
90
+ sys.exit(
91
+ f"tag {raw} does not match pyproject version {declared}. Bump "
92
+ "pyproject.toml and re-tag; a release whose filename disagrees with its "
93
+ "metadata is the supply-chain problem this product exists to catch."
94
+ )
95
+ with open(os.environ["GITHUB_OUTPUT"], "a") as fh:
96
+ fh.write(f"version={tag}\n")
97
+ print(f"tag {raw} matches pyproject version {declared}")
98
+ PY
99
+ id: version
100
+
101
+ - name: "Build sdist + wheel"
102
+ run: |
103
+ set -euo pipefail
104
+ python -m pip install --upgrade pip build
105
+ python -m build --outdir dist .
106
+ ls -l dist
107
+
108
+ - name: "Install the built wheel clean and check it"
109
+ run: |
110
+ set -euo pipefail
111
+ python -m venv /tmp/relcheck
112
+ /tmp/relcheck/bin/python -m pip install --quiet --upgrade pip
113
+ /tmp/relcheck/bin/python -m pip install --quiet dist/*.whl
114
+ cd /tmp # never import hc_source from the checkout
115
+ /tmp/relcheck/bin/python "${GITHUB_WORKSPACE}/scripts/check_wheel.py" \
116
+ --source-root "${GITHUB_WORKSPACE}"
117
+
118
+ - name: "SHA256SUMS"
119
+ run: |
120
+ set -euo pipefail
121
+ cd dist
122
+ sha256sum -- *.whl *.tar.gz > SHA256SUMS
123
+ cat SHA256SUMS
124
+
125
+ - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
126
+ with:
127
+ name: dist
128
+ path: dist/
129
+ if-no-files-found: error
130
+
131
+ github-release:
132
+ name: "GitHub Release"
133
+ needs: [tests, build]
134
+ runs-on: ubuntu-latest
135
+ permissions:
136
+ contents: write # creating a release is the only write this workflow needs
137
+ steps:
138
+ - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
139
+ with:
140
+ name: dist
141
+ path: dist
142
+ - name: "Create the release"
143
+ env:
144
+ GH_TOKEN: "${{ github.token }}"
145
+ TAG: "${{ github.event.inputs.tag || github.ref_name }}"
146
+ VERSION: "${{ needs.build.outputs.version }}"
147
+ REPO: "${{ github.repository }}"
148
+ run: |
149
+ set -euo pipefail
150
+ {
151
+ echo "SourceLock ${VERSION}."
152
+ echo
153
+ echo '```'
154
+ cat dist/SHA256SUMS
155
+ echo '```'
156
+ echo
157
+ echo "Verify before you install:"
158
+ echo
159
+ echo '```'
160
+ echo "sha256sum -c SHA256SUMS"
161
+ echo "pipx install ./sourcelock-${VERSION}-py3-none-any.whl"
162
+ echo '```'
163
+ echo
164
+ echo "The wheel in this release was installed into a clean virtualenv and"
165
+ echo "checked by scripts/check_wheel.py before the release was created:"
166
+ echo "every vendored data file present, every adapter loading, every CLI"
167
+ echo "command answering, and metadata matching pyproject.toml. The full"
168
+ echo "test suite passed on Python 3.11, 3.12, and 3.13 before this build"
169
+ echo "ran."
170
+ } > notes.md
171
+ gh release create "${TAG}" dist/* \
172
+ --repo "${REPO}" \
173
+ --title "SourceLock ${VERSION}" \
174
+ --notes-file notes.md
175
+
176
+ pypi:
177
+ name: "PyPI (Trusted Publishing)"
178
+ needs: [tests, build]
179
+ runs-on: ubuntu-latest
180
+ environment:
181
+ # A GitHub Environment, not just a name: configure it (Settings ->
182
+ # Environments -> pypi) with "Deployment branches and tags" restricted to
183
+ # `v*` so a token-less OIDC exchange can still only fire from a real
184
+ # release tag. Required by PyPI Trusted Publishing's publisher config,
185
+ # which is registered against this exact repo/workflow/environment
186
+ # triple -- see RELEASING.md.
187
+ name: pypi
188
+ url: https://pypi.org/project/sourcelock/
189
+ permissions:
190
+ id-token: write # the OIDC token Trusted Publishing exchanges for an upload credential
191
+ steps:
192
+ - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
193
+ with:
194
+ name: dist
195
+ path: dist
196
+
197
+ - name: "Drop the checksum manifest -- it is a release asset, not a distribution"
198
+ run: rm -f dist/SHA256SUMS
199
+
200
+ - name: "Publish"
201
+ # No password/token input: Trusted Publishing mints a short-lived PyPI
202
+ # API token from this job's OIDC identity (the `id-token: write`
203
+ # permission above) inside the action itself. There is no
204
+ # PYPI_API_TOKEN secret sitting in this job's environment while
205
+ # anything gets pip-installed from the network -- that was the finding
206
+ # this replaces. If the trusted publisher is not yet registered on
207
+ # PyPI's side, this step fails with a clear error; the GitHub Release
208
+ # above already succeeded independently, so a first release still
209
+ # produces installable artifacts even before PyPI is wired up.
210
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,20 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ venv/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ htmlcov/
13
+ *.log
14
+ .DS_Store
15
+
16
+ # ops material (runbooks, signing keys) lives in ~/sourcelock-ops, never here
17
+ sourcelock-ops/
18
+ keys/
19
+ *.key
20
+ sourcelock-manifest.json