From 3f828481421661d73ada5d18d4d4e67c34de9fc6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 09:01:23 +0000 Subject: [PATCH 1/4] chore(main): release 0.1.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ pyproject.toml | 2 +- uv.lock | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) 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/CHANGELOG.md b/CHANGELOG.md index cead319..2f4b0b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project are documented in this file. The project follows Semantic Versioning and uses Conventional Commits for release-PR automation. +## [0.1.0](https://github.com/cometapi-dev/cometapi-python/compare/v0.1.0-alpha.1...v0.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 ([#18](https://github.com/cometapi-dev/cometapi-python/issues/18)) ([f39b4dc](https://github.com/cometapi-dev/cometapi-python/commit/f39b4dc9f2e18e91ab3cbac202246f85658f71fd)) + ## [Unreleased] ### Changed diff --git a/pyproject.toml b/pyproject.toml index 47eb762..a2096d7 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" 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" }, From 1617a6a59826ba4010ca7994c972ba8960efe84c Mon Sep 17 00:00:00 2001 From: CometAPI Date: Tue, 28 Jul 2026 17:27:50 +0800 Subject: [PATCH 2/4] chore: finalize 0.1.0 release 0.1.0 is approved for PyPI publication. --- CHANGELOG.md | 14 ++++---- CONTRIBUTING.md | 9 +++-- README.md | 13 ++++--- SECURITY.md | 17 +++++----- SUPPORT.md | 4 +-- pyproject.toml | 2 +- release-please-config.json | 3 -- scripts/check_version.py | 4 +-- tests/test_release_documents.py | 60 +++++++++------------------------ tests/test_release_workflow.py | 52 +++++++++++++++++----------- 10 files changed, 76 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f4b0b8..7baa76a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,21 +4,17 @@ All notable changes to this project are documented in this file. The project follows Semantic Versioning and uses Conventional Commits for release-PR automation. -## [0.1.0](https://github.com/cometapi-dev/cometapi-python/compare/v0.1.0-alpha.1...v0.1.0) (2026-07-28) +## [Unreleased] +## [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)) - +- 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 ([#18](https://github.com/cometapi-dev/cometapi-python/issues/18)) ([f39b4dc](https://github.com/cometapi-dev/cometapi-python/commit/f39b4dc9f2e18e91ab3cbac202246f85658f71fd)) - -## [Unreleased] - -### Changed +- 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. @@ -65,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..d281ce6 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,15 @@ Project links: [CometAPI](https://www.cometapi.com), ## Installation -Install the exact alpha from PyPI with: +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 +[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 +159,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/SECURITY.md b/SECURITY.md index c6589e4..3b4b6f4 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 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 a2096d7..661e951 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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/scripts/check_version.py b/scripts/check_version.py index a5288b4..c59e728 100644 --- a/scripts/check_version.py +++ b/scripts/check_version.py @@ -67,7 +67,7 @@ def _artifact_version(path: Path) -> str: } -def _require_canonical_tag(tag: str, project_version: str) -> str: +def require_canonical_tag(tag: str, project_version: str) -> str: normalized = normalize_version(project_version) alpha = re.fullmatch(r"(?P\d+\.\d+\.\d+)a(?P\d+)", normalized) expected = ( @@ -310,7 +310,7 @@ def main() -> int: if args.expected: versions.append(("expected", args.expected)) if args.tag: - versions.append(("tag", _require_canonical_tag(args.tag, project))) + versions.append(("tag", require_canonical_tag(args.tag, project))) for value in args.artifacts: path = Path(value) if not path.is_file(): diff --git a/tests/test_release_documents.py b/tests/test_release_documents.py index 9472dcd..126fb5e 100644 --- a/tests/test_release_documents.py +++ b/tests/test_release_documents.py @@ -15,7 +15,11 @@ CANONICAL_SUPPORT, CheckError, ) -from scripts.check_version import require_public_preview_docs, require_releasable_docs +from scripts.check_version import ( + require_canonical_tag, + require_public_preview_docs, + require_releasable_docs, +) PROJECT_ROOT = Path(__file__).resolve().parents[1] VERSION_SCRIPT = PROJECT_ROOT / "scripts" / "check_version.py" @@ -233,64 +237,30 @@ 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 - - -def test_release_version_cli_rejects_tombstoned_initial_alpha_tag( - releasable_documents: Path, -) -> None: - result = subprocess.run( - [ - sys.executable, - str(VERSION_SCRIPT), - "--tag", - "v0.1.0-alpha.1", - "--require-changelog", - ], - cwd=releasable_documents, - text=True, - check=False, - capture_output=True, - ) + assert "version agreement passed: 0.1.0" in result.stdout - assert result.returncode != 0 - assert "release tag must use an approved spelling" in result.stderr +def test_release_version_accepts_approved_initial_alpha_recovery_tag() -> None: + assert require_canonical_tag("v0.1.0-alpha.1+recovery.1", "0.1.0a1") == "0.1.0a1" -def test_release_version_cli_rejects_unapproved_recovery_tag( - releasable_documents: Path, -) -> None: - result = subprocess.run( - [ - sys.executable, - str(VERSION_SCRIPT), - "--tag", - "v0.1.0-alpha.1+recovery.2", - "--require-changelog", - ], - cwd=releasable_documents, - text=True, - check=False, - capture_output=True, - ) - assert result.returncode != 0 - assert "release tag must use an approved spelling" in result.stderr +@pytest.mark.parametrize("tag", ["v0.1.0-alpha.1", "v0.1.0-alpha.1+recovery.2"]) +def test_release_version_rejects_unapproved_initial_alpha_tag(tag: str) -> None: + with pytest.raises(CheckError, match="release tag must use an approved spelling"): + require_canonical_tag(tag, "0.1.0a1") 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( From b3360d6cd60c867e0ac5418ad481d22c22d2036e Mon Sep 17 00:00:00 2001 From: CometAPI Date: Tue, 28 Jul 2026 17:34:05 +0800 Subject: [PATCH 3/4] docs: align stable release state --- AGENTS.md | 15 +++++++-------- ARCHITECTURE.md | 7 ++++--- RELEASING.md | 36 +++++++++++++++++++----------------- ROADMAP.md | 17 +++++++++-------- SECURITY.md | 5 +++-- 5 files changed, 42 insertions(+), 38 deletions(-) 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/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 3b4b6f4..9fa9c7f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -46,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 From c0434521f12badd378d68e40f1e1a2691353349c Mon Sep 17 00:00:00 2001 From: CometAPI Date: Tue, 28 Jul 2026 17:43:06 +0800 Subject: [PATCH 4/4] fix: harden stable release finalization --- README.md | 5 +-- scripts/check_version.py | 4 +-- tests/test_release_documents.py | 55 +++++++++++++++++++++++++++------ 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d281ce6..f051db3 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,14 @@ Project links: [CometAPI](https://www.cometapi.com), ## Installation -Install the stable release 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 'cometapi==0.1.0' ``` -See the immutable +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. diff --git a/scripts/check_version.py b/scripts/check_version.py index c59e728..a5288b4 100644 --- a/scripts/check_version.py +++ b/scripts/check_version.py @@ -67,7 +67,7 @@ def _artifact_version(path: Path) -> str: } -def require_canonical_tag(tag: str, project_version: str) -> str: +def _require_canonical_tag(tag: str, project_version: str) -> str: normalized = normalize_version(project_version) alpha = re.fullmatch(r"(?P\d+\.\d+\.\d+)a(?P\d+)", normalized) expected = ( @@ -310,7 +310,7 @@ def main() -> int: if args.expected: versions.append(("expected", args.expected)) if args.tag: - versions.append(("tag", require_canonical_tag(args.tag, project))) + versions.append(("tag", _require_canonical_tag(args.tag, project))) for value in args.artifacts: path = Path(value) if not path.is_file(): diff --git a/tests/test_release_documents.py b/tests/test_release_documents.py index 126fb5e..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 @@ -15,16 +16,20 @@ CANONICAL_SUPPORT, CheckError, ) -from scripts.check_version import ( - require_canonical_tag, - require_public_preview_docs, - require_releasable_docs, -) +from scripts.check_version import require_public_preview_docs, require_releasable_docs PROJECT_ROOT = Path(__file__).resolve().parents[1] 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'''\ @@ -256,11 +261,41 @@ def test_release_version_cli_accepts_current_stable_tag() -> None: assert "version agreement passed: 0.1.0" in result.stdout -def test_release_version_accepts_approved_initial_alpha_recovery_tag() -> None: - assert require_canonical_tag("v0.1.0-alpha.1+recovery.1", "0.1.0a1") == "0.1.0a1" +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), + "--tag", + "v0.1.0-alpha.1+recovery.1", + "--require-changelog", + ], + cwd=releasable_documents, + text=True, + check=False, + capture_output=True, + ) + + assert result.returncode == 0, result.stderr + assert "version agreement passed: 0.1.0a1" in result.stdout @pytest.mark.parametrize("tag", ["v0.1.0-alpha.1", "v0.1.0-alpha.1+recovery.2"]) -def test_release_version_rejects_unapproved_initial_alpha_tag(tag: str) -> None: - with pytest.raises(CheckError, match="release tag must use an approved spelling"): - require_canonical_tag(tag, "0.1.0a1") +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", tag, "--require-changelog"], + cwd=releasable_documents, + text=True, + check=False, + capture_output=True, + ) + + assert result.returncode != 0 + assert "release tag must use an approved spelling" in result.stderr