diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d7a8735..466df71 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.1" + ".": "0.1.0" } diff --git a/AGENTS.md b/AGENTS.md index 59fd9d3..c8b2ab4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -97,9 +97,9 @@ Post-alpha invariants: 2. Keep `.github/CODEOWNERS` absent until a real multi-maintainer model exists. 3. Keep scheduled and manually dispatched live smoke fail-closed behind `LIVE_SMOKE_ENABLED=true`, and keep `RELEASE_PLEASE_ENABLED` disabled outside - an explicitly authorized release sequence. The stable-readiness - configuration establishes the recovery alpha boundary with a reviewed and - tested `last-release-sha` bridge. + an explicitly authorized release sequence. The reviewed `last-release-sha` + bridge was used once to generate the stable release PR and must remain absent + after its human finalization. 4. Treat the recorded public rules, security reporting, immutable releases, and protected environments as readiness invariants. Any drift invalidates the readiness claim until it is explicitly authorized, restored, and verified. @@ -265,11 +265,10 @@ committed. `v0.1.0-alpha.1+recovery.1`, which maps to package version `0.1.0a1`. Later releases must use their ordinary canonical tag spelling. - Keep Release Please disabled outside an explicitly authorized release - sequence. Its stable-readiness configuration uses the reviewed and tested - `last-release-sha` bridge because the recovery tag's build metadata cannot be - inferred from the manifest. Remove the one-time bridge and prerelease - versioning controls in the human-finalized stable release PR before it is - merged. + sequence. The stable-readiness configuration used a reviewed and tested + `last-release-sha` bridge because the recovery tag's build metadata could not + be inferred from the manifest. The human-finalized stable release PR removed + that bridge and its prerelease-versioning controls; keep them absent. - Keep third-party Actions pinned to full commit SHAs. Grant `id-token: write` only to the reusable publication caller and the protected publishing job; the caller passes this maximum permission and only the publishing job uses diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index bf163b0..9f835fe 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -141,9 +141,10 @@ recovery release uses SemVer build metadata in artifact identity: the PyPI version remains `0.1.0a1`. Release Please remains disabled outside an explicitly authorized release -sequence. The stable-readiness configuration establishes the recovery commit -as the previous-release boundary with a tested `last-release-sha` bridge, so -the one-time build-metadata recovery tag cannot replay earlier history. +sequence. The stable-readiness configuration used a tested `last-release-sha` +bridge to establish the recovery commit as the previous-release boundary and +generate the stable release PR without replaying earlier history. Human +finalization then removed that one-time bridge. ## Rejected 0.1 approaches diff --git a/CHANGELOG.md b/CHANGELOG.md index cead319..7baa76a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,15 @@ automation. ## [Unreleased] -### Changed +## [0.1.0] - 2026-07-28 + +### Features + +- Prepare the stable release ([#19](https://github.com/cometapi-dev/cometapi-python/issues/19)) ([2e5407c](https://github.com/cometapi-dev/cometapi-python/commit/2e5407c106b6bc557c51e629b4713012dbce3744)). + +### Documentation + +- Record Registry Alpha release evidence ([#18](https://github.com/cometapi-dev/cometapi-python/issues/18)) ([f39b4dc](https://github.com/cometapi-dev/cometapi-python/commit/f39b4dc9f2e18e91ab3cbac202246f85658f71fd)). - Release documentation now records completed Registry Alpha publication, provenance, digest, clean-install, import, and mocked-call verification. @@ -53,3 +61,5 @@ automation. - Unsupported aliases `CometClient` and `AsyncCometClient`. - Account, balance, token, log, task, and platform helpers from the 0.1 scope. - The provisional single-maintainer `CODEOWNERS` requirement. + +[0.1.0]: https://github.com/cometapi-dev/cometapi-python/compare/v0.1.0-alpha.1%2Brecovery.1...v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a8afc7a..2ca3b04 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,8 @@ # Contributing to the CometAPI Python SDK -Thank you for helping improve the SDK. The repository has released the -`0.1.0a1` Registry Alpha. Contributions must stay within the supported 0.1 -scope described in `README.md`, `ROADMAP.md`, and `COMPATIBILITY.md`. The -canonical repository is +Thank you for helping improve the SDK. The repository targets the stable 0.1 +contract. Contributions must stay within the supported 0.1 scope described in +`README.md`, `ROADMAP.md`, and `COMPATIBILITY.md`. The canonical repository is . ## Development setup @@ -79,7 +78,7 @@ Use Conventional Commit subjects where practical, for example: ```text feat: add a supported client option fix: preserve an upstream error type -docs: clarify alpha installation +docs: clarify installation test: cover async response streaming ``` diff --git a/README.md b/README.md index c51f2bd..f051db3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # CometAPI Python SDK -> **Pre-release:** `0.1.0a1` is available from PyPI. Its API may change before -> `0.1.0`. +> **Stable release:** `0.1.0` is approved for PyPI publication. `cometapi` is a thin Python adapter over the official OpenAI SDK for the OpenAI-compatible CometAPI endpoint. It changes the default API key and base @@ -30,15 +29,16 @@ Project links: [CometAPI](https://www.cometapi.com), ## Installation -Install the exact alpha from PyPI with: +After the protected publication workflow and public-registry verification +succeed, install the stable release from PyPI with: ```bash -python -m pip install --pre 'cometapi==0.1.0a1' +python -m pip install 'cometapi==0.1.0' ``` -See the immutable -[GitHub prerelease](https://github.com/cometapi-dev/cometapi-python/releases/tag/v0.1.0-alpha.1%2Brecovery.1) -and exact [PyPI release](https://pypi.org/project/cometapi/0.1.0a1/) for the +After those gates succeed, the immutable +[GitHub release](https://github.com/cometapi-dev/cometapi-python/releases/tag/v0.1.0) +and exact [PyPI release](https://pypi.org/project/cometapi/0.1.0/) for the published artifact. ## Authentication and configuration @@ -160,7 +160,7 @@ This is an interoperability option, not the primary `cometapi` experience. ## Explicit non-goals for 0.1 -The alpha does not add Anthropic or Gemini adapters, CometAPI account or +The 0.1 release does not add Anthropic or Gemini adapters, CometAPI account or platform resources, media APIs, provider-neutral message translation, CLI behavior, or custom HTTP/SSE/retry implementations. There are no compatibility aliases named `CometClient` or `AsyncCometClient`. diff --git a/RELEASING.md b/RELEASING.md index 74d7d42..4f5b183 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -46,10 +46,11 @@ one active maintainer. Before the historical first push, scheduled and manually dispatched live execution was required to fail closed unless `LIVE_SMOKE_ENABLED=true`. `RELEASE_PLEASE_ENABLED` was kept disabled. The reviewed stable-readiness -configuration now uses an explicit `last-release-sha` bridge to establish the -recovery alpha as the previous-release boundary; the repository variable stays -disabled until maintainers intentionally start the stable release sequence. An -unset or non-true value prevents the corresponding gated job from executing. +configuration later used an explicit `last-release-sha` bridge to establish the +recovery alpha as the previous-release boundary. Maintainers enabled the +repository variable only to start the stable release sequence, and human +finalization removed the bridge. An unset or non-true variable prevents the +corresponding gated job from executing. The release live-model configuration resolves an unset or empty `COMETAPI_LIVE_MODEL` to `gpt-5.4`. @@ -182,14 +183,14 @@ violations in one run and still returns non-zero when any violation exists. `LIVE_SMOKE_ENABLED=true`. - `release-please.yml` maintains a human-reviewed version and changelog pull request from Conventional Commits after maintainers enable the - `RELEASE_PLEASE_ENABLED` repository variable. The checked-in stable-readiness - configuration establishes the recovery release boundary with the reviewed - `last-release-sha` bridge. Keep the variable disabled except while executing - an explicitly authorized release sequence. When it creates an approved - release with the GitHub workflow token, it polls the GitHub API until that - exact tag and commit are independently reported as immutable, then invokes - the protected publication chain directly; workflow-token release events do - not trigger a second workflow run. + `RELEASE_PLEASE_ENABLED` repository variable. A reviewed one-time + `last-release-sha` bridge established the recovery release boundary and was + removed during human finalization of the stable release PR. Keep the variable + disabled except while executing an explicitly authorized release sequence. + When it creates an approved release with the GitHub workflow token, it polls + the GitHub API until that exact tag and commit are independently reported as + immutable, then invokes the protected publication chain directly; + workflow-token release events do not trigger a second workflow run. - `publish.yml` is called only with the independently verified immutable tag, commit, and default branch. It resolves the tag to the checked-out commit, fetches the protected default branch, and rejects a commit that is not @@ -238,9 +239,9 @@ Maintainers then completed these steps in order: OIDC publication but failed before PyPI accepted any distribution. The recovery build suffix preserves the equivalent PEP 440 package version `0.1.0a1`; it is a one-time exception and must not be incremented or reused - for later releases. Keep Release Please disabled until a separate reviewed - and tested `last-release-sha` bridge establishes this recovery commit as its - previous-release boundary. + for later releases. Release Please was kept disabled until a separate + reviewed and tested `last-release-sha` bridge established this recovery + commit as its previous-release boundary. 5. The release workflow proved `immutable=true`, resolved the tag to the checked-out commit, verified that commit was reachable from the protected default branch, and ran the bounded protected live suite against that exact @@ -280,8 +281,9 @@ changelog, GitHub release, wheel, and source distribution. and source-distribution SHA256 `98d86829ef14771e8b7ec180d452c6638289f49c14a39b7207be5c47cb64cde7`. - `LIVE_SMOKE_ENABLED=false`. Release Please remains disabled outside an - explicitly authorized release sequence; the reviewed `last-release-sha` - bridge is configured for the alpha-to-stable transition. + explicitly authorized release sequence. The reviewed `last-release-sha` + bridge generated the stable release PR and was removed during human + finalization. ## Stable release sequence diff --git a/ROADMAP.md b/ROADMAP.md index 7a23a71..ac25d23 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -314,9 +314,9 @@ Public Preview readiness evidence on 2026-07-27: `LIVE_SMOKE_ENABLED` opt-in was reset to `false` after the run. - At this Public Preview checkpoint, no tag, GitHub release, Trusted Publisher, PyPI OIDC publication, provenance, or public-registry installation existed. - Those later Registry Alpha actions are recorded below. Release Please remains + Those later Registry Alpha actions are recorded below. Release Please remained disabled until a separately reviewed and tested `last-release-sha` bridge - establishes the recovery alpha as its previous-release boundary. + established the recovery alpha as its previous-release boundary. Public Preview remains ready only while: @@ -417,9 +417,9 @@ Accepted release evidence: its source-distribution SHA256 is `98d86829ef14771e8b7ec180d452c6638289f49c14a39b7207be5c47cb64cde7`. - `LIVE_SMOKE_ENABLED=false`. Release Please remains disabled outside an - explicitly authorized release sequence; the reviewed stable-readiness - configuration now establishes the recovery alpha boundary with - `last-release-sha`. + explicitly authorized release sequence. The reviewed stable-readiness + `last-release-sha` bridge generated the stable release PR and was removed + during human finalization. ## `0.1.0`: OpenAI protocol foundation @@ -480,9 +480,10 @@ successfully in the canonical GitHub repository. Scheduled and manually dispatched live smoke must require `LIVE_SMOKE_ENABLED=true`; an unset or other value prevents live execution. Release Please requires `RELEASE_PLEASE_ENABLED=true` and remains disabled -outside an explicitly authorized release sequence. Its stable-readiness -configuration uses the reviewed `last-release-sha` bridge for the recovery -alpha boundary. Release jobs must resolve an unset or empty +outside an explicitly authorized release sequence. Its reviewed one-time +`last-release-sha` bridge established the recovery alpha boundary, generated +the stable release PR, and was removed during human finalization. Release jobs +must resolve an unset or empty `COMETAPI_LIVE_MODEL` to `gpt-5.4` rather than attempt a request with an empty model. diff --git a/SECURITY.md b/SECURITY.md index c6589e4..9fa9c7f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,13 +2,13 @@ ## Supported versions -The SDK is in pre-release development. Published support claims begin only -after a package is independently verified from PyPI. +The SDK's supported 0.1 API surface is stable. Security support applies to +packages that have been independently verified from PyPI. | Version | Status | | --- | --- | -| `0.1.x` prereleases | Best-effort security fixes after verified publication | -| `0.1.x` stable | Planned support after verified publication | +| `0.1.x` stable | Supported | +| `0.1.x` prereleases | Best-effort security fixes | | Older versions | Unsupported | ## Reporting a vulnerability @@ -17,14 +17,13 @@ Do not disclose a suspected vulnerability, credential, customer data, or exploit details in a public issue. Use the repository's -[private security advisory form](https://github.com/cometapi-dev/cometapi-python/security/advisories/new) -when it is available. During private validation, before GitHub Private -Vulnerability Reporting is enabled, email `support@cometapi.com` with the -subject "Security report" and only the minimum details needed to establish a +[private security advisory form](https://github.com/cometapi-dev/cometapi-python/security/advisories/new). +If that form is unavailable, email `support@cometapi.com` with the subject +"Security report" and only the minimum details needed to establish a confidential follow-up channel. Do not disclose sensitive details publicly. The maintainers will acknowledge and triage reports as soon as practical, but -no response-time service-level agreement is promised for prereleases. +no response-time service-level agreement is promised. Coordinated disclosure timing will be agreed with the reporter. ## Credential safety @@ -47,8 +46,9 @@ Long-lived PyPI tokens are not an accepted publication path. A successful upload is incomplete until provenance and a clean public-registry installation have been verified. -Third-party GitHub Actions must be pinned to full commit SHAs. Only the -publishing job may receive `id-token: write`. +Third-party GitHub Actions must be pinned to full commit SHAs. The reusable +workflow caller and protected publishing job may declare `id-token: write`, but +only the publishing job may request the OIDC token. ## Scope diff --git a/SUPPORT.md b/SUPPORT.md index a8225c5..9c6044c 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -2,8 +2,8 @@ ## Project status -The SDK is in pre-release development. Support is best-effort, and no response -or resolution time is guaranteed. +The SDK's supported 0.1 API surface is stable. Support is best-effort, and no +response or resolution time is guaranteed. ## Supported SDK surface diff --git a/pyproject.toml b/pyproject.toml index 47eb762..661e951 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "cometapi" -version = "0.1.0a1" +version = "0.1.0" description = "Typed OpenAI-compatible Python client for the CometAPI gateway" readme = "README.md" requires-python = ">=3.10" @@ -9,7 +9,7 @@ license = "MIT" license-files = ["LICENSE"] keywords = ["ai", "cometapi", "openai", "sdk"] classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", diff --git a/release-please-config.json b/release-please-config.json index dbebe5d..4bc16d8 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -3,9 +3,6 @@ "release-type": "python", "include-component-in-tag": false, "include-v-in-tag": true, - "last-release-sha": "31b68904141489ca04932edbf305ccf88af09372", - "prerelease": false, - "versioning": "prerelease", "packages": { ".": { "package-name": "cometapi", diff --git a/tests/test_release_documents.py b/tests/test_release_documents.py index 9472dcd..9588ac7 100644 --- a/tests/test_release_documents.py +++ b/tests/test_release_documents.py @@ -1,5 +1,6 @@ from __future__ import annotations +import shutil import subprocess import sys from collections.abc import Callable @@ -21,6 +22,14 @@ VERSION_SCRIPT = PROJECT_ROOT / "scripts" / "check_version.py" +def _copy_version_checker(root: Path) -> Path: + scripts = root / "scripts" + scripts.mkdir(exist_ok=True) + for name in ("_checks.py", "check_version.py"): + shutil.copy2(PROJECT_ROOT / "scripts" / name, scripts / name) + return scripts / "check_version.py" + + def _write_release_documents(root: Path) -> None: files = { "pyproject.toml": f'''\ @@ -233,36 +242,35 @@ def test_public_preview_cli_reports_aggregated_violations_and_fails( assert "SECURITY.md: missing canonical public value" in result.stderr -def test_release_version_cli_accepts_approved_initial_alpha_recovery_tag( - releasable_documents: Path, -) -> None: +def test_release_version_cli_accepts_current_stable_tag() -> None: result = subprocess.run( [ sys.executable, str(VERSION_SCRIPT), "--tag", - "v0.1.0-alpha.1+recovery.1", + "v0.1.0", "--require-changelog", ], - cwd=releasable_documents, + cwd=PROJECT_ROOT, text=True, check=False, capture_output=True, ) assert result.returncode == 0, result.stderr - assert "version agreement passed: 0.1.0a1" in result.stdout + assert "version agreement passed: 0.1.0" in result.stdout -def test_release_version_cli_rejects_tombstoned_initial_alpha_tag( +def test_release_version_cli_accepts_approved_initial_alpha_recovery_tag( releasable_documents: Path, ) -> None: + version_script = _copy_version_checker(releasable_documents) result = subprocess.run( [ sys.executable, - str(VERSION_SCRIPT), + str(version_script), "--tag", - "v0.1.0-alpha.1", + "v0.1.0-alpha.1+recovery.1", "--require-changelog", ], cwd=releasable_documents, @@ -271,21 +279,18 @@ def test_release_version_cli_rejects_tombstoned_initial_alpha_tag( capture_output=True, ) - assert result.returncode != 0 - assert "release tag must use an approved spelling" in result.stderr + assert result.returncode == 0, result.stderr + assert "version agreement passed: 0.1.0a1" in result.stdout -def test_release_version_cli_rejects_unapproved_recovery_tag( +@pytest.mark.parametrize("tag", ["v0.1.0-alpha.1", "v0.1.0-alpha.1+recovery.2"]) +def test_release_version_cli_rejects_unapproved_initial_alpha_tag( releasable_documents: Path, + tag: str, ) -> None: + version_script = _copy_version_checker(releasable_documents) result = subprocess.run( - [ - sys.executable, - str(VERSION_SCRIPT), - "--tag", - "v0.1.0-alpha.1+recovery.2", - "--require-changelog", - ], + [sys.executable, str(version_script), "--tag", tag, "--require-changelog"], cwd=releasable_documents, text=True, check=False, diff --git a/tests/test_release_workflow.py b/tests/test_release_workflow.py index 8776894..53a6e07 100644 --- a/tests/test_release_workflow.py +++ b/tests/test_release_workflow.py @@ -30,6 +30,19 @@ TRUST_SCRIPT = PROJECT_ROOT / "scripts" / "verify_release_trust.sh" +def _reviewed_release_please_bridge_config() -> str: + stable = RELEASE_PLEASE_CONFIG.read_text(encoding="utf-8") + marker = ' "include-v-in-tag": true,\n' + if marker not in stable: + raise AssertionError("stable Release Please config lost its tag contract") + bridge = ( + ' "last-release-sha": "31b68904141489ca04932edbf305ccf88af09372",\n' + ' "prerelease": false,\n' + ' "versioning": "prerelease",\n' + ) + return stable.replace(marker, marker + bridge, 1) + + def _git(root: Path, *arguments: str, input_text: str | None = None) -> str: result = subprocess.run( ["git", *arguments], @@ -746,13 +759,20 @@ def test_current_release_please_workflow_is_disabled_by_default() -> None: check_release_please_workflow(RELEASE_PLEASE_WORKFLOW.read_text(encoding="utf-8")) -def test_current_release_please_config_has_reviewed_stable_bridge() -> None: +def test_current_release_please_config_has_reviewed_stable_cleanup() -> None: check_release_please_config( RELEASE_PLEASE_CONFIG.read_text(encoding="utf-8"), RELEASE_PLEASE_MANIFEST.read_text(encoding="utf-8"), ) +def test_release_please_config_accepts_reviewed_stable_bridge() -> None: + check_release_please_config( + _reviewed_release_please_bridge_config(), + '{".": "0.1.0-alpha.1"}\n', + ) + + @pytest.mark.parametrize( ("needle", "replacement", "message"), [ @@ -775,12 +795,12 @@ def test_current_release_please_config_has_reviewed_stable_bridge() -> None: def test_release_please_config_rejects_bridge_drift( needle: str, replacement: str, message: str ) -> None: - text = RELEASE_PLEASE_CONFIG.read_text(encoding="utf-8") + text = _reviewed_release_please_bridge_config() assert needle in text with pytest.raises(RuntimeError, match=message): check_release_please_config( text.replace(needle, replacement, 1), - RELEASE_PLEASE_MANIFEST.read_text(encoding="utf-8"), + '{".": "0.1.0-alpha.1"}\n', ) @@ -806,34 +826,26 @@ def test_release_please_config_rejects_manifest_drift() -> None: def test_release_please_config_accepts_exact_stable_cleanup() -> None: - config = RELEASE_PLEASE_CONFIG.read_text(encoding="utf-8") - for line in ( - ' "last-release-sha": "31b68904141489ca04932edbf305ccf88af09372",\n', - ' "prerelease": false,\n', - ' "versioning": "prerelease",\n', - ): - config = config.replace(line, "", 1) - check_release_please_config(config, '{".": "0.1.0"}\n') + check_release_please_config( + RELEASE_PLEASE_CONFIG.read_text(encoding="utf-8"), + '{".": "0.1.0"}\n', + ) def test_release_please_config_rejects_stable_manifest_with_bridge() -> None: with pytest.raises(RuntimeError, match="remove the one-time bridge"): check_release_please_config( - RELEASE_PLEASE_CONFIG.read_text(encoding="utf-8"), + _reviewed_release_please_bridge_config(), '{".": "0.1.0"}\n', ) def test_release_please_config_rejects_bridge_cleanup_before_stable() -> None: - config = RELEASE_PLEASE_CONFIG.read_text(encoding="utf-8") - for line in ( - ' "last-release-sha": "31b68904141489ca04932edbf305ccf88af09372",\n', - ' "prerelease": false,\n', - ' "versioning": "prerelease",\n', - ): - config = config.replace(line, "", 1) with pytest.raises(RuntimeError, match="only after stable"): - check_release_please_config(config, '{".": "0.1.0-alpha.1"}\n') + check_release_please_config( + RELEASE_PLEASE_CONFIG.read_text(encoding="utf-8"), + '{".": "0.1.0-alpha.1"}\n', + ) @pytest.mark.parametrize( diff --git a/uv.lock b/uv.lock index 4f6a4f9..9892514 100644 --- a/uv.lock +++ b/uv.lock @@ -221,7 +221,7 @@ wheels = [ [[package]] name = "cometapi" -version = "0.1.0a1" +version = "0.1.0" source = { editable = "." } dependencies = [ { name = "openai" },