bronto-sdk 0.2.1__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 (89) hide show
  1. bronto_sdk-0.2.1/.gitignore +69 -0
  2. bronto_sdk-0.2.1/.python-version +1 -0
  3. bronto_sdk-0.2.1/CHANGELOG.md +140 -0
  4. bronto_sdk-0.2.1/CODE_OF_CONDUCT.md +10 -0
  5. bronto_sdk-0.2.1/CONTRIBUTING.md +205 -0
  6. bronto_sdk-0.2.1/LICENSE +21 -0
  7. bronto_sdk-0.2.1/PKG-INFO +382 -0
  8. bronto_sdk-0.2.1/README.md +343 -0
  9. bronto_sdk-0.2.1/SECURITY.md +111 -0
  10. bronto_sdk-0.2.1/VERSION +1 -0
  11. bronto_sdk-0.2.1/api/README.md +53 -0
  12. bronto_sdk-0.2.1/api/openapi.yaml +17180 -0
  13. bronto_sdk-0.2.1/api/upstream.sha256 +1 -0
  14. bronto_sdk-0.2.1/api/vendored.sha256 +1 -0
  15. bronto_sdk-0.2.1/examples/README.md +20 -0
  16. bronto_sdk-0.2.1/examples/async_search.py +56 -0
  17. bronto_sdk-0.2.1/examples/get_monitor.py +32 -0
  18. bronto_sdk-0.2.1/examples/mcp_endpoint.py +33 -0
  19. bronto_sdk-0.2.1/examples/search_logs.py +43 -0
  20. bronto_sdk-0.2.1/justfile +103 -0
  21. bronto_sdk-0.2.1/pyproject.toml +177 -0
  22. bronto_sdk-0.2.1/scripts/dist-check.sh +102 -0
  23. bronto_sdk-0.2.1/scripts/release-notes.sh +42 -0
  24. bronto_sdk-0.2.1/scripts/ruleset-drift.sh +91 -0
  25. bronto_sdk-0.2.1/scripts/spec-verify.sh +75 -0
  26. bronto_sdk-0.2.1/scripts/version-check.sh +93 -0
  27. bronto_sdk-0.2.1/src/bronto_sdk/__init__.py +57 -0
  28. bronto_sdk-0.2.1/src/bronto_sdk/_async_client.py +245 -0
  29. bronto_sdk-0.2.1/src/bronto_sdk/_auth.py +40 -0
  30. bronto_sdk-0.2.1/src/bronto_sdk/_client.py +234 -0
  31. bronto_sdk-0.2.1/src/bronto_sdk/_client_config.py +170 -0
  32. bronto_sdk-0.2.1/src/bronto_sdk/_errors.py +335 -0
  33. bronto_sdk-0.2.1/src/bronto_sdk/_regions.py +73 -0
  34. bronto_sdk-0.2.1/src/bronto_sdk/_request_options.py +216 -0
  35. bronto_sdk-0.2.1/src/bronto_sdk/_transport.py +110 -0
  36. bronto_sdk-0.2.1/src/bronto_sdk/_version.py +8 -0
  37. bronto_sdk-0.2.1/src/bronto_sdk/domain/__init__.py +39 -0
  38. bronto_sdk-0.2.1/src/bronto_sdk/domain/datasets.py +26 -0
  39. bronto_sdk-0.2.1/src/bronto_sdk/domain/query.py +104 -0
  40. bronto_sdk-0.2.1/src/bronto_sdk/domain/time_range.py +310 -0
  41. bronto_sdk-0.2.1/src/bronto_sdk/mcp/__init__.py +77 -0
  42. bronto_sdk-0.2.1/src/bronto_sdk/models/__init__.py +71 -0
  43. bronto_sdk-0.2.1/src/bronto_sdk/models/_common.py +49 -0
  44. bronto_sdk-0.2.1/src/bronto_sdk/models/_dataset.py +50 -0
  45. bronto_sdk-0.2.1/src/bronto_sdk/models/_error.py +22 -0
  46. bronto_sdk-0.2.1/src/bronto_sdk/models/_metric.py +21 -0
  47. bronto_sdk-0.2.1/src/bronto_sdk/models/_monitor.py +258 -0
  48. bronto_sdk-0.2.1/src/bronto_sdk/models/_search.py +344 -0
  49. bronto_sdk-0.2.1/src/bronto_sdk/py.typed +0 -0
  50. bronto_sdk-0.2.1/src/bronto_sdk/resources/__init__.py +28 -0
  51. bronto_sdk-0.2.1/src/bronto_sdk/resources/_datasets.py +133 -0
  52. bronto_sdk-0.2.1/src/bronto_sdk/resources/_metrics.py +99 -0
  53. bronto_sdk-0.2.1/src/bronto_sdk/resources/_monitors.py +240 -0
  54. bronto_sdk-0.2.1/src/bronto_sdk/resources/_search.py +107 -0
  55. bronto_sdk-0.2.1/tests/__init__.py +1 -0
  56. bronto_sdk-0.2.1/tests/conftest.py +153 -0
  57. bronto_sdk-0.2.1/tests/domain/__init__.py +1 -0
  58. bronto_sdk-0.2.1/tests/domain/test_datasets.py +46 -0
  59. bronto_sdk-0.2.1/tests/domain/test_query.py +90 -0
  60. bronto_sdk-0.2.1/tests/domain/test_time_range.py +231 -0
  61. bronto_sdk-0.2.1/tests/mcp/__init__.py +1 -0
  62. bronto_sdk-0.2.1/tests/mcp/test_mcp_registry.py +78 -0
  63. bronto_sdk-0.2.1/tests/unit/__init__.py +1 -0
  64. bronto_sdk-0.2.1/tests/unit/models/__init__.py +1 -0
  65. bronto_sdk-0.2.1/tests/unit/models/test_big_integers.py +117 -0
  66. bronto_sdk-0.2.1/tests/unit/models/test_dataset.py +53 -0
  67. bronto_sdk-0.2.1/tests/unit/models/test_error.py +84 -0
  68. bronto_sdk-0.2.1/tests/unit/models/test_exports.py +52 -0
  69. bronto_sdk-0.2.1/tests/unit/models/test_monitor.py +163 -0
  70. bronto_sdk-0.2.1/tests/unit/models/test_search_request.py +131 -0
  71. bronto_sdk-0.2.1/tests/unit/models/test_search_response.py +200 -0
  72. bronto_sdk-0.2.1/tests/unit/resources/__init__.py +1 -0
  73. bronto_sdk-0.2.1/tests/unit/resources/test_datasets.py +86 -0
  74. bronto_sdk-0.2.1/tests/unit/resources/test_exports.py +60 -0
  75. bronto_sdk-0.2.1/tests/unit/resources/test_metrics.py +90 -0
  76. bronto_sdk-0.2.1/tests/unit/resources/test_monitors.py +205 -0
  77. bronto_sdk-0.2.1/tests/unit/resources/test_search.py +121 -0
  78. bronto_sdk-0.2.1/tests/unit/test_async_client.py +103 -0
  79. bronto_sdk-0.2.1/tests/unit/test_auth.py +16 -0
  80. bronto_sdk-0.2.1/tests/unit/test_client.py +114 -0
  81. bronto_sdk-0.2.1/tests/unit/test_client_parity.py +175 -0
  82. bronto_sdk-0.2.1/tests/unit/test_errors.py +174 -0
  83. bronto_sdk-0.2.1/tests/unit/test_exports.py +28 -0
  84. bronto_sdk-0.2.1/tests/unit/test_regions.py +75 -0
  85. bronto_sdk-0.2.1/tests/unit/test_request_options.py +129 -0
  86. bronto_sdk-0.2.1/tests/unit/test_required_checks.py +257 -0
  87. bronto_sdk-0.2.1/tests/unit/test_transport.py +110 -0
  88. bronto_sdk-0.2.1/tests/unit/test_version.py +91 -0
  89. bronto_sdk-0.2.1/uv.lock +2355 -0
