Skip to content

Commit 54ed218

Browse files
author
CometAPI
committed
feat: prepare the stable release
1 parent f39b4dc commit 54ed218

19 files changed

Lines changed: 1129 additions & 103 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
- name: Run offline unit and contract tests
4646
run: uv run pytest -m "not live"
4747
- name: Check release version agreement
48-
run: uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
48+
run: uv run python scripts/check_version.py --require-changelog
4949
- name: Check canonical public content and identity
5050
run: uv run python scripts/check_version.py --require-public-preview-docs
5151
- name: Scan for credentials and scope mistakes

.github/workflows/publish.yml

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
name: Publish immutable release
22

33
on:
4-
release:
5-
types:
6-
- published
4+
workflow_call:
5+
inputs:
6+
release-tag:
7+
required: true
8+
type: string
9+
release-sha:
10+
required: true
11+
type: string
12+
default-branch:
13+
required: true
14+
type: string
715

816
permissions:
917
contents: read
@@ -29,15 +37,16 @@ jobs:
2937
- name: Check out the published release tag
3038
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3139
with:
32-
ref: refs/tags/${{ github.event.release.tag_name }}
40+
ref: refs/tags/${{ inputs.release-tag }}
3341
fetch-depth: 0
3442
persist-credentials: false
3543
- name: Reject an untrusted release target
3644
id: trust
3745
env:
38-
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
39-
RELEASE_IMMUTABLE: ${{ github.event.release.immutable }}
40-
RELEASE_TAG: ${{ github.event.release.tag_name }}
46+
DEFAULT_BRANCH: ${{ inputs.default-branch }}
47+
EXPECTED_RELEASE_SHA: ${{ inputs.release-sha }}
48+
RELEASE_IMMUTABLE: "true"
49+
RELEASE_TAG: ${{ inputs.release-tag }}
4150
run: bash scripts/verify_release_trust.sh
4251
- name: Set up Python
4352
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
@@ -50,7 +59,7 @@ jobs:
5059
- name: Verify project, manifest, changelog, release docs, and tag agreement
5160
id: version
5261
env:
53-
RELEASE_TAG: ${{ github.event.release.tag_name }}
62+
RELEASE_TAG: ${{ inputs.release-tag }}
5463
run: |
5564
version=$(uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs --print-version)
5665
echo "version=$version" >> "$GITHUB_OUTPUT"
@@ -62,7 +71,7 @@ jobs:
6271
run: uv build
6372
- name: Verify artifact versions against the tag
6473
env:
65-
RELEASE_TAG: ${{ github.event.release.tag_name }}
74+
RELEASE_TAG: ${{ inputs.release-tag }}
6675
run: uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs dist/*
6776
- name: Check package metadata rendering
6877
run: uv run twine check dist/*

.github/workflows/release-please.yml

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,74 @@ jobs:
1717
name: Maintain the reviewed release PR and release
1818
if: vars.RELEASE_PLEASE_ENABLED == 'true'
1919
runs-on: ubuntu-latest
20-
timeout-minutes: 10
20+
timeout-minutes: 15
21+
outputs:
22+
release-created: ${{ steps.release.outputs.release_created }}
23+
release-sha: ${{ steps.verify-release.outputs.release-sha }}
24+
release-tag: ${{ steps.verify-release.outputs.release-tag }}
25+
release-verified: ${{ steps.verify-release.outputs.release-verified }}
2126
permissions:
2227
contents: write
2328
pull-requests: write
2429
steps:
2530
- name: Open or update the release PR, or create its approved release
31+
id: release
2632
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
2733
with:
2834
config-file: release-please-config.json
2935
manifest-file: .release-please-manifest.json
36+
- name: Verify the immutable release created by Release Please
37+
id: verify-release
38+
if: steps.release.outputs.release_created == 'true'
39+
env:
40+
EXPECTED_SHA: ${{ steps.release.outputs.sha }}
41+
EXPECTED_TAG: ${{ steps.release.outputs.tag_name }}
42+
GH_TOKEN: ${{ github.token }}
43+
run: |
44+
test -n "$EXPECTED_TAG"
45+
test -n "$EXPECTED_SHA"
46+
release=""
47+
for attempt in $(seq 1 12); do
48+
release=$(gh api "repos/${{ github.repository }}/releases/tags/$EXPECTED_TAG") || true
49+
if test -n "$release" && test "$(jq -r .immutable <<<"$release")" = "true"; then
50+
break
51+
fi
52+
if test "$attempt" -ge 12; then
53+
echo "release did not become immutable" >&2
54+
exit 1
55+
fi
56+
sleep 5
57+
done
58+
test "$(jq -r .tag_name <<<"$release")" = "$EXPECTED_TAG"
59+
test "$(jq -r .draft <<<"$release")" = "false"
60+
test "$(jq -r .prerelease <<<"$release")" = "false"
61+
test "$(jq -r .immutable <<<"$release")" = "true"
62+
ref=$(gh api "repos/${{ github.repository }}/git/ref/tags/$EXPECTED_TAG")
63+
tag_type=$(jq -r .object.type <<<"$ref")
64+
tag_sha=$(jq -r .object.sha <<<"$ref")
65+
if test "$tag_type" = "tag"; then
66+
tag_sha=$(gh api "repos/${{ github.repository }}/git/tags/$tag_sha" --jq .object.sha)
67+
else
68+
test "$tag_type" = "commit"
69+
fi
70+
test "$tag_sha" = "$EXPECTED_SHA"
71+
{
72+
echo "release-tag=$EXPECTED_TAG"
73+
echo "release-sha=$EXPECTED_SHA"
74+
echo "release-verified=true"
75+
} >> "$GITHUB_OUTPUT"
76+
77+
publish-release:
78+
name: Run the protected publication chain
79+
needs: release-please
80+
if: >-
81+
needs.release-please.outputs.release-created == 'true' &&
82+
needs.release-please.outputs.release-verified == 'true'
83+
permissions:
84+
contents: read
85+
id-token: write
86+
uses: ./.github/workflows/publish.yml
87+
with:
88+
release-tag: ${{ needs.release-please.outputs.release-tag }}
89+
release-sha: ${{ needs.release-please.outputs.release-sha }}
90+
default-branch: ${{ github.event.repository.default_branch }}

AGENTS.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,10 @@ Post-alpha invariants:
9696
authorizes that work, without reopening completed dispositions.
9797
2. Keep `.github/CODEOWNERS` absent until a real multi-maintainer model exists.
9898
3. Keep scheduled and manually dispatched live smoke fail-closed behind
99-
`LIVE_SMOKE_ENABLED=true`, and keep `RELEASE_PLEASE_ENABLED` disabled until
100-
a separate reviewed and tested `last-release-sha` bridge establishes the
101-
recovery alpha as Release Please's previous-release boundary.
99+
`LIVE_SMOKE_ENABLED=true`, and keep `RELEASE_PLEASE_ENABLED` disabled outside
100+
an explicitly authorized release sequence. The stable-readiness
101+
configuration establishes the recovery alpha boundary with a reviewed and
102+
tested `last-release-sha` bridge.
102103
4. Treat the recorded public rules, security reporting, immutable releases, and
103104
protected environments as readiness invariants. Any drift invalidates the
104105
readiness claim until it is explicitly authorized, restored, and verified.
@@ -205,7 +206,7 @@ uv run ruff check src tests scripts
205206
uv run ruff format --check src tests scripts
206207
uv run pyright
207208
uv run pytest -m "not live"
208-
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
209+
uv run python scripts/check_version.py --require-changelog
209210
uv run python scripts/check_secrets.py
210211
uv run python scripts/check_workflows.py
211212
rm -rf dist
@@ -242,8 +243,11 @@ committed.
242243
- Publication uses a reviewed immutable tag, a protected `pypi` environment,
243244
and PyPI OIDC Trusted Publishing.
244245
- The release commit must equal the tag target and belong to the protected
245-
default branch. A protected live-smoke job must check out that exact commit
246-
and succeed before the protected PyPI job can become eligible.
246+
default branch. Release Please must independently confirm that the exact tag
247+
and commit are immutable before directly calling the protected publication
248+
workflow; do not rely on workflow-token release events to trigger it. A
249+
protected live-smoke job must check out that exact commit and succeed before
250+
the protected PyPI job can become eligible.
247251
- Scheduled/default-branch live smoke is monitoring evidence only and cannot
248252
satisfy the exact-release live gate.
249253
- Missing identity, credentials, environments, reviewers, protection,
@@ -260,12 +264,16 @@ committed.
260264
accepted. The sole approved recovery tag is
261265
`v0.1.0-alpha.1+recovery.1`, which maps to package version `0.1.0a1`.
262266
Later releases must use their ordinary canonical tag spelling.
263-
- Keep Release Please disabled after the recovery alpha. Its manifest cannot
264-
infer the previous-release boundary from the recovery tag's build metadata;
265-
enabling it requires a separate reviewed and tested `last-release-sha`
266-
bridge.
267-
- Keep third-party Actions pinned to full commit SHAs and grant
268-
`id-token: write` only to the publishing job.
267+
- Keep Release Please disabled outside an explicitly authorized release
268+
sequence. Its stable-readiness configuration uses the reviewed and tested
269+
`last-release-sha` bridge because the recovery tag's build metadata cannot be
270+
inferred from the manifest. Remove the one-time bridge and prerelease
271+
versioning controls in the human-finalized stable release PR before it is
272+
merged.
273+
- Keep third-party Actions pinned to full commit SHAs. Grant `id-token: write`
274+
only to the reusable publication caller and the protected publishing job;
275+
the caller passes this maximum permission and only the publishing job uses
276+
the OIDC token.
269277
- Keep README, roadmap, compatibility matrix, examples, and changelog aligned
270278
with shipped behavior. Use currently supported model IDs.
271279
- All repository documentation is written in English.

ARCHITECTURE.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ Release evidence is intentionally ordered:
114114
```text
115115
local mocked/package evidence
116116
-> immutable tag commit equals checkout and belongs to protected default branch
117+
-> release API and tag ref confirm the exact immutable identity
117118
-> protected live-smoke job checks that exact commit
118119
-> protected PyPI OIDC job publishes the previously verified artifact
119120
-> public registry digest, provenance, install, import, and mocked smoke
@@ -139,10 +140,10 @@ recovery release uses SemVer build metadata in
139140
`v0.1.0-alpha.1+recovery.1`. The build suffix does not change the package
140141
artifact identity: the PyPI version remains `0.1.0a1`.
141142

142-
Release Please remains disabled after this recovery release because its
143-
manifest version does not include the recovery build metadata. A later,
144-
separately reviewed change must establish the previous-release boundary with a
145-
tested `last-release-sha` bridge before enabling automated release PRs.
143+
Release Please remains disabled outside an explicitly authorized release
144+
sequence. The stable-readiness configuration establishes the recovery commit
145+
as the previous-release boundary with a tested `last-release-sha` bridge, so
146+
the one-time build-metadata recovery tag cannot replay earlier history.
146147

147148
## Rejected 0.1 approaches
148149

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ uv run ruff check src tests scripts
2727
uv run ruff format --check src tests scripts
2828
uv run pyright
2929
uv run pytest -m "not live"
30-
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
30+
uv run python scripts/check_version.py --require-changelog
3131
uv run python scripts/check_secrets.py
3232
uv run python scripts/check_workflows.py
3333
rm -rf dist

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ variable, then the default:
6565

6666
### Chat Completions
6767

68+
<!-- cometapi-readme-example: sync-chat -->
6869
```python
6970
from cometapi import CometAPI
7071

@@ -78,6 +79,7 @@ with CometAPI() as client:
7879

7980
Streaming uses the official OpenAI stream type:
8081

82+
<!-- cometapi-readme-example: sync-chat-stream -->
8183
```python
8284
from cometapi import CometAPI
8385

@@ -93,6 +95,7 @@ with CometAPI() as client:
9395

9496
### Responses and Models
9597

98+
<!-- cometapi-readme-example: sync-responses-models -->
9699
```python
97100
from cometapi import CometAPI
98101

@@ -109,6 +112,7 @@ print(models.data[0].id if models.data else "No models returned")
109112

110113
### Async client
111114

115+
<!-- cometapi-readme-example: async-response -->
112116
```python
113117
import asyncio
114118

@@ -171,7 +175,7 @@ uv run ruff check src tests scripts
171175
uv run ruff format --check src tests scripts
172176
uv run pyright
173177
uv run pytest -m "not live"
174-
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
178+
uv run python scripts/check_version.py --require-changelog
175179
uv run python scripts/check_secrets.py
176180
uv run python scripts/check_workflows.py
177181
rm -rf dist

RELEASING.md

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ one active maintainer.
4545

4646
Before the historical first push, scheduled and manually dispatched live
4747
execution was required to fail closed unless `LIVE_SMOKE_ENABLED=true`.
48-
`RELEASE_PLEASE_ENABLED` was kept disabled and remains disabled until a
49-
separately reviewed and tested `last-release-sha` bridge establishes the
50-
recovery alpha as the previous-release boundary. An unset or non-true value
51-
prevents the corresponding gated job from executing.
48+
`RELEASE_PLEASE_ENABLED` was kept disabled. The reviewed stable-readiness
49+
configuration now uses an explicit `last-release-sha` bridge to establish the
50+
recovery alpha as the previous-release boundary; the repository variable stays
51+
disabled until maintainers intentionally start the stable release sequence. An
52+
unset or non-true value prevents the corresponding gated job from executing.
5253
The release live-model configuration resolves an unset or empty
5354
`COMETAPI_LIVE_MODEL` to `gpt-5.4`.
5455

@@ -78,7 +79,7 @@ uv run ruff check src tests scripts
7879
uv run ruff format --check src tests scripts
7980
uv run pyright
8081
uv run pytest -m "not live"
81-
uv run python scripts/check_version.py --expected 0.1.0a1 --require-changelog
82+
uv run python scripts/check_version.py --require-changelog
8283
uv run python scripts/check_secrets.py
8384
uv run python scripts/check_workflows.py
8485
rm -rf dist
@@ -181,14 +182,18 @@ violations in one run and still returns non-zero when any violation exists.
181182
`LIVE_SMOKE_ENABLED=true`.
182183
- `release-please.yml` maintains a human-reviewed version and changelog pull
183184
request from Conventional Commits after maintainers enable the
184-
`RELEASE_PLEASE_ENABLED` repository variable. Keep it disabled after the
185-
initial `v0.1.0-alpha.1+recovery.1` release: the checked-in manifest version
186-
lacks the recovery tag's build metadata and cannot safely infer the previous
187-
release boundary. Enable it only after a separate reviewed change configures
188-
and tests an explicit `last-release-sha` bridge.
189-
- `publish.yml` runs only for a published immutable GitHub release. It resolves
190-
the tag to the checked-out commit, fetches the protected default branch, and
191-
rejects a commit that is not reachable from that branch. A protected
185+
`RELEASE_PLEASE_ENABLED` repository variable. The checked-in stable-readiness
186+
configuration establishes the recovery release boundary with the reviewed
187+
`last-release-sha` bridge. Keep the variable disabled except while executing
188+
an explicitly authorized release sequence. When it creates an approved
189+
release with the GitHub workflow token, it polls the GitHub API until that
190+
exact tag and commit are independently reported as immutable, then invokes
191+
the protected publication chain directly; workflow-token release events do
192+
not trigger a second workflow run.
193+
- `publish.yml` is called only with the independently verified immutable tag,
194+
commit, and default branch. It resolves the tag to the checked-out commit,
195+
fetches the protected default branch, and rejects a commit that is not
196+
reachable from that branch. A protected
192197
`live-smoke` job then checks out that exact verified commit and must succeed
193198
before the protected `pypi` job becomes eligible. The workflow publishes the
194199
previously verified artifacts with OIDC, then checks the public package
@@ -197,7 +202,9 @@ violations in one run and still returns non-zero when any violation exists.
197202
or empty live-model repository variable resolves to `gpt-5.4`.
198203

199204
Third-party Actions are pinned to full commit SHAs. Workflow permissions are
200-
read-only by default; only the publishing job receives `id-token: write`.
205+
read-only by default. The reusable publication caller and protected publishing
206+
job declare `id-token: write`; the caller passes the maximum permission and
207+
only the publishing job requests the OIDC token.
201208
Publishing uses a protected `pypi` environment and concurrency control.
202209
Arbitrary-branch and manual publication are forbidden.
203210

@@ -272,8 +279,9 @@ changelog, GitHub release, wheel, and source distribution.
272279
SHA256 `a6820347317943ca22f7632acbe354dd992f31a122a6172dfe45b57960e3a093`
273280
and source-distribution SHA256
274281
`98d86829ef14771e8b7ec180d452c6638289f49c14a39b7207be5c47cb64cde7`.
275-
- `LIVE_SMOKE_ENABLED=false`. Release Please remains disabled until a separate
276-
reviewed and tested `last-release-sha` bridge is merged.
282+
- `LIVE_SMOKE_ENABLED=false`. Release Please remains disabled outside an
283+
explicitly authorized release sequence; the reviewed `last-release-sha`
284+
bridge is configured for the alpha-to-stable transition.
277285

278286
## Stable release sequence
279287

@@ -282,8 +290,11 @@ feature or fix pull request
282290
-> required offline CI
283291
-> merge to the default branch
284292
-> automated release pull request
285-
-> human review and merge
293+
-> human finalization of stable docs, metadata, and one-time bridge cleanup
294+
-> required release-PR CI, review, and merge
286295
-> immutable tag and GitHub release
296+
-> bounded API verification of immutable tag and commit identity
297+
-> direct call to the protected publication workflow
287298
-> verify immutable tag commit and protected-default-branch ancestry
288299
-> rebuild and verify exact artifacts
289300
-> protected live smoke against that exact commit
@@ -295,4 +306,11 @@ feature or fix pull request
295306

296307
Stable `0.1.0` additionally requires the complete blocking Python matrix,
297308
executed README examples against the built package, trusted live evidence, and
298-
reviewed release-PR and changelog agreement.
309+
reviewed release-PR and changelog agreement. Before the stable release PR is
310+
merged, its finalization commit must state that `0.1.0` is approved for PyPI
311+
publication, use the stable installation command and classifier, and remove the
312+
one-time `last-release-sha` plus prerelease-versioning controls. The manifest,
313+
project metadata, lock file, and changelog must remain at the exact generated
314+
`0.1.0` version. If GitHub requires approval before checks run on the automated
315+
pull request, approve only that reviewed workflow execution and wait for every
316+
blocking check.

0 commit comments

Comments
 (0)