@@ -0,0 +1,69 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ parts/
18
+ sdist/
19
+ var/
20
+ wheels/
21
+ share/python-wheels/
22
+ *.egg-info/
23
+ .installed.cfg
24
+ *.egg
25
+ MANIFEST
26
+
27
+ # Unit test / coverage reports
28
+ htmlcov/
29
+ .tox/
30
+ .nox/
31
+ .coverage
32
+ .coverage.*
33
+ coverage.xml
34
+ *.cover
35
+ .cache
36
+ .hypothesis/
37
+ .pytest_cache/
38
+
39
+ # Linters / type checkers
40
+ .ruff_cache/
41
+ .mypy_cache/
42
+
43
+ # pyenv / uv — .python-version IS committed here (see the negation below) so that
44
+ # every checkout and CI run develops on the same interpreter.
45
+ .python-version
46
+ !/.python-version
47
+
48
+ # Environments
49
+ .env
50
+ .venv
51
+ env/
52
+ venv/
53
+ ENV/
54
+
55
+ # Visual Studio Code — settings and extension recommendations are checked in
56
+ # so every contributor and CI see the same diagnostics; everything else is local.
57
+ .vscode/*
58
+ !.vscode/settings.json
59
+ !.vscode/extensions.json
60
+ .idea/
61
+
62
+ # Git
63
+ *.orig
64
+
65
+ # Internal planning notes. The full v0.1 plan names private sibling projects and
66
+ # internal infrastructure; it lives outside this repository. Ignored here so a
67
+ # stray copy can never be committed, or swept into an sdist by a build.
68
+ /high-level-overview-plan.md
69
+ /*-plan.md
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,140 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+ Breaking changes are listed first with a ⚠️ prefix.
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [0.2.1] - 2026-11-11
12
+
13
+ ### Added
14
+
15
+ - **Published on [PyPI](https://pypi.org/project/bronto-sdk/)** — install with
16
+ `pip install bronto-sdk` (or `bronto-sdk[mcp]`) instead of a GitHub Release
17
+ URL or a `git+https` requirement. Uploads use Trusted Publishing from the
18
+ release workflow (no API token exists) and carry PEP 740 provenance
19
+ attestations; PyPI receives exactly the files that are Sigstore-signed and
20
+ attached to the GitHub Release, which continues as before.
21
+ [SECURITY.md](https://github.com/bronto-community/bronto-sdk-python/blob/main/SECURITY.md#verifying-a-release)
22
+ covers verifying both.
23
+
24
+ ### Security
25
+
26
+ - Refreshed `uv.lock` to PyJWT 2.15.1 and urllib3 2.8.0. Both reach the lock
27
+ only through the `mcp`/`langchain` extras and dev tooling; the SDK never
28
+ imports them and its published dependency ranges are unchanged, so installed
29
+ users resolve their own versions and are not affected.
30
+ - Refreshed `uv.lock` to certifi 2026.10.23, which drops the emSign root CAs
31
+ whose intermediate keys were compromised (GHSA-6q48-6c7w-9gw2). certifi
32
+ reaches the lock only transitively through `httpx`; the SDK's published
33
+ dependency ranges are unchanged, so installed users resolve their own
34
+ certifi and should upgrade it in their own environments.
35
+
36
+ ### Changed
37
+
38
+ - Refreshed `uv.lock` to pydantic 2.14.1, langchain-core 1.7.3, and newer dev
39
+ tooling (ruff 0.17.3, pytest 9.2.0, pytest-mock 3.17.0, pytest-asyncio
40
+ 1.4.1.post1), and bumped the pinned CI actions. Published dependency ranges
41
+ are unchanged.
42
+
43
+ ## [0.2.0] - 2026-09-29
44
+
45
+ ### Added
46
+
47
+ - **`client.metrics.retrieve_definition(metric_id)`** — `GET
48
+ /metrics/definitions/{id}`, returning a `MetricDefinition`.
49
+
50
+ ## [0.1.2] - 2026-09-18
51
+
52
+ ### Added
53
+
54
+ - **Signed release artifacts.** The wheel and sdist are now signed with
55
+ [Sigstore](https://www.sigstore.dev/) during the release workflow, and a
56
+ `.sigstore.json` bundle is attached to the release beside each one. Signing is
57
+ keyless: the signature is made with a short-lived identity minted for that one
58
+ workflow run, so there is no signing key to hold and the repository still
59
+ stores no secrets. The signature attests that an artifact came from this
60
+ repository's release workflow running on the tag it claims.
61
+
62
+ Verifying needs only pip — `pip install sigstore`, then
63
+ `python -m sigstore verify identity`. [SECURITY.md](SECURITY.md) has the full
64
+ command. The release workflow verifies its own signatures before publishing,
65
+ so an artifact whose signature does not check never reaches a release.
66
+
67
+ ### Note
68
+
69
+ - `v0.1.1` was tagged but never released: its release workflow failed while
70
+ signing, before anything was published. The tag is left in place because
71
+ release tags are immutable here. There is no v0.1.1 release, and nothing was
72
+ ever published under it.
73
+
74
+ ## [0.1.0] - 2026-09-17
75
+
76
+ First release. Everything below is new.
77
+
78
+ ### Added
79
+
80
+ - **Clients.** `BrontoClient` (sync) and `AsyncBrontoClient` (async) over
81
+ `httpx`, with identical method names, context-manager support, `close()` /
82
+ `aclose()`, and `from_env()` reading `BRONTO_API_KEY` plus `BRONTO_REGION` or
83
+ `BRONTO_BASE_URL`. A caller-supplied `http_client` is never closed by the SDK.
84
+ The async client adds a `max_concurrency` semaphore (default 20) bounding
85
+ in-flight requests. `client.get()` / `client.post()` remain public escape
86
+ hatches so an uncovered endpoint never blocks a consumer on an SDK release.
87
+ - **Two-mode credentials.** A client is either *client-bound* (built with
88
+ `api_key` or `bearer_token`) or *per-request* (built with neither, holding no
89
+ credential). The modes never mix and there is no fallback between them, which
90
+ makes a long-lived multi-tenant client safe by construction. Bearer wins over
91
+ API key within one origin; an empty per-request credential fails closed.
92
+ - **Error hierarchy.** `BrontoError`, `BrontoConfigError`,
93
+ `BrontoConnectionError` and `BrontoAPIError`, with status-mapped subclasses
94
+ for 400, 401, 403, 404, 429 (carrying `retry_after`) and 5xx. Errors expose
95
+ `status_code`, `correlation_id`, `details`, `error_type`, `body`, `headers`
96
+ and a `retryable` flag; `str()` always surfaces the status and correlation id.
97
+ No credential reaches an error, a `repr()` or a log line — headers are
98
+ redacted by key name and the stored body is bounded.
99
+ - **Regional registry.** `validate_region`, `rest_base_url`, `mcp_url` and
100
+ `ingest_base_url`. Region is a validated slug rather than a closed enum, so a
101
+ new region works without an SDK release while a value that would smuggle a
102
+ host off `bronto.io` is rejected. `KNOWN_REGIONS` is a hint for error
103
+ messages, never an allow-list. Redirects are not followed, deliberately.
104
+ - **Per-request options.** `RequestOptions` for `timeout`, `headers`, a
105
+ per-request credential, and an `idempotent` flag reserved for v0.2 retries.
106
+ - **`bronto_sdk.models`:** Pydantic v2 models for the covered REST subset —
107
+ `SearchRequest`/`SearchResponse`, `Monitor`/`MonitorsResponse`,
108
+ `MonitorEventsResponse`, `Dataset`/`DatasetsResponse` and `ErrorResponse`.
109
+ Response models accept unknown fields so a new server-side field is additive,
110
+ and no sequence number or epoch-millisecond field is typed `float`.
111
+ - **`bronto_sdk.resources`:** the typed operation surface, wired onto both
112
+ clients as `client.search`, `client.monitors` and `client.datasets` —
113
+ `search.run()`, `monitors.list()`, `monitors.retrieve()`, `monitors.events()`
114
+ and `datasets.list()`, each returning a parsed model. Path parameters are
115
+ URL-quoted.
116
+ - **`bronto_sdk.domain`:** pure Bronto helpers with no I/O. `iso_to_ms`,
117
+ `ms_to_iso`, `parse_time_range` and `parse_window` resolve natural-language
118
+ windows the way the query engine does, with no `dateparser` dependency;
119
+ `quote_value`, `quote_attribute` and `wildcard_pattern` escape values for a
120
+ where clause — **opt-in, never automatic**; `project_dataset` reads both the
121
+ REST and MCP dataset shapes.
122
+ - **`bronto_sdk.mcp`:** the MCP endpoint registry — `url()` (by region, or by
123
+ `base_url` for a staging deployment the region slug cannot name) and
124
+ `auth_headers()`. String and dict helpers only: no `mcp` or `langchain`
125
+ import, and no session opener until v0.2.
126
+ - Documentation: a full README with quickstarts, the credential model, the
127
+ error hierarchy, the domain helpers and a migration table from an ad-hoc
128
+ client, plus four runnable scripts in `examples/`.
129
+
130
+ ### Not in this release
131
+
132
+ Automatic retries and backoff, pagination iterators, an MCP session opener,
133
+ LangChain tool decorators, OpenTelemetry hooks, an ingestion client (URL helper
134
+ only), and REST coverage beyond the five operations above.
135
+
136
+ [Unreleased]: https://github.com/bronto-community/bronto-sdk-python/compare/v0.2.1...HEAD
137
+ [0.2.1]: https://github.com/bronto-community/bronto-sdk-python/compare/v0.2.0...v0.2.1
138
+ [0.2.0]: https://github.com/bronto-community/bronto-sdk-python/compare/v0.1.2...v0.2.0
139
+ [0.1.2]: https://github.com/bronto-community/bronto-sdk-python/compare/v0.1.0...v0.1.2
140
+ [0.1.0]: https://github.com/bronto-community/bronto-sdk-python/releases/tag/v0.1.0
@@ -0,0 +1,10 @@
1
+ # Code of Conduct
2
+
3
+ This project follows the [Contributor Covenant v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
4
+
5
+ In short: be respectful, be constructive, assume good faith. Harassment or
6
+ discriminatory behavior is not tolerated.
7
+
8
+ Report violations privately to the maintainers via
9
+ [GitHub's report-abuse flow](https://github.com/contact/report-abuse) or by
10
+ opening a confidential security advisory on this repository.
@@ -0,0 +1,205 @@
1
+ # Contributing to bronto-sdk
2
+
3
+ We welcome bug reports, feature requests, and pull requests. For anything that
4
+ changes the public API or introduces substantial code, please open (or find) an
5
+ issue first so we can agree on an approach before you write the code. Typo and
6
+ documentation fixes do not need an issue.
7
+
8
+ ## 5-minute path
9
+
10
+ ```sh
11
+ git clone https://github.com/bronto-community/bronto-sdk-python
12
+ cd bronto-sdk-python
13
+ uv sync # create .venv and install the project + dev dependencies
14
+ just prepare # format, lint, typecheck, spec digest gate, tests
15
+ ```
16
+
17
+ No network access or Bronto account is needed to run the test suite. All HTTP is
18
+ mocked with `httpx.MockTransport`, so the fast suite never touches the network.
19
+
20
+ ## Development environment
21
+
22
+ - **[`uv`](https://docs.astral.sh/uv/)** manages the virtual environment and
23
+ dependencies. `uv sync` creates `.venv` and installs everything; commit
24
+ `uv.lock` whenever dependencies change so CI stays reproducible.
25
+ - **[`just`](https://just.systems/)** runs every project command through
26
+ `uv run`. `just --list` shows the recipes; the important ones are below.
27
+ - Minimum supported Python is **3.11**. Local development happens on the newest
28
+ interpreter, but `pyright` and `ruff` are pinned to the 3.11 floor so
29
+ 3.12+-only syntax cannot slip past and break the oldest CI leg.
30
+
31
+ | Task | Command |
32
+ | --- | --- |
33
+ | Everything CI runs | `just prepare` |
34
+ | Format | `just format` / `just format-check` |
35
+ | Lint | `just lint` |
36
+ | Lint the shell gates in `scripts/` | `just lint-shell` |
37
+ | Typecheck (pyright strict on `src/bronto_sdk`) | `just typecheck` |
38
+ | Tests | `just test` / `just test-one <name>` |
39
+ | Build sdist + wheel | `just build` |
40
+ | Verify the vendored spec digest | `just check-spec` |
41
+ | Verify the three version files agree | `just check-version` |
42
+ | Verify the built artifacts | `just check-dist` |
43
+ | Verify the live ruleset matches the recorded checks | `just check-ruleset` |
44
+ | Preview a release body | `just release-notes X.Y.Z` |
45
+
46
+ ## Tests
47
+
48
+ - Prefer test-driven development for new behavior.
49
+ - Use `pytest`, `pytest-mock`, and `pytest-asyncio` (`asyncio_mode = "auto"`).
50
+ - Mock all HTTP with `httpx.MockTransport`; the fast suite must not hit the
51
+ network.
52
+ - Sync and async clients expose identical method names — cover new client
53
+ behavior with the shared sync/async parity fixtures so both stay in step.
54
+ - Coverage target is 90%+, realistic given the small surface.
55
+
56
+ ## The vendored OpenAPI spec
57
+
58
+ `api/openapi.yaml` is a conformance reference, never a codegen source. It is
59
+ digest-gated: any edit must ship with a regenerated `api/vendored.sha256` in the
60
+ same commit. See `api/README.md` for the full re-vendoring checklist. `just
61
+ check-spec` (part of `just prepare` and CI) fails on an unrecorded edit.
62
+
63
+ ## Changelog
64
+
65
+ User-facing changes must add an entry under `## [Unreleased]` in
66
+ `CHANGELOG.md` (Keep a Changelog format). List breaking changes first with a
67
+ `⚠️` prefix. The pull request template has a `## Changelog` section for the same
68
+ purpose.
69
+
70
+ ## Versioning
71
+
72
+ Semver from day one. `v0.x` means the public API may still shift; breaking
73
+ changes are called out with `⚠️` in the changelog. Bump the version with
74
+ `just update-version X.Y.Z`, which writes `VERSION`, `src/bronto_sdk/_version.py`
75
+ and `pyproject.toml` in one step.
76
+
77
+ ## Releasing (maintainers)
78
+
79
+ A `vX.Y.Z` tag push runs `release.yml`, which builds the sdist and wheel,
80
+ attaches both to a **GitHub Release**, then publishes the very same files to
81
+ **[PyPI](https://pypi.org/project/bronto-sdk/)**. PyPI is what consumers
82
+ install from (`pip install bronto-sdk`).
83
+
84
+ PyPI uploads use [Trusted Publishing](https://docs.pypi.org/trusted-publishers/):
85
+ the `publish` job exchanges a short-lived GitHub OIDC token for an upload
86
+ credential, so there is no PyPI API token to store, rotate or leak. Do not add
87
+ one, and do not upload by hand with `twine` — a hand upload has no attestation
88
+ and is indistinguishable from a compromised one.
89
+
90
+ To cut a release:
91
+
92
+ 1. Move the `## [Unreleased]` entries under a new `## [X.Y.Z] - YYYY-MM-DD`
93
+ heading in `CHANGELOG.md`, and add the link reference at the bottom. The
94
+ release body is extracted from this section, so a missing or empty one fails
95
+ the release — preview it with `just release-notes X.Y.Z`.
96
+ 2. `just update-version X.Y.Z`. It verifies its own work; a version it cannot
97
+ write to all three files is now an error rather than a silent no-op.
98
+ 3. `just prepare`, then commit.
99
+ 4. Tag `vX.Y.Z` and push the tag.
100
+ 5. **Approve the deployment.** The `release` job waits on the protected
101
+ `release` environment; approve it from the run's page in the Actions tab.
102
+ Either maintainer on the reviewer list can approve, including the person who
103
+ pushed the tag — the gate is there to make a release a deliberate, recorded
104
+ act, not to force a second pair of hands. The `publish` job runs on its own
105
+ once the GitHub Release exists; it needs no second approval.
106
+ 6. Check [pypi.org/project/bronto-sdk](https://pypi.org/project/bronto-sdk/)
107
+ shows the version, with "Provenance" on each file.
108
+
109
+ If `publish` fails after the GitHub Release succeeded (PyPI outage,
110
+ misconfigured publisher), fix the cause and use **Re-run failed jobs** on the
111
+ same run. It re-uploads the artifacts the release job already signed; nothing
112
+ is rebuilt and no version is spent.
113
+
114
+ **A PyPI version is as permanent as its tag.** PyPI never accepts the same file
115
+ name twice, even after the release is deleted. A bad release is yanked on PyPI
116
+ (which hides it from new resolutions without breaking existing pins) and
117
+ superseded by the next patch version — never deleted and re-uploaded.
118
+
119
+ Signing is automatic and needs nothing from you: the workflow signs both
120
+ artifacts with Sigstore using its own short-lived OIDC identity, verifies the
121
+ signatures before publishing, and attaches a `.sigstore.json` bundle to each.
122
+ [SECURITY.md](SECURITY.md) documents how a consumer checks them.
123
+
124
+ The workflow gates the release on the tag matching the version files, then on
125
+ the same lint/test/build checks a pull request runs — literally the same file,
126
+ `.github/workflows/_checks.yml`, so a release cannot be verified on less than a
127
+ pull request is.
128
+
129
+ **Rehearse with `workflow_dispatch`, not with a tag.** Running `release.yml`
130
+ from `main` exercises the version gate, the full test matrix and the build; the
131
+ release and publish jobs are skipped because there is no tag.
132
+
133
+ ### One-time PyPI setup
134
+
135
+ Already done for this repository; recorded so it can be checked or rebuilt.
136
+ Trusted Publishing only works when both sides name each other exactly:
137
+
138
+ - **PyPI**: the `bronto-sdk` project (owned by the Bronto PyPI organization) has
139
+ a GitHub trusted publisher with owner `bronto-community`, repository
140
+ `bronto-sdk-python`, workflow `release.yml`, environment `pypi`. Before the
141
+ first upload this is a *pending* publisher, added from the organization's
142
+ Publishing page so the project is created under the organization.
143
+ - **GitHub**: a `pypi` environment (Settings → Environments) whose deployment
144
+ policy allows `v*` tags only, and no reviewers — approving `release` is the
145
+ gate. Restricting it to tags means a workflow run from a branch cannot obtain
146
+ a token PyPI will accept, even if the workflow file is edited on that branch.
147
+
148
+ Renaming the workflow file or the environment breaks publishing until the PyPI
149
+ publisher is updated to match.
150
+
151
+ Do not push a throwaway `vX.Y.Zrc1` tag to rehearse. A repository ruleset makes
152
+ every `v*` tag immutable — it cannot be moved or deleted once pushed, by anyone,
153
+ including admins. That is deliberate: a tag a consumer has pinned must never
154
+ change underneath them. The practical consequence is that any `v*` tag you push
155
+ is permanent, so push one only when you mean it. A genuine pre-release is fine
156
+ (a version with a letter in it, like `v1.2.0rc1`, is published as a GitHub
157
+ pre-release and stays out of "latest"); a disposable one is not, because it
158
+ cannot be disposed of.
159
+
160
+ ## What the repository enforces
161
+
162
+ Settings are kept in step with the `bronto-cli` sibling so that the two repos
163
+ are governed alike:
164
+
165
+ - `main` takes changes only through a pull request, with linear history, all
166
+ review threads resolved, and the eight `Checks / …` jobs green. Force-pushes
167
+ and deletion are blocked, with no bypass for anyone.
168
+ - `v*` tags cannot be moved or deleted (above).
169
+ - Actions must be pinned to a commit SHA — GitHub rejects a tag reference
170
+ outright, which is the same rule `zizmor` enforces in CI.
171
+ - Security vulnerabilities are reported privately; see [SECURITY.md](SECURITY.md).
172
+
173
+ The names of those eight required checks are mirrored in
174
+ `.github/required-status-checks.txt`. Two gates keep the mirror honest:
175
+ `tests/unit/test_required_checks.py` fails when the list stops matching the
176
+ names `ci.yml` and `_checks.yml` generate, and `just check-ruleset` (run weekly
177
+ by `ruleset-drift.yml`) fails when it stops matching the live ruleset.
178
+
179
+ ### Renaming a required check
180
+
181
+ Read this before touching a job name or the test matrix. **A pull request that
182
+ renames or removes a required check wedges itself.** The old context stops
183
+ reporting on that very pull request, GitHub shows it as pending rather than
184
+ failed — a check that never reports is never late — and with no bypass actors
185
+ nobody can merge it, including admins.
186
+
187
+ Adding the new name to the ruleset first does not help: a required context that
188
+ no job has produced yet blocks every open pull request immediately. The only
189
+ order that never wedges anything is to **contract first**:
190
+
191
+ 1. Remove the outgoing context from the `protect-main` ruleset
192
+ (Settings → Rules, or `gh api`).
193
+ 2. Merge the pull request that renames the job or changes the matrix. It carries
194
+ the updated `.github/required-status-checks.txt`, which is what the offline
195
+ gate checks.
196
+ 3. Add the new context to the ruleset.
197
+
198
+ Between steps 1 and 3 that one check is not enforced, which is the cost of never
199
+ being locked out. Dropping a Python version renames six contexts at once, so
200
+ expect to repeat step 1 for each.
201
+
202
+ ## Code of Conduct
203
+
204
+ This project follows the [Contributor Covenant](CODE_OF_CONDUCT.md). By
205
+ participating you agree to abide by it.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bronto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.